agentopology 0.9.2 → 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/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/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 +181 -15
- 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/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 +15 -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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [N41] `guard <file> --target claude-code` writes only the guard part of what
|
|
3
|
+
* scaffold would write, into a project that was built by hand: the hook
|
|
4
|
+
* scripts settings.json runs, the gate wrappers and check scripts they call,
|
|
5
|
+
* and the `hooks` and `permissions` keys of settings.json, merged. It never
|
|
6
|
+
* writes an agent, skill, command, rule, .mcp.json or CLAUDE.md, never
|
|
7
|
+
* overwrites a script the user edited or a check that is no longer a stub,
|
|
8
|
+
* and a rerun changes nothing.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { resolve, dirname, join, relative } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { parse } from "../../parser/index.js";
|
|
18
|
+
import { claudeCodeBinding } from "../../bindings/claude-code.js";
|
|
19
|
+
import { guardFiles, planGuardTarget } from "../../guard/target.js";
|
|
20
|
+
import { checkGuards } from "../../guard/check.js";
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const FIXTURE = resolve(here, "fixtures/compile.at");
|
|
23
|
+
const CLI = resolve(here, "../../../dist/cli/index.js");
|
|
24
|
+
const ast = parse(fs.readFileSync(FIXTURE, "utf-8"));
|
|
25
|
+
const SCRIPTS = ".claude/skills/compile/scripts";
|
|
26
|
+
const AGENT = ".claude/agents/drafter/AGENT.md";
|
|
27
|
+
const AGENT_BYTES = "---\nname: drafter\ndescription: by hand\n---\nWrite the copy. Hand-written, do not touch.\n";
|
|
28
|
+
const USER_CHECK = "#!/usr/bin/env python3\nprint('my own check')\n";
|
|
29
|
+
const USER_BRIEF = "#!/usr/bin/env bash\n# mine, implemented\nexit 0\n";
|
|
30
|
+
let root;
|
|
31
|
+
const at = (p) => join(root, p);
|
|
32
|
+
const settings = () => JSON.parse(fs.readFileSync(at(".claude/settings.json"), "utf8"));
|
|
33
|
+
const cli = (...a) => spawnSync("node", [CLI, "guard", FIXTURE, "--target", "claude-code", "--dir", root, ...a], { encoding: "utf8" });
|
|
34
|
+
function snapshot() {
|
|
35
|
+
const out = {};
|
|
36
|
+
const walk = (d) => {
|
|
37
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
38
|
+
const p = join(d, e.name);
|
|
39
|
+
if (e.isDirectory()) {
|
|
40
|
+
out[relative(root, p) + "/"] = "";
|
|
41
|
+
walk(p);
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
out[relative(root, p)] = createHash("sha256").update(fs.readFileSync(p)).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
walk(root);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
root = fs.mkdtempSync(join(os.tmpdir(), "at-guard-target-"));
|
|
52
|
+
fs.mkdirSync(at(".claude/agents/drafter"), { recursive: true });
|
|
53
|
+
fs.writeFileSync(at(AGENT), AGENT_BYTES);
|
|
54
|
+
fs.writeFileSync(at(".claude/settings.json"), JSON.stringify({
|
|
55
|
+
hooks: { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/mine.sh\"" }] }] },
|
|
56
|
+
permissions: { allow: ["Bash(ls:*)"] },
|
|
57
|
+
model: "sonnet",
|
|
58
|
+
}, null, 2));
|
|
59
|
+
fs.mkdirSync(at("docs/tools"), { recursive: true });
|
|
60
|
+
fs.writeFileSync(at("docs/tools/check.py"), USER_CHECK);
|
|
61
|
+
fs.mkdirSync(at(SCRIPTS), { recursive: true });
|
|
62
|
+
fs.writeFileSync(at(`${SCRIPTS}/brief-check.sh`), USER_BRIEF);
|
|
63
|
+
});
|
|
64
|
+
describe("[N41] guard --target", () => {
|
|
65
|
+
it("[N41] keeps the hook scripts, the gates they call and the checks the gates run, and nothing else", () => {
|
|
66
|
+
const paths = guardFiles(claudeCodeBinding.scaffold(ast)).map((f) => f.path).sort();
|
|
67
|
+
expect(paths).toContain(`${SCRIPTS}/before-agent.sh`);
|
|
68
|
+
expect(paths).toContain(`${SCRIPTS}/gate-brief-check.sh`);
|
|
69
|
+
expect(paths).toContain(`${SCRIPTS}/gate-copy-check.sh`);
|
|
70
|
+
expect(paths).toContain(`${SCRIPTS}/brief-check.sh`);
|
|
71
|
+
expect(paths).toContain("docs/tools/check.py");
|
|
72
|
+
expect(paths).toContain(".claude/settings.json");
|
|
73
|
+
for (const p of paths)
|
|
74
|
+
expect(p).not.toMatch(/AGENT\.md|SKILL\.md|\.mcp\.json|CLAUDE\.md|rules\/|commands\/|crontab|schedule-|action-/);
|
|
75
|
+
});
|
|
76
|
+
it("[N41] the function plans the same as the command and writes nothing", () => {
|
|
77
|
+
const before = snapshot();
|
|
78
|
+
const plan = planGuardTarget(ast, root, "compile.at");
|
|
79
|
+
expect(snapshot()).toEqual(before);
|
|
80
|
+
expect(plan.actions.some((a) => a.path === `${SCRIPTS}/before-agent.sh` && a.type === "create")).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it("[N41] writes our guard, leaves the agent and the user's hook and permission", () => {
|
|
83
|
+
const r = cli();
|
|
84
|
+
expect(r.status).toBe(0);
|
|
85
|
+
expect(fs.readFileSync(at(AGENT), "utf8")).toBe(AGENT_BYTES);
|
|
86
|
+
const s = settings();
|
|
87
|
+
expect(JSON.stringify(s.hooks.PreToolUse)).toContain(".claude/hooks/mine.sh");
|
|
88
|
+
expect(s.permissions.allow).toContain("Bash(ls:*)");
|
|
89
|
+
expect(s.permissions.deny).toContain("mcp__notes__delete");
|
|
90
|
+
expect(JSON.stringify(s.hooks.PreToolUse)).toContain("before-agent.sh");
|
|
91
|
+
expect(JSON.stringify(s.hooks.SubagentStop)).toContain("gate-copy-check.sh");
|
|
92
|
+
expect(s.model).toBe("sonnet");
|
|
93
|
+
expect(fs.existsSync(at(`${SCRIPTS}/before-agent.sh`))).toBe(true);
|
|
94
|
+
expect(fs.existsSync(at(`${SCRIPTS}/gate-brief-check.sh`))).toBe(true);
|
|
95
|
+
for (const p of [".mcp.json", "CLAUDE.md", ".claude/skills/compile/SKILL.md", ".claude/rules/compile.md", ".claude/agents/auditor/AGENT.md", "crontab.fragment"]) {
|
|
96
|
+
expect(fs.existsSync(at(p)), p).toBe(false);
|
|
97
|
+
}
|
|
98
|
+
expect(s.hooks.Stop).toBeUndefined(); // a prompt hook is not a guard script
|
|
99
|
+
});
|
|
100
|
+
it("[N41] a second run changes nothing and adds no duplicates", () => {
|
|
101
|
+
cli();
|
|
102
|
+
const first = snapshot();
|
|
103
|
+
const hooks = JSON.stringify(settings().hooks);
|
|
104
|
+
const r = cli();
|
|
105
|
+
expect(r.status).toBe(0);
|
|
106
|
+
expect(snapshot()).toEqual(first);
|
|
107
|
+
expect(JSON.stringify(settings().hooks)).toBe(hooks);
|
|
108
|
+
const perms = settings().permissions.allow;
|
|
109
|
+
expect(new Set(perms).size).toBe(perms.length);
|
|
110
|
+
});
|
|
111
|
+
it("[N41] --dry-run prints the plan and writes nothing", () => {
|
|
112
|
+
const before = snapshot();
|
|
113
|
+
const r = cli("--dry-run");
|
|
114
|
+
expect(r.status).toBe(0);
|
|
115
|
+
expect(r.stdout).toContain("before-agent.sh");
|
|
116
|
+
expect(snapshot()).toEqual(before);
|
|
117
|
+
});
|
|
118
|
+
it("[N41] a hand-written check, and a check no longer a stub, are untouched and reported", () => {
|
|
119
|
+
const r = cli();
|
|
120
|
+
expect(fs.readFileSync(at("docs/tools/check.py"), "utf8")).toBe(USER_CHECK);
|
|
121
|
+
expect(fs.readFileSync(at(`${SCRIPTS}/brief-check.sh`), "utf8")).toBe(USER_BRIEF);
|
|
122
|
+
expect(r.stdout).toContain("docs/tools/check.py");
|
|
123
|
+
expect(r.stdout).toMatch(/brief-check\.sh.*(skipped|kept)/);
|
|
124
|
+
});
|
|
125
|
+
it("[N41] a script the user edited after a guard run is kept on the next one", () => {
|
|
126
|
+
cli();
|
|
127
|
+
const p = at(`${SCRIPTS}/gate-brief-check.sh`);
|
|
128
|
+
fs.appendFileSync(p, "# my edit\n");
|
|
129
|
+
const edited = fs.readFileSync(p, "utf8");
|
|
130
|
+
const r = cli();
|
|
131
|
+
expect(fs.readFileSync(p, "utf8")).toBe(edited);
|
|
132
|
+
expect(r.stdout).toMatch(/gate-brief-check\.sh.*(edited|kept)/);
|
|
133
|
+
});
|
|
134
|
+
it("[N41] then runs guard --check: the gates refuse and the exit is 0", () => {
|
|
135
|
+
const r = cli();
|
|
136
|
+
expect(r.status).toBe(0);
|
|
137
|
+
expect(r.stdout).toMatch(/brief-check[^\n]*refuse/);
|
|
138
|
+
expect(r.stdout).toMatch(/copy-check[^\n]*refuse/);
|
|
139
|
+
const report = checkGuards(ast, root);
|
|
140
|
+
expect(report.items.filter((i) => i.kind === "gate").every((i) => i.strength === "refuse")).toBe(true);
|
|
141
|
+
expect(report.missing).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
it("[N41] on a bare directory it exits 1 until the stubs are written, like --check", () => {
|
|
144
|
+
const bare = fs.mkdtempSync(join(os.tmpdir(), "at-guard-target-bare-"));
|
|
145
|
+
const r = spawnSync("node", [CLI, "guard", FIXTURE, "--target", "claude-code", "--dir", bare], { encoding: "utf8" });
|
|
146
|
+
expect(fs.existsSync(join(bare, SCRIPTS, "before-agent.sh"))).toBe(true);
|
|
147
|
+
expect(r.stdout).toContain("enforcement:");
|
|
148
|
+
});
|
|
149
|
+
it("[N41] picks up the before-action.sh of a gate before a script step and guard --check reports it as refuse", () => {
|
|
150
|
+
const src = `topology ship : [pipeline] {
|
|
151
|
+
meta {
|
|
152
|
+
version: "1.0.0"
|
|
153
|
+
description: "Render then publish"
|
|
154
|
+
}
|
|
155
|
+
orchestrator {
|
|
156
|
+
model: sonnet
|
|
157
|
+
handles: [render, publish]
|
|
158
|
+
}
|
|
159
|
+
action render {
|
|
160
|
+
kind: external
|
|
161
|
+
commands: ["./tool render"]
|
|
162
|
+
}
|
|
163
|
+
action publish {
|
|
164
|
+
kind: external
|
|
165
|
+
commands: ["./tool publish"]
|
|
166
|
+
unit-from: "./tool publish (\\S+)"
|
|
167
|
+
}
|
|
168
|
+
records {
|
|
169
|
+
record trace { path: "logs/{unit}/trace.jsonl" }
|
|
170
|
+
}
|
|
171
|
+
gates {
|
|
172
|
+
gate publish-ready {
|
|
173
|
+
after: render
|
|
174
|
+
before: publish
|
|
175
|
+
ledger: trace
|
|
176
|
+
on-fail: halt
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
flow { render -> publish }
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
const dir = fs.mkdtempSync(join(os.tmpdir(), "at-guard-target-action-"));
|
|
183
|
+
const file = join(dir, "ship.at");
|
|
184
|
+
fs.writeFileSync(file, src);
|
|
185
|
+
const r = spawnSync("node", [CLI, "guard", file, "--target", "claude-code", "--dir", dir], { encoding: "utf8" });
|
|
186
|
+
const script = join(dir, ".claude/skills/ship/scripts/before-action.sh");
|
|
187
|
+
expect(fs.existsSync(script), r.stdout + r.stderr).toBe(true);
|
|
188
|
+
const cfg = fs.readFileSync(join(dir, ".claude/settings.json"), "utf8");
|
|
189
|
+
expect(cfg).toContain("before-action.sh");
|
|
190
|
+
expect(r.status, r.stdout + r.stderr).toBe(0);
|
|
191
|
+
expect(r.stdout).toMatch(/publish-ready[^\n]*refuse/);
|
|
192
|
+
const report = checkGuards(parse(src), dir);
|
|
193
|
+
const gate = report.items.find((i) => i.kind === "gate");
|
|
194
|
+
expect(gate?.strength).toBe("refuse");
|
|
195
|
+
expect(gate?.by).toBe("hook");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
//# sourceMappingURL=guard-target.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard-target.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/guard-target.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=inventory.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/inventory.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -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
|