diff --git a/README.md b/README.md index c4fab78..23f80ae 100644 --- a/README.md +++ b/README.md @@ -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 / 坏样本拒绝)。 diff --git a/bin/trace.mjs b/bin/trace.mjs index 092d7b1..fc056f0 100644 --- a/bin/trace.mjs +++ b/bin/trace.mjs @@ -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 === "-" ? "" : resolve(inputPath); +} + function validateSpec(spec) { const checks = []; const errors = []; @@ -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 [--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 "); + 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) { @@ -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); } diff --git a/test/trace.test.mjs b/test/trace.test.mjs index 6fda23e..49b8c1e 100644 --- a/test/trace.test.mjs +++ b/test/trace.test.mjs @@ -12,11 +12,12 @@ 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) { @@ -24,6 +25,15 @@ function run(args) { } } +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, ""); +}); + test("doctor reports a ready toolchain", () => { const r = run(["doctor", "--json"]); assert.equal(r.code, 0);