@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.
- package/CHANGELOG.md +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- 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
|
-
});
|