diff --git a/README.md b/README.md index a88a638..da36204 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,15 @@ se-trace validate-runtime examples/legal-summarization.cots.json # Walk the evidence chains and surface recursive (model-generated) items (RFC-0002) se-trace recursive examples/legal-summarization.cots.json + +# Render the Context Graph (Mermaid + HTML, RFC-0004) +se-trace context-graph examples/legal-summarization.cots.json -o ./out + +# Replay the audit trail (RFC-0005) +se-trace audit examples/legal-summarization.cots.json + +# COTS Explorer: one self-contained HTML page with all four views (Advancement Plan Phase 5) +se-trace explore examples/legal-summarization.cots.json -o ./out ``` ## What is in this repository @@ -39,6 +48,9 @@ se-trace recursive examples/legal-summarization.cots.json - `src/inspect.ts` - the inspect command. Prints a human-readable summary of a COTS. - `src/cite.ts` - the cite command. Prints the evidence chain for a specific decision. - `src/visualize.ts` - the visualize command. Renders a COTS as a Mermaid diagram + HTML page. +- `src/context-graph.ts` - the context-graph command. Renders the Context + Evidence parts as a typed-edge graph (RFC-0004). +- `src/audit.ts` - the audit command. Replays the audit trail and flags append-only/linkage violations (RFC-0005). +- `src/explore.ts` - the COTS Explorer. One self-contained HTML page combining Reasoning Roadmap, Context Graph, summary, and audit trail. - `src/cli/se-trace.ts` - the CLI entry point, including `validate-runtime` (RFC-0001) and `recursive` (RFC-0002 chain-walking). - `tests/tools.test.ts` - the test suite. diff --git a/src/cli/se-trace.ts b/src/cli/se-trace.ts index 632ecef..df39874 100644 --- a/src/cli/se-trace.ts +++ b/src/cli/se-trace.ts @@ -7,6 +7,8 @@ * - visualize Render a COTS as a Reasoning Roadmap (Mermaid + HTML). * - context-graph Render a COTS's Context + Evidence as a Context Graph (RFC-0004). * - audit [decision-id] Replay a decision's audit trail (RFC-0005). + * - explore Render the COTS Explorer: one self-contained HTML page + * (Reasoning Roadmap + Context Graph + summary + audit trail). */ import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; @@ -17,6 +19,7 @@ import { cite } from "../cite.js"; import { toMermaid, toHtml } from "../visualize.js"; import { toContextGraphMermaid, toContextGraphHtml } from "../context-graph.js"; import { replayAudit } from "../audit.js"; +import { toExplorerHtml } from "../explore.js"; import type { Cots } from "../cots.js"; // Lightweight conformance check (subset of the canonical test suite). For @@ -212,4 +215,19 @@ program if (report.issues.length > 0) process.exit(1); }); +program + .command("explore") + .description("Render the COTS Explorer: a single self-contained HTML page with all four views (Advancement Plan Phase 5)") + .argument("", "Path to a COTS JSON file") + .option("-o, --out ", "Output directory", ".") + .action((path: string, opts: { out: string }) => { + const cots = loadCots(path); + const baseName = basename(path, extname(path)); + const html = toExplorerHtml(cots); + const htmlPath = resolve(opts.out, `${baseName}.explorer.html`); + mkdirSync(dirname(htmlPath), { recursive: true }); + writeFileSync(htmlPath, html); + console.log(`COTS Explorer: ${htmlPath}`); + }); + program.parse(); diff --git a/src/explore.ts b/src/explore.ts new file mode 100644 index 0000000..33e0ce2 --- /dev/null +++ b/src/explore.ts @@ -0,0 +1,97 @@ +/** + * COTS Explorer: render a single self-contained HTML page combining the + * Reasoning Roadmap, the Context Graph, the inspect summary, and the audit + * trail of a COTS. Static output; no server, no external state. + * + * Phase 5 of the Advancement Plan: the demo-able surface on top of + * se-trace visualize / context-graph / inspect / audit. + */ + +import type { Cots } from "./cots.js"; +import { toMermaid } from "./visualize.js"; +import { toContextGraphMermaid } from "./context-graph.js"; +import { inspect } from "./inspect.js"; +import { replayAudit } from "./audit.js"; + +const escapeHtml = (s: string): string => + s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + +export const toExplorerHtml = (cots: Cots): string => { + const roadmap = toMermaid(cots); + const contextGraph = toContextGraphMermaid(cots); + const summary = escapeHtml(inspect(cots)); + const audit = replayAudit(cots); + const auditBlock = escapeHtml(audit.lines.join("\n")); + const auditOk = audit.issues.length === 0; + + return ` + + + + + COTS Explorer -- ${escapeHtml(cots.cots_id)} + + + + +
+

COTS Explorer

+
COTS ${escapeHtml(cots.cots_id)} · produced ${escapeHtml(cots.produced_at)} by ${escapeHtml(cots.produced_by.system_name)} v${escapeHtml(cots.produced_by.system_version)} · schema ${escapeHtml(cots.cots_version)}
+
+ +
+
+
${escapeHtml(roadmap)}
+
+
+
${escapeHtml(contextGraph)}
+
+
+
${summary}
+
+
+
${auditBlock}
+
+
+ + + +`; +}; diff --git a/tests/tools.test.ts b/tests/tools.test.ts index be549f2..afef2e8 100644 --- a/tests/tools.test.ts +++ b/tests/tools.test.ts @@ -13,6 +13,7 @@ import { cite } from "../src/cite.js"; import { toMermaid, toHtml } from "../src/visualize.js"; import { toContextGraphMermaid, toContextGraphHtml } from "../src/context-graph.js"; import { replayAudit } from "../src/audit.js"; +import { toExplorerHtml } from "../src/explore.js"; import type { Cots } from "../src/cots.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -192,6 +193,65 @@ describe("audit (RFC-0005)", () => { }); }); +describe("explore (COTS Explorer)", () => { + it("renders one self-contained HTML page with all four views", () => { + const cots = loadFixture("legal-summarization.cots.json"); + const html = toExplorerHtml(cots); + expect(html).toContain(""); + expect(html).toContain("COTS Explorer"); + expect(html).toContain('data-tab="roadmap"'); + expect(html).toContain('data-tab="context"'); + expect(html).toContain('data-tab="summary"'); + expect(html).toContain('data-tab="audit"'); + expect(html).toContain("graph LR"); + expect(html.match(/graph LR/g)?.length).toBe(2); + expect(html).toContain("Audit trail: OK"); + expect(html).toContain("mermaid"); + }); + + it("escapes HTML in the COTS summary", () => { + const cots = loadFixture("legal-summarization.cots.json"); + cots.decisions[0].decision_text = ""; + const html = toExplorerHtml(cots); + expect(html).not.toContain(""); + expect(html).toContain("<script>"); + }); + + it("shows a badge with issue count when the audit trail has violations", () => { + const cots = loadFixture("legal-summarization.cots.json"); + cots.decisions[0].audit_history.push({ + id: "urn:source-engineering:audit:dup", + action: "appealed", + performed_by: "r", + performed_at: "2026-11-02T09:00:00Z", + notes: "decision_id=urn:source-engineering:decision:nope", + }); + const html = toExplorerHtml(cots); + expect(html).toContain('badge bad'); + }); + + it("writes .explorer.html via the CLI", async () => { + const { execFileSync } = await import("node:child_process"); + const { mkdtempSync, readFileSync, rmSync } = await import("node:fs"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const outDir = mkdtempSync(join(tmpdir(), "se-explore-")); + try { + const fixture = resolve(__dirname, "..", "examples", "legal-summarization.cots.json"); + const out = execFileSync( + "node", + [resolve(__dirname, "..", "dist", "cli", "se-trace.js"), "explore", fixture, "-o", outDir], + { encoding: "utf-8" } + ); + expect(out).toContain("explorer.html"); + const html = readFileSync(join(outDir, "legal-summarization.cots.explorer.html"), "utf-8"); + expect(html).toContain("COTS Explorer"); + } finally { + rmSync(outDir, { recursive: true, force: true }); + } + }); +}); + describe("validate-runtime", () => { it("reports a Layer 2 Validation record when present", async () => { const cots = loadFixture("legal-summarization.cots.json");