//! What the command line tells coding agents, written where they read it. //! Every word comes from this binary, so it works offline and matches the //! installed version. //! //! - `wai-quantum setup-agent [DIR]`: a block in a project's `AGENTS.md`, //! between `` and ``. A //! rerun replaces the block and nothing outside it. //! - `wai-quantum mcp config` and `wai-quantum mcp install --file PATH`: the //! local MCP server's entry, in the common `mcpServers` JSON form, printed //! or merged into a configuration file the person names. //! - `wai-quantum docs …`: the servers' resources, the `AGENTS.md` block, //! `llms.txt` and `llms-full.txt`, and the Agent Skill. The tool lists in //! them are the servers' own: the local server's from [`crate::mcp`], the //! job service's from `docs/service-mcp.json`, which the service's own test //! holds to its live list. use clap::{Args, Subcommand, ValueEnum}; use serde_json::{Map, Value, json}; use std::path::{Path, PathBuf}; use wai_quantum::engine::guide as engine; pub const START: &str = ""; pub const END: &str = ""; /// The job service's MCP endpoint and API. pub const SERVICE_MCP: &str = "https://wai.transaction.science/quantum-jobs/mcp"; const SERVICE_API: &str = "https://wai.transaction.science/quantum-jobs/v1"; const SITE: &str = "https://wai.transaction.science"; const SOURCE: &str = "https://git.transaction.science/Transaction-Science/open-standards"; const README: &str = "https://git.transaction.science/Transaction-Science/open-standards/src/branch/main/wai/wai-quantum-cli/README.md"; /// What a write did. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Change { Created, Added, Refreshed, Replaced, Unchanged, } impl Change { pub fn word(self) -> &'static str { match self { Change::Created => "created", Change::Added => "added", Change::Refreshed => "refreshed", Change::Replaced => "replaced", Change::Unchanged => "unchanged", } } } /// The job service's MCP tools and instructions, as the service serves them. pub fn service() -> Value { serde_json::from_str(include_str!("../docs/service-mcp.json")).unwrap_or(Value::Null) } fn tools_of(v: &Value) -> Vec { v.as_array().cloned().unwrap_or_default() } fn tool_lines(tools: &[Value]) -> String { tools.iter().map(|t| format!("- `{}`: {}\n", t["name"].as_str().unwrap_or(""), t["title"].as_str().unwrap_or(""))).collect() } fn prompt_line() -> String { engine::PROMPTS.iter().map(|p| format!("`{}` ({})", p.name, p.title.to_lowercase())).collect::>().join(", ") } fn rule_lines() -> String { engine::RULES.iter().map(|r| format!("- {}\n", r.text)).collect() } /// The block `setup-agent` writes, markers included. pub fn block() -> String { format!( "{START} ## Quantum programs: wai-quantum This project's quantum programs are OpenQASM 3, read, run and checked by the `wai-quantum` engine on this machine. Every result names its method and its accuracy class and carries a signed receipt. ### How to work 1. Read a program with `wai-quantum lint FILE`: each problem comes with its line and a stable code. `wai-quantum repair FILE` (the MCP tool `repair_qasm`) makes the fixes that are mechanical, on the flagged lines alone, and rejects a proposed fix that changes any other line. 2. Check it does what it must: `wai-quantum test` reads `intent.json` (an exact distribution, or a reference program it must equal or whose state it must prepare) and runs the program with its intents. The engine's verdict states how far it checked (`proof`, `evidence`, `executed`, `parsed`, `refused`) and whether each intent holds, sealed in the run's receipt; signed receipts are saved in `receipts/`. It exits 0 only when every intent holds. For one run: `wai-quantum run FILE --intent ref.qasm` (or a distribution in JSON), or `intent` on `run_quantum_circuit`. Quote the verdict; claim nothing past its level. 3. Run it: `wai-quantum run FILE` (`--json` for the job and its receipt). `wai-quantum verify FILE.json` checks a receipt. 4. A program too large for this machine goes to the free job service: `wai-quantum submit FILE` (no account, no key). ### Tools The local MCP server, `wai-quantum mcp` over stdio (`wai-quantum mcp config` prints its entry): {} The job service's MCP server, streamable HTTP at {SERVICE_MCP} (no account, no key): {} Both serve resources (the language, its diagnostics, the methods, the verdict's levels and worked examples) and prompts: {}. Every answer about a program carries its verdict. The same text offline: `wai-quantum docs language`, `wai-quantum docs diagnostics`, `wai-quantum docs methods`, `wai-quantum docs verdicts`, `wai-quantum docs examples`. ### The language {} The full subset: `wai-quantum docs language` (the resource `{}`). Every refusal and warning ends with a stable code; each code, worked refusals and what to write instead: `wai-quantum docs diagnostics` (`{}`), or `wai-quantum diagnostics` for the table alone. ### Versions `wai-quantum version --json` gives the versions installed and the schema of each document; each MCP tool's `outputSchema` is its answer's. What each version promises and how a change is announced: {}. Deprecated items, with their replacements: {}. ### Rules {}{END}", tool_lines(&tools_of(&crate::mcp::tools())), tool_lines(&tools_of(&service()["tools"])), prompt_line(), engine::SUMMARY, engine::LANGUAGE, engine::DIAGNOSTICS, wai_quantum::engine::schema::POLICY, wai_quantum::engine::schema::DEPRECATIONS, rule_lines(), ) } /// `text` with `block` written into it: in place of the one block it holds, /// or after everything else when it holds none. Text outside the markers is /// kept byte for byte; markers that do not make one block are refused. pub fn merge_block(existing: Option<&str>, block: &str) -> Result<(String, Change), String> { let Some(text) = existing else { return Ok((format!("{block}\n"), Change::Created)); }; let nl = if text.contains("\r\n") { "\r\n" } else { "\n" }; let block = block.replace('\n', nl); // A marker is a line of its own; one named in prose is not a marker. let (mut starts, mut ends) = (Vec::new(), Vec::new()); let mut at = 0; for line in text.split_inclusive('\n') { let bare = line.trim(); let span = (at, at + line.trim_end_matches(['\r', '\n']).len()); if bare == START { starts.push(span); } else if bare == END { ends.push(span); } at += line.len(); } match (starts.as_slice(), ends.as_slice()) { ([], []) => { let sep = if text.is_empty() || text.ends_with(&format!("{nl}{nl}")) { String::new() } else if text.ends_with(nl) { nl.to_string() } else { format!("{nl}{nl}") }; Ok((format!("{text}{sep}{block}{nl}"), Change::Added)) } ([s], [e]) if s.0 < e.0 => { let merged = format!("{}{block}{}", &text[..s.0], &text[e.1..]); let change = if merged == text { Change::Unchanged } else { Change::Refreshed }; Ok((merged, change)) } _ => Err(format!("it has {} line(s) `{START}` and {} line(s) `{END}`, which make no one block; mend it by hand, nothing was written", starts.len(), ends.len())), } } /// The text of `path`, or `None` when there is no such file. pub fn read_if_there(path: &Path) -> Result, String> { match std::fs::read_to_string(path) { Ok(t) => Ok(Some(t)), Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), Err(e) => Err(format!("{}: {e}", path.display())), } } /// `text` written to `path` through a file beside it renamed into place, so /// a reader never sees half of it. A link is followed and its target written; /// the file keeps its permissions. fn write_atomically(path: &Path, text: &str) -> Result<(), String> { let path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()); if let Some(d) = path.parent().filter(|d| !d.as_os_str().is_empty()) { std::fs::create_dir_all(d).map_err(|e| format!("{}: {e}", d.display()))?; } let name = path.file_name().map(|f| f.to_string_lossy().into_owned()).unwrap_or_default(); let tmp = path.with_file_name(format!(".{name}.wai-quantum-{}", std::process::id())); std::fs::write(&tmp, text).map_err(|e| format!("{}: {e}", tmp.display()))?; if let Ok(m) = std::fs::metadata(&path) { let _ = std::fs::set_permissions(&tmp, m.permissions()); } std::fs::rename(&tmp, &path).map_err(|e| { let _ = std::fs::remove_file(&tmp); format!("{}: {e}", path.display()) }) } #[derive(Args)] pub struct SetupAgent { /// The project's directory (default: here) pub dir: Option, /// The instructions file, if not DIR/AGENTS.md #[arg(long, value_name = "FILE")] pub file: Option, /// Change nothing; exit 1 if the block is missing or not the one this version writes #[arg(long)] pub check: bool, } /// `wai-quantum setup-agent`. pub fn setup_agent(a: &SetupAgent) -> Result { let path = a.file.clone().unwrap_or_else(|| a.dir.clone().unwrap_or_else(|| PathBuf::from(".")).join("AGENTS.md")); let existing = read_if_there(&path)?; let (merged, change) = merge_block(existing.as_deref(), &block()).map_err(|e| format!("{}: {e}", path.display()))?; if a.check { let fresh = change == Change::Unchanged; println!("{}: {}", path.display(), if fresh { "the wai-quantum block is this version's" } else { "the wai-quantum block is missing or out of date: run wai-quantum setup-agent" }); return Ok(if fresh { 0 } else { 1 }); } if change != Change::Unchanged { write_atomically(&path, &merged)?; } println!("{}: the wai-quantum block {}", path.display(), change.word()); Ok(0) } #[derive(Subcommand)] pub enum McpCmd { /// Print the local server's entry for an agent client's configuration, in the common `mcpServers` JSON form Config(Entry), /// Merge the local server's entry into a JSON configuration file you name, keeping every other entry Install { /// The client's configuration file; made if it is not there #[arg(long, value_name = "PATH")] file: PathBuf, /// Replace a different entry already under the name #[arg(long)] force: bool, #[command(flatten)] entry: Entry, }, } /// The local server's entry: its name in the configuration, the command a /// client starts, and what it passes. #[derive(Args, Clone, Debug)] pub struct Entry { /// The name the entry goes under #[arg(long, default_value = "wai-quantum")] pub name: String, /// The command the client starts: wai-quantum, or its full path when the client does not search your PATH #[arg(long, default_value = "wai-quantum")] pub command: String, /// The memory a job may take, GiB, when not the server's default #[arg(long)] pub memory_gb: Option, /// The key file the server signs with, when not this user's own #[arg(long, value_name = "FILE")] pub key: Option, } impl Default for Entry { fn default() -> Entry { Entry { name: "wai-quantum".into(), command: "wai-quantum".into(), memory_gb: None, key: None } } } /// The server's entry: `{"command", "args"}`. pub fn entry(e: &Entry) -> Value { let mut args = vec![json!("mcp")]; if let Some(m) = e.memory_gb { args.extend([json!("--memory-gb"), json!(m.to_string())]); } if let Some(k) = &e.key { // A client starts the server from a directory of its own choosing. let k = std::path::absolute(k).unwrap_or_else(|_| k.clone()); args.extend([json!("--key"), json!(k.display().to_string())]); } json!({ "command": e.command, "args": args }) } /// The configuration that holds only this entry. pub fn config(e: &Entry) -> Value { let mut servers = Map::new(); servers.insert(e.name.clone(), entry(e)); json!({ "mcpServers": servers }) } fn pretty(v: &Value) -> String { serde_json::to_string_pretty(v).unwrap_or_default() + "\n" } /// `existing` (a client's JSON configuration, or `None` for no file) with /// the entry merged into its `mcpServers`: every other key and entry kept in /// its order, and a different entry under the name refused unless `force`. pub fn merge_config(existing: Option<&str>, e: &Entry, force: bool) -> Result<(String, Change), String> { let want = entry(e); let blank = existing.is_none_or(|t| t.trim().is_empty()); let mut root: Value = if blank { json!({}) } else { serde_json::from_str(existing.unwrap_or_default()).map_err(|err| format!("not JSON ({err}); a file with comments is not read here; nothing was written"))? }; let o = root.as_object_mut().ok_or("not a JSON object; nothing was written")?; let servers = o.entry("mcpServers").or_insert_with(|| json!({})); let s = servers.as_object_mut().ok_or("its \"mcpServers\" is not an object; nothing was written")?; let change = match s.get(&e.name) { None if blank => Change::Created, None => Change::Added, Some(have) if *have == want => return Ok((existing.unwrap_or_default().to_string(), Change::Unchanged)), Some(have) if !force => return Err(format!("a different `{}` entry is there ({have}); --force replaces it; nothing was written", e.name)), Some(_) => Change::Replaced, }; s.insert(e.name.clone(), want); Ok((pretty(&root), change)) } /// `wai-quantum mcp config` and `wai-quantum mcp install`. pub fn mcp(cmd: &McpCmd) -> Result<(), String> { match cmd { McpCmd::Config(e) => print!("{}", pretty(&config(e))), McpCmd::Install { file, force, entry: e } => { let existing = read_if_there(file)?; let (merged, change) = merge_config(existing.as_deref(), e, *force).map_err(|err| format!("{}: {err}", file.display()))?; if change != Change::Unchanged { write_atomically(file, &merged)?; } println!("{}: the `{}` entry {}", file.display(), e.name, change.word()); } } Ok(()) } #[derive(Args)] pub struct Docs { /// What to print; with none, the list pub topic: Option, /// Write into this directory instead of printing: llms writes llms.txt and llms-full.txt, skill writes wai-quantum/SKILL.md and its references #[arg(long, value_name = "DIR")] pub out: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] pub enum Topic { /// The OpenQASM the engine reads (a resource of both MCP servers) Language, /// The reader's diagnostics: each refusal, and what to write instead Diagnostics, /// The methods and their accuracy classes, at this machine's default job memory Methods, /// The verdict: the levels every answer states, what a verdict records, the intents and the repairs Verdicts, /// The worked examples, each with what it must do Examples, /// The block setup-agent writes into AGENTS.md Agents, /// llms.txt: what agents are told, with the servers' tool lists Llms, /// llms-full.txt: all of it, with every tool's parameters and the command line's help LlmsFull, /// The Agent Skill's SKILL.md (with --out, the skill's directory) Skill, } /// The skill's files, apart from the tool list, which is generated. const SKILL: [(&str, &str); 4] = [ ("SKILL.md", include_str!("../skills/wai-quantum/SKILL.md")), ("reference/install.md", include_str!("../skills/wai-quantum/reference/install.md")), ("reference/workflow.md", include_str!("../skills/wai-quantum/reference/workflow.md")), ("reference/receipts.md", include_str!("../skills/wai-quantum/reference/receipts.md")), ]; /// Every file of the skill, the generated tool list included. pub fn skill_files() -> Vec<(String, String)> { let mut f: Vec<(String, String)> = SKILL.iter().map(|(p, t)| (p.to_string(), t.to_string())).collect(); f.push(("reference/tools.md".into(), skill_tools())); f } /// The skill's tool list, from the servers' own lists. pub fn skill_tools() -> String { let resources: String = tools_of(&engine::resources()).iter().map(|r| format!("- `{}`: {}\n", r["uri"].as_str().unwrap_or(""), r["title"].as_str().unwrap_or(""))).collect(); let prompts: String = engine::PROMPTS.iter().map(|p| format!("- `{}`: {}\n", p.name, p.title)).collect(); format!( "# The tools, resources and prompts Generated by `wai-quantum docs skill` from the servers' own lists; `wai-quantum docs llms-full` gives each tool's parameters. ## The local MCP server: `wai-quantum mcp`, over stdio {} ## The job service's MCP server: {SERVICE_MCP} {} ## Resources, on both servers {resources} ## Prompts, on both servers {prompts}", tool_lines(&tools_of(&crate::mcp::tools())), tool_lines(&tools_of(&service()["tools"])), ) } /// The summary both `llms` files open with. fn header(title: &str) -> String { format!( "# {title} > OpenQASM 3 programs run on classical hardware by one deterministic engine: on this machine with the `wai-quantum` command line or its MCP server, in a browser tab, or on a free job service with no account and no key. Every result names its method and its accuracy class and carries a signed receipt anyone can check. Generated by `wai-quantum docs llms` from the tool lists both MCP servers serve, their resources and prompts, and the command line's own help. Read the versions from the installed command line (`wai-quantum version --json`). ## Rules for agents {}", rule_lines() ) } const START_HERE: &str = "## Start here - Install the command line: `cargo install wai-quantum-cli` (the binary is `wai-quantum`). Prebuilt archives and other ways to install it: [the command line's README](README). - `wai-quantum new my-circuit` makes a project: a program, what it must do (`intent.json`), a test that checks it with the engine and saves signed receipts, CI, an MCP entry and `AGENTS.md`. `sh my-circuit/test.sh` passes as made. `--template` takes bell, ghz, grover or noisy-bell. - `wai-quantum test DIR` checks each intent (an exact distribution, or a reference program to equal or whose state to prepare) and says whether the engine decided it by a proof or by evidence. - `wai-quantum setup-agent` writes these rules and the tool lists into a project's `AGENTS.md`, between `` and ``. - `wai-quantum mcp config` prints the local MCP server's entry; `wai-quantum mcp install --file PATH` merges it into a client's JSON configuration. - `wai-quantum learn` runs “Quantum circuits, checked”, the course for a person new to quantum computing: from a first circuit to resource estimates, each exercise checked by the engine's verdict, in English or Simplified Chinese (`--lang zh-CN`). "; /// How a version is read and where the policy is, for every file agents read. fn versions_section() -> String { use wai_quantum::engine::schema::{DEPRECATIONS, POLICY}; format!( "## Versions and stability `wai-quantum version --json` gives this binary's version, the engine's, the schema of each document it writes and the protocols it speaks. Both MCP servers give the same in `initialize`, under `_meta[\"{}\"]`, and each tool's `outputSchema` is its answer's schema. Every result names the engine that made it and the schema it follows (`engine`, `schema`), inside what its receipt seals. - [What is stable, and for how long]({POLICY}): the public surface (the engine's API, the command line's verbs, flags and `--json`, the job JSON, both servers' tools, the receipt, the Python package, the provider protocol, the editor's settings), what a version number promises, the support windows, and how a change is announced before it happens. - [Deprecations]({DEPRECATIONS}): each deprecated item, the version that deprecated it, and its replacement. ", crate::mcp::VERSIONS_META ) } fn links() -> String { format!( "## The job service over HTTP - [API]({SERVICE_API}): send an OpenQASM program; get counts, expectation values or amplitudes, with the method, its accuracy class and a signed receipt. - [OpenAPI description]({SERVICE_API}/openapi.json) - [Worked examples]({SERVICE_API}/examples): ready-to-run jobs, each with what it shows. ## Pages - [WAI Quantum]({SITE}/quantum/): the home: a circuit run on the job service with its receipt checked, every way to install it, the courses, the lab toolkit and the setup for agents. - [Quantum Studio]({SITE}/quantum-studio/): an editor that checks a program as it is typed and runs it in the tab or on the job service; `#course` opens its course, “Quantum circuits, checked”. - [The job console]({SITE}/quantum-run/): the job service from a browser, or the same engine in the tab. - [The circuit builder]({SITE}/quantum-ide/): build a circuit gate by gate in the browser tab and inspect it. - [Quantum algorithmic design]({SITE}/quantum-learn/): a course on designing with interference on classical hardware, each lab run in the tab. - [WAI]({SITE}/): the media transport and execution standard this quantum stack is part of. - [Source]({SOURCE}): the engine, the command line and the service. " ) } /// `llms.txt`. pub fn llms() -> String { let resources: String = tools_of(&engine::resources()).iter().map(|r| format!("- `{}`: {}\n", r["uri"].as_str().unwrap_or(""), r["title"].as_str().unwrap_or(""))).collect(); let prompts: String = engine::PROMPTS.iter().map(|p| format!("- `{}`: {}\n", p.name, p.title)).collect(); format!( "{} {} ## Receipts {} {} ## The local MCP server: `wai-quantum mcp` (stdio) Runs on this machine, signed with the user's key; nothing leaves the machine. {} ## The job service's MCP server - [MCP endpoint]({SERVICE_MCP}): streamable HTTP, no account and no key, for programs too large for this machine. Its tools: {} ## Resources and prompts, on both servers Resources: {resources} Prompts: {prompts} {} ## Optional - [llms-full.txt]({SITE}/llms-full.txt): every tool with its parameters, the resources in full, the prompts, and the command line's help. ", header("WAI Quantum"), START_HERE.replace("(README)", &format!("({README})")), engine::RECEIPTS, versions_section(), tool_lines(&tools_of(&crate::mcp::tools())), tool_lines(&tools_of(&service()["tools"])).lines().map(|l| format!(" {l}\n")).collect::(), links(), ) } /// A tool in full: its description and its parameters. fn tool_full(t: &Value) -> String { let schema = &t["inputSchema"]; let required: Vec<&str> = schema["required"].as_array().into_iter().flatten().filter_map(Value::as_str).collect(); let mut s = format!("### `{}`: {}\n\n{}\n", t["name"].as_str().unwrap_or(""), t["title"].as_str().unwrap_or(""), t["description"].as_str().unwrap_or("")); let props = schema["properties"].as_object().filter(|p| !p.is_empty()); if let Some(props) = props { s.push_str("\nParameters:\n"); for (k, p) in props { let ty = p["type"].as_str().unwrap_or("any"); let need = if required.contains(&k.as_str()) { ", required" } else { "" }; let one_of = p["enum"].as_array().map(|e| format!(", one of {}", e.iter().filter_map(Value::as_str).collect::>().join(", "))).unwrap_or_default(); let what = p["description"].as_str().map(|d| format!(": {d}")).unwrap_or_default(); s.push_str(&format!("- `{k}` ({ty}{need}{one_of}){what}\n")); } } s.push('\n'); s } /// The command line's help, page by page: the command, then each of its /// subcommands and theirs, at a fixed width. fn help_pages() -> Vec<(String, String)> { fn fixed(c: clap::Command) -> clap::Command { c.term_width(100).mut_args(|a| a.hide_env_values(true)).mut_subcommands(fixed) } let mut root = fixed(::command()); root.build(); let page = |c: &clap::Command| (c.get_bin_name().unwrap_or(c.get_name()).to_string(), c.clone().render_long_help().to_string().lines().map(str::trim_end).collect::>().join("\n").trim_end().to_string()); let mut pages = vec![page(&root)]; for sc in root.get_subcommands().filter(|c| c.get_name() != "help") { pages.push(page(sc)); for sub in sc.get_subcommands().filter(|c| c.get_name() != "help") { pages.push(page(sub)); } } pages } /// `llms-full.txt`. pub fn llms_full() -> String { let mut s = header("WAI Quantum, in full"); s.push('\n'); s.push_str(&START_HERE.replace("(README)", &format!("({README})"))); s.push_str(&format!("\n## Receipts\n\n{}\n\n", engine::RECEIPTS)); s.push_str(&versions_section()); s.push('\n'); s.push_str("## The local MCP server's tools: `wai-quantum mcp` (stdio)\n\nRuns on this machine, signed with the user's key; nothing leaves the machine.\n\n"); let local = crate::mcp::tools(); for t in tools_of(&local) { s.push_str(&tool_full(&t)); } s.push_str(&format!("## The job service's MCP tools: {SERVICE_MCP}\n\nStreamable HTTP, no account and no key.\n\n")); for t in tools_of(&service()["tools"]) { s.push_str(&tool_full(&t)); } s.push_str("## Resources, on both servers\n\n"); // The language, its headings set under this one. s.push_str(&format!("### `{}`: the OpenQASM the engine reads\n\n", engine::LANGUAGE)); for line in engine::language().lines().skip(1) { match line.strip_prefix("## ") { Some(h) => s.push_str(&format!("#### {h}\n")), None => s.push_str(&format!("{line}\n")), } } let d = engine::diagnostics(); let cell = |v: &Value| v.as_str().unwrap_or("").replace('|', "\\|"); s.push_str(&format!("\n### `{}`: the reader's diagnostics\n\n{}\n\nWorked refusals, each what the reader gives its example:\n\n| Code | Meaning | Write instead |\n|---|---|---|\n", engine::DIAGNOSTICS, d["note"].as_str().unwrap_or(""))); for e in d["examples"].as_array().into_iter().flatten() { s.push_str(&format!("| `{}` | {} | {} |\n", cell(&e["code"]), cell(&e["meaning"]), cell(&e["fix"]))); } s.push_str("\nEvery code:\n\n| Code | Outcome | Meaning |\n|---|---|---|\n"); for c in d["codes"].as_array().into_iter().flatten() { s.push_str(&format!("| `{}` | {} | {} |\n", cell(&c["code"]), cell(&c["outcome"]), cell(&c["meaning"]))); } // The methods' reach depends on the machine; what each takes does not. s.push_str(&format!("\n### `{}`: the methods and their accuracy classes\n\n| Method | Accuracy | Circuits | Answers |\n|---|---|---|---|\n", engine::METHODS)); for m in tools_of(&wai_quantum::engine::catalog::describe(1 << 30)) { let answers: Vec<&str> = m["answers"].as_array().into_iter().flatten().filter_map(Value::as_str).collect(); s.push_str(&format!("| `{}` | {} | {} | {} |\n", m["method"].as_str().unwrap_or(""), m["accuracy"].as_str().unwrap_or(""), m["circuits"].as_str().unwrap_or(""), answers.join(", "))); } s.push_str("\nThe accuracy classes:\n\n"); for (k, v) in wai_quantum::engine::catalog::accuracy_classes().as_object().into_iter().flatten() { s.push_str(&format!("- {k}: {}\n", v.as_str().unwrap_or(""))); } s.push_str(&format!("\n{}\n\n{}\n", engine::AUTO, engine::FABRIC)); s.push_str(&format!("\n### `{}`: worked examples, each with what it must do\n\n", engine::EXAMPLES)); for e in engine::examples() { s.push_str(&format!("#### {} (`{}`)\n\n{}\n\n```qasm\n{}```\n\n", e.title, e.file(), e.shows, e.program)); if let Some(r) = e.reference { s.push_str(&format!("`reference.qasm`:\n\n```qasm\n{r}```\n\n")); } s.push_str(&format!("`intent.json`:\n\n```json\n{}\n```\n\n", serde_json::to_string_pretty(&e.intent_file()).unwrap_or_default())); } s.push_str("## Prompts, on both servers\n\n"); for p in &engine::PROMPTS { s.push_str(&format!("### `{}`: {}\n\n{}\n\nArguments:\n", p.name, p.title, p.description)); for (n, d, r) in p.arguments { s.push_str(&format!("- `{n}`{}: {d}\n", if *r { " (required)" } else { "" })); } s.push('\n'); } s.push_str("## The command line\n\n"); for (name, help) in help_pages() { s.push_str(&format!("### `{name}`\n\n```text\n{help}\n```\n\n")); } s.push_str(&links()); s } /// `wai-quantum docs`. pub fn docs(d: &Docs) -> Result<(), String> { let write = |dir: &Path, files: &[(String, String)]| -> Result<(), String> { for (p, text) in files { let path = dir.join(p); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).map_err(|e| format!("{}: {e}", parent.display()))?; } std::fs::write(&path, text).map_err(|e| format!("{}: {e}", path.display()))?; println!("wrote {}", path.display()); } Ok(()) }; let Some(topic) = d.topic else { // The list in the person's language; each topic's text stays as agents read it. let loc = crate::locale::current(); println!("{}", crate::locale::t("docs.header", &[])); for t in Topic::value_variants() { if let Some(v) = t.to_possible_value() { let english = v.get_help().map(|h| h.to_string()).unwrap_or_default(); println!(" {:<12} {}", v.get_name(), crate::locale::topic(loc, v.get_name(), &english).unwrap_or(&english)); } } return Ok(()); }; let resource = |uri: &str| engine::read(uri, crate::DEFAULT_MEMORY_GB << 30).map(|c| c["text"].as_str().unwrap_or("").to_string()); match (topic, &d.out) { (Topic::Llms | Topic::LlmsFull, Some(dir)) => write(dir, &[("llms.txt".into(), llms()), ("llms-full.txt".into(), llms_full())]), (Topic::Skill, Some(dir)) => write(&dir.join("wai-quantum"), &skill_files()), (_, Some(_)) => Err("--out takes llms, llms-full or skill".into()), (Topic::Language, None) => resource(engine::LANGUAGE).map(|t| print!("{t}")), (Topic::Diagnostics, None) => resource(engine::DIAGNOSTICS).map(|t| print!("{t}")), (Topic::Methods, None) => resource(engine::METHODS).map(|t| print!("{t}")), (Topic::Verdicts, None) => resource(engine::VERDICTS).map(|t| print!("{t}")), (Topic::Examples, None) => resource(engine::EXAMPLES).map(|t| print!("{t}")), (Topic::Agents, None) => { println!("{}", block()); Ok(()) } (Topic::Llms, None) => { print!("{}", llms()); Ok(()) } (Topic::LlmsFull, None) => { print!("{}", llms_full()); Ok(()) } (Topic::Skill, None) => { print!("{}", SKILL[0].1); Ok(()) } } } #[cfg(test)] mod tests { use super::*; fn names(tools: &Value) -> Vec { tools_of(tools).iter().map(|t| t["name"].as_str().unwrap().to_string()).collect() } /// The names in the first list of `` - `name` `` lines after `heading`, in order. fn listed(text: &str, heading: &str) -> Vec { let at = text.find(heading).unwrap_or_else(|| panic!("no {heading:?}")); let item = |l: &str| l.trim_start().strip_prefix("- `").and_then(|l| l.split('`').next()).map(str::to_string); text[at..].lines().skip(1).skip_while(|l| item(l).is_none()).map_while(item).collect() } #[test] fn the_block_is_written_refreshed_and_never_touches_what_is_outside() { let b = block(); let (made, c) = merge_block(None, &b).unwrap(); assert_eq!((made.as_str(), c), (format!("{b}\n").as_str(), Change::Created)); // Added after the person's text, which stays byte for byte. let mine = "# My project\n\nKeep tests green.\nMention in prose is no marker."; let (added, c) = merge_block(Some(mine), &b).unwrap(); assert_eq!(c, Change::Added); assert!(added.starts_with(mine) && added.ends_with(&format!("{b}\n")), "{added}"); // A rerun changes nothing; an old block is replaced, the rest kept. assert_eq!(merge_block(Some(&added), &b).unwrap(), (added.clone(), Change::Unchanged)); let around = format!("before\n{START}\nold words\n{END}\nafter\n"); let (fresh, c) = merge_block(Some(&around), &b).unwrap(); assert_eq!(c, Change::Refreshed); assert_eq!(fresh, format!("before\n{b}\nafter\n")); assert_eq!(merge_block(Some(&fresh), &b).unwrap().1, Change::Unchanged); // Markers that make no one block are refused. for bad in [format!("{START}\nx\n"), format!("{END}\nx\n{START}\n"), format!("{START}\n{END}\n{START}\n{END}\n"), format!("x\n{END}\n")] { assert!(merge_block(Some(&bad), &b).unwrap_err().contains("nothing was written"), "{bad}"); } // A file with CRLF line endings gets the block in its own endings. let (crlf, _) = merge_block(Some("one\r\ntwo\r\n"), &b).unwrap(); assert!(crlf.starts_with("one\r\ntwo\r\n\r\n") && !crlf.replace("\r\n", "").contains('\n'), "{crlf:?}"); assert_eq!(merge_block(Some(&crlf), &b).unwrap().1, Change::Unchanged); } #[test] fn setup_agent_writes_the_file_once_and_a_rerun_leaves_it() { let dir = std::env::temp_dir().join(format!("wai-quantum-setup-agent-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let file = dir.join("AGENTS.md"); std::fs::write(&file, "# Rules of this repository\n\nBe kind.\n").unwrap(); let a = SetupAgent { dir: Some(dir.clone()), file: None, check: false }; let check = SetupAgent { dir: Some(dir.clone()), file: None, check: true }; assert_eq!(setup_agent(&check).unwrap(), 1); assert_eq!(setup_agent(&a).unwrap(), 0); let once = std::fs::read_to_string(&file).unwrap(); assert!(once.starts_with("# Rules of this repository\n\nBe kind.\n\n"), "{once}"); assert_eq!(setup_agent(&a).unwrap(), 0); assert_eq!(std::fs::read_to_string(&file).unwrap(), once); assert_eq!(setup_agent(&check).unwrap(), 0); let _ = std::fs::remove_dir_all(&dir); } #[test] fn the_block_lists_the_live_tools_and_keeps_every_rule() { let b = block(); assert_eq!(listed(&b, "The local MCP server"), names(&crate::mcp::tools())); assert_eq!(listed(&b, "The job service's MCP server"), names(&service()["tools"])); assert!(engine::RULES.iter().all(|r| b.contains(r.text)), "{b}"); assert!(b.starts_with(START) && b.ends_with(END) && b.matches(START).count() == 1); } #[test] fn mcp_install_merges_keeps_other_entries_and_refuses_a_different_one() { let e = Entry::default(); assert_eq!(config(&e), json!({ "mcpServers": { "wai-quantum": { "command": "wai-quantum", "args": ["mcp"] } } })); let (made, c) = merge_config(None, &e, false).unwrap(); assert_eq!((serde_json::from_str::(&made).unwrap(), c), (config(&e), Change::Created)); // Other entries and keys are kept, in their order. let theirs = r#"{"theme": "dark", "mcpServers": {"other": {"command": "other-server", "args": []}}, "zeta": 1}"#; let (merged, c) = merge_config(Some(theirs), &e, false).unwrap(); assert_eq!(c, Change::Added); let v: Value = serde_json::from_str(&merged).unwrap(); assert_eq!(v.as_object().unwrap().keys().collect::>(), ["theme", "mcpServers", "zeta"]); assert_eq!(v["mcpServers"].as_object().unwrap().keys().collect::>(), ["other", "wai-quantum"]); assert_eq!(v["mcpServers"]["other"], json!({ "command": "other-server", "args": [] })); // The same entry again changes nothing; a different one is refused without --force. assert_eq!(merge_config(Some(&merged), &e, false).unwrap(), (merged.clone(), Change::Unchanged)); let wider = Entry { memory_gb: Some(8), ..Entry::default() }; assert!(merge_config(Some(&merged), &wider, false).unwrap_err().contains("--force")); let (forced, c) = merge_config(Some(&merged), &wider, true).unwrap(); assert_eq!(c, Change::Replaced); let v: Value = serde_json::from_str(&forced).unwrap(); assert_eq!(v["mcpServers"]["wai-quantum"]["args"], json!(["mcp", "--memory-gb", "8"])); assert_eq!(v["mcpServers"].as_object().unwrap().keys().collect::>(), ["other", "wai-quantum"]); // A file that is not a JSON object, or whose mcpServers is not one, is left alone. for bad in ["[1, 2]", "{\"mcpServers\": []}", "{ // a comment\n}", "{"] { assert!(merge_config(Some(bad), &e, false).unwrap_err().contains("nothing was written"), "{bad}"); } // On disk: made, merged, and refused without a change. let dir = std::env::temp_dir().join(format!("wai-quantum-mcp-install-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); let file = dir.join("nested").join("client.json"); mcp(&McpCmd::Install { file: file.clone(), force: false, entry: e.clone() }).unwrap(); assert_eq!(serde_json::from_str::(&std::fs::read_to_string(&file).unwrap()).unwrap(), config(&e)); std::fs::write(&file, theirs).unwrap(); mcp(&McpCmd::Install { file: file.clone(), force: false, entry: e.clone() }).unwrap(); let after = std::fs::read_to_string(&file).unwrap(); assert!(mcp(&McpCmd::Install { file: file.clone(), force: false, entry: wider.clone() }).is_err()); assert_eq!(std::fs::read_to_string(&file).unwrap(), after); let left: Vec<_> = std::fs::read_dir(file.parent().unwrap()).unwrap().map(|e| e.unwrap().file_name()).collect(); assert_eq!(left.len(), 1, "no temporary file is left behind: {left:?}"); let _ = std::fs::remove_dir_all(&dir); } #[test] fn the_docs_list_exactly_the_servers_tools_and_name_no_version() { let local = names(&crate::mcp::tools()); let remote = names(&service()["tools"]); assert!(!remote.is_empty(), "docs/service-mcp.json holds the service's tools"); let short = llms(); assert_eq!(listed(&short, "## The local MCP server"), local); assert_eq!(listed(&short, "## The job service's MCP server"), remote); assert_eq!(listed(&skill_tools(), "## The local MCP server"), local); assert_eq!(listed(&skill_tools(), "## The job service's MCP server"), remote); let full = llms_full(); for t in tools_of(&crate::mcp::tools()).iter().chain(tools_of(&service()["tools"]).iter()) { assert!(full.contains(&format!("### `{}`: {}\n", t["name"].as_str().unwrap(), t["title"].as_str().unwrap())), "{}", t["name"]); } assert!(full.contains("### `wai-quantum new`") && full.contains("### `wai-quantum discover new`"), "the help of every command"); assert!(engine::RULES.iter().all(|r| short.contains(r.text) && full.contains(r.text))); // They say where the policy and the deprecations are, as does the // block setup-agent writes. for text in [&short, &full, &block()] { assert!(text.contains(wai_quantum::engine::schema::POLICY) && text.contains(wai_quantum::engine::schema::DEPRECATIONS) && text.contains("wai-quantum version --json"), "{text}"); } // A version in the docs would drift from the installed one. assert!(!short.contains(env!("CARGO_PKG_VERSION")) && !full.contains(concat!("wai-quantum ", env!("CARGO_PKG_VERSION")))); } /// `want` is the committed file at `path`; WAI_QUANTUM_BLESS=1 writes it instead. #[cfg(feature = "service")] fn committed(path: &str, want: &str) { let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(path); if std::env::var_os("WAI_QUANTUM_BLESS").is_some() { std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(&path, want).unwrap(); return; } let have = std::fs::read_to_string(&path).unwrap_or_default(); assert!(have == want, "{} is not what this binary generates: run `wai-quantum docs llms --out docs` and `wai-quantum docs skill --out skills` in wai/wai-quantum-cli (or the tests with WAI_QUANTUM_BLESS=1)", path.display()); } // The committed files are the default build's, which has `submit`. #[cfg(feature = "service")] #[test] fn the_committed_docs_are_what_the_binary_generates() { committed("docs/llms.txt", &llms()); committed("docs/llms-full.txt", &llms_full()); committed("skills/wai-quantum/reference/tools.md", &skill_tools()); } #[test] fn the_skill_carries_every_rule_and_sends_nothing_elsewhere() { let skill = SKILL[0].1; let front = skill.strip_prefix("---\n").and_then(|s| s.split_once("\n---\n")).map(|(f, _)| f).expect("SKILL.md opens with frontmatter"); assert!(front.lines().any(|l| l == "name: wai-quantum"), "{front}"); let description = front.lines().find_map(|l| l.strip_prefix("description: ")).expect("a description"); assert!(description.len() > 80 && description.len() <= 1024, "{description}"); for r in engine::RULES { assert!(skill.contains(r.text), "SKILL.md lacks the rule {}", r.id); } // Every address in the skill is this project's own. for (p, text) in skill_files() { for url in text.split(|c: char| c.is_whitespace() || "()<>`\"'".contains(c)).filter(|w| w.starts_with("http")) { assert!(url.starts_with(SITE) || url.starts_with("https://git.transaction.science/Transaction-Science/"), "{p}: {url}"); } } } #[test] fn the_front_door_verbs_parse_as_documented() { use clap::Parser; let parse = |args: &[&str]| crate::Cli::try_parse_from(args.iter().copied()).map(|c| c.command); assert!(matches!(parse(&["wai-quantum", "mcp"]), Ok(crate::Command::Mcp { cmd: None, .. }))); assert!(matches!(parse(&["wai-quantum", "mcp", "--memory-gb", "4"]), Ok(crate::Command::Mcp { cmd: None, memory_gb: 4, .. }))); match parse(&["wai-quantum", "mcp", "install", "--file", "c.json", "--force", "--name", "q"]) { Ok(crate::Command::Mcp { cmd: Some(McpCmd::Install { file, force: true, entry }), .. }) => assert_eq!((file, entry.name.as_str(), entry.command.as_str()), (PathBuf::from("c.json"), "q", "wai-quantum")), _ => panic!("mcp install"), } assert!(matches!(parse(&["wai-quantum", "mcp", "config"]), Ok(crate::Command::Mcp { cmd: Some(McpCmd::Config(_)), .. }))); assert!(parse(&["wai-quantum", "mcp", "install"]).is_err(), "install takes --file"); assert!(matches!(parse(&["wai-quantum", "new", "d", "--template", "grover"]), Ok(crate::Command::New(_)))); assert!(parse(&["wai-quantum", "new", "d", "--template", "teleport"]).is_err()); assert!(matches!(parse(&["wai-quantum", "test"]), Ok(crate::Command::Test(_)))); assert!(matches!(parse(&["wai-quantum", "setup-agent", ".", "--check"]), Ok(crate::Command::SetupAgent(SetupAgent { check: true, .. })))); assert!(matches!(parse(&["wai-quantum", "docs", "llms-full"]), Ok(crate::Command::Docs(Docs { topic: Some(Topic::LlmsFull), .. })))); assert!(parse(&["wai-quantum", "docs", "nothing"]).is_err()); } #[test] fn the_registry_manifest_names_this_package_and_version() { let v: Value = serde_json::from_str(include_str!("../server.json")).unwrap(); assert_eq!(v["version"], env!("CARGO_PKG_VERSION")); let name = v["name"].as_str().unwrap(); let (ns, n) = name.split_once('/').unwrap(); assert!(ns.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'-') && n.bytes().all(|b| b.is_ascii_alphanumeric() || b"._-".contains(&b)), "{name}"); assert!(v["description"].as_str().unwrap().len() <= 100); let p = &v["packages"][0]; assert_eq!((p["identifier"].as_str(), p["version"].as_str()), (Some(env!("CARGO_PKG_NAME")), Some(env!("CARGO_PKG_VERSION")))); assert_eq!(p["packageArguments"][0]["value"], "mcp"); assert_eq!(p["transport"]["type"], "stdio"); // The registry finds this token in the README as crates.io renders it, which drops // HTML comments: it is shown as code, and the `` after it ends the name. let token = format!("`mcp-name: {name}`"); let readme = include_str!("../README.md"); assert!(readme.contains(&token) && !readme.contains(&format!("