use std::collections::BTreeSet;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
const GUIDE_BUDGET: usize = 6 * 1024;
const CHUNK_BUDGET: u64 = 24 * 1024;
const CONTEXT_PREAMBLE: &str = "Context first: call query_project { operation: \"context\" } with a byte budget and read only the surface it points you to; reach for this guide's detail after that, not before.";
const TOPICS: [(&str, &str); 14] = [
("core", "Noxid agent quick reference"),
("scaffolding", "Noxid scaffolding"),
("interop", "Noxid JavaScript interop"),
("types", "Noxid types and data vocabulary"),
("routing", "Noxid routing"),
("storage", "Noxid storage"),
("realtime", "Noxid realtime"),
("observability", "Noxid observability"),
("reactivity", "Noxid reactivity"),
("motion", "Noxid motion"),
("remote-actions", "Noxid typed remote actions"),
("ssr", "Noxid rendering"),
("testing", "Noxid testing"),
("semantic-tools", "Noxid semantic tools"),
];
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
struct TempDir(PathBuf);
impl TempDir {
fn new(label: &str) -> Self {
let ordinal = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"noxid-wo53-qa1-{label}-{}-{ordinal}",
std::process::id()
));
fs::create_dir_all(&path).expect("create QA temporary directory");
Self(path)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn noxid(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(args)
.output()
.expect("run noxid")
}
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
#[test]
fn public_agent_guide_topic_set_is_context_first_and_byte_bounded() {
for (topic, heading) in TOPICS {
let output = noxid(&["agent-guide", topic]);
assert!(
output.status.success(),
"agent-guide {topic} failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
output.stdout.len() <= GUIDE_BUDGET,
"public agent-guide {topic} output is {} bytes, over {GUIDE_BUDGET}",
output.stdout.len()
);
let text = String::from_utf8(output.stdout).expect("guide output is UTF-8");
assert_eq!(
text.lines().next(),
Some(CONTEXT_PREAMBLE),
"agent-guide {topic} did not open with the common instruction"
);
assert!(
text.contains(heading),
"agent-guide {topic} omitted {heading}"
);
}
let usage = noxid(&[]);
assert!(
!usage.status.success(),
"missing command unexpectedly succeeded"
);
let usage = format!(
"{}{}",
String::from_utf8_lossy(&usage.stdout),
String::from_utf8_lossy(&usage.stderr)
);
let expected = TOPICS
.iter()
.map(|(topic, _)| *topic)
.collect::<Vec<_>>()
.join("|");
assert!(
usage.contains(&format!("noxid agent-guide <{expected}>")),
"usage did not enumerate the complete guide topic set: {usage}"
);
}
#[test]
fn describe_cli_and_mcp_return_one_budgeted_routing_guide() {
let guide = noxid(&["agent-guide", "routing"]);
let described = noxid(&["describe", "guide", "routing"]);
assert!(guide.status.success(), "agent-guide routing failed");
assert!(
described.status.success(),
"describe guide routing failed: {}",
String::from_utf8_lossy(&described.stderr)
);
assert_eq!(
described.stdout, guide.stdout,
"CLI describe did not return exactly the routing topic"
);
let request = r#"{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"query_project","arguments":{"operation":"describe","kind":"guide","name":"routing"}}}"#;
let mut child = Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(["mcp", "../../examples/counter/Counter.nox"])
.current_dir(env!("CARGO_MANIFEST_DIR"))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("start noxid MCP");
{
let mut stdin = child.stdin.take().expect("MCP stdin");
stdin
.write_all(format!("{request}\n").as_bytes())
.expect("write MCP request");
}
let output = child.wait_with_output().expect("finish MCP request");
assert!(
output.status.success(),
"MCP describe failed: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
output.stdout.len() <= GUIDE_BUDGET,
"MCP routing response is {} bytes, over {GUIDE_BUDGET}",
output.stdout.len()
);
let response = String::from_utf8(output.stdout).expect("MCP response is UTF-8");
for contract in [
r#""kind":"guide""#,
r#""name":"routing""#,
"Context first: call query_project",
"Noxid routing",
] {
assert!(
response.contains(contract),
"MCP response omitted {contract}"
);
}
assert!(
!response.contains("Noxid storage"),
"MCP leaked storage guide"
);
assert!(
!response.contains("Noxid realtime"),
"MCP leaked realtime guide"
);
}
#[test]
fn llms_index_is_a_bijection_over_scoped_budgeted_chunks() {
let root = root();
let index = fs::read_to_string(root.join("llms.txt")).expect("read llms index");
let full = fs::read_to_string(root.join("llms-full.txt")).expect("read full guide");
let mut indexed = BTreeSet::new();
let mut chunk_lines = BTreeSet::new();
for line in index.lines().filter(|line| line.starts_with("- llms/")) {
let row = line.strip_prefix("- llms/").expect("index row prefix");
let (name, rest) = row.split_once(" (").expect("chunk name and size");
let (size, scope) = rest.split_once(" bytes) - ").expect("chunk size and scope");
let declared = size.parse::<u64>().expect("decimal chunk size");
assert!(!scope.trim().is_empty(), "chunk {name} has an empty scope");
assert!(
indexed.insert(name.to_string()),
"chunk {name} is indexed more than once"
);
let path = root.join("llms").join(name);
let actual = fs::metadata(&path)
.unwrap_or_else(|error| panic!("indexed chunk {name} does not resolve: {error}"))
.len();
assert_eq!(declared, actual, "index mis-sized chunk {name}");
assert!(
actual <= CHUNK_BUDGET,
"chunk {name} is {actual} bytes, over {CHUNK_BUDGET}"
);
let body = fs::read_to_string(path).expect("chunk is UTF-8");
for body_line in body.lines().filter(|body_line| !body_line.is_empty()) {
if !(body_line.starts_with("## ") && body_line.contains("(continued, part ")) {
chunk_lines.insert(body_line.to_string());
}
}
}
let actual = fs::read_dir(root.join("llms"))
.expect("read chunk directory")
.map(|entry| entry.expect("chunk entry").file_name())
.filter_map(|name| name.into_string().ok())
.filter(|name| name.ends_with(".txt"))
.collect::<BTreeSet<_>>();
assert_eq!(
indexed, actual,
"index and chunk directory are not a bijection"
);
assert!(indexed.len() >= 20, "unexpectedly small chunk topic set");
let first_topic = full
.lines()
.position(|line| line.starts_with("## "))
.expect("full guide contains topics");
for line in full
.lines()
.skip(first_topic)
.filter(|line| !line.is_empty())
{
assert!(
chunk_lines.contains(line),
"full-guide line is absent from every chunk: {line}"
);
}
}
#[test]
fn website_seo_step_copies_every_context_file_byte_for_byte() {
let root = root();
let temp = TempDir::new("website-copy");
fs::write(
temp.0.join("index.html"),
"<!doctype html><html><head><title>Noxid</title></head><body></body></html>\n",
)
.expect("write HTML shell");
fs::write(
temp.0.join("app.routes.json"),
r#"{"routes":[{"pattern":"/","middleware":[],"parameters":[],"metadata":{"title":"Noxid"}}]}"#,
)
.expect("write route manifest");
let output = Command::new("node")
.arg(root.join("tools/noxid-seo.mjs"))
.arg(&temp.0)
.current_dir(&root)
.output()
.expect("run website SEO copy step");
assert!(
output.status.success(),
"SEO copy failed:\n{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
fs::read(root.join("llms.txt")).expect("source index"),
fs::read(temp.0.join("llms.txt")).expect("served index")
);
assert_eq!(
fs::read(root.join("llms-full.txt")).expect("source full guide"),
fs::read(temp.0.join("llms-full.txt")).expect("served full guide")
);
let expected = fs::read_dir(root.join("llms"))
.expect("source chunks")
.map(|entry| entry.expect("source chunk").file_name())
.filter_map(|name| name.into_string().ok())
.filter(|name| name.ends_with(".txt"))
.collect::<BTreeSet<_>>();
let copied = fs::read_dir(temp.0.join("llms"))
.expect("copied chunks")
.map(|entry| entry.expect("copied chunk").file_name())
.filter_map(|name| name.into_string().ok())
.filter(|name| name.ends_with(".txt"))
.collect::<BTreeSet<_>>();
assert_eq!(expected, copied, "website copy omitted or added chunks");
for name in expected {
assert_eq!(
fs::read(root.join("llms").join(&name)).expect("source chunk bytes"),
fs::read(temp.0.join("llms").join(&name)).expect("copied chunk bytes"),
"website copy changed chunk {name}"
);
}
}