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,731 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parse } from "../../parser/index.js";
|
|
3
|
+
import { exporters, markdownExporter, mermaidExporter, jsonExporter, } from "../index.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Test topologies
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const SIMPLE_PIPELINE = `
|
|
8
|
+
topology simple-pipeline : [pipeline, human-gate] {
|
|
9
|
+
meta {
|
|
10
|
+
version: "1.0.0"
|
|
11
|
+
description: "Research, write, and review"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
orchestrator {
|
|
15
|
+
model: sonnet
|
|
16
|
+
generates: "commands/create.md"
|
|
17
|
+
handles: [intake, done]
|
|
18
|
+
outputs: {
|
|
19
|
+
topic-type: technical | creative
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
roles {
|
|
24
|
+
researcher: "Gather information and compile research notes"
|
|
25
|
+
writer: "Draft content based on research"
|
|
26
|
+
reviewer: "Review drafts for accuracy"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
action intake {
|
|
30
|
+
kind: inline
|
|
31
|
+
description: "Parse user request"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
agent researcher {
|
|
35
|
+
role: researcher
|
|
36
|
+
model: gpt-4o
|
|
37
|
+
permissions: supervised
|
|
38
|
+
phase: 1
|
|
39
|
+
tools: [Read, Grep, Glob, WebSearch]
|
|
40
|
+
writes: ["workspace/research.md"]
|
|
41
|
+
prompt {
|
|
42
|
+
You are a research specialist.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
agent writer {
|
|
47
|
+
role: writer
|
|
48
|
+
model: sonnet
|
|
49
|
+
permissions: autonomous
|
|
50
|
+
phase: 2
|
|
51
|
+
tools: [Read, Write, Glob]
|
|
52
|
+
reads: ["workspace/research.md"]
|
|
53
|
+
writes: ["workspace/draft.md"]
|
|
54
|
+
outputs: {
|
|
55
|
+
confidence: high | medium | low
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
agent reviewer {
|
|
60
|
+
role: reviewer
|
|
61
|
+
model: opus
|
|
62
|
+
permissions: supervised
|
|
63
|
+
phase: 3
|
|
64
|
+
tools: [Read, Grep, Glob]
|
|
65
|
+
reads: ["workspace/draft.md", "workspace/research.md"]
|
|
66
|
+
writes: ["workspace/review.md"]
|
|
67
|
+
outputs: {
|
|
68
|
+
verdict: approve | revise | reject
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
flow {
|
|
73
|
+
intake -> researcher
|
|
74
|
+
researcher -> writer
|
|
75
|
+
writer -> reviewer
|
|
76
|
+
reviewer -> writer [when reviewer.verdict == revise, max 2]
|
|
77
|
+
reviewer -> researcher [when reviewer.verdict == reject, max 1]
|
|
78
|
+
reviewer -> done [when reviewer.verdict == approve]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
gates {
|
|
82
|
+
gate quality-check {
|
|
83
|
+
after: writer
|
|
84
|
+
before: reviewer
|
|
85
|
+
run: "scripts/check-quality.sh"
|
|
86
|
+
checks: [grammar, formatting]
|
|
87
|
+
on-fail: bounce-back
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
action done {
|
|
92
|
+
kind: report
|
|
93
|
+
description: "Deliver final content"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
memory {
|
|
97
|
+
workspace {
|
|
98
|
+
path: "workspace/"
|
|
99
|
+
structure: [research, drafts, reviews]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
triggers {
|
|
104
|
+
command create {
|
|
105
|
+
pattern: "/create <TOPIC>"
|
|
106
|
+
argument: TOPIC
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
settings {
|
|
111
|
+
allow: ["Read", "Grep", "Glob", "WebSearch"]
|
|
112
|
+
deny: ["Bash(rm -rf *)"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
const FAN_OUT_TOPOLOGY = `
|
|
117
|
+
topology code-review : [pipeline, fan-out] {
|
|
118
|
+
meta {
|
|
119
|
+
version: "2.0.0"
|
|
120
|
+
description: "Code review with fan-out"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
orchestrator {
|
|
124
|
+
model: opus
|
|
125
|
+
handles: [intake, create-report]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
roles {
|
|
129
|
+
analyzer: "Static analysis"
|
|
130
|
+
security: "Security scanning"
|
|
131
|
+
reviewer: "Code review"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
action intake {
|
|
135
|
+
kind: external
|
|
136
|
+
source: "github-pr"
|
|
137
|
+
description: "Fetch PR diff"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
action create-report {
|
|
141
|
+
kind: report
|
|
142
|
+
description: "Final report"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
agent analyzer {
|
|
146
|
+
role: analyzer
|
|
147
|
+
model: gemini-2.0-flash
|
|
148
|
+
phase: 1
|
|
149
|
+
tools: [Read, Grep]
|
|
150
|
+
behavior: advisory
|
|
151
|
+
outputs: {
|
|
152
|
+
risk-level: low | medium | high
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
agent security-scanner {
|
|
157
|
+
role: security
|
|
158
|
+
model: sonnet
|
|
159
|
+
phase: 1
|
|
160
|
+
tools: [Read, Grep]
|
|
161
|
+
behavior: advisory
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
agent reviewer {
|
|
165
|
+
role: reviewer
|
|
166
|
+
model: opus
|
|
167
|
+
phase: 2
|
|
168
|
+
tools: [Read, Grep, Glob]
|
|
169
|
+
outputs: {
|
|
170
|
+
verdict: approve | reject
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
flow {
|
|
175
|
+
intake -> [analyzer, security-scanner]
|
|
176
|
+
analyzer -> reviewer
|
|
177
|
+
security-scanner -> reviewer
|
|
178
|
+
reviewer -> create-report [when reviewer.verdict == approve]
|
|
179
|
+
reviewer -> create-report [when reviewer.verdict == reject]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
hooks {
|
|
183
|
+
hook log-findings {
|
|
184
|
+
on: AgentStop
|
|
185
|
+
run: "scripts/log.sh"
|
|
186
|
+
type: command
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
const MINIMAL_TOPOLOGY = `
|
|
192
|
+
topology minimal : [pipeline] {
|
|
193
|
+
meta {
|
|
194
|
+
version: "0.1.0"
|
|
195
|
+
description: "Bare minimum"
|
|
196
|
+
}
|
|
197
|
+
orchestrator {
|
|
198
|
+
model: sonnet
|
|
199
|
+
handles: [start]
|
|
200
|
+
}
|
|
201
|
+
action start {
|
|
202
|
+
kind: inline
|
|
203
|
+
description: "Entry"
|
|
204
|
+
}
|
|
205
|
+
agent worker {
|
|
206
|
+
model: haiku
|
|
207
|
+
phase: 1
|
|
208
|
+
}
|
|
209
|
+
flow {
|
|
210
|
+
start -> worker
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Registry tests
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
describe("exporter registry", () => {
|
|
218
|
+
it("has markdown, mermaid, and json exporters", () => {
|
|
219
|
+
expect(exporters).toHaveProperty("markdown");
|
|
220
|
+
expect(exporters).toHaveProperty("mermaid");
|
|
221
|
+
expect(exporters).toHaveProperty("json");
|
|
222
|
+
});
|
|
223
|
+
it("each exporter has required fields", () => {
|
|
224
|
+
for (const exp of Object.values(exporters)) {
|
|
225
|
+
expect(exp.name).toBeTruthy();
|
|
226
|
+
expect(exp.description).toBeTruthy();
|
|
227
|
+
expect(exp.extension).toBeTruthy();
|
|
228
|
+
expect(typeof exp.export).toBe("function");
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Markdown exporter tests
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
describe("markdownExporter", () => {
|
|
236
|
+
it("has correct metadata", () => {
|
|
237
|
+
expect(markdownExporter.name).toBe("markdown");
|
|
238
|
+
expect(markdownExporter.extension).toBe(".md");
|
|
239
|
+
});
|
|
240
|
+
it("generates a single .md file", () => {
|
|
241
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
242
|
+
const files = markdownExporter.export(ast);
|
|
243
|
+
expect(files).toHaveLength(1);
|
|
244
|
+
expect(files[0].path).toBe("simple-pipeline.md");
|
|
245
|
+
});
|
|
246
|
+
describe("header section", () => {
|
|
247
|
+
it("includes topology name as title", () => {
|
|
248
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
249
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
250
|
+
expect(md).toContain("# Simple Pipeline");
|
|
251
|
+
});
|
|
252
|
+
it("includes description as italic", () => {
|
|
253
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
254
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
255
|
+
expect(md).toContain("*Research, write, and review*");
|
|
256
|
+
});
|
|
257
|
+
it("includes version badge", () => {
|
|
258
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
259
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
260
|
+
expect(md).toContain("version-1.0.0");
|
|
261
|
+
});
|
|
262
|
+
it("includes pattern badges", () => {
|
|
263
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
264
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
265
|
+
expect(md).toContain("pattern-pipeline");
|
|
266
|
+
expect(md).toContain("pattern-human-gate");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe("overview section", () => {
|
|
270
|
+
it("shows component counts inline", () => {
|
|
271
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
272
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
273
|
+
expect(md).toContain("At a Glance");
|
|
274
|
+
expect(md).toContain("**3** Agents");
|
|
275
|
+
expect(md).toContain("**2** Actions");
|
|
276
|
+
expect(md).toContain("**1** Gate");
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe("orchestrator section", () => {
|
|
280
|
+
it("renders orchestrator with model", () => {
|
|
281
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
282
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
283
|
+
expect(md).toContain("### Orchestrator");
|
|
284
|
+
expect(md).toContain("Model: `sonnet`");
|
|
285
|
+
expect(md).toContain("`intake`");
|
|
286
|
+
});
|
|
287
|
+
it("renders orchestrator outputs", () => {
|
|
288
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
289
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
290
|
+
expect(md).toContain("`topic-type`");
|
|
291
|
+
expect(md).toContain("`technical`");
|
|
292
|
+
expect(md).toContain("`creative`");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
describe("agents section", () => {
|
|
296
|
+
it("renders each agent", () => {
|
|
297
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
298
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
299
|
+
expect(md).toContain("#### Researcher");
|
|
300
|
+
expect(md).toContain("#### Writer");
|
|
301
|
+
expect(md).toContain("#### Reviewer");
|
|
302
|
+
});
|
|
303
|
+
it("shows agent model inline with heading", () => {
|
|
304
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
305
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
306
|
+
expect(md).toContain("#### Researcher `gpt-4o`");
|
|
307
|
+
});
|
|
308
|
+
it("shows agent phase", () => {
|
|
309
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
310
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
311
|
+
expect(md).toContain("Phase **1**");
|
|
312
|
+
expect(md).toContain("Phase **2**");
|
|
313
|
+
expect(md).toContain("Phase **3**");
|
|
314
|
+
});
|
|
315
|
+
it("shows agent tools", () => {
|
|
316
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
317
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
318
|
+
expect(md).toContain("`Read`");
|
|
319
|
+
expect(md).toContain("`WebSearch`");
|
|
320
|
+
});
|
|
321
|
+
it("shows reads and writes", () => {
|
|
322
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
323
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
324
|
+
expect(md).toContain("`workspace/research.md`");
|
|
325
|
+
expect(md).toContain("`workspace/draft.md`");
|
|
326
|
+
});
|
|
327
|
+
it("shows agent outputs", () => {
|
|
328
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
329
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
330
|
+
expect(md).toContain("`confidence`");
|
|
331
|
+
expect(md).toContain("`high`");
|
|
332
|
+
});
|
|
333
|
+
it("shows inline prompt in collapsible details", () => {
|
|
334
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
335
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
336
|
+
expect(md).toContain("<details>");
|
|
337
|
+
expect(md).toContain("<summary>Prompt</summary>");
|
|
338
|
+
expect(md).toContain("You are a research specialist.");
|
|
339
|
+
expect(md).toContain("</details>");
|
|
340
|
+
});
|
|
341
|
+
it("renders role description from roles block", () => {
|
|
342
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
343
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
344
|
+
expect(md).toContain("Gather information and compile research notes");
|
|
345
|
+
});
|
|
346
|
+
it("shows advisory behavior", () => {
|
|
347
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
348
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
349
|
+
expect(md).toContain("Behavior: `advisory`");
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
describe("actions section", () => {
|
|
353
|
+
it("renders actions with kind and description", () => {
|
|
354
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
355
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
356
|
+
expect(md).toContain("#### Intake `inline`");
|
|
357
|
+
expect(md).toContain("Parse user request");
|
|
358
|
+
});
|
|
359
|
+
it("shows action source", () => {
|
|
360
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
361
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
362
|
+
expect(md).toContain("Source: `github-pr`");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
describe("gates section", () => {
|
|
366
|
+
it("renders gates with checks", () => {
|
|
367
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
368
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
369
|
+
expect(md).toContain("#### Quality Check");
|
|
370
|
+
expect(md).toContain("After: `writer`");
|
|
371
|
+
expect(md).toContain("Before: `reviewer`");
|
|
372
|
+
expect(md).toContain("`grammar`");
|
|
373
|
+
expect(md).toContain("`formatting`");
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
describe("flow section", () => {
|
|
377
|
+
it("renders flow as code block", () => {
|
|
378
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
379
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
380
|
+
expect(md).toContain("## Flow");
|
|
381
|
+
expect(md).toContain("intake ───> researcher");
|
|
382
|
+
expect(md).toContain("researcher ───> writer");
|
|
383
|
+
});
|
|
384
|
+
it("shows edge conditions", () => {
|
|
385
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
386
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
387
|
+
expect(md).toContain("when reviewer.verdict == revise");
|
|
388
|
+
expect(md).toContain("max 2");
|
|
389
|
+
});
|
|
390
|
+
it("shows edge conditions for approve", () => {
|
|
391
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
392
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
393
|
+
expect(md).toContain("when reviewer.verdict == approve");
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
describe("roles section", () => {
|
|
397
|
+
it("renders roles as list", () => {
|
|
398
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
399
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
400
|
+
expect(md).toContain("## Roles");
|
|
401
|
+
expect(md).toContain("**Researcher**");
|
|
402
|
+
expect(md).toContain("**Writer**");
|
|
403
|
+
expect(md).toContain("**Reviewer**");
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
describe("memory section", () => {
|
|
407
|
+
it("renders memory blocks", () => {
|
|
408
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
409
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
410
|
+
expect(md).toContain("## Memory");
|
|
411
|
+
expect(md).toContain("**Workspace**");
|
|
412
|
+
expect(md).toContain("`workspace/`");
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
describe("triggers section", () => {
|
|
416
|
+
it("renders triggers as list", () => {
|
|
417
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
418
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
419
|
+
expect(md).toContain("## Triggers");
|
|
420
|
+
expect(md).toContain("`create`");
|
|
421
|
+
expect(md).toContain("`/create <TOPIC>`");
|
|
422
|
+
expect(md).toContain("`TOPIC`");
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
describe("hooks section", () => {
|
|
426
|
+
it("renders hooks as list", () => {
|
|
427
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
428
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
429
|
+
expect(md).toContain("## Hooks");
|
|
430
|
+
expect(md).toContain("`log-findings`");
|
|
431
|
+
expect(md).toContain("`AgentStop`");
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
describe("settings section", () => {
|
|
435
|
+
it("renders allow/deny lists", () => {
|
|
436
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
437
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
438
|
+
expect(md).toContain("## Settings");
|
|
439
|
+
expect(md).toContain("**Allow:**");
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
describe("footer", () => {
|
|
443
|
+
it("includes AgenTopology link", () => {
|
|
444
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
445
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
446
|
+
expect(md).toContain("agentopology.com");
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
describe("minimal topology", () => {
|
|
450
|
+
it("works with minimal input", () => {
|
|
451
|
+
const ast = parse(MINIMAL_TOPOLOGY);
|
|
452
|
+
const files = markdownExporter.export(ast);
|
|
453
|
+
expect(files).toHaveLength(1);
|
|
454
|
+
expect(files[0].path).toBe("minimal.md");
|
|
455
|
+
const md = files[0].content;
|
|
456
|
+
expect(md).toContain("# Minimal");
|
|
457
|
+
expect(md).toContain("#### Worker");
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
// Mermaid exporter tests
|
|
463
|
+
// ---------------------------------------------------------------------------
|
|
464
|
+
describe("mermaidExporter", () => {
|
|
465
|
+
it("has correct metadata", () => {
|
|
466
|
+
expect(mermaidExporter.name).toBe("mermaid");
|
|
467
|
+
expect(mermaidExporter.extension).toBe(".mmd");
|
|
468
|
+
});
|
|
469
|
+
it("generates a single .mmd file", () => {
|
|
470
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
471
|
+
const files = mermaidExporter.export(ast);
|
|
472
|
+
expect(files).toHaveLength(1);
|
|
473
|
+
expect(files[0].path).toBe("simple-pipeline.mmd");
|
|
474
|
+
});
|
|
475
|
+
describe("header", () => {
|
|
476
|
+
it("includes topology name as comment", () => {
|
|
477
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
478
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
479
|
+
expect(mmd).toContain("%% simple-pipeline v1.0.0");
|
|
480
|
+
});
|
|
481
|
+
it("includes description as comment", () => {
|
|
482
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
483
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
484
|
+
expect(mmd).toContain("%% Research, write, and review");
|
|
485
|
+
});
|
|
486
|
+
it("starts with flowchart TD", () => {
|
|
487
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
488
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
489
|
+
expect(mmd).toContain("flowchart TD");
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
describe("style classes", () => {
|
|
493
|
+
it("defines all node type classes", () => {
|
|
494
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
495
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
496
|
+
expect(mmd).toContain("classDef orchestrator");
|
|
497
|
+
expect(mmd).toContain("classDef agent");
|
|
498
|
+
expect(mmd).toContain("classDef action");
|
|
499
|
+
expect(mmd).toContain("classDef gate");
|
|
500
|
+
expect(mmd).toContain("classDef human");
|
|
501
|
+
expect(mmd).toContain("classDef group");
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
describe("node shapes", () => {
|
|
505
|
+
it("renders orchestrator as stadium shape", () => {
|
|
506
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
507
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
508
|
+
expect(mmd).toMatch(/orchestrator\(\[".*"\]\)/);
|
|
509
|
+
});
|
|
510
|
+
it("renders actions as subroutine shape", () => {
|
|
511
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
512
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
513
|
+
expect(mmd).toMatch(/intake\[\[".*"\]\]/);
|
|
514
|
+
});
|
|
515
|
+
it("renders gates as diamond shape", () => {
|
|
516
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
517
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
518
|
+
expect(mmd).toMatch(/quality_check\{".*"\}/);
|
|
519
|
+
});
|
|
520
|
+
it("renders agents as rectangles", () => {
|
|
521
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
522
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
523
|
+
expect(mmd).toMatch(/researcher\[".*"\]/);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
describe("node labels", () => {
|
|
527
|
+
it("includes agent model in label", () => {
|
|
528
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
529
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
530
|
+
expect(mmd).toContain("Model: gpt-4o");
|
|
531
|
+
expect(mmd).toContain("Model: sonnet");
|
|
532
|
+
expect(mmd).toContain("Model: opus");
|
|
533
|
+
});
|
|
534
|
+
it("includes agent phase in label", () => {
|
|
535
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
536
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
537
|
+
expect(mmd).toContain("Phase: 1");
|
|
538
|
+
expect(mmd).toContain("Phase: 2");
|
|
539
|
+
expect(mmd).toContain("Phase: 3");
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
describe("phase subgraphs", () => {
|
|
543
|
+
it("groups agents by phase", () => {
|
|
544
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
545
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
546
|
+
expect(mmd).toContain('subgraph Phase_1["Phase 1"]');
|
|
547
|
+
expect(mmd).toContain('subgraph Phase_2["Phase 2"]');
|
|
548
|
+
expect(mmd).toContain('subgraph Phase_3["Phase 3"]');
|
|
549
|
+
});
|
|
550
|
+
it("groups same-phase agents together", () => {
|
|
551
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
552
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
553
|
+
expect(mmd).toContain('subgraph Phase_1["Phase 1"]');
|
|
554
|
+
const phase1Start = mmd.indexOf('subgraph Phase_1');
|
|
555
|
+
const phase1End = mmd.indexOf("end", phase1Start);
|
|
556
|
+
const phase1Content = mmd.slice(phase1Start, phase1End);
|
|
557
|
+
expect(phase1Content).toContain("analyzer");
|
|
558
|
+
expect(phase1Content).toContain("security_scanner");
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
describe("edges", () => {
|
|
562
|
+
it("renders simple edges", () => {
|
|
563
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
564
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
565
|
+
expect(mmd).toContain("intake --> researcher");
|
|
566
|
+
expect(mmd).toContain("researcher --> writer");
|
|
567
|
+
});
|
|
568
|
+
it("renders conditional edges with annotations", () => {
|
|
569
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
570
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
571
|
+
expect(mmd).toContain('reviewer -->|"reviewer.verdict == revise, max 2"| writer');
|
|
572
|
+
});
|
|
573
|
+
it("renders edges with only condition", () => {
|
|
574
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
575
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
576
|
+
expect(mmd).toContain('reviewer -->|"reviewer.verdict == approve"| done');
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
describe("hyphenated IDs", () => {
|
|
580
|
+
it("converts hyphens to underscores in IDs", () => {
|
|
581
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
582
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
583
|
+
expect(mmd).toContain("security_scanner");
|
|
584
|
+
expect(mmd).toContain("create_report");
|
|
585
|
+
expect(mmd).not.toMatch(/\bsecurity-scanner\b.*\[/);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
describe("class assignments", () => {
|
|
589
|
+
it("assigns correct classes to nodes", () => {
|
|
590
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
591
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
592
|
+
expect(mmd).toContain(":::orchestrator");
|
|
593
|
+
expect(mmd).toContain(":::agent");
|
|
594
|
+
expect(mmd).toContain(":::action");
|
|
595
|
+
expect(mmd).toContain(":::gate");
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
describe("minimal topology", () => {
|
|
599
|
+
it("works with minimal input", () => {
|
|
600
|
+
const ast = parse(MINIMAL_TOPOLOGY);
|
|
601
|
+
const files = mermaidExporter.export(ast);
|
|
602
|
+
expect(files).toHaveLength(1);
|
|
603
|
+
expect(files[0].path).toBe("minimal.mmd");
|
|
604
|
+
const mmd = files[0].content;
|
|
605
|
+
expect(mmd).toContain("flowchart TD");
|
|
606
|
+
expect(mmd).toContain("start --> worker");
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
// ---------------------------------------------------------------------------
|
|
611
|
+
// Cross-exporter tests
|
|
612
|
+
// ---------------------------------------------------------------------------
|
|
613
|
+
describe("cross-exporter", () => {
|
|
614
|
+
const topologies = [
|
|
615
|
+
{ name: "simple-pipeline", source: SIMPLE_PIPELINE },
|
|
616
|
+
{ name: "fan-out", source: FAN_OUT_TOPOLOGY },
|
|
617
|
+
{ name: "minimal", source: MINIMAL_TOPOLOGY },
|
|
618
|
+
];
|
|
619
|
+
for (const { name, source } of topologies) {
|
|
620
|
+
it(`both exporters produce non-empty output for ${name}`, () => {
|
|
621
|
+
const ast = parse(source);
|
|
622
|
+
const mdFiles = markdownExporter.export(ast);
|
|
623
|
+
const mmdFiles = mermaidExporter.export(ast);
|
|
624
|
+
expect(mdFiles.length).toBeGreaterThan(0);
|
|
625
|
+
expect(mmdFiles.length).toBeGreaterThan(0);
|
|
626
|
+
for (const file of [...mdFiles, ...mmdFiles]) {
|
|
627
|
+
expect(file.content.length).toBeGreaterThan(0);
|
|
628
|
+
expect(file.path.length).toBeGreaterThan(0);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
it("all nodes appear in both export formats", () => {
|
|
633
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
634
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
635
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
636
|
+
for (const node of ast.nodes) {
|
|
637
|
+
expect(md.toLowerCase()).toContain(node.id.replace(/-/g, " "));
|
|
638
|
+
expect(mmd).toContain(node.id.replace(/-/g, "_"));
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
it("all edges appear in both export formats", () => {
|
|
642
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
643
|
+
const md = markdownExporter.export(ast)[0].content;
|
|
644
|
+
const mmd = mermaidExporter.export(ast)[0].content;
|
|
645
|
+
for (const edge of ast.edges) {
|
|
646
|
+
expect(md).toContain(edge.from);
|
|
647
|
+
expect(md).toContain(edge.to);
|
|
648
|
+
expect(mmd).toContain(edge.from.replace(/-/g, "_"));
|
|
649
|
+
expect(mmd).toContain(edge.to.replace(/-/g, "_"));
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
// ---------------------------------------------------------------------------
|
|
654
|
+
// JSON exporter tests
|
|
655
|
+
// ---------------------------------------------------------------------------
|
|
656
|
+
describe("jsonExporter", () => {
|
|
657
|
+
it("has correct metadata", () => {
|
|
658
|
+
expect(jsonExporter.name).toBe("json");
|
|
659
|
+
expect(jsonExporter.extension).toBe(".json");
|
|
660
|
+
});
|
|
661
|
+
it("produces a single .json file", () => {
|
|
662
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
663
|
+
const files = jsonExporter.export(ast);
|
|
664
|
+
expect(files).toHaveLength(1);
|
|
665
|
+
expect(files[0].path).toBe("simple-pipeline.json");
|
|
666
|
+
});
|
|
667
|
+
it("produces valid JSON", () => {
|
|
668
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
669
|
+
const content = jsonExporter.export(ast)[0].content;
|
|
670
|
+
const parsed = JSON.parse(content);
|
|
671
|
+
expect(parsed).toBeDefined();
|
|
672
|
+
expect(parsed.topology.name).toBe("simple-pipeline");
|
|
673
|
+
});
|
|
674
|
+
it("preserves topology fields", () => {
|
|
675
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
676
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
677
|
+
expect(parsed.topology.version).toBe("1.0.0");
|
|
678
|
+
expect(parsed.topology.description).toBe("Research, write, and review");
|
|
679
|
+
expect(parsed.topology.patterns).toContain("pipeline");
|
|
680
|
+
});
|
|
681
|
+
it("includes nodes", () => {
|
|
682
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
683
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
684
|
+
const nodeIds = parsed.nodes.map((n) => n.id);
|
|
685
|
+
expect(nodeIds).toContain("researcher");
|
|
686
|
+
expect(nodeIds).toContain("writer");
|
|
687
|
+
expect(nodeIds).toContain("reviewer");
|
|
688
|
+
});
|
|
689
|
+
it("includes edges", () => {
|
|
690
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
691
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
692
|
+
expect(parsed.edges.length).toBeGreaterThan(0);
|
|
693
|
+
expect(parsed.edges[0]).toHaveProperty("from");
|
|
694
|
+
expect(parsed.edges[0]).toHaveProperty("to");
|
|
695
|
+
});
|
|
696
|
+
it("strips internal _-prefixed fields", () => {
|
|
697
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
698
|
+
const content = jsonExporter.export(ast)[0].content;
|
|
699
|
+
const parsed = JSON.parse(content);
|
|
700
|
+
for (const key of Object.keys(parsed)) {
|
|
701
|
+
expect(key).not.toMatch(/^_/);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
it("works with fan-out topology", () => {
|
|
705
|
+
const ast = parse(FAN_OUT_TOPOLOGY);
|
|
706
|
+
const files = jsonExporter.export(ast);
|
|
707
|
+
expect(files).toHaveLength(1);
|
|
708
|
+
expect(files[0].path).toBe("code-review.json");
|
|
709
|
+
const parsed = JSON.parse(files[0].content);
|
|
710
|
+
expect(parsed.topology.name).toBe("code-review");
|
|
711
|
+
});
|
|
712
|
+
it("works with minimal topology", () => {
|
|
713
|
+
const ast = parse(MINIMAL_TOPOLOGY);
|
|
714
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
715
|
+
const agents = parsed.nodes.filter((n) => n.id === "worker");
|
|
716
|
+
expect(agents).toHaveLength(1);
|
|
717
|
+
});
|
|
718
|
+
it("includes settings", () => {
|
|
719
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
720
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
721
|
+
expect(parsed.settings.allow).toContain("Read");
|
|
722
|
+
expect(parsed.settings.deny).toContain("Bash(rm -rf *)");
|
|
723
|
+
});
|
|
724
|
+
it("includes gate nodes", () => {
|
|
725
|
+
const ast = parse(SIMPLE_PIPELINE);
|
|
726
|
+
const parsed = JSON.parse(jsonExporter.export(ast)[0].content);
|
|
727
|
+
const gates = parsed.nodes.filter((n) => n.id === "quality-check");
|
|
728
|
+
expect(gates).toHaveLength(1);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
//# sourceMappingURL=exporters.test.js.map
|