@davidorex/pi-workflows 0.2.0 → 0.3.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 (216) hide show
  1. package/CHANGELOG.md +7 -4
  2. package/README.md +12 -2
  3. package/agents/architecture-designer.agent.yaml +58 -0
  4. package/agents/architecture-inferrer.agent.yaml +60 -0
  5. package/agents/gap-identifier.agent.yaml +66 -0
  6. package/agents/handoff-writer.agent.yaml +64 -0
  7. package/agents/plan-creator.agent.yaml +63 -0
  8. package/agents/project-definer.agent.yaml +53 -0
  9. package/agents/project-inferrer.agent.yaml +63 -0
  10. package/agents/requirements-gatherer.agent.yaml +58 -0
  11. package/dist/agent-spec.d.ts +27 -0
  12. package/dist/agent-spec.d.ts.map +1 -0
  13. package/dist/agent-spec.js +112 -0
  14. package/dist/agent-spec.js.map +1 -0
  15. package/dist/checkpoint.d.ts +50 -0
  16. package/dist/checkpoint.d.ts.map +1 -0
  17. package/dist/checkpoint.js +109 -0
  18. package/dist/checkpoint.js.map +1 -0
  19. package/dist/completion.d.ts +18 -0
  20. package/dist/completion.d.ts.map +1 -0
  21. package/dist/completion.js +58 -0
  22. package/dist/completion.js.map +1 -0
  23. package/dist/dag.d.ts +56 -0
  24. package/dist/dag.d.ts.map +1 -0
  25. package/dist/dag.js +178 -0
  26. package/dist/dag.js.map +1 -0
  27. package/dist/dispatch.d.ts +35 -0
  28. package/dist/dispatch.d.ts.map +1 -0
  29. package/dist/dispatch.js +311 -0
  30. package/dist/dispatch.js.map +1 -0
  31. package/dist/expression.d.ts +64 -0
  32. package/dist/expression.d.ts.map +1 -0
  33. package/dist/expression.js +294 -0
  34. package/dist/expression.js.map +1 -0
  35. package/dist/format.d.ts +19 -0
  36. package/dist/format.d.ts.map +1 -0
  37. package/dist/format.js +42 -0
  38. package/dist/format.js.map +1 -0
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +724 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/output.d.ts +12 -0
  44. package/dist/output.d.ts.map +1 -0
  45. package/dist/output.js +59 -0
  46. package/dist/output.js.map +1 -0
  47. package/dist/state.d.ts +66 -0
  48. package/dist/state.d.ts.map +1 -0
  49. package/dist/state.js +227 -0
  50. package/dist/state.js.map +1 -0
  51. package/dist/step-agent.d.ts +35 -0
  52. package/dist/step-agent.d.ts.map +1 -0
  53. package/dist/step-agent.js +171 -0
  54. package/dist/step-agent.js.map +1 -0
  55. package/dist/step-command.d.ts +21 -0
  56. package/dist/step-command.d.ts.map +1 -0
  57. package/dist/step-command.js +150 -0
  58. package/dist/step-command.js.map +1 -0
  59. package/dist/step-foreach.d.ts +33 -0
  60. package/dist/step-foreach.d.ts.map +1 -0
  61. package/dist/step-foreach.js +112 -0
  62. package/dist/step-foreach.js.map +1 -0
  63. package/dist/step-gate.d.ts +18 -0
  64. package/dist/step-gate.d.ts.map +1 -0
  65. package/dist/step-gate.js +137 -0
  66. package/dist/step-gate.js.map +1 -0
  67. package/dist/step-loop.d.ts +32 -0
  68. package/dist/step-loop.d.ts.map +1 -0
  69. package/dist/step-loop.js +278 -0
  70. package/dist/step-loop.js.map +1 -0
  71. package/dist/step-monitor.d.ts +56 -0
  72. package/dist/step-monitor.d.ts.map +1 -0
  73. package/dist/step-monitor.js +317 -0
  74. package/dist/step-monitor.js.map +1 -0
  75. package/dist/step-parallel.d.ts +43 -0
  76. package/dist/step-parallel.d.ts.map +1 -0
  77. package/dist/step-parallel.js +122 -0
  78. package/dist/step-parallel.js.map +1 -0
  79. package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
  80. package/dist/step-pause.d.ts.map +1 -0
  81. package/dist/step-pause.js +22 -0
  82. package/dist/step-pause.js.map +1 -0
  83. package/dist/step-shared.d.ts +63 -0
  84. package/dist/step-shared.d.ts.map +1 -0
  85. package/dist/step-shared.js +136 -0
  86. package/dist/step-shared.js.map +1 -0
  87. package/dist/step-transform.d.ts +11 -0
  88. package/dist/step-transform.d.ts.map +1 -0
  89. package/dist/step-transform.js +41 -0
  90. package/dist/step-transform.js.map +1 -0
  91. package/dist/template.d.ts +51 -0
  92. package/dist/template.d.ts.map +1 -0
  93. package/{src/template.ts → dist/template.js} +32 -38
  94. package/dist/template.js.map +1 -0
  95. package/dist/test-helpers.d.ts +17 -0
  96. package/dist/test-helpers.d.ts.map +1 -0
  97. package/dist/test-helpers.js +46 -0
  98. package/dist/test-helpers.js.map +1 -0
  99. package/dist/tui.d.ts +47 -0
  100. package/dist/tui.d.ts.map +1 -0
  101. package/dist/tui.js +143 -0
  102. package/dist/tui.js.map +1 -0
  103. package/dist/types.d.ts +165 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +3 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/workflow-discovery.d.ts +23 -0
  108. package/dist/workflow-discovery.d.ts.map +1 -0
  109. package/dist/workflow-discovery.js +103 -0
  110. package/dist/workflow-discovery.js.map +1 -0
  111. package/dist/workflow-executor.d.ts +50 -0
  112. package/dist/workflow-executor.d.ts.map +1 -0
  113. package/dist/workflow-executor.js +738 -0
  114. package/dist/workflow-executor.js.map +1 -0
  115. package/dist/workflow-sdk.d.ts +44 -0
  116. package/dist/workflow-sdk.d.ts.map +1 -0
  117. package/dist/workflow-sdk.js +457 -0
  118. package/dist/workflow-sdk.js.map +1 -0
  119. package/dist/workflow-spec.d.ts +33 -0
  120. package/dist/workflow-spec.d.ts.map +1 -0
  121. package/dist/workflow-spec.js +476 -0
  122. package/dist/workflow-spec.js.map +1 -0
  123. package/dist/workflows-dir.d.ts +3 -0
  124. package/dist/workflows-dir.d.ts.map +1 -0
  125. package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
  126. package/dist/workflows-dir.js.map +1 -0
  127. package/package.json +53 -38
  128. package/schemas/decomposition-specs.schema.json +48 -48
  129. package/schemas/execution-results.schema.json +48 -48
  130. package/schemas/investigation-findings.schema.json +43 -43
  131. package/schemas/pattern-analysis.schema.json +41 -41
  132. package/schemas/phase.schema.json +48 -0
  133. package/schemas/plan-breakdown.schema.json +20 -20
  134. package/schemas/quality-analysis.schema.json +33 -33
  135. package/schemas/research-findings.schema.json +42 -42
  136. package/schemas/structure-analysis.schema.json +40 -40
  137. package/schemas/synthesis.schema.json +31 -31
  138. package/schemas/verifier-output.schema.json +94 -94
  139. package/skill-narrative.md +107 -0
  140. package/skills/pi-workflows/SKILL.md +185 -0
  141. package/skills/pi-workflows/references/bundled-resources.md +87 -0
  142. package/templates/architecture-designer/task.md +117 -0
  143. package/templates/architecture-inferrer/task.md +61 -0
  144. package/templates/gap-identifier/task.md +103 -0
  145. package/templates/handoff-writer/task.md +91 -0
  146. package/templates/plan-creator/task.md +143 -0
  147. package/templates/project-definer/task.md +67 -0
  148. package/templates/project-inferrer/task.md +56 -0
  149. package/templates/requirements-gatherer/task.md +90 -0
  150. package/workflows/analyze-existing-project.workflow.yaml +94 -0
  151. package/workflows/create-handoff.workflow.yaml +66 -0
  152. package/workflows/create-phase.workflow.yaml +1 -1
  153. package/workflows/do-gap.workflow.yaml +4 -4
  154. package/workflows/fix-audit.workflow.yaml +1 -1
  155. package/workflows/gap-to-phase.workflow.yaml +1 -1
  156. package/workflows/init-new-project.workflow.yaml +81 -0
  157. package/workflows/plan-from-requirements.workflow.yaml +59 -0
  158. package/workflows/self-implement.workflow.yaml +3 -3
  159. package/workflows/typed-analysis.workflow.yaml +4 -4
  160. package/src/agent-spec.test.ts +0 -289
  161. package/src/agent-spec.ts +0 -122
  162. package/src/block-validation.test.ts +0 -350
  163. package/src/checkpoint.test.ts +0 -237
  164. package/src/checkpoint.ts +0 -140
  165. package/src/completion.test.ts +0 -143
  166. package/src/completion.ts +0 -68
  167. package/src/dag.test.ts +0 -350
  168. package/src/dag.ts +0 -219
  169. package/src/dispatch.test.ts +0 -475
  170. package/src/dispatch.ts +0 -353
  171. package/src/expression.test.ts +0 -353
  172. package/src/expression.ts +0 -332
  173. package/src/format.test.ts +0 -80
  174. package/src/format.ts +0 -41
  175. package/src/graduated-failure.test.ts +0 -556
  176. package/src/index.test.ts +0 -114
  177. package/src/index.ts +0 -516
  178. package/src/loop.test.ts +0 -551
  179. package/src/output.test.ts +0 -213
  180. package/src/output.ts +0 -70
  181. package/src/parallel-integration.test.ts +0 -175
  182. package/src/resume.test.ts +0 -192
  183. package/src/state.test.ts +0 -192
  184. package/src/state.ts +0 -254
  185. package/src/step-agent.test.ts +0 -327
  186. package/src/step-agent.ts +0 -178
  187. package/src/step-command.test.ts +0 -330
  188. package/src/step-command.ts +0 -132
  189. package/src/step-foreach.test.ts +0 -647
  190. package/src/step-foreach.ts +0 -148
  191. package/src/step-gate.test.ts +0 -185
  192. package/src/step-gate.ts +0 -116
  193. package/src/step-loop.test.ts +0 -626
  194. package/src/step-loop.ts +0 -323
  195. package/src/step-parallel.test.ts +0 -475
  196. package/src/step-parallel.ts +0 -168
  197. package/src/step-pause.test.ts +0 -30
  198. package/src/step-shared.test.ts +0 -355
  199. package/src/step-shared.ts +0 -157
  200. package/src/step-transform.test.ts +0 -155
  201. package/src/step-transform.ts +0 -47
  202. package/src/template-integration.test.ts +0 -72
  203. package/src/template.test.ts +0 -162
  204. package/src/test-helpers.ts +0 -51
  205. package/src/tui.test.ts +0 -355
  206. package/src/tui.ts +0 -182
  207. package/src/types.ts +0 -195
  208. package/src/verifier-schema.test.ts +0 -226
  209. package/src/workflow-discovery.test.ts +0 -105
  210. package/src/workflow-discovery.ts +0 -114
  211. package/src/workflow-executor.test.ts +0 -1532
  212. package/src/workflow-executor.ts +0 -810
  213. package/src/workflow-sdk.test.ts +0 -238
  214. package/src/workflow-sdk.ts +0 -262
  215. package/src/workflow-spec.test.ts +0 -576
  216. package/src/workflow-spec.ts +0 -471
@@ -1,576 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import path from "node:path";
4
- import { readFileSync } from "node:fs";
5
- import { parseWorkflowSpec, WorkflowSpecError } from "./workflow-spec.ts";
6
-
7
- describe("parseWorkflowSpec", () => {
8
- it("parses a minimal valid spec", () => {
9
- const yaml = `
10
- name: test
11
- steps:
12
- step1:
13
- agent: my-agent
14
- `;
15
- const spec = parseWorkflowSpec(yaml, "/test.workflow.yaml", "project");
16
- assert.strictEqual(spec.name, "test");
17
- assert.strictEqual(spec.steps.step1.agent, "my-agent");
18
- assert.strictEqual(spec.source, "project");
19
- assert.strictEqual(spec.filePath, "/test.workflow.yaml");
20
- assert.strictEqual(spec.triggerTurn, true); // default
21
- });
22
-
23
- it("parses a full spec", () => {
24
- const yaml = `
25
- name: bugfix
26
- description: Fix a bug
27
- version: "1"
28
- triggerTurn: false
29
- input:
30
- type: object
31
- required: [description]
32
- properties:
33
- description:
34
- type: string
35
- steps:
36
- diagnose:
37
- agent: diagnostician
38
- input:
39
- description: \${{ input.description }}
40
- output:
41
- format: json
42
- schema: ./schemas/diagnosis.schema.json
43
- fix:
44
- agent: fixer
45
- model: claude-sonnet-4-6
46
- input:
47
- diagnosis: \${{ steps.diagnose.output }}
48
- `;
49
- const spec = parseWorkflowSpec(yaml, "/bugfix.workflow.yaml", "user");
50
- assert.strictEqual(spec.name, "bugfix");
51
- assert.strictEqual(spec.description, "Fix a bug");
52
- assert.strictEqual(spec.triggerTurn, false);
53
- assert.strictEqual(spec.steps.diagnose.agent, "diagnostician");
54
- assert.strictEqual(spec.steps.diagnose.output?.schema, "./schemas/diagnosis.schema.json");
55
- assert.strictEqual(spec.steps.fix.model, "claude-sonnet-4-6");
56
- });
57
-
58
- it("throws on missing name", () => {
59
- assert.throws(
60
- () => parseWorkflowSpec("steps:\n s:\n agent: a", "/t.yaml", "project"),
61
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("name"),
62
- );
63
- });
64
-
65
- it("throws on missing steps", () => {
66
- assert.throws(
67
- () => parseWorkflowSpec("name: test", "/t.yaml", "project"),
68
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("steps"),
69
- );
70
- });
71
-
72
- it("throws on empty steps", () => {
73
- assert.throws(
74
- () => parseWorkflowSpec("name: test\nsteps: {}", "/t.yaml", "project"),
75
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("non-empty"),
76
- );
77
- });
78
-
79
- it("throws on step with no type", () => {
80
- assert.throws(
81
- () => parseWorkflowSpec("name: test\nsteps:\n s:\n model: foo", "/t.yaml", "project"),
82
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("must have exactly one of"),
83
- );
84
- });
85
-
86
- it("throws on invalid YAML", () => {
87
- assert.throws(
88
- () => parseWorkflowSpec("name: [[[invalid", "/t.yaml", "project"),
89
- (err: unknown) => err instanceof WorkflowSpecError,
90
- );
91
- });
92
-
93
- // ── Completion field tests ──
94
-
95
- it("parses completion with template", () => {
96
- const yaml = `
97
- name: test
98
- steps:
99
- s:
100
- agent: a
101
- completion:
102
- template: |
103
- Result: \${{ steps.s.textOutput }}
104
- `;
105
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
106
- assert.ok(spec.completion);
107
- assert.strictEqual(spec.completion.template, "Result: ${{ steps.s.textOutput }}\n");
108
- assert.strictEqual(spec.completion.message, undefined);
109
- });
110
-
111
- it("parses completion with message and include", () => {
112
- const yaml = `
113
- name: test
114
- steps:
115
- s:
116
- agent: a
117
- completion:
118
- message: Present these findings.
119
- include:
120
- - steps.s.textOutput
121
- - steps.s.usage
122
- `;
123
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
124
- assert.ok(spec.completion);
125
- assert.strictEqual(spec.completion.message, "Present these findings.");
126
- assert.deepStrictEqual(spec.completion.include, ["steps.s.textOutput", "steps.s.usage"]);
127
- assert.strictEqual(spec.completion.template, undefined);
128
- });
129
-
130
- it("parses completion with message only (no include)", () => {
131
- const yaml = `
132
- name: test
133
- steps:
134
- s:
135
- agent: a
136
- completion:
137
- message: Just an instruction.
138
- `;
139
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
140
- assert.ok(spec.completion);
141
- assert.strictEqual(spec.completion.message, "Just an instruction.");
142
- assert.strictEqual(spec.completion.include, undefined);
143
- });
144
-
145
- it("throws when completion has both template and message", () => {
146
- const yaml = `
147
- name: test
148
- steps:
149
- s:
150
- agent: a
151
- completion:
152
- template: some template
153
- message: some message
154
- `;
155
- assert.throws(
156
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
157
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("cannot have both"),
158
- );
159
- });
160
-
161
- it("throws when completion has neither template nor message", () => {
162
- const yaml = `
163
- name: test
164
- steps:
165
- s:
166
- agent: a
167
- completion:
168
- include:
169
- - steps.s.textOutput
170
- `;
171
- assert.throws(
172
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
173
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("must have either"),
174
- );
175
- });
176
-
177
- it("throws when completion is not an object", () => {
178
- const yaml = `
179
- name: test
180
- steps:
181
- s:
182
- agent: a
183
- completion: just a string
184
- `;
185
- assert.throws(
186
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
187
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("must be an object"),
188
- );
189
- });
190
-
191
- it("throws when completion.include is not an array", () => {
192
- const yaml = `
193
- name: test
194
- steps:
195
- s:
196
- agent: a
197
- completion:
198
- message: ok
199
- include: not-an-array
200
- `;
201
- assert.throws(
202
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
203
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("array of strings"),
204
- );
205
- });
206
-
207
- it("has no completion when field is absent", () => {
208
- const yaml = `
209
- name: test
210
- steps:
211
- s:
212
- agent: a
213
- `;
214
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
215
- assert.strictEqual(spec.completion, undefined);
216
- });
217
-
218
- // ── Phase 2 step type tests ──
219
-
220
- it("parses gate step", () => {
221
- const yaml = `
222
- name: test
223
- steps:
224
- verify:
225
- gate:
226
- check: npm test
227
- onPass: continue
228
- onFail: fail
229
- `;
230
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
231
- assert.ok(spec.steps.verify.gate);
232
- assert.strictEqual(spec.steps.verify.gate.check, "npm test");
233
- assert.strictEqual(spec.steps.verify.gate.onPass, "continue");
234
- assert.strictEqual(spec.steps.verify.gate.onFail, "fail");
235
- assert.strictEqual(spec.steps.verify.agent, undefined);
236
- });
237
-
238
- it("parses transform step", () => {
239
- const yaml = `
240
- name: test
241
- steps:
242
- prep:
243
- agent: analyzer
244
- combine:
245
- transform:
246
- mapping:
247
- summary: \${{ steps.prep.output.summary }}
248
- count: 42
249
- `;
250
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
251
- assert.ok(spec.steps.combine.transform);
252
- assert.strictEqual(typeof spec.steps.combine.transform.mapping, "object");
253
- assert.strictEqual((spec.steps.combine.transform.mapping as any).count, 42);
254
- assert.strictEqual(spec.steps.combine.agent, undefined);
255
- });
256
-
257
- it("parses loop step", () => {
258
- const yaml = `
259
- name: test
260
- steps:
261
- retry:
262
- loop:
263
- maxAttempts: 3
264
- steps:
265
- attempt:
266
- agent: fixer
267
- check:
268
- gate:
269
- check: npm test
270
- `;
271
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
272
- assert.ok(spec.steps.retry.loop);
273
- assert.strictEqual(spec.steps.retry.loop.maxAttempts, 3);
274
- assert.strictEqual(Object.keys(spec.steps.retry.loop.steps).length, 2);
275
- assert.ok(spec.steps.retry.loop.steps.attempt.agent);
276
- assert.ok(spec.steps.retry.loop.steps.check.gate);
277
- });
278
-
279
- it("rejects step with both agent and gate", () => {
280
- const yaml = `
281
- name: test
282
- steps:
283
- bad:
284
- agent: my-agent
285
- gate:
286
- check: npm test
287
- `;
288
- assert.throws(
289
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
290
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("must have exactly one of"),
291
- );
292
- });
293
-
294
- it("rejects step with no type (no agent, gate, transform, or loop)", () => {
295
- const yaml = `
296
- name: test
297
- steps:
298
- empty:
299
- when: \${{ input.enabled }}
300
- `;
301
- assert.throws(
302
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
303
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("must have exactly one of"),
304
- );
305
- });
306
-
307
- it("rejects step with workflow (not yet supported)", () => {
308
- const yaml = `
309
- name: test
310
- steps:
311
- nested:
312
- workflow: other-workflow
313
- `;
314
- assert.throws(
315
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
316
- (err: unknown) => err instanceof WorkflowSpecError && err.message.includes("not yet supported"),
317
- );
318
- });
319
-
320
- it("parses artifacts", () => {
321
- const yaml = `
322
- name: test
323
- steps:
324
- s:
325
- agent: a
326
- artifacts:
327
- report:
328
- path: ./output/report.md
329
- from: \${{ steps.s.textOutput }}
330
- data:
331
- path: ./output/data.json
332
- from: \${{ steps.s.output }}
333
- schema: ./schemas/data.schema.json
334
- `;
335
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
336
- assert.ok(spec.artifacts);
337
- assert.strictEqual(Object.keys(spec.artifacts).length, 2);
338
- assert.strictEqual(spec.artifacts.report.path, "./output/report.md");
339
- assert.strictEqual(spec.artifacts.report.from, "${{ steps.s.textOutput }}");
340
- assert.strictEqual(spec.artifacts.data.schema, "./schemas/data.schema.json");
341
- });
342
-
343
- it("preserves step order", () => {
344
- const yaml = `
345
- name: test
346
- steps:
347
- third:
348
- agent: c
349
- first:
350
- agent: a
351
- second:
352
- agent: b
353
- `;
354
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
355
- const stepNames = Object.keys(spec.steps);
356
- assert.deepStrictEqual(stepNames, ["third", "first", "second"]);
357
- });
358
-
359
- it("parses parallel step", () => {
360
- const yaml = `
361
- name: test
362
- steps:
363
- both:
364
- parallel:
365
- a:
366
- agent: analyzer-a
367
- b:
368
- agent: analyzer-b
369
- `;
370
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
371
- assert.ok(spec.steps.both.parallel);
372
- assert.ok(spec.steps.both.parallel!.a);
373
- assert.ok(spec.steps.both.parallel!.b);
374
- assert.strictEqual(spec.steps.both.parallel!.a.agent, "analyzer-a");
375
- assert.strictEqual(spec.steps.both.parallel!.b.agent, "analyzer-b");
376
- });
377
-
378
- it("rejects empty parallel step", () => {
379
- const yaml = `
380
- name: test
381
- steps:
382
- both:
383
- parallel: {}
384
- `;
385
- assert.throws(
386
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
387
- (err: any) => err.message.includes("non-empty"),
388
- );
389
- });
390
-
391
- it("rejects step with both agent and parallel", () => {
392
- const yaml = `
393
- name: test
394
- steps:
395
- both:
396
- agent: default
397
- parallel:
398
- a:
399
- agent: default
400
- `;
401
- assert.throws(
402
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
403
- (err: any) => err.message.includes("exactly one"),
404
- );
405
- });
406
-
407
- it("validates sub-steps within parallel", () => {
408
- const yaml = `
409
- name: test
410
- steps:
411
- both:
412
- parallel:
413
- a:
414
- agent: analyzer
415
- b:
416
- notAType: true
417
- `;
418
- assert.throws(
419
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
420
- (err: any) => err.message.includes("exactly one"),
421
- );
422
- });
423
-
424
- // ── forEach and as field tests ──
425
-
426
- it("parses forEach and as fields", () => {
427
- const yaml = `
428
- name: test
429
- steps:
430
- process:
431
- forEach: \${{ input.items }}
432
- as: item
433
- transform:
434
- mapping:
435
- value: \${{ item }}
436
- `;
437
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
438
- assert.strictEqual(spec.steps.process.forEach, "${{ input.items }}");
439
- assert.strictEqual(spec.steps.process.as, "item");
440
- assert.ok(spec.steps.process.transform);
441
- });
442
-
443
- it("parses forEach without as (default)", () => {
444
- const yaml = `
445
- name: test
446
- steps:
447
- process:
448
- forEach: \${{ input.items }}
449
- transform:
450
- mapping:
451
- value: \${{ item }}
452
- `;
453
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
454
- assert.strictEqual(spec.steps.process.forEach, "${{ input.items }}");
455
- assert.strictEqual(spec.steps.process.as, undefined);
456
- });
457
-
458
- it("rejects forEach with non-string value", () => {
459
- const yaml = `
460
- name: test
461
- steps:
462
- process:
463
- forEach: 42
464
- transform:
465
- mapping:
466
- value: test
467
- `;
468
- assert.throws(
469
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
470
- (err: any) => err.message.includes("forEach must be a string"),
471
- );
472
- });
473
-
474
- it("rejects as with non-string value", () => {
475
- const yaml = `
476
- name: test
477
- steps:
478
- process:
479
- forEach: \${{ input.items }}
480
- as: 42
481
- transform:
482
- mapping:
483
- value: test
484
- `;
485
- assert.throws(
486
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
487
- (err: any) => err.message.includes("as must be a string"),
488
- );
489
- });
490
-
491
- // ── command step type tests ──
492
-
493
- it("parses command step", () => {
494
- const yaml = `
495
- name: test
496
- steps:
497
- run:
498
- command: echo hello
499
- `;
500
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
501
- assert.strictEqual(spec.steps.run.command, "echo hello");
502
- assert.strictEqual(spec.steps.run.agent, undefined);
503
- });
504
-
505
- it("parses command step with output format", () => {
506
- const yaml = `
507
- name: test
508
- steps:
509
- run:
510
- command: cat data.json
511
- output:
512
- format: json
513
- `;
514
- const spec = parseWorkflowSpec(yaml, "/t.yaml", "project");
515
- assert.strictEqual(spec.steps.run.command, "cat data.json");
516
- assert.strictEqual(spec.steps.run.output?.format, "json");
517
- });
518
-
519
- it("rejects command with non-string value", () => {
520
- const yaml = `
521
- name: test
522
- steps:
523
- run:
524
- command: 42
525
- `;
526
- assert.throws(
527
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
528
- (err: any) => err.message.includes("command must be a string"),
529
- );
530
- });
531
-
532
- it("rejects command + agent together", () => {
533
- const yaml = `
534
- name: test
535
- steps:
536
- bad:
537
- command: echo hello
538
- agent: my-agent
539
- `;
540
- assert.throws(
541
- () => parseWorkflowSpec(yaml, "/t.yaml", "project"),
542
- (err: any) => err.message.includes("exactly one"),
543
- );
544
- });
545
- });
546
-
547
- describe("self-implement workflow spec", () => {
548
- it("parses self-implement.workflow.yaml successfully", () => {
549
- const content = readFileSync(path.resolve(import.meta.dirname, "..", "workflows", "self-implement.workflow.yaml"), "utf-8");
550
- const spec = parseWorkflowSpec(content, "workflows/self-implement.workflow.yaml", "project");
551
-
552
- assert.strictEqual(spec.name, "self-implement");
553
- assert.strictEqual(spec.version, "1");
554
- assert.ok(spec.input);
555
- assert.deepStrictEqual((spec.input as any).required, ["phaseSpec", "architecture", "conventions"]);
556
-
557
- // Step names
558
- const stepNames = Object.keys(spec.steps);
559
- assert.deepStrictEqual(stepNames, ["plan", "implement", "verify", "check"]);
560
-
561
- // Step types
562
- assert.strictEqual(spec.steps.plan.agent, "plan-decomposer");
563
- assert.strictEqual(spec.steps.implement.agent, "spec-implementer");
564
- assert.strictEqual(spec.steps.implement.forEach, "${{ steps.plan.output.plans }}");
565
- assert.strictEqual(spec.steps.implement.as, "plan");
566
- assert.strictEqual(spec.steps.verify.agent, "verifier");
567
- assert.ok(spec.steps.check.gate);
568
- assert.strictEqual(spec.steps.check.gate!.onFail, "fail");
569
-
570
- // Completion
571
- assert.ok(spec.completion);
572
- assert.ok(spec.completion!.message);
573
- assert.ok(spec.completion!.include);
574
- assert.strictEqual(spec.completion!.include!.length, 2);
575
- });
576
- });