- new DIR --template bell|ghz|grover|noisy-bell: the program, intent.json, test.sh, ci/check.sh and an example workflow, mcp.json and AGENTS.md; its test passes as made. - test: every intent checked by the engine (an exact distribution by the state vector or, under noise, the density matrix; a reference by the equivalence ladder; a state from |0...0>), the run and the check both sealed with the user's key, the check binding the run's receipt. - setup-agent: the block between <!-- wai-quantum:start --> and <!-- wai-quantum:end --> in AGENTS.md, from the live tool lists and the engine's rules; a rerun replaces only the block; --check for CI. - mcp config / mcp install --file PATH: the server's entry in the common mcpServers form, merged keeping every other entry; a different entry is refused without --force. - The stdio server serves the engine's resources and prompts. - docs: the resources offline, llms.txt and llms-full.txt (committed in docs/, held by a test to both servers' tool lists and this help), and the Agent Skill in skills/wai-quantum/. server.json for the registry. - Tests for every verb, an end-to-end test through the binary, and smoke checks. Co-Authored-By: Claude Opus 5.5 <[email protected]>
126 lines
7 KiB
Rust
126 lines
7 KiB
Rust
//! The front door as a person or an agent uses it, through the binary: a
|
|
//! project made with `new`, its own `test.sh` run as made, `setup-agent` run
|
|
//! twice, `mcp config` and `mcp install`, the docs, and the stdio server's
|
|
//! resources.
|
|
#![cfg(unix)]
|
|
|
|
use serde_json::Value;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Output};
|
|
|
|
const BIN: &str = env!("CARGO_BIN_EXE_wai-quantum");
|
|
|
|
fn scratch(name: &str) -> PathBuf {
|
|
let d = std::env::temp_dir().join(format!("wai-quantum-front-door-{name}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
std::fs::create_dir_all(&d).unwrap();
|
|
d
|
|
}
|
|
|
|
/// The binary with `args`, its key kept in `home`.
|
|
fn run(home: &Path, args: &[&str]) -> Output {
|
|
Command::new(BIN).args(args).env("XDG_CONFIG_HOME", home.join("config")).env_remove("WAI_QUANTUM_KEY").output().unwrap()
|
|
}
|
|
|
|
fn text(o: &Output) -> String {
|
|
format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))
|
|
}
|
|
|
|
#[test]
|
|
fn every_template_s_test_passes_as_made() {
|
|
for t in ["bell", "ghz", "grover", "noisy-bell"] {
|
|
let home = scratch(t);
|
|
let dir = home.join("project");
|
|
let made = run(&home, &["new", dir.to_str().unwrap(), "--template", t]);
|
|
assert!(made.status.success(), "{t}: {}", text(&made));
|
|
// The scaffolded test, as a person runs it.
|
|
let test = Command::new("sh").arg(dir.join("test.sh")).env("WAI_QUANTUM", BIN).env("XDG_CONFIG_HOME", home.join("config")).env_remove("WAI_QUANTUM_KEY").output().unwrap();
|
|
assert!(test.status.success(), "{t}: {}", text(&test));
|
|
assert!(text(&test).contains("both receipts verify"), "{t}: {}", text(&test));
|
|
// The check's record says each intent held, and how.
|
|
let record: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("receipts").join(format!("{t}.intent.json"))).unwrap()).unwrap();
|
|
assert_eq!(record["result"]["holds"], true, "{t}: {record}");
|
|
let v = run(&home, &["verify", dir.join("receipts").join(format!("{t}.intent.json")).to_str().unwrap()]);
|
|
assert!(v.status.success() && text(&v).contains("signature VALID"), "{t}: {}", text(&v));
|
|
// The CI script finds the command line it is given and runs the same test.
|
|
let ci = Command::new("sh").arg(dir.join("ci/check.sh")).env("WAI_QUANTUM", BIN).env("XDG_CONFIG_HOME", home.join("config")).output().unwrap();
|
|
assert!(ci.status.success(), "{t}: {}", text(&ci));
|
|
// The project's MCP entry is the one `mcp config` prints.
|
|
let entry: Value = serde_json::from_str(&std::fs::read_to_string(dir.join("mcp.json")).unwrap()).unwrap();
|
|
let printed: Value = serde_json::from_slice(&run(&home, &["mcp", "config"]).stdout).unwrap();
|
|
assert_eq!(entry, printed);
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_test_that_fails_exits_1_and_says_why() {
|
|
let home = scratch("fails");
|
|
let dir = home.join("p");
|
|
assert!(run(&home, &["new", dir.to_str().unwrap()]).status.success());
|
|
std::fs::write(dir.join("bell.qasm"), "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nc = measure q;\n").unwrap();
|
|
let o = run(&home, &["test", dir.to_str().unwrap()]);
|
|
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
|
assert!(text(&o).contains("fails") && text(&o).contains("FAILED"), "{}", text(&o));
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|
|
|
|
#[test]
|
|
fn setup_agent_twice_changes_the_file_once() {
|
|
let home = scratch("agents");
|
|
std::fs::write(home.join("AGENTS.md"), "# This repository\n\nRun the tests before you push.\n").unwrap();
|
|
let first = run(&home, &["setup-agent", home.to_str().unwrap()]);
|
|
assert!(first.status.success() && text(&first).contains("added"), "{}", text(&first));
|
|
let once = std::fs::read_to_string(home.join("AGENTS.md")).unwrap();
|
|
assert!(once.starts_with("# This repository\n\nRun the tests before you push.\n\n<!-- wai-quantum:start -->"), "{once}");
|
|
let second = run(&home, &["setup-agent", home.to_str().unwrap()]);
|
|
assert!(second.status.success() && text(&second).contains("unchanged"), "{}", text(&second));
|
|
assert_eq!(std::fs::read_to_string(home.join("AGENTS.md")).unwrap(), once);
|
|
assert!(run(&home, &["setup-agent", home.to_str().unwrap(), "--check"]).status.success());
|
|
// The block is the one `docs agents` prints.
|
|
let printed = String::from_utf8(run(&home, &["docs", "agents"]).stdout).unwrap();
|
|
assert!(once.ends_with(&printed), "{printed}");
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|
|
|
|
#[test]
|
|
fn mcp_install_merges_into_the_file_named() {
|
|
let home = scratch("install");
|
|
let file = home.join("client.json");
|
|
std::fs::write(&file, "{\"mcpServers\": {\"other\": {\"command\": \"x\"}}}").unwrap();
|
|
let o = run(&home, &["mcp", "install", "--file", file.to_str().unwrap()]);
|
|
assert!(o.status.success(), "{}", text(&o));
|
|
let v: Value = serde_json::from_str(&std::fs::read_to_string(&file).unwrap()).unwrap();
|
|
assert_eq!(v["mcpServers"]["other"]["command"], "x");
|
|
assert_eq!(v["mcpServers"]["wai-quantum"]["args"][0], "mcp");
|
|
let refused = run(&home, &["mcp", "install", "--file", file.to_str().unwrap(), "--memory-gb", "4"]);
|
|
assert!(!refused.status.success() && text(&refused).contains("--force"), "{}", text(&refused));
|
|
let forced = run(&home, &["mcp", "install", "--file", file.to_str().unwrap(), "--memory-gb", "4", "--force"]);
|
|
assert!(forced.status.success(), "{}", text(&forced));
|
|
let v: Value = serde_json::from_str(&std::fs::read_to_string(&file).unwrap()).unwrap();
|
|
assert_eq!(v["mcpServers"]["wai-quantum"]["args"], serde_json::json!(["mcp", "--memory-gb", "4"]));
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|
|
|
|
#[test]
|
|
fn the_docs_and_the_stdio_server_s_resources_agree() {
|
|
let home = scratch("docs");
|
|
let language = String::from_utf8(run(&home, &["docs", "language"]).stdout).unwrap();
|
|
assert!(language.starts_with("# The OpenQASM the engine reads"), "{language}");
|
|
let out = home.join("out");
|
|
assert!(run(&home, &["docs", "llms", "--out", out.to_str().unwrap()]).status.success());
|
|
assert!(out.join("llms.txt").is_file() && out.join("llms-full.txt").is_file());
|
|
assert!(run(&home, &["docs", "skill", "--out", out.to_str().unwrap()]).status.success());
|
|
assert!(out.join("wai-quantum/SKILL.md").is_file() && out.join("wai-quantum/reference/tools.md").is_file());
|
|
// The same text over MCP.
|
|
let mut child = Command::new(BIN).args(["mcp"]).env("XDG_CONFIG_HOME", home.join("config")).stdin(std::process::Stdio::piped()).stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::null()).spawn().unwrap();
|
|
{
|
|
use std::io::Write;
|
|
let mut stdin = child.stdin.take().unwrap();
|
|
writeln!(stdin, r#"{{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{{"uri":"wai-quantum://docs/language"}}}}"#).unwrap();
|
|
}
|
|
let o = child.wait_with_output().unwrap();
|
|
let reply: Value = serde_json::from_slice(&o.stdout).unwrap();
|
|
assert_eq!(reply["result"]["contents"][0]["text"].as_str(), Some(language.as_str()));
|
|
let _ = std::fs::remove_dir_all(&home);
|
|
}
|