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 @@ -25,6 +25,9 @@ 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

# 一次渲染全部示例到画廊目录
node bin/trace.mjs demo --all /tmp/loop-trace/gallery

# 管道友好:输入传 "-" 即可从 stdin 读 IR
cat examples/strands-forcing.json | node bin/trace.mjs validate - --json
```
Expand Down
37 changes: 31 additions & 6 deletions bin/trace.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
// agent-loop-trace v0.1 — zero-dep validate + deliver, mirrors archify chain.
import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
import { readFileSync, writeFileSync, mkdirSync, renameSync, readdirSync } from "node:fs";
import { createHash } from "node:crypto";
import { dirname, resolve } from "node:path";

Expand Down Expand Up @@ -195,12 +195,37 @@ if (cmd === "doctor") {
const receipt = { schemaVersion: 1, ok: true, command: "deliver", input: inAbs, output: outAbs, specification: { sha256: sha256Bytes(raw), bytes: raw.length }, artifact: { sha256: sha256Bytes(Buffer.from(html)), bytes: Buffer.byteLength(html) }, validation: { checksPassed: checks.length, checkCount: checks.length, compositionStatus: "pass", errors: 0, warnings: 0 } };
console.log(JSON.stringify(receipt, null, 2));
} else if (cmd === "demo") {
const [dir] = args;
const all = args.includes("--all");
const [dir] = args.filter((a) => a !== "--all");
if (!dir) {
console.error("usage: trace.mjs demo <dir> [--all]");
process.exit(2);
}
mkdirSync(resolve(dir), { recursive: true });
const spec = loadJson(resolve(ROOT, "examples/strands-forcing.json"));
const { checks } = validateSpec(spec);
writeFileSync(resolve(dir, "loop-trace-demo.html"), renderHtml(spec, { checks }));
console.log(`Demo ready: ${resolve(dir, "loop-trace-demo.html")}`);
let rendered = 0;
if (all) {
const names = readdirSync(resolve(ROOT, "examples"))
.filter((f) => f.endsWith(".json"))
.sort();
for (const name of names) {
const spec = loadJson(resolve(ROOT, "examples", name));
const { checks } = validateSpec(spec);
const out = resolve(dir, `loop-trace-${name.replace(/\.json$/, "")}.html`);
writeFileSync(out, renderHtml(spec, { checks }));
console.log(`Demo ready: ${out}`);
rendered += 1;
}
} else {
const spec = loadJson(resolve(ROOT, "examples/strands-forcing.json"));
const { checks } = validateSpec(spec);
writeFileSync(resolve(dir, "loop-trace-demo.html"), renderHtml(spec, { checks }));
console.log(`Demo ready: ${resolve(dir, "loop-trace-demo.html")}`);
rendered = 1;
}
if (rendered === 0) {
console.error("no example specs found under examples/");
process.exit(1);
}
} else {
console.error('usage: trace.mjs [doctor|validate|deliver|demo] ... (input "-" reads the spec from stdin)');
process.exit(2);
Expand Down
18 changes: 18 additions & 0 deletions test/trace.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,21 @@ test("deliver refuses to render an invalid spec", () => {
const receipt = JSON.parse(r.stdout);
assert.equal(receipt.ok, false);
});

test("demo --all renders every example into a gallery dir", () => {
const dir = mkdtempSync(join(tmpdir(), "loop-trace-gallery-"));
const r = run(["demo", "--all", dir]);
assert.equal(r.code, 0);
for (const name of ["strands-forcing", "dspy-reward"]) {
const html = readFileSync(join(dir, `loop-trace-${name}.html`), "utf8");
assert.match(html, /<svg id="diagram"/);
}
});

test("demo (single) still writes loop-trace-demo.html", () => {
const dir = mkdtempSync(join(tmpdir(), "loop-trace-demo-"));
const r = run(["demo", dir]);
assert.equal(r.code, 0);
const html = readFileSync(join(dir, "loop-trace-demo.html"), "utf8");
assert.match(html, /<svg id="diagram"/);
});
Loading