Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ node bin/trace.mjs doctor
node bin/trace.mjs validate examples/strands-forcing.json --json
node bin/trace.mjs deliver examples/strands-forcing.json /tmp/loop-trace/demo.html --json
# 用浏览器打开 /tmp/loop-trace/demo.html:T 切主题,E 导出 PNG

# 管道友好:输入传 "-" 即可从 stdin 读 IR
cat examples/strands-forcing.json | node bin/trace.mjs validate - --json
```

零依赖回归测试:`npm test`(Node 内置 node:test,覆盖 doctor / validate / deliver / 坏样本拒绝)。
Expand Down
28 changes: 23 additions & 5 deletions bin/trace.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ function loadJson(p) {
return JSON.parse(readFileSync(p, "utf8"));
}

// "-" streams the spec from stdin so agent pipelines can pipe IR directly:
// cat spec.json | node bin/trace.mjs validate - --json
function readSpecRaw(inputPath) {
return inputPath === "-" ? readFileSync(0) : readFileSync(resolve(inputPath));
}

function specLabel(inputPath) {
return inputPath === "-" ? "<stdin>" : resolve(inputPath);
}

function validateSpec(spec) {
const checks = [];
const errors = [];
Expand Down Expand Up @@ -152,16 +162,24 @@ if (cmd === "doctor") {
console.log(jsonOut ? JSON.stringify(out, null, 2) : "agent-loop-trace ready.");
} else if (cmd === "validate") {
const [input] = args;
const spec = loadJson(resolve(input));
if (!input) {
console.error("usage: trace.mjs validate <spec.json | -> [--json]");
process.exit(2);
}
const spec = JSON.parse(readSpecRaw(input).toString("utf8"));
const { checks, errors, warnings } = validateSpec(spec);
const receipt = { schemaVersion: 1, ok: errors === 0, command: "validate", input: resolve(input), checks, composition: { profile: "showcase", status: errors === 0 ? "pass" : "fail", summary: { errors, warnings } } };
const receipt = { schemaVersion: 1, ok: errors === 0, command: "validate", input: specLabel(input), checks, composition: { profile: "showcase", status: errors === 0 ? "pass" : "fail", summary: { errors, warnings } } };
if (jsonOut) console.log(JSON.stringify(receipt, null, 2));
else console.log(errors === 0 ? `pass ${checks.length}/${checks.length}` : `fail ${errors} checks`);
process.exit(errors === 0 ? 0 : 1);
} else if (cmd === "deliver") {
const [input, output] = args;
const inAbs = resolve(input), outAbs = resolve(output);
const raw = readFileSync(inAbs);
if (!input || !output) {
console.error("usage: trace.mjs deliver <spec.json | -> <out.html>");
process.exit(2);
}
const inAbs = specLabel(input), outAbs = resolve(output);
const raw = readSpecRaw(input);
const spec = JSON.parse(raw.toString("utf8"));
const { checks, errors, warnings } = validateSpec(spec);
if (errors > 0) {
Expand All @@ -184,6 +202,6 @@ if (cmd === "doctor") {
writeFileSync(resolve(dir, "loop-trace-demo.html"), renderHtml(spec, { checks }));
console.log(`Demo ready: ${resolve(dir, "loop-trace-demo.html")}`);
} else {
console.error("usage: trace.mjs [doctor|validate|deliver|demo] ...");
console.error('usage: trace.mjs [doctor|validate|deliver|demo] ... (input "-" reads the spec from stdin)');
process.exit(2);
}
12 changes: 11 additions & 1 deletion test/trace.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,28 @@ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const BIN = join(ROOT, "bin", "trace.mjs");
const EXAMPLES = ["examples/strands-forcing.json", "examples/dspy-reward.json"];

function run(args) {
function run(args, opts = {}) {
try {
const stdout = execFileSync(process.execPath, [BIN, ...args], {
encoding: "utf8",
cwd: ROOT,
...opts,
});
return { code: 0, stdout };
} catch (error) {
return { code: error.status ?? 1, stdout: String(error.stdout ?? "") };
}
}

test("validate reads a spec from stdin via '-'", () => {
const input = readFileSync(join(ROOT, EXAMPLES[0]), "utf8");
const r = run(["validate", "-", "--json"], { input });
assert.equal(r.code, 0);
const receipt = JSON.parse(r.stdout);
assert.equal(receipt.ok, true);
assert.equal(receipt.input, "<stdin>");
});

test("doctor reports a ready toolchain", () => {
const r = run(["doctor", "--json"]);
assert.equal(r.code, 0);
Expand Down
Loading