open-standards/wai/wai-quantum-cli/src/repair.rs
Transaction Science b0edea785a wai-quantum-cli: run --intent, repair, test on the engine's verdict, and the MCP verdict in every answer
run takes --intent (a reference program, a distribution, intents, or a
project's intent.json), --verdict and --repair, prints the verdict and exits
by whether the intent holds. repair makes the mechanical fixes on the flagged
lines and checks a proposal against them. test runs the program with its
intents, so the verdict is sealed in the run's receipt. The MCP server's
run_quantum_circuit takes intent and repair and always states its verdict,
repair_qasm is new, every answer about a program carries its verdict, every
refusal its codes, and every tool a versioned output schema. The committed
agent docs and the Agent Skill are regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 23:44:27 -04:00

122 lines
5.1 KiB
Rust

//! `wai-quantum repair`: a program repaired from its diagnostics, on the
//! lines they flag alone ([`wai_quantum::engine::repair`]). The repaired
//! program goes to standard output (or back to its file with `--write`), and
//! each repair and what needs a person to standard error; `--json` prints the
//! engine's whole answer, its verdict included.
//!
//! `--proposed FILE` checks a repair someone else wrote (a person's, an
//! agent's): it is taken only if it changes no line the diagnostics do not
//! flag. Exit 0 when the program reads, 1 when something still needs a
//! person, 2 when a proposal is rejected.
use serde_json::{Value, json};
use std::path::PathBuf;
use wai_quantum::engine::repair;
#[derive(clap::Args)]
pub struct Args {
/// The program; `-` or nothing reads standard input
pub file: Option<PathBuf>,
/// A repaired version to check: taken only if it changes no line but those the diagnostics flag
#[arg(long, value_name = "FILE")]
pub proposed: Option<PathBuf>,
/// The bound for an unbounded `while` on measured bits (Q310): it runs at most this many times
#[arg(long, value_name = "N")]
pub max_iterations: Option<u64>,
/// Round each angle the bit-exact method refuses (Q500) to the nearest multiple of 2π/2^K, its rounding stated on its line
#[arg(long, value_name = "K")]
pub dyadic_bits: Option<u32>,
/// A register's size, for one used before it is declared (Q102), as q=3 (repeatable)
#[arg(long = "size", value_name = "NAME=N", value_parser = size_arg)]
pub sizes: Vec<(String, u32)>,
/// Only check the proposal: make none of the engine's own fixes
#[arg(long)]
pub no_rules: bool,
/// Write the repaired program back to its file
#[arg(long, conflicts_with = "json")]
pub write: bool,
/// Print the engine's answer as JSON: the program, each repair, what needs a person, and the verdict
#[arg(long)]
pub json: bool,
}
fn size_arg(s: &str) -> Result<(String, u32), String> {
let (k, v) = s.split_once('=').ok_or("give a register and its size, as q=3")?;
let n: u32 = v.trim().parse().map_err(|_| format!("{v}: a size is a whole number"))?;
Ok((k.trim().to_string(), n))
}
/// The repair's choices as the engine takes them.
pub fn options(a: &Args, proposed: Option<String>) -> Value {
let mut o = json!({});
if let Some(n) = a.max_iterations {
o["max_iterations"] = json!(n);
}
if let Some(k) = a.dyadic_bits {
o["dyadic_bits"] = json!(k);
}
if !a.sizes.is_empty() {
o["sizes"] = Value::Object(a.sizes.iter().map(|(k, n)| (k.clone(), json!(n))).collect());
}
if let Some(p) = proposed {
o["proposed"] = json!(p);
}
if a.no_rules {
o["rules"] = json!(false);
}
o
}
/// What was repaired and what needs a person, as a reader takes it in.
pub fn report(v: &Value) -> String {
let mut s = String::new();
for r in v["repairs"].as_array().into_iter().flatten() {
let code = r["code"].as_str().unwrap_or("—");
s += &format!("repaired {code}: {}\n", r["rule"].as_str().unwrap_or(""));
for e in r["edits"].as_array().into_iter().flatten() {
let line = e["line"].as_u64().unwrap_or(0);
match (e["before"].as_str(), e["after"].as_str()) {
(Some(b), Some(a)) => s += &format!(" line {line}: {b}\n → {a}\n"),
(None, Some(a)) => s += &format!(" added before line {line}: {a}\n"),
(Some(b), None) => s += &format!(" removed line {line}: {b}\n"),
_ => {}
}
}
}
for p in v["needs_person"].as_array().into_iter().flatten() {
s += &format!("needs a person {}: {}\n {}\n", p["code"].as_str().unwrap_or(""), p["message"].as_str().unwrap_or(""), p["advice"].as_str().unwrap_or(""));
}
s += &format!("verdict {}: {}\n", v["verdict"]["level"].as_str().unwrap_or(""), v["verdict"]["statement"].as_str().unwrap_or(""));
s
}
/// `wai-quantum repair`.
pub fn main(a: Args) {
let src = crate::read_text(a.file.as_deref()).unwrap_or_else(|e| crate::die(&e));
let proposed = a.proposed.as_deref().map(|p| crate::read_text(Some(p)).unwrap_or_else(|e| crate::die(&e)));
let v = match repair::repair(&src, &options(&a, proposed)) {
Ok(v) => v,
Err(e) => {
if a.json {
println!("{}", json!({ "error": e, "refusal": wai_quantum::engine::refusal::refusal_json(&e) }));
} else {
eprintln!("wai-quantum: {e}");
}
std::process::exit(2);
}
};
let text = v["qasm"].as_str().unwrap_or("");
if a.json {
println!("{v}");
} else if a.write {
let Some(path) = a.file.as_deref().filter(|p| *p != std::path::Path::new("-")) else { crate::die("--write needs the program's file") };
if v["changed"] == true {
std::fs::write(path, text).unwrap_or_else(|e| crate::die(&format!("{}: {e}", path.display())));
}
eprint!("{}", report(&v));
} else {
print!("{text}");
eprint!("{}", report(&v));
}
std::process::exit(if v["ok"] == true { 0 } else { 1 });
}