agentopology 0.1.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/LICENSE +191 -0
- package/README.md +450 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/analyzer/__tests__/analyzer.test.js +258 -0
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
- package/dist/analyzer/index.d.ts +44 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +308 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
- package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/bindings.test.js +2089 -0
- package/dist/bindings/__tests__/bindings.test.js.map +1 -0
- package/dist/bindings/claude-code.d.ts +12 -0
- package/dist/bindings/claude-code.d.ts.map +1 -0
- package/dist/bindings/claude-code.js +2063 -0
- package/dist/bindings/claude-code.js.map +1 -0
- package/dist/bindings/codex.d.ts +18 -0
- package/dist/bindings/codex.d.ts.map +1 -0
- package/dist/bindings/codex.js +1633 -0
- package/dist/bindings/codex.js.map +1 -0
- package/dist/bindings/copilot-cli.d.ts +20 -0
- package/dist/bindings/copilot-cli.d.ts.map +1 -0
- package/dist/bindings/copilot-cli.js +1515 -0
- package/dist/bindings/copilot-cli.js.map +1 -0
- package/dist/bindings/cursor.d.ts +17 -0
- package/dist/bindings/cursor.d.ts.map +1 -0
- package/dist/bindings/cursor.js +1715 -0
- package/dist/bindings/cursor.js.map +1 -0
- package/dist/bindings/gemini-cli.d.ts +18 -0
- package/dist/bindings/gemini-cli.d.ts.map +1 -0
- package/dist/bindings/gemini-cli.js +1327 -0
- package/dist/bindings/gemini-cli.js.map +1 -0
- package/dist/bindings/index.d.ts +20 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +33 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/kiro.d.ts +17 -0
- package/dist/bindings/kiro.d.ts.map +1 -0
- package/dist/bindings/kiro.js +1624 -0
- package/dist/bindings/kiro.js.map +1 -0
- package/dist/bindings/openclaw.d.ts +18 -0
- package/dist/bindings/openclaw.d.ts.map +1 -0
- package/dist/bindings/openclaw.js +1578 -0
- package/dist/bindings/openclaw.js.map +1 -0
- package/dist/bindings/types.d.ts +33 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +23 -0
- package/dist/bindings/types.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +679 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/docs/__tests__/docs.test.d.ts +2 -0
- package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
- package/dist/docs/__tests__/docs.test.js +438 -0
- package/dist/docs/__tests__/docs.test.js.map +1 -0
- package/dist/docs/content.d.ts +16 -0
- package/dist/docs/content.d.ts.map +1 -0
- package/dist/docs/content.js +3273 -0
- package/dist/docs/content.js.map +1 -0
- package/dist/docs/index.d.ts +17 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +117 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
- package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
- package/dist/exporters/__tests__/exporters.test.js +731 -0
- package/dist/exporters/__tests__/exporters.test.js.map +1 -0
- package/dist/exporters/index.d.ts +13 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +18 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/json.d.ts +8 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +25 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/markdown.d.ts +9 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +686 -0
- package/dist/exporters/markdown.js.map +1 -0
- package/dist/exporters/mermaid.d.ts +11 -0
- package/dist/exporters/mermaid.d.ts.map +1 -0
- package/dist/exporters/mermaid.js +161 -0
- package/dist/exporters/mermaid.js.map +1 -0
- package/dist/exporters/types.d.ts +22 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +10 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/import/__tests__/import.test.d.ts +2 -0
- package/dist/import/__tests__/import.test.d.ts.map +1 -0
- package/dist/import/__tests__/import.test.js +781 -0
- package/dist/import/__tests__/import.test.js.map +1 -0
- package/dist/import/claude-code.d.ts +38 -0
- package/dist/import/claude-code.d.ts.map +1 -0
- package/dist/import/claude-code.js +897 -0
- package/dist/import/claude-code.js.map +1 -0
- package/dist/import/index.d.ts +18 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +28 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/serializer.d.ts +11 -0
- package/dist/import/serializer.d.ts.map +1 -0
- package/dist/import/serializer.js +960 -0
- package/dist/import/serializer.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/__tests__/parser.test.d.ts +2 -0
- package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
- package/dist/parser/__tests__/parser.test.js +6514 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -0
- package/dist/parser/ast.d.ts +708 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +12 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/index.d.ts +253 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +1985 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lexer.d.ts +100 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +304 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/validator.d.ts +42 -0
- package/dist/parser/validator.d.ts.map +1 -0
- package/dist/parser/validator.js +2568 -0
- package/dist/parser/validator.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +295 -0
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
- package/dist/scaffold/incremental.d.ts +43 -0
- package/dist/scaffold/incremental.d.ts.map +1 -0
- package/dist/scaffold/incremental.js +185 -0
- package/dist/scaffold/incremental.js.map +1 -0
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +13 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/manifest.d.ts +30 -0
- package/dist/scaffold/manifest.d.ts.map +1 -0
- package/dist/scaffold/manifest.js +71 -0
- package/dist/scaffold/manifest.js.map +1 -0
- package/dist/scaffold/merge.d.ts +52 -0
- package/dist/scaffold/merge.d.ts.map +1 -0
- package/dist/scaffold/merge.js +134 -0
- package/dist/scaffold/merge.js.map +1 -0
- package/dist/scaffold/types.d.ts +51 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +10 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/sync/index.d.ts +34 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +392 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/visualizer/index.d.ts +17 -0
- package/dist/visualizer/index.d.ts.map +1 -0
- package/dist/visualizer/index.js +1976 -0
- package/dist/visualizer/index.js.map +1 -0
- package/examples/code-review.at +167 -0
- package/examples/data-processing.at +296 -0
- package/examples/openclaw-assistant.at +142 -0
- package/examples/scheduled-monitor.at +193 -0
- package/examples/simple-pipeline.at +163 -0
- package/package.json +77 -0
- package/skill/SKILL.md +403 -0
- package/skill/scripts/emit.ts +853 -0
- package/skill/scripts/visualize.ts +89 -0
- package/spec/grammar.md +1466 -0
- package/spec/reserved-keywords.md +147 -0
- package/spec/validation.md +535 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parse } from "../../parser/index.js";
|
|
3
|
+
import { validate } from "../../parser/validator.js";
|
|
4
|
+
import { serializeAST } from "../serializer.js";
|
|
5
|
+
import { importClaudeCode, parseAgentMd, parseSettingsJson, parseMcpJson, parseCommandMd, parseActionScript, parseSkillMd } from "../claude-code.js";
|
|
6
|
+
import { importFromPlatform } from "../index.js";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Test data
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const SIMPLE_PIPELINE = `
|
|
11
|
+
topology simple-pipeline : [pipeline] {
|
|
12
|
+
meta {
|
|
13
|
+
version: "1.0.0"
|
|
14
|
+
description: "Test pipeline"
|
|
15
|
+
}
|
|
16
|
+
orchestrator {
|
|
17
|
+
model: sonnet
|
|
18
|
+
handles: [intake, done]
|
|
19
|
+
}
|
|
20
|
+
action intake {
|
|
21
|
+
kind: inline
|
|
22
|
+
description: "Start"
|
|
23
|
+
}
|
|
24
|
+
agent researcher {
|
|
25
|
+
model: gpt-4o
|
|
26
|
+
permissions: supervised
|
|
27
|
+
phase: 1
|
|
28
|
+
tools: [Read, Grep]
|
|
29
|
+
reads: ["workspace/data.md"]
|
|
30
|
+
writes: ["workspace/output.md"]
|
|
31
|
+
outputs: {
|
|
32
|
+
quality: high | low
|
|
33
|
+
}
|
|
34
|
+
prompt {
|
|
35
|
+
You are a researcher.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
agent writer {
|
|
39
|
+
model: sonnet
|
|
40
|
+
phase: 2
|
|
41
|
+
tools: [Read, Write]
|
|
42
|
+
prompt {
|
|
43
|
+
You are a writer.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
action done {
|
|
47
|
+
kind: report
|
|
48
|
+
description: "Finish"
|
|
49
|
+
}
|
|
50
|
+
flow {
|
|
51
|
+
intake -> researcher
|
|
52
|
+
researcher -> writer
|
|
53
|
+
writer -> done
|
|
54
|
+
}
|
|
55
|
+
settings {
|
|
56
|
+
allow: ["Read", "Grep"]
|
|
57
|
+
deny: ["Bash(rm -rf *)"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
const BASIC_AGENT_MD = `---
|
|
62
|
+
name: researcher
|
|
63
|
+
description: "Gather info"
|
|
64
|
+
model: gpt-4o
|
|
65
|
+
tools:
|
|
66
|
+
- Read
|
|
67
|
+
- Grep
|
|
68
|
+
- Glob
|
|
69
|
+
mcpServers:
|
|
70
|
+
- supabase
|
|
71
|
+
permissionMode: plan
|
|
72
|
+
maxTurns: 25
|
|
73
|
+
sandbox: docker
|
|
74
|
+
fallback-chain: sonnet, haiku
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
You are the Researcher agent.
|
|
78
|
+
|
|
79
|
+
## Role
|
|
80
|
+
Gather information and compile research notes
|
|
81
|
+
|
|
82
|
+
## Instructions
|
|
83
|
+
You are a research specialist focused on gathering comprehensive information.
|
|
84
|
+
Always cite your sources.
|
|
85
|
+
|
|
86
|
+
## Reads
|
|
87
|
+
- workspace/data.md
|
|
88
|
+
- workspace/refs.md
|
|
89
|
+
|
|
90
|
+
## Writes
|
|
91
|
+
- workspace/output.md
|
|
92
|
+
|
|
93
|
+
## Outputs
|
|
94
|
+
- confidence: high | medium | low
|
|
95
|
+
|
|
96
|
+
You have a maximum of 5m to complete your work.
|
|
97
|
+
|
|
98
|
+
If you fail: retry-then-escalate
|
|
99
|
+
`;
|
|
100
|
+
const HUMAN_AGENT_MD = `# Binding Review (Human-in-the-Loop)
|
|
101
|
+
|
|
102
|
+
Review the generated binding code before proceeding.
|
|
103
|
+
|
|
104
|
+
Timeout: 1h
|
|
105
|
+
On timeout: skip
|
|
106
|
+
`;
|
|
107
|
+
const GROUP_AGENT_MD = `---
|
|
108
|
+
name: gap-debate
|
|
109
|
+
type: group
|
|
110
|
+
members:
|
|
111
|
+
- sdk-specialist
|
|
112
|
+
- binding-veteran
|
|
113
|
+
- platform-expert
|
|
114
|
+
description: "Three-perspective debate"
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
# Gap Debate (Group Chat)
|
|
118
|
+
|
|
119
|
+
Three-perspective debate on gap resolution.
|
|
120
|
+
|
|
121
|
+
Speaker selection: round-robin
|
|
122
|
+
Max rounds: 3
|
|
123
|
+
Termination: consensus reached
|
|
124
|
+
`;
|
|
125
|
+
const SIMPLE_SKILL_MD = `---
|
|
126
|
+
name: test-pipeline
|
|
127
|
+
description: "Test pipeline skill"
|
|
128
|
+
version: "1.0.0"
|
|
129
|
+
topology: test-pipeline
|
|
130
|
+
patterns:
|
|
131
|
+
- pipeline
|
|
132
|
+
entry: commands/start.md
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
# Test Pipeline Topology Skill
|
|
136
|
+
|
|
137
|
+
Test pipeline skill
|
|
138
|
+
|
|
139
|
+
Version: 1.0.0
|
|
140
|
+
Patterns: pipeline
|
|
141
|
+
|
|
142
|
+
## Orchestrator
|
|
143
|
+
|
|
144
|
+
Model: opus
|
|
145
|
+
Handles: intake, done
|
|
146
|
+
Generates: commands/start.md
|
|
147
|
+
|
|
148
|
+
### Outputs
|
|
149
|
+
- verdict: pass | fail
|
|
150
|
+
|
|
151
|
+
## Flow
|
|
152
|
+
|
|
153
|
+
- intake -> researcher
|
|
154
|
+
- researcher -> writer
|
|
155
|
+
- writer -> done
|
|
156
|
+
|
|
157
|
+
## Gates
|
|
158
|
+
|
|
159
|
+
### Quality Check
|
|
160
|
+
After: writer
|
|
161
|
+
Before: done
|
|
162
|
+
Run: scripts/check.sh
|
|
163
|
+
Checks: grammar, formatting
|
|
164
|
+
On fail: bounce-back
|
|
165
|
+
|
|
166
|
+
## Triggers
|
|
167
|
+
|
|
168
|
+
### /start
|
|
169
|
+
Pattern: \`/start\`
|
|
170
|
+
`;
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Serializer tests
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
describe("serializeAST", () => {
|
|
175
|
+
it("round-trips a simple pipeline", () => {
|
|
176
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
177
|
+
const serialized = serializeAST(ast);
|
|
178
|
+
const reparsed = parse(serialized);
|
|
179
|
+
expect(reparsed.topology.name).toBe("simple-pipeline");
|
|
180
|
+
expect(reparsed.topology.version).toBe("1.0.0");
|
|
181
|
+
expect(reparsed.topology.patterns).toContain("pipeline");
|
|
182
|
+
});
|
|
183
|
+
it("preserves agents", () => {
|
|
184
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
185
|
+
const serialized = serializeAST(ast);
|
|
186
|
+
const reparsed = parse(serialized);
|
|
187
|
+
const agents = reparsed.nodes.filter((n) => n.type === "agent");
|
|
188
|
+
expect(agents.length).toBe(2);
|
|
189
|
+
const researcher = agents.find((a) => a.id === "researcher");
|
|
190
|
+
expect(researcher).toBeDefined();
|
|
191
|
+
expect(researcher.model).toBe("gpt-4o");
|
|
192
|
+
expect(researcher.tools).toContain("Read");
|
|
193
|
+
});
|
|
194
|
+
it("preserves actions", () => {
|
|
195
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
196
|
+
const serialized = serializeAST(ast);
|
|
197
|
+
const reparsed = parse(serialized);
|
|
198
|
+
const actions = reparsed.nodes.filter((n) => n.type === "action");
|
|
199
|
+
expect(actions.length).toBe(2);
|
|
200
|
+
});
|
|
201
|
+
it("preserves edges", () => {
|
|
202
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
203
|
+
const serialized = serializeAST(ast);
|
|
204
|
+
const reparsed = parse(serialized);
|
|
205
|
+
expect(reparsed.edges.length).toBe(3);
|
|
206
|
+
expect(reparsed.edges[0].from).toBe("intake");
|
|
207
|
+
expect(reparsed.edges[0].to).toBe("researcher");
|
|
208
|
+
});
|
|
209
|
+
it("preserves settings", () => {
|
|
210
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
211
|
+
const serialized = serializeAST(ast);
|
|
212
|
+
const reparsed = parse(serialized);
|
|
213
|
+
expect(reparsed.settings.allow).toContain("Read");
|
|
214
|
+
expect(reparsed.settings.deny).toContain("Bash(rm -rf *)");
|
|
215
|
+
});
|
|
216
|
+
it("preserves outputs", () => {
|
|
217
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
218
|
+
const serialized = serializeAST(ast);
|
|
219
|
+
const reparsed = parse(serialized);
|
|
220
|
+
const researcher = reparsed.nodes.find((n) => n.id === "researcher");
|
|
221
|
+
expect(researcher.outputs).toBeDefined();
|
|
222
|
+
expect(researcher.outputs.quality).toContain("high");
|
|
223
|
+
expect(researcher.outputs.quality).toContain("low");
|
|
224
|
+
});
|
|
225
|
+
it("preserves prompts", () => {
|
|
226
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
227
|
+
const serialized = serializeAST(ast);
|
|
228
|
+
const reparsed = parse(serialized);
|
|
229
|
+
const researcher = reparsed.nodes.find((n) => n.id === "researcher");
|
|
230
|
+
expect(researcher.prompt).toContain("researcher");
|
|
231
|
+
});
|
|
232
|
+
it("preserves reads and writes", () => {
|
|
233
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
234
|
+
const serialized = serializeAST(ast);
|
|
235
|
+
const reparsed = parse(serialized);
|
|
236
|
+
const researcher = reparsed.nodes.find((n) => n.id === "researcher");
|
|
237
|
+
expect(researcher.reads).toContain("workspace/data.md");
|
|
238
|
+
expect(researcher.writes).toContain("workspace/output.md");
|
|
239
|
+
});
|
|
240
|
+
it("preserves orchestrator", () => {
|
|
241
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
242
|
+
const serialized = serializeAST(ast);
|
|
243
|
+
const reparsed = parse(serialized);
|
|
244
|
+
const orch = reparsed.nodes.find((n) => n.type === "orchestrator");
|
|
245
|
+
expect(orch).toBeDefined();
|
|
246
|
+
expect(orch.model).toBe("sonnet");
|
|
247
|
+
expect(orch.handles).toContain("intake");
|
|
248
|
+
});
|
|
249
|
+
it("outputs valid .at that passes validation", () => {
|
|
250
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
251
|
+
const serialized = serializeAST(ast);
|
|
252
|
+
const reparsed = parse(serialized);
|
|
253
|
+
const results = validate(reparsed);
|
|
254
|
+
const errors = results.filter((r) => r.level === "error");
|
|
255
|
+
expect(errors).toHaveLength(0);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// AGENT.md parser tests
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
describe("parseAgentMd", () => {
|
|
262
|
+
it("parses basic agent fields", () => {
|
|
263
|
+
const node = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
264
|
+
expect(node).toBeDefined();
|
|
265
|
+
expect(node.type).toBe("agent");
|
|
266
|
+
expect(node.id).toBe("researcher");
|
|
267
|
+
const agent = node;
|
|
268
|
+
expect(agent.model).toBe("gpt-4o");
|
|
269
|
+
expect(agent.tools).toContain("Read");
|
|
270
|
+
expect(agent.tools).toContain("Grep");
|
|
271
|
+
expect(agent.tools).toContain("Glob");
|
|
272
|
+
});
|
|
273
|
+
it("reverse-maps permission mode", () => {
|
|
274
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
275
|
+
expect(agent.permissions).toBe("supervised");
|
|
276
|
+
});
|
|
277
|
+
it("parses maxTurns", () => {
|
|
278
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
279
|
+
expect(agent.maxTurns).toBe(25);
|
|
280
|
+
});
|
|
281
|
+
it("parses mcpServers", () => {
|
|
282
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
283
|
+
expect(agent.mcpServers).toContain("supabase");
|
|
284
|
+
});
|
|
285
|
+
it("parses sandbox", () => {
|
|
286
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
287
|
+
expect(agent.sandbox).toBe("docker");
|
|
288
|
+
});
|
|
289
|
+
it("parses fallback chain", () => {
|
|
290
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
291
|
+
expect(agent.fallbackChain).toContain("sonnet");
|
|
292
|
+
expect(agent.fallbackChain).toContain("haiku");
|
|
293
|
+
});
|
|
294
|
+
it("extracts role section", () => {
|
|
295
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
296
|
+
expect(agent.role).toContain("Gather information");
|
|
297
|
+
});
|
|
298
|
+
it("extracts prompt from ## Instructions", () => {
|
|
299
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
300
|
+
expect(agent.prompt).toContain("research specialist");
|
|
301
|
+
expect(agent.prompt).toContain("cite your sources");
|
|
302
|
+
});
|
|
303
|
+
it("extracts reads and writes", () => {
|
|
304
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
305
|
+
expect(agent.reads).toContain("workspace/data.md");
|
|
306
|
+
expect(agent.reads).toContain("workspace/refs.md");
|
|
307
|
+
expect(agent.writes).toContain("workspace/output.md");
|
|
308
|
+
});
|
|
309
|
+
it("extracts outputs", () => {
|
|
310
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
311
|
+
expect(agent.outputs).toBeDefined();
|
|
312
|
+
expect(agent.outputs.confidence).toContain("high");
|
|
313
|
+
expect(agent.outputs.confidence).toContain("medium");
|
|
314
|
+
expect(agent.outputs.confidence).toContain("low");
|
|
315
|
+
});
|
|
316
|
+
it("extracts timeout", () => {
|
|
317
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
318
|
+
expect(agent.timeout).toBe("5m");
|
|
319
|
+
});
|
|
320
|
+
it("extracts onFail", () => {
|
|
321
|
+
const agent = parseAgentMd("researcher", BASIC_AGENT_MD);
|
|
322
|
+
expect(agent.onFail).toBe("retry-then-escalate");
|
|
323
|
+
});
|
|
324
|
+
it("detects human nodes", () => {
|
|
325
|
+
const node = parseAgentMd("binding-review", HUMAN_AGENT_MD);
|
|
326
|
+
expect(node).toBeDefined();
|
|
327
|
+
expect(node.type).toBe("human");
|
|
328
|
+
expect(node.timeout).toBe("1h");
|
|
329
|
+
expect(node.onTimeout).toBe("skip");
|
|
330
|
+
});
|
|
331
|
+
it("detects group nodes", () => {
|
|
332
|
+
const node = parseAgentMd("gap-debate", GROUP_AGENT_MD);
|
|
333
|
+
expect(node).toBeDefined();
|
|
334
|
+
expect(node.type).toBe("group");
|
|
335
|
+
const group = node;
|
|
336
|
+
expect(group.members).toContain("sdk-specialist");
|
|
337
|
+
expect(group.members).toContain("binding-veteran");
|
|
338
|
+
expect(group.members).toContain("platform-expert");
|
|
339
|
+
expect(group.speakerSelection).toBe("round-robin");
|
|
340
|
+
expect(group.maxRounds).toBe(3);
|
|
341
|
+
});
|
|
342
|
+
it("preserves unknown frontmatter as extensions", () => {
|
|
343
|
+
const md = `---
|
|
344
|
+
name: test
|
|
345
|
+
model: sonnet
|
|
346
|
+
custom-field: custom-value
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Instructions
|
|
350
|
+
Test prompt.
|
|
351
|
+
`;
|
|
352
|
+
const agent = parseAgentMd("test", md);
|
|
353
|
+
expect(agent.extensions).toBeDefined();
|
|
354
|
+
expect(agent.extensions["claude-code"]["custom-field"]).toBe("custom-value");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// SKILL.md parser tests
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
describe("parseSkillMd", () => {
|
|
361
|
+
it("detects topology skill", () => {
|
|
362
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
363
|
+
expect(result.isTopology).toBe(true);
|
|
364
|
+
expect(result.name).toBe("test-pipeline");
|
|
365
|
+
expect(result.version).toBe("1.0.0");
|
|
366
|
+
});
|
|
367
|
+
it("extracts patterns", () => {
|
|
368
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
369
|
+
expect(result.patterns).toContain("pipeline");
|
|
370
|
+
});
|
|
371
|
+
it("extracts orchestrator", () => {
|
|
372
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
373
|
+
expect(result.orchestrator).toBeDefined();
|
|
374
|
+
expect(result.orchestrator.model).toBe("opus");
|
|
375
|
+
expect(result.orchestrator.handles).toContain("intake");
|
|
376
|
+
expect(result.orchestrator.handles).toContain("done");
|
|
377
|
+
});
|
|
378
|
+
it("extracts orchestrator outputs", () => {
|
|
379
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
380
|
+
expect(result.orchestrator.outputs).toBeDefined();
|
|
381
|
+
expect(result.orchestrator.outputs.verdict).toContain("pass");
|
|
382
|
+
});
|
|
383
|
+
it("extracts flow edges", () => {
|
|
384
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
385
|
+
expect(result.edges.length).toBe(3);
|
|
386
|
+
expect(result.edges[0].from).toBe("intake");
|
|
387
|
+
expect(result.edges[0].to).toBe("researcher");
|
|
388
|
+
});
|
|
389
|
+
it("extracts agent IDs from flow", () => {
|
|
390
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
391
|
+
expect(result.agentIds.has("researcher")).toBe(true);
|
|
392
|
+
expect(result.agentIds.has("writer")).toBe(true);
|
|
393
|
+
});
|
|
394
|
+
it("extracts gates", () => {
|
|
395
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
396
|
+
expect(result.gates.length).toBe(1);
|
|
397
|
+
expect(result.gates[0].id).toBe("quality-check");
|
|
398
|
+
expect(result.gates[0].after).toBe("writer");
|
|
399
|
+
expect(result.gates[0].onFail).toBe("bounce-back");
|
|
400
|
+
});
|
|
401
|
+
it("extracts triggers", () => {
|
|
402
|
+
const result = parseSkillMd(SIMPLE_SKILL_MD);
|
|
403
|
+
expect(result.triggers.length).toBe(1);
|
|
404
|
+
expect(result.triggers[0].name).toBe("start");
|
|
405
|
+
expect(result.triggers[0].pattern).toBe("/start");
|
|
406
|
+
});
|
|
407
|
+
it("non-topology skill is detected", () => {
|
|
408
|
+
const md = `---
|
|
409
|
+
name: helper-skill
|
|
410
|
+
description: "A helper"
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
# Helper Skill
|
|
414
|
+
`;
|
|
415
|
+
const result = parseSkillMd(md);
|
|
416
|
+
expect(result.isTopology).toBe(false);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// settings.json parser tests
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
describe("parseSettingsJson", () => {
|
|
423
|
+
it("extracts allow/deny/ask", () => {
|
|
424
|
+
const { settings } = parseSettingsJson(JSON.stringify({
|
|
425
|
+
allow: ["Read", "Grep"],
|
|
426
|
+
deny: ["Bash(rm -rf *)"],
|
|
427
|
+
ask: ["Write"],
|
|
428
|
+
}));
|
|
429
|
+
expect(settings.allow).toContain("Read");
|
|
430
|
+
expect(settings.deny).toContain("Bash(rm -rf *)");
|
|
431
|
+
expect(settings.ask).toContain("Write");
|
|
432
|
+
});
|
|
433
|
+
it("extracts hooks", () => {
|
|
434
|
+
const { hooks } = parseSettingsJson(JSON.stringify({
|
|
435
|
+
hooks: {
|
|
436
|
+
PostToolUse: [{
|
|
437
|
+
hooks: [{ command: "bash scripts/log.sh", type: "command", timeout: 5 }],
|
|
438
|
+
matcher: "Bash",
|
|
439
|
+
}],
|
|
440
|
+
},
|
|
441
|
+
}));
|
|
442
|
+
expect(hooks.length).toBe(1);
|
|
443
|
+
expect(hooks[0].on).toBe("PostToolUse");
|
|
444
|
+
expect(hooks[0].run).toBe("bash scripts/log.sh");
|
|
445
|
+
});
|
|
446
|
+
it("skips gate hooks", () => {
|
|
447
|
+
const { hooks } = parseSettingsJson(JSON.stringify({
|
|
448
|
+
hooks: {
|
|
449
|
+
PreToolUse: [{
|
|
450
|
+
hooks: [{ command: "bash scripts/gate-quality.sh", type: "command" }],
|
|
451
|
+
matcher: "Task",
|
|
452
|
+
}],
|
|
453
|
+
},
|
|
454
|
+
}));
|
|
455
|
+
expect(hooks.length).toBe(0);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
// .mcp.json parser tests
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
describe("parseMcpJson", () => {
|
|
462
|
+
it("extracts MCP servers", () => {
|
|
463
|
+
const servers = parseMcpJson(JSON.stringify({
|
|
464
|
+
mcpServers: {
|
|
465
|
+
supabase: { command: "npx", args: ["supabase-mcp"], env: {} },
|
|
466
|
+
},
|
|
467
|
+
}));
|
|
468
|
+
expect(servers.supabase).toBeDefined();
|
|
469
|
+
expect(servers.supabase.command).toBe("npx");
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// Command parser tests
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
describe("parseCommandMd", () => {
|
|
476
|
+
it("extracts trigger from command file", () => {
|
|
477
|
+
const trigger = parseCommandMd("start.md", `---
|
|
478
|
+
description: "Start"
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
# /start <TOPIC>
|
|
482
|
+
|
|
483
|
+
## Arguments
|
|
484
|
+
- TOPIC: the topic
|
|
485
|
+
`);
|
|
486
|
+
expect(trigger).toBeDefined();
|
|
487
|
+
expect(trigger.name).toBe("start");
|
|
488
|
+
expect(trigger.pattern).toBe("/start <TOPIC>");
|
|
489
|
+
expect(trigger.argument).toBe("TOPIC");
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
// Action script parser tests
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
describe("parseActionScript", () => {
|
|
496
|
+
it("extracts action from script", () => {
|
|
497
|
+
const action = parseActionScript("action-intake.sh", `#!/usr/bin/env bash
|
|
498
|
+
# Action: intake — Parse user request
|
|
499
|
+
# Auto-generated by agentopology scaffold — edit as needed.
|
|
500
|
+
set -euo pipefail
|
|
501
|
+
|
|
502
|
+
echo "TODO: implement inline action"
|
|
503
|
+
`);
|
|
504
|
+
expect(action).toBeDefined();
|
|
505
|
+
expect(action.id).toBe("intake");
|
|
506
|
+
expect(action.description).toBe("Parse user request");
|
|
507
|
+
expect(action.kind).toBe("inline");
|
|
508
|
+
});
|
|
509
|
+
it("detects external actions", () => {
|
|
510
|
+
const action = parseActionScript("action-fetch.sh", `#!/usr/bin/env bash
|
|
511
|
+
# Action: fetch — Get external data
|
|
512
|
+
# External source: github-pr
|
|
513
|
+
set -euo pipefail
|
|
514
|
+
`);
|
|
515
|
+
expect(action).toBeDefined();
|
|
516
|
+
expect(action.kind).toBe("external");
|
|
517
|
+
expect(action.source).toBe("github-pr");
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
// ---------------------------------------------------------------------------
|
|
521
|
+
// Full import tests
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
describe("importClaudeCode", () => {
|
|
524
|
+
it("imports basic agent files", () => {
|
|
525
|
+
const files = [
|
|
526
|
+
{
|
|
527
|
+
path: "agents/worker/AGENT.md",
|
|
528
|
+
content: `---
|
|
529
|
+
name: worker
|
|
530
|
+
model: sonnet
|
|
531
|
+
tools:
|
|
532
|
+
- Read
|
|
533
|
+
- Write
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Instructions
|
|
537
|
+
Do work.
|
|
538
|
+
`,
|
|
539
|
+
},
|
|
540
|
+
];
|
|
541
|
+
const ast = importClaudeCode(files, "test-import");
|
|
542
|
+
expect(ast.topology.name).toBe("test-import");
|
|
543
|
+
expect(ast.nodes.length).toBeGreaterThanOrEqual(1);
|
|
544
|
+
const worker = ast.nodes.find((n) => n.id === "worker");
|
|
545
|
+
expect(worker).toBeDefined();
|
|
546
|
+
expect(worker.type).toBe("agent");
|
|
547
|
+
expect(worker.model).toBe("sonnet");
|
|
548
|
+
});
|
|
549
|
+
it("imports with SKILL.md topology metadata", () => {
|
|
550
|
+
const files = [
|
|
551
|
+
{
|
|
552
|
+
path: "agents/researcher/AGENT.md",
|
|
553
|
+
content: `---
|
|
554
|
+
name: researcher
|
|
555
|
+
model: sonnet
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Instructions
|
|
559
|
+
Research things.
|
|
560
|
+
`,
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
path: "skills/my-pipeline/SKILL.md",
|
|
564
|
+
content: SIMPLE_SKILL_MD,
|
|
565
|
+
},
|
|
566
|
+
];
|
|
567
|
+
const ast = importClaudeCode(files, "fallback-name");
|
|
568
|
+
expect(ast.topology.name).toBe("test-pipeline"); // from SKILL.md, not fallback
|
|
569
|
+
expect(ast.topology.version).toBe("1.0.0");
|
|
570
|
+
expect(ast.edges.length).toBe(3);
|
|
571
|
+
});
|
|
572
|
+
it("imports settings.json", () => {
|
|
573
|
+
const files = [
|
|
574
|
+
{
|
|
575
|
+
path: "agents/worker/AGENT.md",
|
|
576
|
+
content: `---
|
|
577
|
+
name: worker
|
|
578
|
+
model: sonnet
|
|
579
|
+
---
|
|
580
|
+
`,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
path: ".claude/settings.json",
|
|
584
|
+
content: JSON.stringify({ allow: ["Read"], deny: ["Bash"] }),
|
|
585
|
+
},
|
|
586
|
+
];
|
|
587
|
+
const ast = importClaudeCode(files, "test");
|
|
588
|
+
expect(ast.settings.allow).toContain("Read");
|
|
589
|
+
});
|
|
590
|
+
it("imports .mcp.json", () => {
|
|
591
|
+
const files = [
|
|
592
|
+
{
|
|
593
|
+
path: "agents/worker/AGENT.md",
|
|
594
|
+
content: `---
|
|
595
|
+
name: worker
|
|
596
|
+
model: sonnet
|
|
597
|
+
---
|
|
598
|
+
`,
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
path: ".mcp.json",
|
|
602
|
+
content: JSON.stringify({ mcpServers: { db: { command: "npx", args: ["db-mcp"] } } }),
|
|
603
|
+
},
|
|
604
|
+
];
|
|
605
|
+
const ast = importClaudeCode(files, "test");
|
|
606
|
+
expect(ast.mcpServers.db).toBeDefined();
|
|
607
|
+
});
|
|
608
|
+
it("generates linear flow when no SKILL.md", () => {
|
|
609
|
+
const files = [
|
|
610
|
+
{
|
|
611
|
+
path: "agents/alpha/AGENT.md",
|
|
612
|
+
content: `---
|
|
613
|
+
name: alpha
|
|
614
|
+
model: sonnet
|
|
615
|
+
---
|
|
616
|
+
`,
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
path: "agents/beta/AGENT.md",
|
|
620
|
+
content: `---
|
|
621
|
+
name: beta
|
|
622
|
+
model: sonnet
|
|
623
|
+
---
|
|
624
|
+
`,
|
|
625
|
+
},
|
|
626
|
+
];
|
|
627
|
+
const ast = importClaudeCode(files, "test");
|
|
628
|
+
expect(ast.edges.length).toBe(1);
|
|
629
|
+
expect(ast.edges[0].from).toBe("alpha");
|
|
630
|
+
expect(ast.edges[0].to).toBe("beta");
|
|
631
|
+
});
|
|
632
|
+
it("handles human and group nodes", () => {
|
|
633
|
+
const files = [
|
|
634
|
+
{ path: "agents/review/AGENT.md", content: HUMAN_AGENT_MD },
|
|
635
|
+
{ path: "agents/debate/AGENT.md", content: GROUP_AGENT_MD },
|
|
636
|
+
];
|
|
637
|
+
const ast = importClaudeCode(files, "test");
|
|
638
|
+
const human = ast.nodes.find((n) => n.type === "human");
|
|
639
|
+
const group = ast.nodes.find((n) => n.type === "group");
|
|
640
|
+
expect(human).toBeDefined();
|
|
641
|
+
expect(group).toBeDefined();
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
// importFromPlatform dispatch tests
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
describe("importFromPlatform", () => {
|
|
648
|
+
it("dispatches to claude-code", () => {
|
|
649
|
+
const files = [
|
|
650
|
+
{
|
|
651
|
+
path: "agents/worker/AGENT.md",
|
|
652
|
+
content: `---
|
|
653
|
+
name: worker
|
|
654
|
+
model: sonnet
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Instructions
|
|
658
|
+
Work.
|
|
659
|
+
`,
|
|
660
|
+
},
|
|
661
|
+
];
|
|
662
|
+
const atSource = importFromPlatform(files, "claude-code", "test");
|
|
663
|
+
expect(atSource).toContain("topology test");
|
|
664
|
+
expect(atSource).toContain("agent worker");
|
|
665
|
+
});
|
|
666
|
+
it("throws for unsupported binding", () => {
|
|
667
|
+
expect(() => importFromPlatform([], "unknown", "test")).toThrow("Import not supported");
|
|
668
|
+
});
|
|
669
|
+
it("generates parseable .at output", () => {
|
|
670
|
+
const files = [
|
|
671
|
+
{
|
|
672
|
+
path: "agents/researcher/AGENT.md",
|
|
673
|
+
content: BASIC_AGENT_MD,
|
|
674
|
+
},
|
|
675
|
+
];
|
|
676
|
+
const atSource = importFromPlatform(files, "claude-code", "my-topology");
|
|
677
|
+
// Should parse without throwing
|
|
678
|
+
const ast = parse(atSource);
|
|
679
|
+
expect(ast.topology.name).toBe("my-topology");
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
// Round-trip test: scaffold → import
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
describe("round-trip: parse → serialize → parse", () => {
|
|
686
|
+
it("round-trips a complex topology", () => {
|
|
687
|
+
const source = `
|
|
688
|
+
topology complex : [pipeline, fan-out] {
|
|
689
|
+
meta {
|
|
690
|
+
version: "2.0.0"
|
|
691
|
+
description: "Complex topology"
|
|
692
|
+
domain: testing
|
|
693
|
+
}
|
|
694
|
+
orchestrator {
|
|
695
|
+
model: opus
|
|
696
|
+
handles: [intake, report]
|
|
697
|
+
}
|
|
698
|
+
roles {
|
|
699
|
+
lead: "Lead researcher"
|
|
700
|
+
}
|
|
701
|
+
action intake { kind: inline description: "Start" }
|
|
702
|
+
agent lead-researcher {
|
|
703
|
+
role: lead
|
|
704
|
+
model: gpt-4o
|
|
705
|
+
permissions: supervised
|
|
706
|
+
phase: 1
|
|
707
|
+
tools: [Read, Grep, WebSearch]
|
|
708
|
+
reads: ["workspace/data.md"]
|
|
709
|
+
writes: ["workspace/output.md"]
|
|
710
|
+
outputs: {
|
|
711
|
+
confidence: high | medium | low
|
|
712
|
+
}
|
|
713
|
+
timeout: 10m
|
|
714
|
+
on-fail: retry
|
|
715
|
+
retry: 3
|
|
716
|
+
prompt {
|
|
717
|
+
You are the lead researcher.
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
agent writer {
|
|
721
|
+
model: sonnet
|
|
722
|
+
phase: 2
|
|
723
|
+
tools: [Read, Write]
|
|
724
|
+
prompt {
|
|
725
|
+
You write documents.
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
action report { kind: report description: "Done" }
|
|
729
|
+
flow {
|
|
730
|
+
intake -> lead-researcher
|
|
731
|
+
lead-researcher -> writer
|
|
732
|
+
writer -> report
|
|
733
|
+
}
|
|
734
|
+
gates {
|
|
735
|
+
gate quality {
|
|
736
|
+
after: writer
|
|
737
|
+
run: "scripts/check.sh"
|
|
738
|
+
checks: [grammar]
|
|
739
|
+
on-fail: bounce-back
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
memory {
|
|
743
|
+
workspace {
|
|
744
|
+
path: "workspace/"
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
triggers {
|
|
748
|
+
command start {
|
|
749
|
+
pattern: "/start"
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
settings {
|
|
753
|
+
allow: ["Read", "Grep"]
|
|
754
|
+
}
|
|
755
|
+
mcp-servers {
|
|
756
|
+
db {
|
|
757
|
+
command: npx
|
|
758
|
+
args: ["db-mcp"]
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
`;
|
|
763
|
+
const ast = parse(source);
|
|
764
|
+
const serialized = serializeAST(ast);
|
|
765
|
+
const reparsed = parse(serialized);
|
|
766
|
+
// Verify key fields survived
|
|
767
|
+
expect(reparsed.topology.name).toBe("complex");
|
|
768
|
+
expect(reparsed.topology.version).toBe("2.0.0");
|
|
769
|
+
expect(reparsed.topology.domain).toBe("testing");
|
|
770
|
+
expect(reparsed.nodes.filter((n) => n.type === "agent").length).toBe(2);
|
|
771
|
+
expect(reparsed.edges.length).toBe(3);
|
|
772
|
+
expect(reparsed.triggers.length).toBe(1);
|
|
773
|
+
expect(Object.keys(reparsed.mcpServers).length).toBe(1);
|
|
774
|
+
const lead = reparsed.nodes.find((n) => n.id === "lead-researcher");
|
|
775
|
+
expect(lead.model).toBe("gpt-4o");
|
|
776
|
+
expect(lead.timeout).toBe("10m");
|
|
777
|
+
expect(lead.retry).toBe(3);
|
|
778
|
+
expect(lead.outputs.confidence).toContain("high");
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
//# sourceMappingURL=import.test.js.map
|