@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
package/src/loop.test.ts DELETED
@@ -1,551 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import { executeWorkflow } from "./workflow-executor.ts";
4
- import type { WorkflowSpec } from "./types.ts";
5
- import { mockCtx, mockPi, makeSpec } from "./test-helpers.ts";
6
- import fs from "node:fs";
7
- import path from "node:path";
8
- import os from "node:os";
9
-
10
- function defaultOptions(tmpDir?: string) {
11
- const cwd = tmpDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "wf-loop-"));
12
- return {
13
- ctx: mockCtx(cwd),
14
- pi: mockPi(),
15
- loadAgent: () => ({ name: "default" }),
16
- _cwd: cwd,
17
- };
18
- }
19
-
20
- // Skip integration tests unless RUN_INTEGRATION=1 and pi is available
21
- let hasPi = false;
22
- if (process.env.RUN_INTEGRATION === "1") {
23
- try {
24
- const { execSync } = await import("node:child_process");
25
- execSync("pi --version", { stdio: "ignore" });
26
- hasPi = true;
27
- } catch {}
28
- }
29
-
30
- describe("loop steps", () => {
31
- it("breaks on gate pass", async (t: any) => {
32
- const spec = makeSpec({
33
- steps: {
34
- retry: {
35
- loop: {
36
- maxAttempts: 3,
37
- steps: {
38
- check: {
39
- gate: {
40
- check: "echo pass",
41
- onPass: "break",
42
- onFail: "continue",
43
- },
44
- },
45
- },
46
- },
47
- },
48
- },
49
- });
50
-
51
- const opts = defaultOptions();
52
-
53
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
54
-
55
- const result = await executeWorkflow(spec, {}, opts);
56
- assert.strictEqual(result.status, "completed");
57
- assert.strictEqual(result.steps.retry.output.iterations, 1); // broke on first pass
58
- });
59
-
60
- it("retries on gate fail and exhausts", async (t: any) => {
61
- const spec = makeSpec({
62
- steps: {
63
- retry: {
64
- loop: {
65
- maxAttempts: 2,
66
- steps: {
67
- check: {
68
- gate: {
69
- check: "exit 1",
70
- onPass: "break",
71
- onFail: "continue",
72
- },
73
- },
74
- },
75
- },
76
- },
77
- },
78
- });
79
-
80
- const opts = defaultOptions();
81
-
82
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
83
-
84
- const result = await executeWorkflow(spec, {}, opts);
85
- assert.strictEqual(result.steps.retry.status, "failed");
86
- assert.strictEqual(result.steps.retry.output.iterations, 2);
87
- });
88
-
89
- it("accumulates prior attempts in loop scope", async (t: any) => {
90
- // This test verifies that the loop scope includes priorAttempts.
91
- // We use a transform step inside the loop to capture the iteration count.
92
- const spec = makeSpec({
93
- steps: {
94
- retry: {
95
- loop: {
96
- maxAttempts: 3,
97
- steps: {
98
- capture: {
99
- transform: {
100
- mapping: {
101
- iteration: "${{ loop.iteration }}",
102
- priorCount: "${{ loop.priorAttempts.length }}",
103
- },
104
- },
105
- },
106
- check: {
107
- gate: {
108
- check: "exit 1",
109
- onPass: "break",
110
- onFail: "continue",
111
- },
112
- },
113
- },
114
- },
115
- },
116
- },
117
- });
118
-
119
- const opts = defaultOptions();
120
-
121
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
122
-
123
- const result = await executeWorkflow(spec, {}, opts);
124
- const attempts = result.steps.retry.output.attempts;
125
-
126
- // First iteration: iteration=0, priorCount=0
127
- assert.strictEqual(attempts[0].steps.capture.output.iteration, 0);
128
-
129
- // Second iteration: iteration=1, priorCount=1
130
- assert.strictEqual(attempts[1].steps.capture.output.iteration, 1);
131
- });
132
-
133
- it("runs onExhausted when all attempts fail", { skip: !hasPi ? "pi not available" : undefined, timeout: 60000 }, async (t: any) => {
134
- const spec = makeSpec({
135
- steps: {
136
- retry: {
137
- loop: {
138
- maxAttempts: 2,
139
- steps: {
140
- check: {
141
- gate: { check: "exit 1", onFail: "continue" },
142
- },
143
- },
144
- onExhausted: {
145
- agent: "default",
146
- },
147
- },
148
- },
149
- },
150
- });
151
-
152
- const opts = defaultOptions();
153
-
154
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
155
-
156
- const result = await executeWorkflow(spec, {}, opts);
157
- assert.ok(result.steps.retry.output.lastIteration._exhausted);
158
- });
159
-
160
- it("agent step inside loop with retry", { skip: !hasPi ? "pi not available" : undefined, timeout: 120000 }, async (t: any) => {
161
- // Agent runs, gate fails, agent retries with priorAttempts context
162
- const spec = makeSpec({
163
- steps: {
164
- retry: {
165
- loop: {
166
- maxAttempts: 2,
167
- steps: {
168
- work: {
169
- agent: "default",
170
- input: {
171
- attempt: "${{ loop.iteration }}",
172
- },
173
- },
174
- check: {
175
- gate: {
176
- check: "exit 1",
177
- onPass: "break",
178
- onFail: "continue",
179
- },
180
- },
181
- },
182
- },
183
- },
184
- },
185
- });
186
-
187
- const opts = defaultOptions();
188
-
189
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
190
-
191
- const result = await executeWorkflow(spec, {}, opts);
192
- assert.strictEqual(result.steps.retry.output.iterations, 2);
193
- // Both iterations should have a 'work' step that completed
194
- assert.strictEqual(result.steps.retry.output.attempts[0].steps.work.status, "completed");
195
- assert.strictEqual(result.steps.retry.output.attempts[1].steps.work.status, "completed");
196
- });
197
-
198
- it("gate onFail: fail stops the loop", async (t: any) => {
199
- const spec = makeSpec({
200
- steps: {
201
- retry: {
202
- loop: {
203
- maxAttempts: 3,
204
- steps: {
205
- check: {
206
- gate: {
207
- check: "exit 1",
208
- onFail: "fail",
209
- },
210
- },
211
- },
212
- },
213
- },
214
- },
215
- });
216
-
217
- const opts = defaultOptions();
218
-
219
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
220
-
221
- const result = await executeWorkflow(spec, {}, opts);
222
- assert.strictEqual(result.status, "failed");
223
- assert.strictEqual(result.steps.retry.status, "failed");
224
- assert.strictEqual(result.steps.retry.output.iterations, 1); // stopped after first iteration
225
- });
226
-
227
- it("gate onFail: break stops the loop without marking failed", async (t: any) => {
228
- const spec = makeSpec({
229
- steps: {
230
- retry: {
231
- loop: {
232
- maxAttempts: 3,
233
- steps: {
234
- check: {
235
- gate: {
236
- check: "exit 1",
237
- onFail: "break",
238
- },
239
- },
240
- },
241
- },
242
- },
243
- },
244
- });
245
-
246
- const opts = defaultOptions();
247
-
248
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
249
-
250
- const result = await executeWorkflow(spec, {}, opts);
251
- // onFail: break stops the loop but loopStatus remains "failed" (no gate passed)
252
- assert.strictEqual(result.steps.retry.status, "failed");
253
- assert.strictEqual(result.steps.retry.output.iterations, 1);
254
- });
255
-
256
- it("transform step inside loop", async (t: any) => {
257
- const spec = makeSpec({
258
- steps: {
259
- retry: {
260
- loop: {
261
- maxAttempts: 2,
262
- steps: {
263
- compute: {
264
- transform: {
265
- mapping: {
266
- value: "${{ loop.iteration }}",
267
- },
268
- },
269
- },
270
- check: {
271
- gate: {
272
- check: "exit 1",
273
- onPass: "break",
274
- onFail: "continue",
275
- },
276
- },
277
- },
278
- },
279
- },
280
- },
281
- });
282
-
283
- const opts = defaultOptions();
284
-
285
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
286
-
287
- const result = await executeWorkflow(spec, {}, opts);
288
- const attempts = result.steps.retry.output.attempts;
289
-
290
- // First iteration: value=0
291
- assert.strictEqual(attempts[0].steps.compute.output.value, 0);
292
- assert.strictEqual(attempts[0].steps.compute.status, "completed");
293
-
294
- // Second iteration: value=1
295
- assert.strictEqual(attempts[1].steps.compute.output.value, 1);
296
- });
297
-
298
- it("loop aggregates usage across iterations", async (t: any) => {
299
- const spec = makeSpec({
300
- steps: {
301
- retry: {
302
- loop: {
303
- maxAttempts: 3,
304
- steps: {
305
- check: {
306
- gate: {
307
- check: "exit 1",
308
- onPass: "break",
309
- onFail: "continue",
310
- },
311
- },
312
- },
313
- },
314
- },
315
- },
316
- });
317
-
318
- const opts = defaultOptions();
319
-
320
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
321
-
322
- const result = await executeWorkflow(spec, {}, opts);
323
- // Gates have zero usage, but the aggregation should still work
324
- assert.strictEqual(result.steps.retry.usage.cost, 0);
325
- assert.strictEqual(result.steps.retry.usage.turns, 0);
326
- });
327
-
328
- it("when conditional skips sub-steps inside loop", async (t: any) => {
329
- const spec = makeSpec({
330
- steps: {
331
- retry: {
332
- loop: {
333
- maxAttempts: 2,
334
- steps: {
335
- skipped: {
336
- when: "${{ loop.iteration == 99 }}",
337
- transform: {
338
- mapping: { value: "should not appear" },
339
- },
340
- },
341
- check: {
342
- gate: {
343
- check: "exit 1",
344
- onPass: "break",
345
- onFail: "continue",
346
- },
347
- },
348
- },
349
- },
350
- },
351
- },
352
- });
353
-
354
- const opts = defaultOptions();
355
-
356
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
357
-
358
- const result = await executeWorkflow(spec, {}, opts);
359
- const attempts = result.steps.retry.output.attempts;
360
-
361
- // Both iterations should have skipped the transform step
362
- assert.strictEqual(attempts[0].steps.skipped.status, "skipped");
363
- assert.strictEqual(attempts[1].steps.skipped.status, "skipped");
364
- });
365
-
366
- it("loop with dynamic attempts via expression", async (t: any) => {
367
- const spec = makeSpec({
368
- input: {
369
- type: "object",
370
- properties: { maxRetries: { type: "number" } },
371
- },
372
- steps: {
373
- retry: {
374
- loop: {
375
- maxAttempts: 5, // fallback
376
- attempts: "${{ input.maxRetries }}",
377
- steps: {
378
- check: {
379
- gate: {
380
- check: "exit 1",
381
- onPass: "break",
382
- onFail: "continue",
383
- },
384
- },
385
- },
386
- },
387
- },
388
- },
389
- });
390
-
391
- const opts = defaultOptions();
392
-
393
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
394
-
395
- const result = await executeWorkflow(spec, { maxRetries: 2 }, opts);
396
- assert.strictEqual(result.steps.retry.output.iterations, 2);
397
- assert.strictEqual(result.steps.retry.output.maxAttempts, 2);
398
- });
399
-
400
- it("loop followed by regular step", async (t: any) => {
401
- const spec = makeSpec({
402
- steps: {
403
- retry: {
404
- loop: {
405
- maxAttempts: 2,
406
- steps: {
407
- check: {
408
- gate: {
409
- check: "echo pass",
410
- onPass: "break",
411
- onFail: "continue",
412
- },
413
- },
414
- },
415
- },
416
- },
417
- after: {
418
- transform: {
419
- mapping: { completed: true },
420
- },
421
- },
422
- },
423
- });
424
-
425
- const opts = defaultOptions();
426
-
427
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
428
-
429
- const result = await executeWorkflow(spec, {}, opts);
430
- assert.strictEqual(result.status, "completed");
431
- assert.strictEqual(result.steps.retry.status, "completed");
432
- assert.strictEqual(result.steps.after.status, "completed");
433
- assert.deepStrictEqual(result.steps.after.output, { completed: true });
434
- });
435
-
436
- it("failed loop prevents subsequent steps", async (t: any) => {
437
- const spec = makeSpec({
438
- steps: {
439
- retry: {
440
- loop: {
441
- maxAttempts: 1,
442
- steps: {
443
- check: {
444
- gate: {
445
- check: "exit 1",
446
- onFail: "continue",
447
- },
448
- },
449
- },
450
- },
451
- },
452
- after: {
453
- transform: {
454
- mapping: { shouldNotRun: true },
455
- },
456
- },
457
- },
458
- });
459
-
460
- const opts = defaultOptions();
461
-
462
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
463
-
464
- const result = await executeWorkflow(spec, {}, opts);
465
- assert.strictEqual(result.status, "failed");
466
- assert.strictEqual(result.steps.retry.status, "failed");
467
- assert.ok(!result.steps.after); // never executed
468
- });
469
-
470
- it("current iteration sub-steps visible to later sub-steps", async (t: any) => {
471
- const spec = makeSpec({
472
- steps: {
473
- retry: {
474
- loop: {
475
- maxAttempts: 1,
476
- steps: {
477
- first: {
478
- transform: {
479
- mapping: { value: 42 },
480
- },
481
- },
482
- second: {
483
- transform: {
484
- mapping: {
485
- fromFirst: "${{ steps.first.output.value }}",
486
- },
487
- },
488
- },
489
- check: {
490
- gate: {
491
- check: "echo pass",
492
- onPass: "break",
493
- },
494
- },
495
- },
496
- },
497
- },
498
- },
499
- });
500
-
501
- const opts = defaultOptions();
502
-
503
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
504
-
505
- const result = await executeWorkflow(spec, {}, opts);
506
- assert.strictEqual(result.status, "completed");
507
- const attempts = result.steps.retry.output.attempts;
508
- assert.strictEqual(attempts[0].steps.second.output.fromFirst, 42);
509
- });
510
-
511
- it("outer steps accessible from inside loop", async (t: any) => {
512
- const spec = makeSpec({
513
- steps: {
514
- setup: {
515
- transform: {
516
- mapping: { context: "from-outer" },
517
- },
518
- },
519
- retry: {
520
- loop: {
521
- maxAttempts: 1,
522
- steps: {
523
- capture: {
524
- transform: {
525
- mapping: {
526
- outerValue: "${{ steps.setup.output.context }}",
527
- },
528
- },
529
- },
530
- check: {
531
- gate: {
532
- check: "echo pass",
533
- onPass: "break",
534
- },
535
- },
536
- },
537
- },
538
- },
539
- },
540
- });
541
-
542
- const opts = defaultOptions();
543
-
544
- t.after(() => { fs.rmSync(path.dirname(spec.filePath), { recursive: true, force: true }); fs.rmSync(opts._cwd, { recursive: true, force: true }); });
545
-
546
- const result = await executeWorkflow(spec, {}, opts);
547
- assert.strictEqual(result.status, "completed");
548
- const attempts = result.steps.retry.output.attempts;
549
- assert.strictEqual(attempts[0].steps.capture.output.outerValue, "from-outer");
550
- });
551
- });