-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-runtime-contracts.mjs
More file actions
116 lines (104 loc) · 6.39 KB
/
Copy pathcheck-runtime-contracts.mjs
File metadata and controls
116 lines (104 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env node
import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { buildGraph, classifyKind, serializeGraph } from "../skills/codebase-visualize/lib/scanner.mjs";
import { buildHtml, parseGraph, safeJson } from "../skills/codebase-visualize/lib/renderer.mjs";
import { parseTargetArgs, scanTarget, usage } from "../skills/codebase-visualize/lib/commands.mjs";
import { assertInside, atomicWrite, targetPaths } from "../skills/codebase-visualize/lib/runtime.mjs";
import { PRODUCT_VERSION } from "../skills/codebase-visualize/lib/version.mjs";
function assert(condition, message) {
if (!condition) throw new Error(message);
}
function rejects(action, pattern, message) {
try {
action();
} catch (error) {
assert(pattern.test(error.message), `${message}: ${error.message}`);
return;
}
throw new Error(`${message}: operation unexpectedly succeeded`);
}
const scratch = mkdtempSync(join(tmpdir(), "codebase-visualize-contracts-"));
try {
const target = join(scratch, "target");
mkdirSync(join(target, "src"), { recursive: true });
mkdirSync(join(target, "docs"), { recursive: true });
mkdirSync(join(target, ".agents", "skills", "codebase-visualize"), { recursive: true });
writeFileSync(join(target, "src", "index.js"), "export const marker = '</script><script>bad()</script>';\n");
writeFileSync(join(target, "docs", "architecture.md"), "Read `src/index.js` and [source](../src/index.js).\n");
writeFileSync(join(target, ".agents", "skills", "codebase-visualize", "SKILL.md"), "must stay excluded\n");
const graph = buildGraph(target, { generatedAt: "2026-01-01T00:00:00.000Z" });
const references = graph.edges.filter((edge) => edge.type === "references");
assert(graph.metadata.producer_version === PRODUCT_VERSION, "graph product version drifted");
assert(graph.metadata.counts.files === 2, "graph file count is incorrect");
assert(references.length === 1, "Markdown reference inference is incorrect");
assert(!graph.nodes.some((node) => node.path.startsWith(".agents/")), "project-local Codex skill install was scanned");
assert(references[0].to === "src/index.js", "Markdown reference target is incorrect");
assert(classifyKind("users.test.js", ".js", "tests/users.test.js") === "tests", "test classification drifted");
const html = buildHtml(graph);
assert(html.includes(".layout{"), "rendered HTML is missing embedded CSS");
assert(html.includes("id=\"graph-data\""), "rendered HTML is missing embedded graph data");
assert(html.includes("Copy agent briefing"), "rendered HTML is missing agent briefing action");
assert(html.includes("How to use this map"), "rendered HTML is missing cold-user guidance");
assert(html.includes("No path references"), "rendered HTML is missing low-signal reference guidance");
assert(html.includes("label-muted"), "rendered HTML is missing graph label-clutter class");
assert(!html.includes("</script><script>bad()"), "repository content escaped the graph-data script");
assert(safeJson({ value: "</script>&" }).includes("\\u003c/script\\u003e\\u0026"), "safeJson escaping drifted");
parseGraph(serializeGraph(graph));
rejects(() => parseGraph("{"), /not valid JSON/, "malformed JSON was accepted");
rejects(() => parseGraph("{}"), /schema_version/, "malformed graph shape was accepted");
rejects(() => parseGraph(JSON.stringify({ ...graph, schema_version: "2.0.0" })), /unsupported graph schema/, "unsupported graph schema was accepted");
rejects(() => parseGraph(JSON.stringify({ ...graph, nodes: [] })), /non-empty array/, "empty graph was accepted");
const paths = targetPaths(target);
assert(paths.graphPath.endsWith("docs\\ai\\visualize\\codebase-graph.json") || paths.graphPath.endsWith("docs/ai/visualize/codebase-graph.json"), "graph path escaped the namespace");
assert(paths.mapPath.endsWith("docs\\ai\\visualize\\codebase-map.html") || paths.mapPath.endsWith("docs/ai/visualize/codebase-map.html"), "map path escaped the namespace");
rejects(() => assertInside(target, join(target, "..", "escape.json")), /outside target namespace/, "namespace escape was accepted");
rejects(() => parseTargetArgs(["--out", "escape.json"], "scan"), /unsupported argument/, "public --out was accepted");
rejects(() => parseTargetArgs(["--target"], "scan"), /requires a path/, "missing target value was accepted");
for (const command of ["scan", "render", "visualize"]) {
const help = usage(command);
assert(help.includes("codebase-graph.json"), `${command} help is missing graph output path`);
assert(help.includes("codebase-map.html"), `${command} help is missing map output path`);
assert(help.includes("Re-run to refresh generated"), `${command} help is missing rerun guidance`);
assert(help.includes("Open codebase-map.html directly in a browser"), `${command} help is missing browser-open guidance`);
}
const atomicPath = join(scratch, "atomic", "value.txt");
atomicWrite(atomicPath, "first\n");
atomicWrite(atomicPath, "second\n");
assert(readFileSync(atomicPath, "utf8") === "second\n", "atomic replacement failed");
rejects(
() => atomicWrite(atomicPath, "partial\n", { write: () => { throw new Error("simulated atomic failure"); } }),
/simulated atomic failure/,
"atomic failure was swallowed",
);
assert(readFileSync(atomicPath, "utf8") === "second\n", "failed atomic write replaced the previous artifact");
const failedTarget = join(scratch, "failed-target");
mkdirSync(failedTarget);
const failedPaths = targetPaths(failedTarget);
rejects(
() => scanTarget(failedTarget, { write: () => { throw new Error("simulated write failure"); } }),
/simulated write failure/,
"simulated write failure was swallowed",
);
assert(!existsSync(failedPaths.graphPath), "failed scan left a graph artifact");
const cli = join("skills", "codebase-visualize", "scripts", "scan.mjs");
const result = spawnSync(process.execPath, [cli, "--out", join(scratch, "escape.json")], {
cwd: process.cwd(),
encoding: "utf8",
shell: false,
});
assert(result.status !== 0, "public CLI accepted --out");
assert(!existsSync(join(scratch, "escape.json")), "rejected CLI created an output");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
console.log("check:runtime OK - modules, safety boundary, escaping, and failures verified.");