agentopology 0.8.0 → 0.9.1

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.
Files changed (54) hide show
  1. package/README.md +5 -5
  2. package/dist/__tests__/e2e/import-project.test.d.ts +10 -0
  3. package/dist/__tests__/e2e/import-project.test.d.ts.map +1 -0
  4. package/dist/__tests__/e2e/import-project.test.js +260 -0
  5. package/dist/__tests__/e2e/import-project.test.js.map +1 -0
  6. package/dist/__tests__/e2e/unknown-names.test.d.ts +12 -0
  7. package/dist/__tests__/e2e/unknown-names.test.d.ts.map +1 -0
  8. package/dist/__tests__/e2e/unknown-names.test.js +278 -0
  9. package/dist/__tests__/e2e/unknown-names.test.js.map +1 -0
  10. package/dist/__tests__/open-findings.test.js +3 -0
  11. package/dist/__tests__/open-findings.test.js.map +1 -1
  12. package/dist/cli/index.js +42 -27
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/docs/content.d.ts.map +1 -1
  15. package/dist/docs/content.js +20 -19
  16. package/dist/docs/content.js.map +1 -1
  17. package/dist/import/claude-code.d.ts +2 -1
  18. package/dist/import/claude-code.d.ts.map +1 -1
  19. package/dist/import/claude-code.js +15 -7
  20. package/dist/import/claude-code.js.map +1 -1
  21. package/dist/import/flow.d.ts +64 -0
  22. package/dist/import/flow.d.ts.map +1 -0
  23. package/dist/import/flow.js +176 -0
  24. package/dist/import/flow.js.map +1 -0
  25. package/dist/import/index.d.ts +11 -0
  26. package/dist/import/index.d.ts.map +1 -1
  27. package/dist/import/index.js +15 -4
  28. package/dist/import/index.js.map +1 -1
  29. package/dist/import/serializer.d.ts.map +1 -1
  30. package/dist/import/serializer.js +2 -0
  31. package/dist/import/serializer.js.map +1 -1
  32. package/dist/import/walk.d.ts +56 -0
  33. package/dist/import/walk.d.ts.map +1 -0
  34. package/dist/import/walk.js +163 -0
  35. package/dist/import/walk.js.map +1 -0
  36. package/dist/parser/ast.d.ts +2 -0
  37. package/dist/parser/ast.d.ts.map +1 -1
  38. package/dist/parser/ast.js.map +1 -1
  39. package/dist/parser/index.d.ts.map +1 -1
  40. package/dist/parser/index.js +10 -0
  41. package/dist/parser/index.js.map +1 -1
  42. package/dist/parser/known-fields.d.ts +82 -0
  43. package/dist/parser/known-fields.d.ts.map +1 -0
  44. package/dist/parser/known-fields.js +263 -0
  45. package/dist/parser/known-fields.js.map +1 -0
  46. package/dist/parser/validator.d.ts.map +1 -1
  47. package/dist/parser/validator.js +33 -0
  48. package/dist/parser/validator.js.map +1 -1
  49. package/examples/matchmat-ship.at +2 -8
  50. package/examples/memory-system.at +0 -1
  51. package/package.json +1 -1
  52. package/spec/grammar.md +3 -1
  53. package/spec/testing.md +9 -0
  54. package/spec/validation.md +4 -2
package/README.md CHANGED
@@ -96,7 +96,7 @@ who works, when they work, and what they know when they work. One file, two ways
96
96
  ┌────────────┐
97
97
  ┌──────────────┐ │ Parser & │ scaffold ──▶ Platform configs
98
98
  │ .at file │─▶│ Validator │──┤ ├── .claude/agents/
99
- │ (you write) │ │ 101 rules │ │ ├── <topology>.workflow.js
99
+ │ (you write) │ │ 103 rules │ │ ├── <topology>.workflow.js
100
100
  └──────────────┘ └────────────┘ │ ├── .codex/ .cursor/rules/
101
101
  │ ├── .openclaw/ .github/agents/
102
102
  │ └── .kiro/ …
@@ -234,7 +234,7 @@ In Claude Code, type `/agentopology` — or just ask naturally:
234
234
  Say **"I want a code review team with an analyzer, security scanner, and reviewer"** — the skill:
235
235
 
236
236
  1. Generates the `.at` file with the right syntax
237
- 2. Validates it against 101 rules
237
+ 2. Validates it against 103 rules
238
238
  3. Scaffolds it to Claude Code, OpenClaw, Cursor, or any target
239
239
 
240
240
  **Full agent team in under 2 minutes.** No docs to read. No syntax to memorize. You describe what you want, the skill handles the rest.
@@ -566,7 +566,7 @@ import { parse, validate, bindings } from "agentopology";
566
566
  // Parse
567
567
  const ast = parse(atSource);
568
568
 
569
- // Validate (101 built-in rules, V1-V101)
569
+ // Validate (103 built-in rules, V1-V103)
570
570
  const issues = validate(ast);
571
571
 
572
572
  // Scaffold — generate platform files
@@ -624,7 +624,7 @@ You can `agentopology visualize` it into an interactive graph. You can hand it t
624
624
  | **Switch platforms** | Rewrite everything | Change `--target` |
625
625
  | **Add an agent** | Update 5-12 files across 3 tools | Add 4 lines to `.at` file |
626
626
  | **See the architecture** | Read YAML, JSON, TOML, Markdown across 6 dirs | One `.at` file. Or `visualize` it. |
627
- | **Validate** | Hope for the best | 101 built-in rules catch errors before deploy |
627
+ | **Validate** | Hope for the best | 103 built-in rules catch errors before deploy |
628
628
  | **Onboard someone** | "Read these 15 files and figure it out" | "Read this `.at` file" |
629
629
  | **Version control** | Diff 47 generated files | Diff one `.at` file |
630
630
  | **Move to a new tool** | Start over | `--target new-tool` |
@@ -646,7 +646,7 @@ You can `agentopology visualize` it into an interactive graph. You can hand it t
646
646
  ## CLI Reference
647
647
 
648
648
  ```
649
- agentopology validate <file> Validate an .at file (101 rules)
649
+ agentopology validate <file> Validate an .at file (103 rules)
650
650
  agentopology plan <file> Resolve for interpreted execution — no files written
651
651
  agentopology scaffold <file> --target <t> Generate platform configs
652
652
  agentopology sync <file> --target <t> --dir <d> Sync platform files back to .at
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Import on a real repo root: it reads only what it needs, and it recovers the
3
+ * flow where the project records one and says where it does not.
4
+ *
5
+ * Measured on a client repo with 31 agents: over two minutes in the walk, most
6
+ * of it `.claude/worktrees` (dozens of full checkouts), and `edges: []` although
7
+ * two .at files in the repo held 52 edges.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=import-project.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-project.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/import-project.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Import on a real repo root: it reads only what it needs, and it recovers the
3
+ * flow where the project records one and says where it does not.
4
+ *
5
+ * Measured on a client repo with 31 agents: over two minutes in the walk, most
6
+ * of it `.claude/worktrees` (dozens of full checkouts), and `edges: []` although
7
+ * two .at files in the repo held 52 edges.
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
10
+ import { execFileSync, spawnSync } from "node:child_process";
11
+ import * as fs from "node:fs";
12
+ import * as os from "node:os";
13
+ import { resolve, dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { parse } from "../../parser/index.js";
16
+ import { validate } from "../../parser/validator.js";
17
+ import { importClaudeCodeWithReport, readClaudeProject, readProject } from "../../import/index.js";
18
+ import { MAX_FILE_BYTES, MAX_TOTAL_BYTES } from "../../import/walk.js";
19
+ const CLI = resolve(dirname(fileURLToPath(import.meta.url)), "../../../dist/cli/index.js");
20
+ let root;
21
+ const put = (rel, content = "x\n") => {
22
+ fs.mkdirSync(dirname(join(root, rel)), { recursive: true });
23
+ fs.writeFileSync(join(root, rel), content);
24
+ };
25
+ const agent = (id) => put(`.claude/agents/${id}.md`, `---\nname: ${id}\ndescription: "${id}"\n---\n\nDo the ${id} work.\n`);
26
+ const paths = () => readClaudeProject(root).files.map((f) => f.path).sort();
27
+ const imported = () => {
28
+ const { source, report } = importClaudeCodeWithReport(readClaudeProject(root).files, "shop");
29
+ return { source, report, ast: parse(source) };
30
+ };
31
+ const SHOP_AT = `topology shop : [pipeline, human-gate] {
32
+ meta {
33
+ version: "2.1.0"
34
+ description: "The shop pipeline"
35
+ }
36
+ orchestrator {
37
+ model: sonnet
38
+ handles: [intake]
39
+ }
40
+ action intake {
41
+ kind: inline
42
+ description: "Take the order"
43
+ }
44
+ agent writer {
45
+ model: sonnet
46
+ description: "Writes"
47
+ writes: ["out/draft.md"]
48
+ }
49
+ human approve {
50
+ description: "An editor approves"
51
+ }
52
+ agent reviewer {
53
+ model: sonnet
54
+ description: "Reviews"
55
+ reads: ["out/draft.md"]
56
+ outputs: {
57
+ verdict: pass | fail
58
+ }
59
+ }
60
+ agent ghost {
61
+ model: sonnet
62
+ description: "Declared, never written to disk"
63
+ }
64
+ gates {
65
+ gate lint {
66
+ after: writer
67
+ before: approve
68
+ run: "scripts/lint.sh"
69
+ }
70
+ }
71
+ flow {
72
+ intake -> writer
73
+ writer -> approve
74
+ approve -> reviewer
75
+ reviewer -> ghost [when reviewer.verdict == pass]
76
+ reviewer -> writer [when reviewer.verdict == fail, max 2]
77
+ }
78
+ }
79
+ `;
80
+ beforeEach(() => {
81
+ root = fs.mkdtempSync(join(os.tmpdir(), "at-import-"));
82
+ agent("writer");
83
+ agent("reviewer");
84
+ });
85
+ afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
86
+ describe("import reads only what it needs", () => {
87
+ it("[I1] worktrees, build output and the rest of the tree are never read", () => {
88
+ for (const noise of [".worktrees/w1", ".claude/worktrees/agent-1", "dist", "build", "coverage", "node_modules/p"]) {
89
+ put(`${noise}/.claude/agents/stale.md`, "---\nname: stale\n---\n");
90
+ put(`${noise}/old.at`, "not a topology");
91
+ }
92
+ put("src/app.ts", "code");
93
+ const files = paths();
94
+ expect(files.filter((p) => /stale|old\.at|app\.ts/.test(p))).toEqual([]);
95
+ expect(files).toEqual([".claude/agents/reviewer.md", ".claude/agents/writer.md"]);
96
+ });
97
+ it("[I1] a skill or agent the author named build or dist is kept", () => {
98
+ put(".claude/skills/build/SKILL.md", "---\nname: build\ndescription: b\n---\n");
99
+ put(".claude/agents/dist/AGENT.md", "---\nname: dist\ndescription: d\n---\n");
100
+ expect(paths()).toEqual(expect.arrayContaining([".claude/skills/build/SKILL.md", ".claude/agents/dist/AGENT.md"]));
101
+ expect(imported().ast.nodes.map((n) => n.id)).toContain("dist");
102
+ // the generic walk used by sync and openclaw keeps them too
103
+ expect(readProject(root).files.map((f) => f.path)).toContain(".claude/skills/build/SKILL.md");
104
+ });
105
+ it("[I1] an agents/x.md in the source tree is not an agent, and a nested .claude is named, not read", () => {
106
+ put("docs/agents/guide.md", "# not an agent");
107
+ put("packages/app/.claude/agents/inner.md", "---\nname: inner\n---\n");
108
+ const project = readClaudeProject(root);
109
+ expect(imported().ast.nodes.map((n) => n.id).sort()).toEqual(["reviewer", "writer"]);
110
+ expect(project.nestedConfigs).toEqual([join("packages", "app", ".claude")]);
111
+ });
112
+ it("[I1] a symlink cycle ends, and a large or binary file in .claude is not read", () => {
113
+ fs.symlinkSync(root, join(root, ".claude/loop"));
114
+ put(".claude/skills/video/clip.mp4", "binary");
115
+ expect(paths().filter((p) => p.includes("loop") || p.endsWith(".mp4"))).toEqual([]);
116
+ });
117
+ it("[I1] a folder with no .claude is read as a config folder given directly", () => {
118
+ const direct = join(root, ".claude");
119
+ expect(readClaudeProject(direct).files.map((f) => f.path).sort()).toEqual([".claude/agents/reviewer.md", ".claude/agents/writer.md"]);
120
+ const bare = fs.mkdtempSync(join(os.tmpdir(), "at-bare-"));
121
+ expect(readClaudeProject(bare)).toBeNull();
122
+ fs.rmSync(bare, { recursive: true });
123
+ });
124
+ });
125
+ describe("import always ends, and says why", () => {
126
+ const run = (...extra) => {
127
+ const r = spawnSync(process.execPath, [CLI, "import", "--target", "claude-code", "--dir", root, "--name", "shop", "--output", join(root, "o"), ...extra], { encoding: "utf8" });
128
+ return { code: r.status, out: `${r.stdout}${r.stderr}` };
129
+ };
130
+ it("[I5] an unreadable file, an unreadable folder, a broken link and an oversized file do not stop the import, and each is named", () => {
131
+ put(".claude/agents/locked.md", "secret");
132
+ fs.chmodSync(join(root, ".claude/agents/locked.md"), 0o000);
133
+ put(".claude/locked/a.md");
134
+ fs.chmodSync(join(root, ".claude/locked"), 0o000);
135
+ fs.symlinkSync(join(root, "nowhere"), join(root, ".claude/agents/broken.md"));
136
+ put(".claude/skills/logs/run.md");
137
+ fs.truncateSync(join(root, ".claude/skills/logs/run.md"), MAX_FILE_BYTES + 1);
138
+ const { code, out } = run();
139
+ fs.chmodSync(join(root, ".claude/agents/locked.md"), 0o644);
140
+ fs.chmodSync(join(root, ".claude/locked"), 0o755);
141
+ expect(code).toBe(0);
142
+ expect(fs.existsSync(join(root, "o/shop.at"))).toBe(true);
143
+ expect(out).toContain("Not read: .claude/agents/locked.md (EACCES");
144
+ expect(out).toContain("Not read: .claude/locked (EACCES");
145
+ expect(out).toContain("Not read: .claude/skills/logs/run.md (5 MB, over the 5 MB limit)");
146
+ });
147
+ it("[I5] a link at the root is not followed out of the project; a linked skill inside .claude still is", () => {
148
+ const outside = fs.mkdtempSync(join(os.tmpdir(), "at-outside-"));
149
+ fs.writeFileSync(join(outside, "elsewhere.at"), "not read");
150
+ fs.mkdirSync(join(outside, "linked"));
151
+ fs.writeFileSync(join(outside, "linked/SKILL.md"), "---\nname: linked\ndescription: l\n---\n");
152
+ fs.symlinkSync(outside, join(root, "shots"));
153
+ fs.mkdirSync(join(root, ".claude/skills"), { recursive: true });
154
+ fs.symlinkSync(join(outside, "linked"), join(root, ".claude/skills/linked"));
155
+ const files = paths();
156
+ fs.rmSync(outside, { recursive: true });
157
+ expect(files.filter((p) => p.includes("elsewhere"))).toEqual([]);
158
+ expect(files).toContain(".claude/skills/linked/SKILL.md");
159
+ });
160
+ it("[I5] past the total budget the read stops with a message, exit 1, and no partial .at", () => {
161
+ for (let i = 0; i * MAX_FILE_BYTES <= MAX_TOTAL_BYTES; i++) {
162
+ put(`.claude/skills/s/f${i}.md`);
163
+ fs.truncateSync(join(root, `.claude/skills/s/f${i}.md`), MAX_FILE_BYTES);
164
+ }
165
+ const { code, out } = run();
166
+ expect(code).toBe(1);
167
+ expect(out).toContain("Importing from claude-code files");
168
+ expect(out).toMatch(/Import error: Stopped reading after 256 MB/);
169
+ expect(fs.existsSync(join(root, "o/shop.at"))).toBe(false);
170
+ });
171
+ it("[I5] what import holds does not grow with the source tree", () => {
172
+ for (let d = 0; d < 20; d++)
173
+ put(`src/d${d}/f.ts`, "x".repeat(1024));
174
+ const project = readClaudeProject(root);
175
+ expect(project.files.reduce((n, f) => n + f.content.length, 0)).toBeLessThan(1024);
176
+ });
177
+ });
178
+ describe("import recovers the flow a .at records", () => {
179
+ beforeEach(() => put(".claude/topologies/shop.at", SHOP_AT));
180
+ it("[I2] every edge comes back with its conditions, plus the actions, humans and gates on the way", () => {
181
+ const { ast } = imported();
182
+ expect(ast.edges.map((e) => `${e.from}>${e.to}`)).toEqual([
183
+ "intake>writer", "writer>approve", "approve>reviewer", "reviewer>ghost", "reviewer>writer",
184
+ ]);
185
+ expect(ast.edges.find((e) => e.to === "ghost")?.condition).toBe("reviewer.verdict == pass");
186
+ expect(ast.edges.find((e) => e.from === "reviewer" && e.to === "writer")?.maxIterations).toBe(2);
187
+ expect(ast.nodes.map((n) => `${n.type}:${n.id}`)).toEqual(expect.arrayContaining(["action:intake", "human:approve", "gate:lint", "orchestrator:orchestrator"]));
188
+ expect(ast.topology.version).toBe("2.1.0");
189
+ expect(ast.topology.patterns).toEqual(["pipeline", "human-gate"]);
190
+ });
191
+ it("[I2] the agent's file wins; the .at fills only what a file cannot say", () => {
192
+ put(".claude/agents/writer.md", '---\nname: writer\ndescription: "From the file"\nmodel: haiku\n---\n\nBody.\n');
193
+ const writer = imported().ast.nodes.find((n) => n.id === "writer");
194
+ expect(writer).toMatchObject({ description: "From the file", model: "haiku", writes: ["out/draft.md"], file: ".claude/agents/writer.md" });
195
+ });
196
+ it("[I2] the import validates, and the JSON export carries the edges", () => {
197
+ const { source, ast } = imported();
198
+ expect(validate(ast).filter((r) => r.level === "error")).toEqual([]);
199
+ fs.writeFileSync(join(root, "out.at"), source);
200
+ const dir = join(root, "export");
201
+ execFileSync(process.execPath, [CLI, "export", join(root, "out.at"), "--format", "json", "--output", dir], { stdio: "ignore" });
202
+ const json = JSON.parse(fs.readFileSync(join(dir, fs.readdirSync(dir)[0]), "utf-8"));
203
+ expect(json.edges).toHaveLength(5);
204
+ });
205
+ it("[I2] a gate a skill only describes gives way to the .at's, and a noted heading gives a clean id", () => {
206
+ put(".claude/skills/shop/SKILL.md", "---\nname: shop\ntopology: shop\n---\n\n## Gates\n\n### lint (after writer, before approve)\nRun: lint — every draft\n");
207
+ const gates = imported().ast.nodes.filter((n) => n.type === "gate");
208
+ expect(gates).toEqual([expect.objectContaining({ id: "lint", run: "scripts/lint.sh" })]);
209
+ });
210
+ it("[I3] an agent the .at declares without a file stays in the flow and is reported", () => {
211
+ const { report, ast } = imported();
212
+ expect(ast.nodes.map((n) => n.id)).toContain("ghost");
213
+ expect(report.noFile).toEqual([{ id: "ghost", source: ".claude/topologies/shop.at", workflow: false }]);
214
+ expect(report.dropped).toEqual([]);
215
+ });
216
+ it("[I3] another system's .at, a broken .at and a second copy give no flow, and each is named", () => {
217
+ put("examples/other.at", SHOP_AT.replaceAll("writer", "painter").replaceAll("reviewer", "framer").replace("topology shop", "topology other"));
218
+ put("broken.at", "topology {");
219
+ put("docs/backup/shop.at", SHOP_AT);
220
+ const { report, ast } = imported();
221
+ expect(ast.edges).toHaveLength(5);
222
+ expect(report.sources.map((s) => s.path)).toEqual([".claude/topologies/shop.at"]);
223
+ const reason = (p) => report.skipped.find((s) => s.path === p)?.reason;
224
+ expect(reason("examples/other.at")).toBe("0 of its 3 agents are here");
225
+ expect(reason("broken.at")).toMatch(/^does not parse/);
226
+ expect(reason("docs/backup/shop.at")).toBe('another copy of topology "shop"');
227
+ });
228
+ it("[I3] names that match only by suffix are said, not mapped", () => {
229
+ fs.rmSync(join(root, ".claude/agents"), { recursive: true });
230
+ agent("shop-writer");
231
+ agent("shop-reviewer");
232
+ const { report, ast } = imported();
233
+ expect(ast.edges).toEqual([]);
234
+ expect(report.skipped[0].reason).toContain("2 more match only by suffix");
235
+ });
236
+ });
237
+ describe("import says what it does not know", () => {
238
+ it("[I3] with no flow source: no edge is invented, every agent is on-demand and named, and the .at says so", () => {
239
+ const { report, source, ast } = imported();
240
+ expect(ast.edges).toEqual([]);
241
+ expect(report.unwired.sort()).toEqual(["reviewer", "writer"]);
242
+ expect(source).toContain("# Flow: none found. Nothing on disk records one, so none was invented.");
243
+ expect(source).toContain("# Flow: 0 of 2 agents wired");
244
+ });
245
+ it("[I3] an exact shared path is offered as a possible handoff, outside the flow; a shared folder is not", () => {
246
+ put(".claude/agents/writer.md", "---\nname: writer\ndescription: w\n---\n\n## Writes\n- out/draft.md\n- workspace/\n");
247
+ put(".claude/agents/reviewer.md", "---\nname: reviewer\ndescription: r\n---\n\n## Reads\n- out/draft.md\n- workspace/\n");
248
+ const { report, ast } = imported();
249
+ expect(ast.edges).toEqual([]);
250
+ expect(report.suggested).toEqual([{ from: "writer", to: "reviewer", via: "out/draft.md" }]);
251
+ });
252
+ it("[I3] the CLI prints the report and writes the same lines at the top of the .at", () => {
253
+ put(".claude/topologies/shop.at", SHOP_AT);
254
+ const out = execFileSync(process.execPath, [CLI, "import", "--target", "claude-code", "--dir", root, "--name", "shop", "--output", join(root, "o")], { encoding: "utf8" });
255
+ expect(out).toContain("Flow: 5 edges from .claude/topologies/shop.at (2 agents match)");
256
+ expect(out).toContain("Flow: 3 of 3 agents wired");
257
+ expect(fs.readFileSync(join(root, "o/shop.at"), "utf-8")).toMatch(/^# Flow: 5 edges from/);
258
+ });
259
+ });
260
+ //# sourceMappingURL=import-project.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-project.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/import-project.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Nothing written in a .at file vanishes in silence (N23, N24).
3
+ *
4
+ * Measured on 0.8.0 (harness-venture, lane B, 2026-11-08): in 15 of 17 probe
5
+ * cases an unknown field or block was gone from the AST while validate printed
6
+ * "All validation rules passed" and exited 0; scaffold dropped it too. Only an
7
+ * unknown `on-timeout` value was caught (V59). Each case below is the same
8
+ * small topology with one thing added. V102 now names every name the parser
9
+ * does not read; V103 checks `behavior`.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=unknown-names.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown-names.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/unknown-names.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Nothing written in a .at file vanishes in silence (N23, N24).
3
+ *
4
+ * Measured on 0.8.0 (harness-venture, lane B, 2026-11-08): in 15 of 17 probe
5
+ * cases an unknown field or block was gone from the AST while validate printed
6
+ * "All validation rules passed" and exited 0; scaffold dropped it too. Only an
7
+ * unknown `on-timeout` value was caught (V59). Each case below is the same
8
+ * small topology with one thing added. V102 now names every name the parser
9
+ * does not read; V103 checks `behavior`.
10
+ */
11
+ import { describe, it, expect } from "vitest";
12
+ import { spawnSync } from "node:child_process";
13
+ import * as fs from "node:fs";
14
+ import * as os from "node:os";
15
+ import { resolve, dirname, join } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { parse } from "../../parser/index.js";
18
+ import { validate } from "../../parser/validator.js";
19
+ import { topics } from "../../docs/content.js";
20
+ import { serializeAST } from "../../import/serializer.js";
21
+ const CLI = resolve(dirname(fileURLToPath(import.meta.url)), "../../../dist/cli/index.js");
22
+ const BASELINE = `topology probe : [pipeline] {
23
+ meta {
24
+ version: "1.0.0"
25
+ description: "Probe baseline"
26
+ }
27
+
28
+ orchestrator {
29
+ model: sonnet
30
+ handles: [b]
31
+ }
32
+
33
+ agent a {
34
+ model: sonnet
35
+ description: "Writes the draft"
36
+ produces: [r]
37
+ prompt {
38
+ Write the draft.
39
+ }
40
+ }
41
+
42
+ action b {
43
+ kind: inline
44
+ description: "Collect the draft"
45
+ }
46
+
47
+ human h {
48
+ description: "Approves the draft"
49
+ }
50
+
51
+ gates {
52
+ gate g {
53
+ after: a
54
+ before: b
55
+ run: "scripts/check.sh"
56
+ on-fail: halt
57
+ }
58
+ }
59
+
60
+ hooks {
61
+ hook k {
62
+ on: Stop
63
+ run: "scripts/stop.sh"
64
+ }
65
+ }
66
+
67
+ schedule {
68
+ job j {
69
+ every: 1h
70
+ agent: a
71
+ }
72
+ }
73
+
74
+ artifacts {
75
+ artifact r {
76
+ type: markdown
77
+ path: "out/r.md"
78
+ }
79
+ }
80
+
81
+ flow {
82
+ a -> b -> h
83
+ }
84
+ }
85
+ `;
86
+ /** Where each kind of addition goes: the line it is written after. */
87
+ const ANCHOR = {
88
+ agent: ` description: "Writes the draft"\n`,
89
+ action: ` description: "Collect the draft"\n`,
90
+ human: ` description: "Approves the draft"\n`,
91
+ gate: ` on-fail: halt\n`,
92
+ hook: ` run: "scripts/stop.sh"\n`,
93
+ job: ` agent: a\n`,
94
+ artifact: ` path: "out/r.md"\n`,
95
+ meta: ` description: "Probe baseline"\n`,
96
+ top: ` flow {\n`,
97
+ };
98
+ function withLine(where, text) {
99
+ const anchor = ANCHOR[where];
100
+ if (!BASELINE.includes(anchor))
101
+ throw new Error(`anchor missing: ${where}`);
102
+ if (where === "top")
103
+ return BASELINE.replace(anchor, `${text}\n\n${anchor}`);
104
+ const indent = anchor.match(/^ */)[0];
105
+ return BASELINE.replace(anchor, anchor + text.split("\n").map((l) => indent + l).join("\n") + "\n");
106
+ }
107
+ const CASES = [
108
+ { id: "C01", what: "unknown field on an agent", source: withLine("agent", "zzmarker: zzAAA101"), name: "zzmarker", marker: "zzAAA101", block: `agent "a"` },
109
+ { id: "C02", what: "misspelled field on an agent", source: withLine("agent", "modle: opus"), name: "modle", marker: "zzNONE", block: `agent "a"` },
110
+ { id: "C03", what: "unknown field on an action", source: withLine("action", "zzfield: zzAAA103"), name: "zzfield", marker: "zzAAA103", block: `action "b"` },
111
+ { id: "C04", what: "unknown field on a human", source: withLine("human", "zzfield: zzAAA104"), name: "zzfield", marker: "zzAAA104", block: `human "h"` },
112
+ { id: "C05", what: "unknown field on a gate", source: withLine("gate", "zzfield: zzAAA105"), name: "zzfield", marker: "zzAAA105", block: `gate "g"` },
113
+ { id: "C06", what: "unknown field on a hook", source: withLine("hook", "zzfield: zzAAA106"), name: "zzfield", marker: "zzAAA106", block: `hook "k"` },
114
+ { id: "C07", what: "unknown field on a schedule job", source: withLine("job", "zzfield: zzAAA107"), name: "zzfield", marker: "zzAAA107", block: `job "j"` },
115
+ { id: "C08", what: "unknown field on an artifact", source: withLine("artifact", "zzfield: zzAAA108"), name: "zzfield", marker: "zzAAA108", block: `artifact "r"` },
116
+ { id: "C09", what: "unknown field in meta", source: withLine("meta", "zzfield: zzAAA109"), name: "zzfield", marker: "zzAAA109", block: `"meta"` },
117
+ { id: "C10", what: "unknown top-level block", source: withLine("top", " zzblock {\n x: zzAAA110\n }"), name: "zzblock", marker: "zzAAA110", block: "the topology" },
118
+ { id: "C11", what: "unknown named top-level block", source: withLine("top", " contract c1 {\n clause q1 {\n text: \"zzAAA111\"\n }\n }"), name: "contract", marker: "zzAAA111", block: "the topology" },
119
+ { id: "C12", what: "unknown block nested in an agent", source: withLine("agent", "zzblock {\n x: zzAAA112\n}"), name: "zzblock", marker: "zzAAA112", block: `agent "a"` },
120
+ { id: "C13", what: "agent field written on an action", source: withLine("action", "produces: [zzAAA113]"), name: "produces", marker: "zzAAA113", block: `action "b"` },
121
+ { id: "C16a", what: "v8 field serves: on an action", source: withLine("action", "serves: zzAAA116"), name: "serves", marker: "zzAAA116", block: `action "b"` },
122
+ { id: "C16b", what: "v8 field strength: on an action", source: withLine("action", "strength: zzAAA117"), name: "strength", marker: "zzAAA117", block: `action "b"` },
123
+ { id: "C17", what: "v8 field state: at the top level", source: withLine("top", " state: zzAAA118"), name: "state", marker: "zzAAA118", block: "" },
124
+ { id: "C17b", what: "v8 state block at the top level", source: withLine("top", " state draft {\n x: zzAAA119\n }"), name: "state", marker: "zzAAA119", block: "the topology" },
125
+ ];
126
+ const run = (source) => {
127
+ const ast = parse(source);
128
+ return { ast, results: validate(ast) };
129
+ };
130
+ const named = (results, ...words) => results.filter((r) => words.every((w) => r.message.includes(w)));
131
+ function cli(source, ...args) {
132
+ const dir = fs.mkdtempSync(join(os.tmpdir(), "at-unknown-"));
133
+ const file = join(dir, "probe.at");
134
+ fs.writeFileSync(file, source);
135
+ try {
136
+ const r = spawnSync(process.execPath, [CLI, ...args.map((a) => (a === "FILE" ? file : a.replace("DIR", dir)))], { encoding: "utf8" });
137
+ return { code: r.status, out: r.stdout + r.stderr, dir };
138
+ }
139
+ finally {
140
+ // dir is removed by the caller when it reads output files
141
+ }
142
+ }
143
+ describe("a name the parser does not read is reported, never dropped", () => {
144
+ it("[N23] the baseline still passes", () => {
145
+ expect(run(BASELINE).results).toEqual([]);
146
+ });
147
+ it("[N23] an unknown field on an agent is reported, not dropped", () => {
148
+ const { results } = run(CASES[0].source);
149
+ const hit = named(results, "zzmarker", `agent "a"`);
150
+ expect(hit.length).toBeGreaterThan(0);
151
+ expect(hit[0]).toMatchObject({ rule: "V102", level: "error" });
152
+ });
153
+ it("[N23] a misspelled field is reported with the field it resembles", () => {
154
+ const hit = named(run(CASES[1].source).results, "modle");
155
+ expect(hit.length).toBeGreaterThan(0);
156
+ expect(hit[0].message).toContain("Did you mean `model`?");
157
+ });
158
+ for (const c of CASES.slice(2, 8)) {
159
+ it(`[N23] an unknown field is reported on every block that holds fields: ${c.what}`, () => {
160
+ expect(named(run(c.source).results, c.name, c.block).length).toBeGreaterThan(0);
161
+ });
162
+ }
163
+ it("[N23] an unknown field in meta is reported", () => {
164
+ expect(named(run(CASES[8].source).results, "zzfield", `"meta"`).length).toBeGreaterThan(0);
165
+ });
166
+ it("[N23] an unknown top-level block is reported, not dropped", () => {
167
+ expect(named(run(CASES[9].source).results, "zzblock", "not a topology section").length).toBeGreaterThan(0);
168
+ expect(named(run(CASES[10].source).results, "contract c1", "not a topology section").length).toBeGreaterThan(0);
169
+ });
170
+ it("[N23] an unknown block nested in a node is reported", () => {
171
+ expect(named(run(CASES[11].source).results, "zzblock", `agent "a"`).length).toBeGreaterThan(0);
172
+ });
173
+ it("[N23] a field valid on another node type is reported on the wrong one", () => {
174
+ const hit = named(run(CASES[12].source).results, "`produces` is not an action field", "agent");
175
+ expect(hit.length).toBeGreaterThan(0);
176
+ });
177
+ it("[N23] the v8 words serves, strength and state are reported until the language has them", () => {
178
+ for (const c of CASES.slice(13)) {
179
+ expect(named(run(c.source).results, c.name).length, c.id).toBeGreaterThan(0);
180
+ }
181
+ });
182
+ it("[N23] nothing written in the file vanishes between parse and validate", () => {
183
+ for (const c of CASES) {
184
+ const { ast, results } = run(c.source);
185
+ const kept = JSON.stringify(ast).includes(c.marker);
186
+ const reported = named(results, c.name).length > 0;
187
+ expect(kept || reported, `${c.id} ${c.what}`).toBe(true);
188
+ }
189
+ });
190
+ it("[N23] validate does not print \"All validation rules passed\" when anything was dropped, and exits 1", () => {
191
+ for (const c of [CASES[0], CASES[10], CASES[13]]) {
192
+ const r = cli(c.source, "validate", "FILE");
193
+ fs.rmSync(r.dir, { recursive: true, force: true });
194
+ expect(r.out, c.id).not.toContain("All validation rules passed");
195
+ expect(r.out, c.id).toContain("V102");
196
+ expect(r.code, c.id).toBe(1);
197
+ }
198
+ });
199
+ it("[N23] scaffold refuses a file with an unknown field instead of dropping it", () => {
200
+ const r = cli(CASES[0].source, "scaffold", "FILE", "--target", "claude-code", "--output", "DIR/out");
201
+ const wrote = fs.existsSync(join(r.dir, "out", ".claude"));
202
+ fs.rmSync(r.dir, { recursive: true, force: true });
203
+ expect(r.code).not.toBe(0);
204
+ expect(r.out).toContain("zzmarker");
205
+ expect(wrote).toBe(false);
206
+ });
207
+ it("[N23] a gate's description is kept, since V96 sends a gate's prose there", () => {
208
+ const source = withLine("gate", 'description: "zzAAA130 checks the draft"');
209
+ const { ast, results } = run(source);
210
+ expect(results).toEqual([]);
211
+ const gate = ast.nodes.find((n) => n.id === "g");
212
+ expect(gate.description).toBe("zzAAA130 checks the draft");
213
+ const again = parse(serializeAST(ast)).nodes.find((n) => n.id === "g");
214
+ expect(again.description).toBe("zzAAA130 checks the draft");
215
+ });
216
+ it("[N23] the on-timeout value is still caught by V59 (the case that already worked)", () => {
217
+ const { ast, results } = run(withLine("human", "on-timeout: zzAAA114"));
218
+ expect(JSON.stringify(ast)).toContain("zzAAA114");
219
+ expect(results.some((r) => r.rule === "V59")).toBe(true);
220
+ });
221
+ });
222
+ describe("an enum field rejects a value outside its documented set", () => {
223
+ it("[N24] an agent's behavior must be advisory or blocking", () => {
224
+ const hit = run(withLine("agent", "behavior: zzAAA125")).results.filter((r) => r.rule === "V103");
225
+ expect(hit).toHaveLength(1);
226
+ expect(hit[0].message).toContain("zzAAA125");
227
+ expect(hit[0].message).toContain("advisory or blocking");
228
+ });
229
+ it("[N24] a gate's behavior must be advisory or blocking", () => {
230
+ expect(run(withLine("gate", "behavior: loud")).results.some((r) => r.rule === "V103")).toBe(true);
231
+ });
232
+ it("[N24] advisory and blocking pass", () => {
233
+ for (const v of ["advisory", "blocking"]) {
234
+ expect(run(withLine("agent", `behavior: ${v}`)).results).toEqual([]);
235
+ }
236
+ });
237
+ });
238
+ /**
239
+ * Guard: every field the docs list passes without a V102. A rule that flags a
240
+ * documented field is worse than the bug it fixes. The docs used to list
241
+ * agent fields by their AST names (`maxTurns`), which the parser drops.
242
+ */
243
+ describe("every field in the docs is a name the parser reads", () => {
244
+ const PAGES = [
245
+ { topic: "topology", section: "## Meta Block Fields", template: (f) => withLine("meta", f) },
246
+ { topic: "agent", section: "## All Fields", template: (f) => withLine("agent", f) },
247
+ { topic: "action", section: "## Fields", template: (f) => withLine("action", f) },
248
+ { topic: "human", section: "## Fields", template: (f) => withLine("human", f) },
249
+ { topic: "gate", section: "## Fields", template: (f) => withLine("gate", f) },
250
+ { topic: "hooks", section: "## Fields", template: (f) => withLine("hook", f) },
251
+ { topic: "schedule", section: "## Fields", template: (f) => withLine("job", f) },
252
+ { topic: "artifacts", section: "## Fields", template: (f) => withLine("artifact", f) },
253
+ { topic: "orchestrator", section: "## Fields", template: (f) => BASELINE.replace(" handles: [b]\n", ` handles: [b]\n ${f}\n`) },
254
+ { topic: "group", section: "## Fields", template: (f) => withLine("top", ` group gc {\n ${f}\n }`) },
255
+ { topic: "tools", section: "## Fields", template: (f) => withLine("top", ` tools {\n tool t {\n ${f}\n }\n }`) },
256
+ { topic: "skills", section: "## Fields", template: (f) => withLine("top", ` skill s {\n ${f}\n }`) },
257
+ ];
258
+ /** First-column names of the tables under `section`, until the next `## `. */
259
+ function documented(topic, section) {
260
+ const page = topics[topic].content();
261
+ const start = page.indexOf(section);
262
+ expect(start, `${topic}: "${section}" not found`).toBeGreaterThan(-1);
263
+ const end = page.indexOf("\n## ", start + section.length);
264
+ const body = page.slice(start, end === -1 ? undefined : end);
265
+ return [...body.matchAll(/^\|\s*`?([a-zA-Z][\w-]*)`?\s*\|/gm)]
266
+ .map((m) => m[1])
267
+ .filter((n) => !["Field", "id", "name", "NAME"].includes(n));
268
+ }
269
+ for (const p of PAGES) {
270
+ it(`[N23] every field in docs ${p.topic} passes without a V102`, () => {
271
+ const fields = documented(p.topic, p.section);
272
+ expect(fields.length).toBeGreaterThan(0);
273
+ const flagged = fields.filter((f) => run(p.template(`${f}: x`)).results.some((r) => r.rule === "V102"));
274
+ expect(flagged, `${p.topic}: documented but reported as unknown`).toEqual([]);
275
+ });
276
+ }
277
+ });
278
+ //# sourceMappingURL=unknown-names.test.js.map