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