open-standards/wai/wai-quantum-cli/src/guide.rs
Transaction Science 1bc4164367
All checks were successful
wai-quantum editors / does this change touch it (pull_request) Successful in 8s
wai-quantum contract / does this change touch it (pull_request) Successful in 8s
wai-quantum for Python / does this change touch it (pull_request) Successful in 10s
wai-quantum service / does this change touch it (pull_request) Successful in 10s
wai-quantum command line / does this change touch it (pull_request) Successful in 11s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 11s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 12s
wai-quantum docs, course and locales / does this change touch it (pull_request) Successful in 14s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 8s
wai-quantum contract / the policy is linked, and the deprecations page reads (pull_request) Successful in 8s
wai-quantum views in agent hosts / does this change touch it (pull_request) Successful in 13s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 35s
wai-quantum editors / editors · macos (pull_request) Successful in 1m31s
wai-quantum service / service energy · linux packages (pull_request) Successful in 1m38s
wai-quantum for Python / python · macos (pull_request) Successful in 1m59s
wai-quantum command line / command line · macos (pull_request) Successful in 2m5s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m29s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (pull_request) Successful in 19s
wai-quantum editors / editors · linux (pull_request) Successful in 2m51s
wai-quantum docs, course and locales / the course and the command line's languages · linux (pull_request) Successful in 2m13s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 3m14s
wai-quantum for Python / python · linux (pull_request) Successful in 3m31s
wai-quantum service / service · macos (pull_request) Successful in 3m52s
wai-quantum contract / API compatibility · linux (pull_request) Successful in 4m56s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m3s
wai-quantum service / service · linux (pull_request) Successful in 5m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m17s
wai-quantum contract / schemas and the committed tool lists · linux (pull_request) Successful in 6m39s
wai-quantum views in agent hosts / views · the page and its engine (pull_request) Successful in 3m51s
wai-quantum views in agent hosts / views · a scripted host (pull_request) Successful in 3m39s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m7s
wai-quantum command line / command line · linux (pull_request) Successful in 8m54s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m41s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 8m33s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 10m35s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 16m15s
wai-quantum-cli docs: llms.txt names the site's parts as the site does, and lists WAI Quantum's home
The site gave each part of WAI Quantum one name (wai-transaction-science-web cf63cd3), but
the generated agent docs still called them by their old names: "the console", "the quantum
IDE", and "the circuits course" for a course whose title is "Quantum circuits, checked". They
also did not list /quantum/, now the front door.

guide.rs's Pages section now lists WAI Quantum's home first, then Quantum Studio, the job
console and the circuit builder. It says "the job service" where it meant the service, and
names the course by its title, as does the Start here line for `wai-quantum learn`.
docs/llms.txt and docs/llms-full.txt are generated again from it, and the guide's tests (the
committed docs are what the binary generates) pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:44:32 -05:00

884 lines
44 KiB
Rust

//! 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 `<!-- wai-quantum:start -->` and `<!-- wai-quantum:end -->`. 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 = "<!-- wai-quantum:start -->";
pub const END: &str = "<!-- wai-quantum:end -->";
/// 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<Value> {
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::<Vec<_>>().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}
<!-- Written by `wai-quantum setup-agent`; a rerun replaces this block and nothing outside it. -->
## 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<Option<String>, 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<PathBuf>,
/// The instructions file, if not DIR/AGENTS.md
#[arg(long, value_name = "FILE")]
pub file: Option<PathBuf>,
/// 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<i32, String> {
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<u64>,
/// The key file the server signs with, when not this user's own
#[arg(long, value_name = "FILE")]
pub key: Option<PathBuf>,
}
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<Topic>,
/// 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<PathBuf>,
}
#[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 `<!-- wai-quantum:start -->` and `<!-- wai-quantum:end -->`.
- `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::<String>(),
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::<Vec<_>>().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(<crate::Cli as clap::CommandFactory>::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::<Vec<_>>().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<String> {
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<String> {
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 <!-- wai-quantum:start --> 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<!-- wai-quantum:start -->"), "{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::<Value>(&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::<Vec<_>>(), ["theme", "mcpServers", "zeta"]);
assert_eq!(v["mcpServers"].as_object().unwrap().keys().collect::<Vec<_>>(), ["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::<Vec<_>>(), ["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::<Value>(&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 `</code>` after it ends the name.
let token = format!("`mcp-name: {name}`");
let readme = include_str!("../README.md");
assert!(readme.contains(&token) && !readme.contains(&format!("<!-- mcp-name: {name}")), "README.md shows {token} as text");
}
}