agentopology 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -19
- package/dist/__tests__/e2e/action-guard.test.d.ts +13 -0
- package/dist/__tests__/e2e/action-guard.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/action-guard.test.js +299 -0
- package/dist/__tests__/e2e/action-guard.test.js.map +1 -0
- package/dist/__tests__/e2e/compile.test.js +1 -1
- package/dist/__tests__/e2e/compile.test.js.map +1 -1
- package/dist/__tests__/e2e/conformance.test.d.ts +13 -0
- package/dist/__tests__/e2e/conformance.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/conformance.test.js +109 -0
- package/dist/__tests__/e2e/conformance.test.js.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts +11 -0
- package/dist/__tests__/e2e/contract-drift.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/contract-drift.test.js +142 -0
- package/dist/__tests__/e2e/contract-drift.test.js.map +1 -0
- package/dist/__tests__/e2e/fail-closed.test.d.ts +11 -0
- package/dist/__tests__/e2e/fail-closed.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/fail-closed.test.js +111 -0
- package/dist/__tests__/e2e/fail-closed.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/guard-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-check.test.js +316 -0
- package/dist/__tests__/e2e/guard-check.test.js.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts +11 -0
- package/dist/__tests__/e2e/guard-target.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/guard-target.test.js +198 -0
- package/dist/__tests__/e2e/guard-target.test.js.map +1 -0
- package/dist/__tests__/e2e/inventory.test.d.ts +18 -0
- package/dist/__tests__/e2e/inventory.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/inventory.test.js +187 -0
- package/dist/__tests__/e2e/inventory.test.js.map +1 -0
- package/dist/__tests__/e2e/matcher.test.d.ts +14 -0
- package/dist/__tests__/e2e/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/matcher.test.js +145 -0
- package/dist/__tests__/e2e/matcher.test.js.map +1 -0
- package/dist/__tests__/e2e/step-check.test.d.ts +9 -0
- package/dist/__tests__/e2e/step-check.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/step-check.test.js +158 -0
- package/dist/__tests__/e2e/step-check.test.js.map +1 -0
- package/dist/__tests__/e2e/strength.test.d.ts +17 -0
- package/dist/__tests__/e2e/strength.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/strength.test.js +130 -0
- package/dist/__tests__/e2e/strength.test.js.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts +2 -0
- package/dist/__tests__/e2e/verified-on.test.d.ts.map +1 -0
- package/dist/__tests__/e2e/verified-on.test.js +57 -0
- package/dist/__tests__/e2e/verified-on.test.js.map +1 -0
- package/dist/__tests__/open-findings.test.js +1 -0
- package/dist/__tests__/open-findings.test.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +6 -686
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.js +0 -12
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -1
- package/dist/bindings/__tests__/hook-command.test.js +2 -2
- package/dist/bindings/__tests__/hook-command.test.js.map +1 -1
- package/dist/bindings/__tests__/mod-plugin.test.js +2 -2
- package/dist/bindings/__tests__/mod-plugin.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts +45 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +191 -19
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/index.d.ts +5 -5
- package/dist/bindings/index.d.ts.map +1 -1
- package/dist/bindings/index.js +9 -16
- package/dist/bindings/index.js.map +1 -1
- package/dist/bindings/lib/stub.d.ts +15 -1
- package/dist/bindings/lib/stub.d.ts.map +1 -1
- package/dist/bindings/lib/stub.js +27 -2
- package/dist/bindings/lib/stub.js.map +1 -1
- package/dist/bindings/types.d.ts +6 -1
- package/dist/bindings/types.d.ts.map +1 -1
- package/dist/cli/__tests__/cli.test.js +25 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -1
- package/dist/cli/index.js +222 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/conformance/claude-code.d.ts +99 -0
- package/dist/conformance/claude-code.d.ts.map +1 -0
- package/dist/conformance/claude-code.js +242 -0
- package/dist/conformance/claude-code.js.map +1 -0
- package/dist/conformance/extract.d.ts +70 -0
- package/dist/conformance/extract.d.ts.map +1 -0
- package/dist/conformance/extract.js +185 -0
- package/dist/conformance/extract.js.map +1 -0
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +57 -32
- package/dist/docs/content.js.map +1 -1
- package/dist/guard/check.d.ts +53 -0
- package/dist/guard/check.d.ts.map +1 -0
- package/dist/guard/check.js +366 -0
- package/dist/guard/check.js.map +1 -0
- package/dist/guard/target.d.ts +45 -0
- package/dist/guard/target.d.ts.map +1 -0
- package/dist/guard/target.js +168 -0
- package/dist/guard/target.js.map +1 -0
- package/dist/import/__tests__/import.test.js +0 -554
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/index.d.ts +0 -1
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -7
- package/dist/import/index.js.map +1 -1
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +41 -0
- package/dist/import/serializer.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/inventory/index.d.ts +65 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/index.js +272 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/parser/ast.d.ts +46 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/index.d.ts +6 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +66 -4
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/known-fields.d.ts +3 -0
- package/dist/parser/known-fields.d.ts.map +1 -1
- package/dist/parser/known-fields.js +5 -3
- package/dist/parser/known-fields.js.map +1 -1
- package/dist/parser/ledger.d.ts +36 -0
- package/dist/parser/ledger.d.ts.map +1 -0
- package/dist/parser/ledger.js +72 -0
- package/dist/parser/ledger.js.map +1 -0
- package/dist/parser/run.d.ts +6 -0
- package/dist/parser/run.d.ts.map +1 -1
- package/dist/parser/run.js +10 -0
- package/dist/parser/run.js.map +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +255 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +0 -8
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/merge.d.ts.map +1 -1
- package/dist/scaffold/merge.js +1 -3
- package/dist/scaffold/merge.js.map +1 -1
- package/dist/step/check.d.ts +56 -0
- package/dist/step/check.d.ts.map +1 -0
- package/dist/step/check.js +125 -0
- package/dist/step/check.js.map +1 -0
- package/dist/sync/index.d.ts +0 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +0 -109
- package/dist/sync/index.js.map +1 -1
- package/package.json +3 -1
- package/spec/claude-code/README.md +19 -0
- package/spec/claude-code/hooks-contract.2.1.295.json +143 -0
- package/spec/claude-code/hooks-contract.json +142 -0
- package/spec/grammar.md +45 -7
- package/spec/testing.md +16 -0
- package/spec/validation.md +11 -2
- package/dist/__tests__/live/zz-debug.test.d.ts +0 -2
- package/dist/__tests__/live/zz-debug.test.d.ts.map +0 -1
- package/dist/__tests__/live/zz-debug.test.js +0 -4
- package/dist/__tests__/live/zz-debug.test.js.map +0 -1
- package/dist/bindings/copilot-cli.d.ts +0 -20
- package/dist/bindings/copilot-cli.d.ts.map +0 -1
- package/dist/bindings/copilot-cli.js +0 -1516
- package/dist/bindings/copilot-cli.js.map +0 -1
- package/dist/bindings/gemini-cli.d.ts +0 -18
- package/dist/bindings/gemini-cli.d.ts.map +0 -1
- package/dist/bindings/gemini-cli.js +0 -1318
- package/dist/bindings/gemini-cli.js.map +0 -1
- package/dist/bindings/kiro.d.ts +0 -17
- package/dist/bindings/kiro.d.ts.map +0 -1
- package/dist/bindings/kiro.js +0 -1615
- package/dist/bindings/kiro.js.map +0 -1
- package/dist/bindings/openclaw.d.ts +0 -18
- package/dist/bindings/openclaw.d.ts.map +0 -1
- package/dist/bindings/openclaw.js +0 -1947
- package/dist/bindings/openclaw.js.map +0 -1
- package/dist/import/openclaw.d.ts +0 -70
- package/dist/import/openclaw.d.ts.map +0 -1
- package/dist/import/openclaw.js +0 -1060
- package/dist/import/openclaw.js.map +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agentopology inventory <dir>` and `validate <file> --inventory` (N34):
|
|
3
|
+
* which live things in a project the .at accounts for, and which things the
|
|
4
|
+
* .at points at that are not there.
|
|
5
|
+
*
|
|
6
|
+
* inventory lists what is live under a project (the .claude tree, each
|
|
7
|
+
* settings.json hook handler, .mcp.json servers, package.json scripts), with a
|
|
8
|
+
* sha256 fingerprint per file. It reads and prints; it never writes.
|
|
9
|
+
*
|
|
10
|
+
* Coverage matches each live thing to the .at in two ways: the file or hook is
|
|
11
|
+
* what scaffold would write from this .at, or the .at points at it by hand
|
|
12
|
+
* (file:, a hook's run, a gate's run, a tool's script, at:, an mcp server).
|
|
13
|
+
* A live thing nothing accounts for is unmapped (a warning). A path the .at
|
|
14
|
+
* points at that is not on disk is missing (an error, exit 1). Things tagged
|
|
15
|
+
* "e" (the platform, the machine) are counted apart and never required.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import * as fs from "node:fs";
|
|
21
|
+
import * as os from "node:os";
|
|
22
|
+
import { resolve, dirname, join } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { parse } from "../../parser/index.js";
|
|
25
|
+
import { claudeCodeBinding } from "../../bindings/claude-code.js";
|
|
26
|
+
import { collectInventory, coverage } from "../../inventory/index.js";
|
|
27
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const FIXTURE = resolve(here, "fixtures/compile.at");
|
|
29
|
+
const CLI = resolve(here, "../../../dist/cli/index.js");
|
|
30
|
+
const source = fs.readFileSync(FIXTURE, "utf-8");
|
|
31
|
+
const ast = parse(source);
|
|
32
|
+
let root;
|
|
33
|
+
const at = (p) => join(root, p);
|
|
34
|
+
const write = (p, text) => { fs.mkdirSync(dirname(at(p)), { recursive: true }); fs.writeFileSync(at(p), text); };
|
|
35
|
+
const settings = () => JSON.parse(fs.readFileSync(at(".claude/settings.json"), "utf8"));
|
|
36
|
+
const report = () => coverage(ast, collectInventory(root), root);
|
|
37
|
+
const ids = (items) => items.map((i) => i.id);
|
|
38
|
+
const cli = (...a) => spawnSync("node", [CLI, ...a], { encoding: "utf-8", cwd: root });
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
root = fs.mkdtempSync(join(os.tmpdir(), "at-inventory-"));
|
|
41
|
+
for (const f of claudeCodeBinding.scaffold(ast))
|
|
42
|
+
write(f.path, f.content);
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
45
|
+
describe("inventory lists what is live", () => {
|
|
46
|
+
it("[N34] [IV1] agents, skills, skill scripts, settings hooks and mcp servers, each file with its sha256", () => {
|
|
47
|
+
const items = collectInventory(root);
|
|
48
|
+
const byId = new Map(items.map((i) => [i.id, i]));
|
|
49
|
+
expect(byId.get("agent:drafter")).toMatchObject({ kind: "agent", path: ".claude/agents/drafter/AGENT.md" });
|
|
50
|
+
expect(byId.get("skill:tone")).toMatchObject({ kind: "skill", path: ".claude/skills/tone/SKILL.md" });
|
|
51
|
+
expect(byId.get("skill-file:compile/scripts/before-agent.sh")).toMatchObject({ kind: "skill-file" });
|
|
52
|
+
expect(byId.get("mcp:search")).toMatchObject({ kind: "mcp-server" });
|
|
53
|
+
expect(items.filter((i) => i.kind === "hook").length).toBeGreaterThan(3);
|
|
54
|
+
const tone = byId.get("skill:tone");
|
|
55
|
+
expect(tone.fingerprint).toBe(createHash("sha256").update(fs.readFileSync(at(tone.path))).digest("hex"));
|
|
56
|
+
});
|
|
57
|
+
it("[N34] [IV1] the listing is sorted and stable, and settings.local.json is suggested as e (machine-local)", () => {
|
|
58
|
+
write(".claude/settings.local.json", "{}\n");
|
|
59
|
+
const a = collectInventory(root);
|
|
60
|
+
expect(ids(a)).toEqual([...ids(a)].sort());
|
|
61
|
+
expect(collectInventory(root)).toEqual(a);
|
|
62
|
+
expect(a.find((i) => i.path === ".claude/settings.local.json")).toMatchObject({ tag: "e" });
|
|
63
|
+
});
|
|
64
|
+
it("[N34] [IV1] worktrees and node_modules are skipped, and a skill's working folder is one item, not every file in it", () => {
|
|
65
|
+
write(".claude/worktrees/agent-1/.claude/agents/copy.md", "x\n");
|
|
66
|
+
write(".claude/node_modules/pkg/index.js", "x\n");
|
|
67
|
+
write(".claude/skills/tone/scripts/node_modules/dep/index.js", "x\n");
|
|
68
|
+
write(".claude/skills/tone/references/style.md", "x\n");
|
|
69
|
+
write(".claude/skills/tone/workspace/p1/render.mp4", "x\n");
|
|
70
|
+
write(".claude/skills/tone/workspace/p2/render.mp4", "x\n");
|
|
71
|
+
write(".claude/skills/tone/scripts/__pycache__/lock.cpython-312.pyc", "x\n");
|
|
72
|
+
const got = ids(collectInventory(root));
|
|
73
|
+
expect(got.filter((id) => /worktrees|node_modules|__pycache__/.test(id))).toEqual([]);
|
|
74
|
+
expect(got).toContain("skill-file:tone/references/style.md");
|
|
75
|
+
expect(got.filter((id) => id.includes("tone/workspace"))).toEqual(["skill-dir:tone/workspace"]);
|
|
76
|
+
});
|
|
77
|
+
it("[N34] [IV1] package.json scripts are live things too", () => {
|
|
78
|
+
write("package.json", JSON.stringify({ scripts: { post: "node media.js post", test: "vitest" } }));
|
|
79
|
+
expect(ids(collectInventory(root))).toEqual(expect.arrayContaining(["script:post", "script:test"]));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("coverage: the .at against what is live", () => {
|
|
83
|
+
it("[N34] [IV2] a freshly scaffolded project is fully covered, nothing unmapped, nothing missing", () => {
|
|
84
|
+
const r = report();
|
|
85
|
+
expect(r.unmapped).toEqual([]);
|
|
86
|
+
expect(r.missing).toEqual([]);
|
|
87
|
+
expect(r.mapped).toBe(r.total);
|
|
88
|
+
expect(r.total).toBeGreaterThan(20);
|
|
89
|
+
});
|
|
90
|
+
it("[N34] [IV3] hand-built things nothing in the .at accounts for are unmapped, by kind", () => {
|
|
91
|
+
write(".claude/agents/stray.md", "---\nname: stray\n---\nA hand-made agent.\n");
|
|
92
|
+
write(".claude/commands/ship.md", "Ship it.\n");
|
|
93
|
+
const s = settings();
|
|
94
|
+
s.hooks.PreToolUse = [...(s.hooks.PreToolUse ?? []), { matcher: "mcp__chrome__upload_image", hooks: [{ type: "command", command: "python3 \"$CLAUDE_PROJECT_DIR/tools/lock.py\"" }] }];
|
|
95
|
+
fs.writeFileSync(at(".claude/settings.json"), JSON.stringify(s, null, 2));
|
|
96
|
+
const mcp = JSON.parse(fs.readFileSync(at(".mcp.json"), "utf8"));
|
|
97
|
+
mcp.mcpServers.chrome = { command: "chrome-mcp" };
|
|
98
|
+
fs.writeFileSync(at(".mcp.json"), JSON.stringify(mcp));
|
|
99
|
+
const r = report();
|
|
100
|
+
const un = ids(r.unmapped);
|
|
101
|
+
expect(un).toEqual(expect.arrayContaining(["agent:stray", "command:ship", "mcp:chrome"]));
|
|
102
|
+
expect(un.some((id) => id.startsWith("hook:PreToolUse") && id.includes("tools/lock.py"))).toBe(true);
|
|
103
|
+
expect(r.unmapped).toHaveLength(4);
|
|
104
|
+
expect(r.mapped).toBe(r.total - 4);
|
|
105
|
+
});
|
|
106
|
+
it("[N34] [IV3] a hand-written file inside a skill the .at declares counts under that skill", () => {
|
|
107
|
+
write(".claude/skills/tone/references/house-style.md", "Short sentences.\n");
|
|
108
|
+
expect(report().unmapped).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
it("[N34] [IV3] a declared store's data files count under the store", () => {
|
|
111
|
+
const mem = parse(fs.readFileSync(resolve(here, "../../../examples/memory-system.at"), "utf-8").replace('".memory/semantic/"', '".claude/stores/semantic/"'));
|
|
112
|
+
write(".claude/stores/semantic/index.db", "x\n");
|
|
113
|
+
write(".claude/stores/stray/rows.json", "x\n");
|
|
114
|
+
const un = ids(coverage(mem, collectInventory(root), root).unmapped);
|
|
115
|
+
expect(un).toContain("claude-file:stores/stray/rows.json");
|
|
116
|
+
expect(un).not.toContain("claude-file:stores/semantic/index.db");
|
|
117
|
+
});
|
|
118
|
+
it("[N34] [IV4] a path the .at points at that is not on disk is missing, named with what points at it", () => {
|
|
119
|
+
fs.rmSync(at("docs/tools/check.py"));
|
|
120
|
+
fs.rmSync(at("tools/render.py"));
|
|
121
|
+
const r = report();
|
|
122
|
+
expect(r.missing.map((m) => m.path).sort()).toEqual(["docs/tools/check.py", "tools/render.py"]);
|
|
123
|
+
expect(r.missing.find((m) => m.path === "docs/tools/check.py").from).toContain("copy-check");
|
|
124
|
+
expect(r.missing.find((m) => m.path === "tools/render.py").from).toContain("render");
|
|
125
|
+
});
|
|
126
|
+
it("[N34] [IV4] a generated file that is not there is reported as not scaffolded, not as missing", () => {
|
|
127
|
+
fs.rmSync(at(".claude/skills/compile/scripts/before-agent.sh"));
|
|
128
|
+
const r = report();
|
|
129
|
+
expect(r.missing).toEqual([]);
|
|
130
|
+
expect(r.notScaffolded).toContain(".claude/skills/compile/scripts/before-agent.sh");
|
|
131
|
+
});
|
|
132
|
+
it("[N34] [IV5] things tagged e are counted apart and never required", () => {
|
|
133
|
+
write(".claude/agents/stray.md", "x\n");
|
|
134
|
+
const items = collectInventory(root).map((i) => (i.id === "agent:stray" ? { ...i, tag: "e" } : i));
|
|
135
|
+
const r = coverage(ast, items, root);
|
|
136
|
+
expect(r.unmapped).toEqual([]);
|
|
137
|
+
expect(ids(r.external)).toEqual(["agent:stray"]);
|
|
138
|
+
expect(r.total).toBe(items.length - 1);
|
|
139
|
+
});
|
|
140
|
+
it("[N34] [IV6] a hand-written hook the .at declares by its run maps the hand-built settings entry", () => {
|
|
141
|
+
const withLock = parse(source.replace(" hook finished {", ` hook lock {\n on: PreToolUse\n matcher: "Bash"\n run: "tools/lock.py"\n }\n hook finished {`));
|
|
142
|
+
write("tools/lock.py", "print('ok')\n");
|
|
143
|
+
const s = settings();
|
|
144
|
+
s.hooks.PreToolUse = [...(s.hooks.PreToolUse ?? []), { matcher: "Bash", hooks: [{ type: "command", command: "python3 \"$CLAUDE_PROJECT_DIR/tools/lock.py\" --strict" }] }];
|
|
145
|
+
fs.writeFileSync(at(".claude/settings.json"), JSON.stringify(s, null, 2));
|
|
146
|
+
expect(coverage(withLock, collectInventory(root), root).unmapped).toEqual([]);
|
|
147
|
+
expect(coverage(ast, collectInventory(root), root).unmapped).toHaveLength(1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe("matcher gaps against the inventory's tool names", () => {
|
|
151
|
+
it("[N36] a refuse hook that guards a server is checked against every tool the inventory lists for it", () => {
|
|
152
|
+
const locked = parse(source.replace(" hook finished {", ` hook lock-search {\n on: PreToolUse\n matcher: "mcp__search__find"\n run: "scripts/lock.sh"\n strength: refuse\n }\n hook finished {`));
|
|
153
|
+
const items = [...collectInventory(root), { id: "mcp-tool:mcp__search__find", kind: "mcp-tool" }, { id: "mcp-tool:mcp__search__crawl", kind: "mcp-tool" }];
|
|
154
|
+
const r = coverage(locked, items, root);
|
|
155
|
+
// crawl comes from the inventory; find.all from the drafter's own tools: list.
|
|
156
|
+
expect(r.gaps.map((g) => g.tool).sort()).toEqual(["mcp__search__crawl", "mcp__search__find.all"]);
|
|
157
|
+
expect(ids(r.unmapped)).not.toContain("mcp-tool:mcp__search__crawl");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe("the CLI", () => {
|
|
161
|
+
it("[N34] [IV7] inventory <dir> prints JSON and writes nothing", () => {
|
|
162
|
+
const before = fs.readdirSync(root, { recursive: true }).sort();
|
|
163
|
+
const r = cli("inventory", root);
|
|
164
|
+
expect(r.status).toBe(0);
|
|
165
|
+
const json = JSON.parse(r.stdout);
|
|
166
|
+
expect(json.version).toBe(1);
|
|
167
|
+
expect(json.items.map((i) => i.id)).toContain("agent:drafter");
|
|
168
|
+
expect(fs.readdirSync(root, { recursive: true }).sort()).toEqual(before);
|
|
169
|
+
});
|
|
170
|
+
it("[N34] [IV7] validate --inventory <dir> prints coverage; unmapped warns, exit 0", () => {
|
|
171
|
+
write(".claude/agents/stray.md", "x\n");
|
|
172
|
+
const r = cli("validate", FIXTURE, "--inventory", root);
|
|
173
|
+
expect(r.stdout).toMatch(/coverage: \d+ of \d+ live things are in the \.at/);
|
|
174
|
+
expect(r.stdout).toContain("agent:stray");
|
|
175
|
+
expect(r.status).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
it("[N34] [IV7] validate --inventory reads a saved inventory.json; a missing path exits 1", () => {
|
|
178
|
+
fs.rmSync(at("tools/render.py"));
|
|
179
|
+
const saved = join(root, "inventory.json");
|
|
180
|
+
fs.writeFileSync(saved, cli("inventory", root).stdout);
|
|
181
|
+
const r = cli("validate", FIXTURE, "--inventory", saved, "--dir", root);
|
|
182
|
+
expect(r.stdout).toMatch(/missing \(1\)/);
|
|
183
|
+
expect(r.stdout).toContain("tools/render.py");
|
|
184
|
+
expect(r.status).toBe(1);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
//# sourceMappingURL=inventory.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/inventory.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook matchers, read the way Claude Code reads them (N36), and the two hook
|
|
3
|
+
* findings that ride with them (N29, N30).
|
|
4
|
+
*
|
|
5
|
+
* From https://code.claude.com/docs/en/hooks.md (checked 2026-11-10 against
|
|
6
|
+
* the raw page): a matcher of only letters, digits, `_`, `-`, spaces, `,` and
|
|
7
|
+
* `|` is an exact name or a list of names; anything else is a JavaScript
|
|
8
|
+
* regex, unanchored. `*`, `""` and no matcher match all. MCP tools are named
|
|
9
|
+
* `mcp__<server>__<tool>`, so `mcp__memory` (exact) matches no tool. An
|
|
10
|
+
* argument filter goes in the handler's `if` (permission-rule syntax, tool
|
|
11
|
+
* events only; on any other event the hook never runs). Only exit 2 blocks.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=matcher.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/matcher.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook matchers, read the way Claude Code reads them (N36), and the two hook
|
|
3
|
+
* findings that ride with them (N29, N30).
|
|
4
|
+
*
|
|
5
|
+
* From https://code.claude.com/docs/en/hooks.md (checked 2026-11-10 against
|
|
6
|
+
* the raw page): a matcher of only letters, digits, `_`, `-`, spaces, `,` and
|
|
7
|
+
* `|` is an exact name or a list of names; anything else is a JavaScript
|
|
8
|
+
* regex, unanchored. `*`, `""` and no matcher match all. MCP tools are named
|
|
9
|
+
* `mcp__<server>__<tool>`, so `mcp__memory` (exact) matches no tool. An
|
|
10
|
+
* argument filter goes in the handler's `if` (permission-rule syntax, tool
|
|
11
|
+
* events only; on any other event the hook never runs). Only exit 2 blocks.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
import { parse } from "../../parser/index.js";
|
|
15
|
+
import { validate } from "../../parser/validator.js";
|
|
16
|
+
import { serializeAST } from "../../import/serializer.js";
|
|
17
|
+
import { claudeCodeBinding } from "../../bindings/claude-code.js";
|
|
18
|
+
import { matcherMatches } from "../../conformance/claude-code.js";
|
|
19
|
+
import { matcherGaps } from "../../inventory/index.js";
|
|
20
|
+
const topology = (hooks, agentExtra = "") => `topology probe : [pipeline] {
|
|
21
|
+
meta {
|
|
22
|
+
version: "1.0.0"
|
|
23
|
+
description: "Matcher probe"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
orchestrator {
|
|
27
|
+
model: sonnet
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
agent writer {
|
|
31
|
+
model: sonnet
|
|
32
|
+
description: "Writes the post"
|
|
33
|
+
tools: [Read, mcp.chrome.file_upload, mcp.chrome.upload_image, mcp.chrome.browser_batch]
|
|
34
|
+
${agentExtra} prompt {
|
|
35
|
+
Write the post.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
flow {
|
|
40
|
+
writer -> writer [max 1]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hooks {
|
|
44
|
+
${hooks} }
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
const hook = (name, fields) => ` hook ${name} {\n${fields.split("\n").map((l) => ` ${l}`).join("\n")}\n }\n`;
|
|
48
|
+
const rules = (src, rule, level) => validate(parse(src)).filter((r) => r.rule === rule && (!level || r.level === level));
|
|
49
|
+
const files = (src) => claudeCodeBinding.scaffold(parse(src));
|
|
50
|
+
const settingsOf = (src) => JSON.parse(files(src).find((f) => f.path === ".claude/settings.json").content);
|
|
51
|
+
describe("a matcher is read the way Claude Code reads it", () => {
|
|
52
|
+
it("[N36] exact names and lists, unanchored regexes, match-all", () => {
|
|
53
|
+
expect(matcherMatches("PreToolUse", "Bash", "Bash")).toBe(true);
|
|
54
|
+
expect(matcherMatches("PreToolUse", "Bash", "BashOutput")).toBe(false);
|
|
55
|
+
expect(matcherMatches("PreToolUse", "Edit|Write", "Write")).toBe(true);
|
|
56
|
+
expect(matcherMatches("PreToolUse", "Edit, Write", "Edit")).toBe(true);
|
|
57
|
+
expect(matcherMatches("SubagentStop", "code-reviewer", "code-reviewer")).toBe(true);
|
|
58
|
+
expect(matcherMatches("PreToolUse", "^Notebook", "NotebookEdit")).toBe(true);
|
|
59
|
+
expect(matcherMatches("PreToolUse", "Edit.*", "NotebookEdit")).toBe(true);
|
|
60
|
+
expect(matcherMatches("PreToolUse", "^Edit$", "NotebookEdit")).toBe(false);
|
|
61
|
+
expect(matcherMatches("PreToolUse", "*", "anything")).toBe(true);
|
|
62
|
+
expect(matcherMatches("PreToolUse", "", "anything")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it("[N36] MCP: the server prefix alone matches no tool; with .* it matches every tool on it", () => {
|
|
65
|
+
expect(matcherMatches("PreToolUse", "mcp__memory", "mcp__memory__create_entities")).toBe(false);
|
|
66
|
+
expect(matcherMatches("PreToolUse", "mcp__memory__.*", "mcp__memory__create_entities")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("[N36] on FileChanged and StopFailure a hyphen keeps the matcher on the regex path", () => {
|
|
69
|
+
expect(matcherMatches("FileChanged", "a-b", "xa-by")).toBe(true);
|
|
70
|
+
expect(matcherMatches("PreToolUse", "a-b", "xa-by")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("V107: a matcher that can never match is an error", () => {
|
|
74
|
+
it("[N30] permission-rule syntax in a matcher never matches a tool name; the filter belongs in if:", () => {
|
|
75
|
+
const e = rules(topology(hook("guard", 'on: PreToolUse\nmatcher: "Bash(rm -rf *)"\nrun: "scripts/guard.sh"')), "V107", "error");
|
|
76
|
+
expect(e).toHaveLength(1);
|
|
77
|
+
expect(e[0].message).toMatch(/matcher: "Bash".*if: "Bash\(rm -rf \*\)"/);
|
|
78
|
+
});
|
|
79
|
+
it("[N36] the .at's dotted mcp form is not a tool name; the server prefix alone matches nothing", () => {
|
|
80
|
+
const dotted = rules(topology(hook("lock", 'on: PreToolUse\nmatcher: "mcp.chrome.*"\nrun: "scripts/lock.sh"')), "V107", "error");
|
|
81
|
+
expect(dotted[0]?.message).toContain("mcp__chrome__.*");
|
|
82
|
+
const bare = rules(topology(hook("lock", 'on: PreToolUse\nmatcher: "mcp__chrome"\nrun: "scripts/lock.sh"')), "V107", "error");
|
|
83
|
+
expect(bare[0]?.message).toContain("mcp__chrome__.*");
|
|
84
|
+
});
|
|
85
|
+
it("[N36] a matcher that is not a valid regex is an error; agent-level hooks are checked too", () => {
|
|
86
|
+
expect(rules(topology(hook("bad", 'on: PreToolUse\nmatcher: "Edit|(Write"\nrun: "scripts/x.sh"')), "V107", "error")).toHaveLength(1);
|
|
87
|
+
const agentHook = ` hooks {\n hook lint {\n on: PostToolUse\n matcher: "Write(*.md)"\n run: "scripts/lint.sh"\n }\n }\n`;
|
|
88
|
+
expect(rules(topology(hook("ok", 'on: PreToolUse\nmatcher: "Bash"\nrun: "scripts/x.sh"'), agentHook), "V107", "error")).toHaveLength(1);
|
|
89
|
+
});
|
|
90
|
+
it("[N36] good matchers pass", () => {
|
|
91
|
+
const src = topology(hook("a", 'on: PreToolUse\nmatcher: "Bash"\nrun: "scripts/a.sh"') +
|
|
92
|
+
hook("b", 'on: PreToolUse\nmatcher: "mcp__chrome__.*"\nrun: "scripts/b.sh"') +
|
|
93
|
+
hook("c", 'on: PostToolUse\nmatcher: "Edit|Write"\nrun: "scripts/c.sh"'));
|
|
94
|
+
expect(rules(src, "V107")).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("if: filters on the tool's arguments", () => {
|
|
98
|
+
const src = topology(hook("guard", 'on: PreToolUse\nmatcher: "Bash"\nif: "Bash(rm -rf *)"\nrun: "scripts/guard.sh"'));
|
|
99
|
+
it("[N30] if: is read, round-trips, and is written on the settings.json handler", () => {
|
|
100
|
+
expect(parse(src).hooks[0]).toMatchObject({ matcher: "Bash", if: "Bash(rm -rf *)" });
|
|
101
|
+
expect(parse(serializeAST(parse(src))).hooks).toEqual(parse(src).hooks);
|
|
102
|
+
const entry = settingsOf(src).hooks.PreToolUse.find((e) => e.matcher === "Bash");
|
|
103
|
+
expect(entry.hooks[0].if).toBe("Bash(rm -rf *)");
|
|
104
|
+
expect(validate(parse(src)).filter((r) => r.level === "error")).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
it("[N30] if: on an event that is not a tool event is an error: the hook would never run", () => {
|
|
107
|
+
const e = rules(topology(hook("g", 'on: Stop\nif: "Bash(rm *)"\nrun: "scripts/g.sh"')), "V107", "error");
|
|
108
|
+
expect(e[0]?.message).toMatch(/never runs/);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("a run: that is a shell command, not a script", () => {
|
|
112
|
+
it("[N29] run: \"exit 2\" is written as the command itself, with no stub script named after exit", () => {
|
|
113
|
+
const out = files(topology(hook("block", 'on: PreToolUse\nmatcher: "Bash"\nrun: "exit 2"')));
|
|
114
|
+
expect(out.some((f) => /\/exit$/.test(f.path))).toBe(false);
|
|
115
|
+
const s = JSON.parse(out.find((f) => f.path === ".claude/settings.json").content);
|
|
116
|
+
expect(s.hooks.PreToolUse[0].hooks[0].command).toBe("exit 2");
|
|
117
|
+
});
|
|
118
|
+
it("[N29] V108: exit 1 (or any code but 0 and 2) does not block; it is a warning", () => {
|
|
119
|
+
const w = rules(topology(hook("block", 'on: PreToolUse\nmatcher: "Bash"\nrun: "exit 1"')), "V108", "warning");
|
|
120
|
+
expect(w[0]?.message).toMatch(/only exit 2 blocks/);
|
|
121
|
+
expect(rules(topology(hook("block", 'on: PreToolUse\nmatcher: "Bash"\nrun: "exit 2"')), "V108")).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe("V109: a refuse hook that misses a tool its server offers", () => {
|
|
125
|
+
const lock = (matcher, strength = "strength: refuse\n") => topology(hook("lock-chrome", `on: PreToolUse\nmatcher: "${matcher}"\nrun: "scripts/lock.sh"\n${strength}`.trimEnd()));
|
|
126
|
+
it("[N36] lock-chrome covers file_upload and browser_batch but not upload_image: named as a gap", () => {
|
|
127
|
+
const src = lock("mcp__chrome__(file_upload|browser_batch)");
|
|
128
|
+
const w = rules(src, "V109", "warning");
|
|
129
|
+
expect(w).toHaveLength(1);
|
|
130
|
+
expect(w[0].message).toContain("mcp__chrome__upload_image");
|
|
131
|
+
expect(w[0].message).toContain("lock-chrome");
|
|
132
|
+
expect(matcherGaps(parse(src))).toEqual([{ hook: "lock-chrome", server: "chrome", tool: "mcp__chrome__upload_image" }]);
|
|
133
|
+
});
|
|
134
|
+
it("[N36] a matcher that covers the server has no gap; a hook that declares no refuse is not held to it", () => {
|
|
135
|
+
expect(rules(lock("mcp__chrome__.*"), "V109")).toEqual([]);
|
|
136
|
+
expect(rules(lock("mcp__chrome__(file_upload|browser_batch)", ""), "V109")).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
it("[N36] tool names the inventory lists count too", () => {
|
|
139
|
+
const ast = parse(lock("mcp__chrome__.*").replace("mcp.chrome.upload_image, ", ""));
|
|
140
|
+
const gaps = matcherGaps(parse(lock("mcp__chrome__file_upload")), ["mcp__chrome__navigate"]);
|
|
141
|
+
expect(gaps.map((g) => g.tool)).toEqual(expect.arrayContaining(["mcp__chrome__navigate", "mcp__chrome__upload_image", "mcp__chrome__browser_batch"]));
|
|
142
|
+
expect(matcherGaps(ast, ["mcp__chrome__navigate"])).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=matcher.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/matcher.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agentopology step check <file.at> <step-id> [--unit U] [--dir D]` (N39).
|
|
3
|
+
* A hook guards a Claude Code session only: a script run from a terminal or a
|
|
4
|
+
* scheduler never sees it. The step calls step check at its start (by: code)
|
|
5
|
+
* and stops on exit 1. It reads ledgers and runs check scripts; it never
|
|
6
|
+
* writes.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=step-check.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-check.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/step-check.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agentopology step check <file.at> <step-id> [--unit U] [--dir D]` (N39).
|
|
3
|
+
* A hook guards a Claude Code session only: a script run from a terminal or a
|
|
4
|
+
* scheduler never sees it. The step calls step check at its start (by: code)
|
|
5
|
+
* and stops on exit 1. It reads ledgers and runs check scripts; it never
|
|
6
|
+
* writes.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import { resolve, dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { parse } from "../../parser/index.js";
|
|
15
|
+
import { checkStep, renderStepReport } from "../../step/check.js";
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const CLI = resolve(here, "../../../dist/cli/index.js");
|
|
18
|
+
const SRC = `topology ship : [pipeline] {
|
|
19
|
+
meta { version: "1.0.0" description: "Render, then publish" }
|
|
20
|
+
orchestrator { model: sonnet handles: [render, publish, notify, tidy] }
|
|
21
|
+
action render { kind: external commands: ["./tool render"] }
|
|
22
|
+
action publish { kind: external commands: ["./tool publish"] unit-from: "./tool publish (\\S+)" }
|
|
23
|
+
action notify { kind: external commands: ["./tool notify"] }
|
|
24
|
+
action tidy { kind: external commands: ["./tool tidy"] }
|
|
25
|
+
action archive { kind: external commands: ["./tool archive"] }
|
|
26
|
+
records {
|
|
27
|
+
record trace { path: "logs/{unit}/trace.jsonl" }
|
|
28
|
+
}
|
|
29
|
+
gates {
|
|
30
|
+
gate publish-ready {
|
|
31
|
+
after: render
|
|
32
|
+
before: publish
|
|
33
|
+
ledger: trace
|
|
34
|
+
}
|
|
35
|
+
gate notify-ready {
|
|
36
|
+
before: notify
|
|
37
|
+
run: "./check-notify.sh"
|
|
38
|
+
}
|
|
39
|
+
gate both {
|
|
40
|
+
after: render
|
|
41
|
+
before: archive
|
|
42
|
+
ledger: trace
|
|
43
|
+
run: "./check-notify.sh"
|
|
44
|
+
}
|
|
45
|
+
gate empty-gate {
|
|
46
|
+
before: tidy
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
flow { render -> publish -> notify -> tidy -> archive }
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
const ast = parse(SRC);
|
|
53
|
+
// With one record declared, a gate with no ledger: reads it. Case (c) needs a topology with none.
|
|
54
|
+
const bare = parse(SRC.replace(/ records \{[\s\S]*?\n \}\n/, "").replace(/ledger: trace\n/g, ""));
|
|
55
|
+
let dir;
|
|
56
|
+
const at = (p) => join(dir, p);
|
|
57
|
+
const rec = (step, ok, unit) => ({ step, ...(unit ? { unit } : {}), ok, at: "2026-01-01T00:00:00Z" });
|
|
58
|
+
const ledger = (unit, lines) => {
|
|
59
|
+
fs.mkdirSync(at(`logs/${unit}`), { recursive: true });
|
|
60
|
+
fs.writeFileSync(at(`logs/${unit}/trace.jsonl`), lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
|
|
61
|
+
};
|
|
62
|
+
const script = (code, name = "check-notify.sh") => fs.writeFileSync(at(name), `#!/bin/sh\necho "checked from $(pwd)" > ran.txt\necho "script said no" >&2\nexit ${code}\n`, { mode: 0o755 });
|
|
63
|
+
beforeEach(() => { dir = fs.mkdtempSync(join(os.tmpdir(), "at-step-check-")); });
|
|
64
|
+
afterEach(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
65
|
+
describe("[N39] step check: the three ways a gate is satisfied", () => {
|
|
66
|
+
it("[N39] SC1 (a) a ledger whose last record for the after: step is ok satisfies the gate", () => {
|
|
67
|
+
ledger("ep42", [rec("render", true, "ep42")]);
|
|
68
|
+
const r = checkStep(ast, "publish", { unit: "ep42", dir });
|
|
69
|
+
expect(r.ok).toBe(true);
|
|
70
|
+
expect(r.gates).toEqual([expect.objectContaining({ gate: "publish-ready", ok: true })]);
|
|
71
|
+
});
|
|
72
|
+
it("[N39] SC2 (a) no record, a later ok:false, or no ledger file fails and says why", () => {
|
|
73
|
+
let r = checkStep(ast, "publish", { unit: "ep42", dir });
|
|
74
|
+
expect(r.ok).toBe(false);
|
|
75
|
+
expect(r.gates[0].reasons.join(" ")).toContain("logs/ep42/trace.jsonl");
|
|
76
|
+
ledger("ep42", [rec("render", true, "ep42"), rec("render", false, "ep42")]);
|
|
77
|
+
r = checkStep(ast, "publish", { unit: "ep42", dir });
|
|
78
|
+
expect(r.ok).toBe(false);
|
|
79
|
+
expect(r.gates[0].reasons.join(" ")).toMatch(/not ok/);
|
|
80
|
+
});
|
|
81
|
+
it("[N39] SC3 (a) the unit filter: another unit's ok record does not count, and a path with {unit} needs --unit", () => {
|
|
82
|
+
ledger("ep41", [rec("render", true, "ep41")]);
|
|
83
|
+
ledger("ep42", [rec("render", true, "ep41")]);
|
|
84
|
+
expect(checkStep(ast, "publish", { unit: "ep42", dir }).ok).toBe(false);
|
|
85
|
+
const none = checkStep(ast, "publish", { dir });
|
|
86
|
+
expect(none.ok).toBe(false);
|
|
87
|
+
expect(none.gates[0].reasons.join(" ")).toMatch(/--unit/);
|
|
88
|
+
});
|
|
89
|
+
it("[N39] SC4 a unit that would climb out of the project fails instead of reading a path", () => {
|
|
90
|
+
expect(checkStep(ast, "publish", { unit: "../../etc", dir }).ok).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
it("[N39] SC5 (b) a run: that exits 0 from D satisfies the gate; non-zero fails, with its output", () => {
|
|
93
|
+
script(0);
|
|
94
|
+
let r = checkStep(ast, "notify", { dir });
|
|
95
|
+
expect(r.ok).toBe(true);
|
|
96
|
+
expect(fs.readFileSync(at("ran.txt"), "utf8")).toContain(fs.realpathSync(dir));
|
|
97
|
+
script(1);
|
|
98
|
+
r = checkStep(ast, "notify", { dir });
|
|
99
|
+
expect(r.ok).toBe(false);
|
|
100
|
+
expect(r.gates[0].reasons.join(" ")).toContain("script said no");
|
|
101
|
+
});
|
|
102
|
+
it("[N39] SC6 (c) a gate with neither ledger nor run: is unsatisfiable: nothing to check", () => {
|
|
103
|
+
const r = checkStep(bare, "tidy", { dir });
|
|
104
|
+
expect(r.ok).toBe(false);
|
|
105
|
+
expect(r.gates[0].reasons.join(" ")).toContain("nothing to check");
|
|
106
|
+
// a ledger answers "did the after: step finish ok?": with no after: there is nothing to look up, even with one record declared
|
|
107
|
+
expect(checkStep(ast, "tidy", { unit: "ep42", dir }).gates[0].reasons.join(" ")).toContain("nothing to check");
|
|
108
|
+
});
|
|
109
|
+
it("[N39] SC7 a gate with a ledger and a run: needs both", () => {
|
|
110
|
+
script(0);
|
|
111
|
+
expect(checkStep(ast, "archive", { unit: "ep42", dir }).ok).toBe(false);
|
|
112
|
+
ledger("ep42", [rec("render", true, "ep42")]);
|
|
113
|
+
expect(checkStep(ast, "archive", { unit: "ep42", dir }).ok).toBe(true);
|
|
114
|
+
script(3);
|
|
115
|
+
expect(checkStep(ast, "archive", { unit: "ep42", dir }).ok).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
it("[N39] SC8 a step no gate guards passes with the line \"no gate guards <step>\"", () => {
|
|
118
|
+
const r = checkStep(ast, "render", { dir });
|
|
119
|
+
expect(r).toMatchObject({ ok: true, gates: [] });
|
|
120
|
+
expect(renderStepReport(r)).toEqual(["no gate guards render"]);
|
|
121
|
+
});
|
|
122
|
+
it("[N39] SC9 it never writes: the ledger is left as it was", () => {
|
|
123
|
+
ledger("ep42", [rec("render", true, "ep42")]);
|
|
124
|
+
const before = fs.readFileSync(at("logs/ep42/trace.jsonl"), "utf8");
|
|
125
|
+
checkStep(ast, "publish", { unit: "ep42", dir });
|
|
126
|
+
expect(fs.readFileSync(at("logs/ep42/trace.jsonl"), "utf8")).toBe(before);
|
|
127
|
+
expect(fs.readdirSync(dir).sort()).toEqual(["logs"]);
|
|
128
|
+
});
|
|
129
|
+
it("[N39] SC10 the rendering names each failing gate and why", () => {
|
|
130
|
+
const text = renderStepReport(checkStep(ast, "publish", { unit: "ep42", dir })).join("\n");
|
|
131
|
+
expect(text).toContain("publish-ready");
|
|
132
|
+
expect(text).toMatch(/fail/i);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("[N39] step check: the CLI", () => {
|
|
136
|
+
const run = (...args) => spawnSync("node", [CLI, "step", "check", ...args], { encoding: "utf8" });
|
|
137
|
+
let file;
|
|
138
|
+
beforeEach(() => { file = at("ship.at"); fs.writeFileSync(file, SRC); });
|
|
139
|
+
it("[N39] SC11 exits 0 when satisfied, 1 when not, naming the gate", () => {
|
|
140
|
+
ledger("ep42", [rec("render", true, "ep42")]);
|
|
141
|
+
const ok = run(file, "publish", "--unit", "ep42", "--dir", dir);
|
|
142
|
+
expect(ok.status).toBe(0);
|
|
143
|
+
const bad = run(file, "publish", "--unit", "ep43", "--dir", dir);
|
|
144
|
+
expect(bad.status).toBe(1);
|
|
145
|
+
expect(bad.stdout + bad.stderr).toContain("publish-ready");
|
|
146
|
+
});
|
|
147
|
+
it("[N39] SC12 an unguarded step exits 0 and says so; --dir defaults to .", () => {
|
|
148
|
+
const r = spawnSync("node", [CLI, "step", "check", file, "render"], { encoding: "utf8", cwd: dir });
|
|
149
|
+
expect(r.status).toBe(0);
|
|
150
|
+
expect(r.stdout).toContain("no gate guards render");
|
|
151
|
+
});
|
|
152
|
+
it("[N39] SC13 a step that is no node, or missing arguments, exits 1", () => {
|
|
153
|
+
expect(run(file, "nope", "--dir", dir).status).toBe(1);
|
|
154
|
+
expect(run(file).status).toBe(1);
|
|
155
|
+
expect(run().status).toBe(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=step-check.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-check.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/step-check.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How strongly a step is enforced, written in the file (N32).
|
|
3
|
+
*
|
|
4
|
+
* `strength: refuse | detect | hope | none` says what stops a step: code or a
|
|
5
|
+
* hook refuses it, a critic detects it after the fact, prose hopes, or
|
|
6
|
+
* nothing. `by: code | hook | settings | gate` says which mechanism. A gate or
|
|
7
|
+
* hook can cite the code that refuses (`at: "file:line"`, `match:` the text
|
|
8
|
+
* expected there) and name its way around (`bypass:`). A flow edge can cite
|
|
9
|
+
* its guard (`[guard: lock-bash]`) or write its strength down
|
|
10
|
+
* (`[strength: none]`), so an absence is declared rather than implied.
|
|
11
|
+
*
|
|
12
|
+
* The validator checks the words and the references (V104 to V106). Whether
|
|
13
|
+
* the cited code is there and says what `match:` claims is read from disk by
|
|
14
|
+
* `guard --check`, not here: validate never reads the project.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=strength.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strength.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/strength.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|