@calmo/task-runner 1.2.3 → 3.0.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 (87) hide show
  1. package/.jules/sentinel.md +4 -0
  2. package/AGENTS.md +53 -1
  3. package/CHANGELOG.md +41 -15
  4. package/README.md +1 -1
  5. package/coverage/coverage-final.json +8 -4
  6. package/coverage/index.html +24 -9
  7. package/coverage/lcov-report/index.html +24 -9
  8. package/coverage/lcov-report/src/EventBus.ts.html +8 -8
  9. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +143 -62
  10. package/coverage/lcov-report/src/TaskRunner.ts.html +213 -21
  11. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +313 -0
  12. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +124 -0
  13. package/coverage/lcov-report/src/TaskStateManager.ts.html +511 -0
  14. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +125 -137
  15. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  16. package/coverage/lcov-report/src/contracts/index.html +1 -1
  17. package/coverage/lcov-report/src/index.html +59 -14
  18. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  19. package/coverage/lcov-report/src/strategies/index.html +116 -0
  20. package/coverage/lcov.info +383 -184
  21. package/coverage/src/EventBus.ts.html +8 -8
  22. package/coverage/src/TaskGraphValidator.ts.html +143 -62
  23. package/coverage/src/TaskRunner.ts.html +213 -21
  24. package/coverage/src/TaskRunnerBuilder.ts.html +313 -0
  25. package/coverage/src/TaskRunnerExecutionConfig.ts.html +124 -0
  26. package/coverage/src/TaskStateManager.ts.html +511 -0
  27. package/coverage/src/WorkflowExecutor.ts.html +125 -137
  28. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  29. package/coverage/src/contracts/index.html +1 -1
  30. package/coverage/src/index.html +59 -14
  31. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  32. package/coverage/src/strategies/index.html +116 -0
  33. package/dist/TaskGraphValidator.js +39 -16
  34. package/dist/TaskGraphValidator.js.map +1 -1
  35. package/dist/TaskRunner.d.ts +12 -2
  36. package/dist/TaskRunner.js +55 -3
  37. package/dist/TaskRunner.js.map +1 -1
  38. package/dist/TaskRunnerBuilder.d.ts +33 -0
  39. package/dist/TaskRunnerBuilder.js +54 -0
  40. package/dist/TaskRunnerBuilder.js.map +1 -0
  41. package/dist/TaskRunnerExecutionConfig.d.ts +13 -0
  42. package/dist/TaskRunnerExecutionConfig.js +2 -0
  43. package/dist/TaskRunnerExecutionConfig.js.map +1 -0
  44. package/dist/TaskStateManager.d.ts +54 -0
  45. package/dist/TaskStateManager.js +130 -0
  46. package/dist/TaskStateManager.js.map +1 -0
  47. package/dist/TaskStatus.d.ts +1 -1
  48. package/dist/TaskStep.d.ts +2 -1
  49. package/dist/WorkflowExecutor.d.ts +11 -10
  50. package/dist/WorkflowExecutor.js +64 -73
  51. package/dist/WorkflowExecutor.js.map +1 -1
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +3 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/strategies/IExecutionStrategy.d.ts +16 -0
  56. package/dist/strategies/IExecutionStrategy.js +2 -0
  57. package/dist/strategies/IExecutionStrategy.js.map +1 -0
  58. package/dist/strategies/StandardExecutionStrategy.d.ts +9 -0
  59. package/dist/strategies/StandardExecutionStrategy.js +25 -0
  60. package/dist/strategies/StandardExecutionStrategy.js.map +1 -0
  61. package/openspec/changes/add-concurrency-control/proposal.md +14 -0
  62. package/openspec/changes/add-concurrency-control/tasks.md +9 -0
  63. package/openspec/changes/add-task-retry-policy/proposal.md +13 -0
  64. package/openspec/changes/add-task-retry-policy/tasks.md +10 -0
  65. package/openspec/changes/add-workflow-preview/proposal.md +12 -0
  66. package/openspec/changes/add-workflow-preview/tasks.md +7 -0
  67. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +10 -0
  68. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +18 -0
  69. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +17 -0
  70. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +14 -0
  71. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +8 -0
  72. package/package.json +1 -1
  73. package/src/TaskGraphValidator.ts +46 -19
  74. package/src/TaskRunner.ts +68 -4
  75. package/src/TaskRunnerBuilder.ts +76 -0
  76. package/src/TaskRunnerExecutionConfig.ts +13 -0
  77. package/src/TaskStateManager.ts +142 -0
  78. package/src/TaskStatus.ts +1 -1
  79. package/src/TaskStep.ts +2 -1
  80. package/src/WorkflowExecutor.ts +75 -79
  81. package/src/index.ts +4 -0
  82. package/src/strategies/IExecutionStrategy.ts +21 -0
  83. package/src/strategies/StandardExecutionStrategy.ts +35 -0
  84. package/test-report.xml +121 -43
  85. package/GEMINI.md +0 -46
  86. package/openspec/changes/add-external-task-cancellation/tasks.md +0 -10
  87. /package/openspec/changes/{add-external-task-cancellation → archive/2026-01-18-add-external-task-cancellation}/proposal.md +0 -0
@@ -25,28 +25,28 @@
25
25
  <div class='fl pad1y space-right2'>
26
26
  <span class="strong">100% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>48/48</span>
28
+ <span class='fraction'>38/38</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
33
  <span class="strong">100% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>14/14</span>
35
+ <span class='fraction'>12/12</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
40
  <span class="strong">100% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>5/5</span>
42
+ <span class='fraction'>6/6</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
47
  <span class="strong">100% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>48/48</span>
49
+ <span class='fraction'>38/38</span>
50
50
  </div>
51
51
 
52
52
 
@@ -183,11 +183,7 @@
183
183
  <a name='L118'></a><a href='#L118'>118</a>
184
184
  <a name='L119'></a><a href='#L119'>119</a>
185
185
  <a name='L120'></a><a href='#L120'>120</a>
186
- <a name='L121'></a><a href='#L121'>121</a>
187
- <a name='L122'></a><a href='#L122'>122</a>
188
- <a name='L123'></a><a href='#L123'>123</a>
189
- <a name='L124'></a><a href='#L124'>124</a>
190
- <a name='L125'></a><a href='#L125'>125</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
186
+ <a name='L121'></a><a href='#L121'>121</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
191
187
  <span class="cline-any cline-neutral">&nbsp;</span>
192
188
  <span class="cline-any cline-neutral">&nbsp;</span>
193
189
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -196,96 +192,88 @@
196
192
  <span class="cline-any cline-neutral">&nbsp;</span>
197
193
  <span class="cline-any cline-neutral">&nbsp;</span>
198
194
  <span class="cline-any cline-neutral">&nbsp;</span>
199
- <span class="cline-any cline-yes">18x</span>
200
195
  <span class="cline-any cline-neutral">&nbsp;</span>
201
196
  <span class="cline-any cline-neutral">&nbsp;</span>
202
197
  <span class="cline-any cline-neutral">&nbsp;</span>
203
198
  <span class="cline-any cline-neutral">&nbsp;</span>
204
199
  <span class="cline-any cline-neutral">&nbsp;</span>
205
200
  <span class="cline-any cline-neutral">&nbsp;</span>
206
- <span class="cline-any cline-yes">18x</span>
207
- <span class="cline-any cline-yes">18x</span>
208
201
  <span class="cline-any cline-neutral">&nbsp;</span>
209
202
  <span class="cline-any cline-neutral">&nbsp;</span>
210
203
  <span class="cline-any cline-neutral">&nbsp;</span>
204
+ <span class="cline-any cline-yes">41x</span>
205
+ <span class="cline-any cline-yes">41x</span>
206
+ <span class="cline-any cline-yes">41x</span>
207
+ <span class="cline-any cline-yes">41x</span>
211
208
  <span class="cline-any cline-neutral">&nbsp;</span>
212
209
  <span class="cline-any cline-neutral">&nbsp;</span>
213
210
  <span class="cline-any cline-neutral">&nbsp;</span>
214
211
  <span class="cline-any cline-neutral">&nbsp;</span>
215
212
  <span class="cline-any cline-neutral">&nbsp;</span>
216
- <span class="cline-any cline-yes">18x</span>
217
213
  <span class="cline-any cline-neutral">&nbsp;</span>
218
- <span class="cline-any cline-yes">18x</span>
219
- <span class="cline-any cline-yes">18x</span>
220
- <span class="cline-any cline-yes">18x</span>
221
214
  <span class="cline-any cline-neutral">&nbsp;</span>
222
215
  <span class="cline-any cline-neutral">&nbsp;</span>
223
- <span class="cline-any cline-yes">18x</span>
224
216
  <span class="cline-any cline-neutral">&nbsp;</span>
225
- <span class="cline-any cline-yes">18x</span>
226
217
  <span class="cline-any cline-neutral">&nbsp;</span>
227
- <span class="cline-any cline-yes">25x</span>
228
218
  <span class="cline-any cline-neutral">&nbsp;</span>
229
- <span class="cline-any cline-yes">25x</span>
230
219
  <span class="cline-any cline-neutral">&nbsp;</span>
220
+ <span class="cline-any cline-yes">41x</span>
221
+ <span class="cline-any cline-yes">41x</span>
231
222
  <span class="cline-any cline-neutral">&nbsp;</span>
232
- <span class="cline-any cline-yes">18x</span>
233
- <span class="cline-any cline-yes">18x</span>
234
223
  <span class="cline-any cline-neutral">&nbsp;</span>
224
+ <span class="cline-any cline-yes">41x</span>
225
+ <span class="cline-any cline-yes">2x</span>
226
+ <span class="cline-any cline-yes">2x</span>
227
+ <span class="cline-any cline-yes">2x</span>
228
+ <span class="cline-any cline-yes">2x</span>
235
229
  <span class="cline-any cline-neutral">&nbsp;</span>
236
230
  <span class="cline-any cline-neutral">&nbsp;</span>
231
+ <span class="cline-any cline-yes">39x</span>
237
232
  <span class="cline-any cline-neutral">&nbsp;</span>
233
+ <span class="cline-any cline-yes">39x</span>
238
234
  <span class="cline-any cline-neutral">&nbsp;</span>
235
+ <span class="cline-any cline-yes">9x</span>
239
236
  <span class="cline-any cline-neutral">&nbsp;</span>
240
237
  <span class="cline-any cline-neutral">&nbsp;</span>
238
+ <span class="cline-any cline-yes">39x</span>
239
+ <span class="cline-any cline-yes">9x</span>
241
240
  <span class="cline-any cline-neutral">&nbsp;</span>
242
241
  <span class="cline-any cline-neutral">&nbsp;</span>
242
+ <span class="cline-any cline-yes">39x</span>
243
243
  <span class="cline-any cline-neutral">&nbsp;</span>
244
+ <span class="cline-any cline-yes">39x</span>
244
245
  <span class="cline-any cline-neutral">&nbsp;</span>
245
- <span class="cline-any cline-yes">43x</span>
246
- <span class="cline-any cline-yes">43x</span>
246
+ <span class="cline-any cline-yes">39x</span>
247
247
  <span class="cline-any cline-neutral">&nbsp;</span>
248
- <span class="cline-any cline-yes">43x</span>
249
- <span class="cline-any cline-yes">57x</span>
250
- <span class="cline-any cline-yes">57x</span>
251
248
  <span class="cline-any cline-neutral">&nbsp;</span>
252
249
  <span class="cline-any cline-neutral">&nbsp;</span>
253
- <span class="cline-any cline-yes">57x</span>
254
- <span class="cline-any cline-yes">43x</span>
255
- <span class="cline-any cline-yes">43x</span>
256
250
  <span class="cline-any cline-neutral">&nbsp;</span>
257
- <span class="cline-any cline-yes">23x</span>
258
- <span class="cline-any cline-yes">20x</span>
259
- <span class="cline-any cline-yes">7x</span>
260
- <span class="cline-any cline-yes">7x</span>
261
251
  <span class="cline-any cline-neutral">&nbsp;</span>
262
252
  <span class="cline-any cline-neutral">&nbsp;</span>
253
+ <span class="cline-any cline-yes">75x</span>
254
+ <span class="cline-any cline-yes">1x</span>
263
255
  <span class="cline-any cline-neutral">&nbsp;</span>
264
- <span class="cline-any cline-yes">57x</span>
265
- <span class="cline-any cline-yes">7x</span>
266
256
  <span class="cline-any cline-neutral">&nbsp;</span>
257
+ <span class="cline-any cline-yes">74x</span>
267
258
  <span class="cline-any cline-neutral">&nbsp;</span>
268
259
  <span class="cline-any cline-neutral">&nbsp;</span>
269
- <span class="cline-any cline-yes">7x</span>
270
- <span class="cline-any cline-yes">7x</span>
271
- <span class="cline-any cline-yes">7x</span>
272
- <span class="cline-any cline-yes">50x</span>
273
- <span class="cline-any cline-yes">31x</span>
274
- <span class="cline-any cline-yes">31x</span>
260
+ <span class="cline-any cline-yes">74x</span>
261
+ <span class="cline-any cline-yes">9x</span>
275
262
  <span class="cline-any cline-neutral">&nbsp;</span>
276
263
  <span class="cline-any cline-neutral">&nbsp;</span>
264
+ <span class="cline-any cline-yes">65x</span>
277
265
  <span class="cline-any cline-neutral">&nbsp;</span>
278
266
  <span class="cline-any cline-neutral">&nbsp;</span>
279
- <span class="cline-any cline-yes">43x</span>
280
- <span class="cline-any cline-yes">38x</span>
281
267
  <span class="cline-any cline-neutral">&nbsp;</span>
282
268
  <span class="cline-any cline-neutral">&nbsp;</span>
269
+ <span class="cline-any cline-yes">39x</span>
283
270
  <span class="cline-any cline-neutral">&nbsp;</span>
284
- <span class="cline-any cline-yes">43x</span>
285
- <span class="cline-any cline-yes">31x</span>
286
- <span class="cline-any cline-yes">31x</span>
271
+ <span class="cline-any cline-yes">39x</span>
272
+ <span class="cline-any cline-yes">39x</span>
273
+ <span class="cline-any cline-yes">39x</span>
287
274
  <span class="cline-any cline-neutral">&nbsp;</span>
288
- <span class="cline-any cline-yes">31x</span>
275
+ <span class="cline-any cline-yes">39x</span>
276
+ <span class="cline-any cline-yes">9x</span>
289
277
  <span class="cline-any cline-neutral">&nbsp;</span>
290
278
  <span class="cline-any cline-neutral">&nbsp;</span>
291
279
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -293,145 +281,145 @@
293
281
  <span class="cline-any cline-neutral">&nbsp;</span>
294
282
  <span class="cline-any cline-neutral">&nbsp;</span>
295
283
  <span class="cline-any cline-neutral">&nbsp;</span>
296
- <span class="cline-any cline-yes">31x</span>
297
- <span class="cline-any cline-yes">31x</span>
298
284
  <span class="cline-any cline-neutral">&nbsp;</span>
299
- <span class="cline-any cline-yes">31x</span>
300
- <span class="cline-any cline-yes">31x</span>
301
- <span class="cline-any cline-yes">29x</span>
302
285
  <span class="cline-any cline-neutral">&nbsp;</span>
303
- <span class="cline-any cline-yes">2x</span>
304
286
  <span class="cline-any cline-neutral">&nbsp;</span>
305
287
  <span class="cline-any cline-neutral">&nbsp;</span>
288
+ <span class="cline-any cline-yes">104x</span>
289
+ <span class="cline-any cline-neutral">&nbsp;</span>
290
+ <span class="cline-any cline-neutral">&nbsp;</span>
291
+ <span class="cline-any cline-yes">104x</span>
292
+ <span class="cline-any cline-yes">101x</span>
293
+ <span class="cline-any cline-neutral">&nbsp;</span>
294
+ <span class="cline-any cline-yes">101x</span>
295
+ <span class="cline-any cline-neutral">&nbsp;</span>
296
+ <span class="cline-any cline-yes">101x</span>
297
+ <span class="cline-any cline-neutral">&nbsp;</span>
306
298
  <span class="cline-any cline-neutral">&nbsp;</span>
299
+ <span class="cline-any cline-yes">101x</span>
307
300
  <span class="cline-any cline-neutral">&nbsp;</span>
308
- <span class="cline-any cline-yes">31x</span>
309
- <span class="cline-any cline-yes">31x</span>
310
- <span class="cline-any cline-yes">31x</span>
301
+ <span class="cline-any cline-neutral">&nbsp;</span>
302
+ <span class="cline-any cline-yes">101x</span>
311
303
  <span class="cline-any cline-neutral">&nbsp;</span>
312
304
  <span class="cline-any cline-neutral">&nbsp;</span>
313
305
  <span class="cline-any cline-neutral">&nbsp;</span>
314
306
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { TaskStep } from "./TaskStep.js";
315
307
  import { TaskResult } from "./TaskResult.js";
316
308
  import { EventBus } from "./EventBus.js";
309
+ import { TaskStateManager } from "./TaskStateManager.js";
310
+ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
317
311
  &nbsp;
318
312
  /**
319
313
  * Handles the execution of the workflow steps.
320
314
  * @template TContext The shape of the shared context object.
321
315
  */
322
316
  export class WorkflowExecutor&lt;TContext&gt; {
323
- private running = new Set&lt;string&gt;();
324
- &nbsp;
325
317
  /**
326
318
  * @param context The shared context object.
327
319
  * @param eventBus The event bus to emit events.
320
+ * @param stateManager Manages execution state.
321
+ * @param strategy Execution strategy.
328
322
  */
329
323
  constructor(
330
324
  private context: TContext,
331
- private eventBus: EventBus&lt;TContext&gt;
325
+ private eventBus: EventBus&lt;TContext&gt;,
326
+ private stateManager: TaskStateManager&lt;TContext&gt;,
327
+ private strategy: IExecutionStrategy&lt;TContext&gt;
332
328
  ) {}
333
329
  &nbsp;
334
330
  /**
335
331
  * Executes the given steps.
336
332
  * @param steps The list of steps to execute.
333
+ * @param signal Optional AbortSignal for cancellation.
337
334
  * @returns A Promise that resolves to a map of task results.
338
335
  */
339
- async execute(steps: TaskStep&lt;TContext&gt;[]): Promise&lt;Map&lt;string, TaskResult&gt;&gt; {
336
+ async execute(
337
+ steps: TaskStep&lt;TContext&gt;[],
338
+ signal?: AbortSignal
339
+ ): Promise&lt;Map&lt;string, TaskResult&gt;&gt; {
340
340
  this.eventBus.emit("workflowStart", { context: this.context, steps });
341
+ this.stateManager.initialize(steps);
342
+ &nbsp;
343
+ // Check if already aborted
344
+ if (signal?.aborted) {
345
+ this.stateManager.cancelAllPending("Workflow cancelled before execution started.");
346
+ const results = this.stateManager.getResults();
347
+ this.eventBus.emit("workflowEnd", { context: this.context, results });
348
+ return results;
349
+ }
341
350
  &nbsp;
342
- const results = new Map&lt;string, TaskResult&gt;();
343
351
  const executingPromises = new Set&lt;Promise&lt;void&gt;&gt;();
344
- const pendingSteps = new Set(steps);
345
352
  &nbsp;
346
- // Initial pass
347
- this.processQueue(pendingSteps, results, executingPromises);
353
+ const onAbort = () =&gt; {
354
+ // Mark all pending tasks as cancelled
355
+ this.stateManager.cancelAllPending("Workflow cancelled.");
356
+ };
348
357
  &nbsp;
349
- while (results.size &lt; steps.length &amp;&amp; executingPromises.size &gt; 0) {
350
- // Wait for the next task to finish
351
- await Promise.race(executingPromises);
352
- // After a task finishes, check for new work
353
- this.processQueue(pendingSteps, results, executingPromises);
358
+ if (signal) {
359
+ signal.addEventListener("abort", onAbort);
354
360
  }
355
361
  &nbsp;
356
- this.eventBus.emit("workflowEnd", { context: this.context, results });
357
- return results;
358
- }
359
- &nbsp;
360
- /**
361
- * Logic to identify tasks that can be started or must be skipped.
362
- * Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
363
- */
364
- private processQueue(
365
- pendingSteps: Set&lt;TaskStep&lt;TContext&gt;&gt;,
366
- results: Map&lt;string, TaskResult&gt;,
367
- executingPromises: Set&lt;Promise&lt;void&gt;&gt;
368
- ): void {
369
- const toRemove: TaskStep&lt;TContext&gt;[] = [];
370
- const toRun: TaskStep&lt;TContext&gt;[] = [];
371
- &nbsp;
372
- for (const step of pendingSteps) {
373
- const deps = step.dependencies ?? [];
374
- let blocked = false;
375
- let failedDep: string | undefined;
362
+ try {
363
+ // Initial pass
364
+ this.processLoop(executingPromises, signal);
376
365
  &nbsp;
377
- for (const dep of deps) {
378
- const depResult = results.get(dep);
379
- if (!depResult) {
380
- // Dependency not finished yet
381
- blocked = true;
382
- } else if (depResult.status !== "success") {
383
- failedDep = dep;
366
+ while (
367
+ this.stateManager.hasPendingTasks() ||
368
+ this.stateManager.hasRunningTasks()
369
+ ) {
370
+ // Safety check: if no tasks are running and we still have pending tasks,
371
+ // it means we are stuck (e.g. cycle or unhandled dependency).
372
+ // Since valid graphs shouldn't have this, we break to avoid infinite loop.
373
+ if (executingPromises.size === 0) {
384
374
  break;
375
+ } else {
376
+ // Wait for the next task to finish
377
+ await Promise.race(executingPromises);
385
378
  }
386
- }
387
379
  &nbsp;
388
- if (failedDep) {
389
- const result: TaskResult = {
390
- status: "skipped",
391
- message: `Skipped due to failed dependency: ${failedDep}`,
392
- };
393
- results.set(step.name, result);
394
- this.eventBus.emit("taskSkipped", { step, result });
395
- toRemove.push(step);
396
- } else if (!blocked) {
397
- toRun.push(step);
398
- toRemove.push(step);
380
+ if (signal?.aborted) {
381
+ this.stateManager.cancelAllPending("Workflow cancelled.");
382
+ } else {
383
+ // After a task finishes, check for new work
384
+ this.processLoop(executingPromises, signal);
385
+ }
399
386
  }
400
- }
401
387
  &nbsp;
402
- // Cleanup pending set
403
- for (const step of toRemove) {
404
- pendingSteps.delete(step);
405
- }
388
+ // Ensure everything is accounted for (e.g. if loop exited early)
389
+ this.stateManager.cancelAllPending("Workflow cancelled.");
406
390
  &nbsp;
407
- // Execute ready tasks
408
- for (const step of toRun) {
409
- const taskPromise = this.runStep(step, results).then(() =&gt; {
410
- executingPromises.delete(taskPromise);
411
- });
412
- executingPromises.add(taskPromise);
391
+ const results = this.stateManager.getResults();
392
+ this.eventBus.emit("workflowEnd", { context: this.context, results });
393
+ return results;
394
+ } finally {
395
+ if (signal) {
396
+ signal.removeEventListener("abort", onAbort);
397
+ }
413
398
  }
414
399
  }
415
400
  &nbsp;
416
401
  /**
417
- * Handles the lifecycle of a single task execution.
402
+ * Logic to identify tasks that can be started and run them.
418
403
  */
419
- private async runStep(step: TaskStep&lt;TContext&gt;, results: Map&lt;string, TaskResult&gt;): Promise&lt;void&gt; {
420
- this.running.add(step.name);
421
- this.eventBus.emit("taskStart", { step });
404
+ private processLoop(
405
+ executingPromises: Set&lt;Promise&lt;void&gt;&gt;,
406
+ signal?: AbortSignal
407
+ ): void {
408
+ const toRun = this.stateManager.processDependencies();
422
409
  &nbsp;
423
- try {
424
- const result = await step.run(this.context);
425
- results.set(step.name, result);
426
- } catch (e) {
427
- results.set(step.name, {
428
- status: "failure",
429
- error: e instanceof Error ? e.message : String(e),
430
- });
431
- } finally {
432
- this.running.delete(step.name);
433
- const result = results.get(step.name)!;
434
- this.eventBus.emit("taskEnd", { step, result });
410
+ // Execute ready tasks
411
+ for (const step of toRun) {
412
+ this.stateManager.markRunning(step);
413
+ &nbsp;
414
+ const taskPromise = this.strategy.execute(step, this.context, signal)
415
+ .then((result) =&gt; {
416
+ this.stateManager.markCompleted(step, result);
417
+ })
418
+ .finally(() =&gt; {
419
+ executingPromises.delete(taskPromise);
420
+ });
421
+ &nbsp;
422
+ executingPromises.add(taskPromise);
435
423
  }
436
424
  }
437
425
  }
@@ -442,7 +430,7 @@ export class WorkflowExecutor&lt;TContext&gt; {
442
430
  <div class='footer quiet pad2 space-top1 center small'>
443
431
  Code coverage generated by
444
432
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
445
- at 2026-01-18T14:25:37.520Z
433
+ at 2026-01-18T16:53:50.068Z
446
434
  </div>
447
435
  <script src="../prettify.js"></script>
448
436
  <script>
@@ -202,7 +202,7 @@ export type ListenerMap&lt;TContext&gt; = {
202
202
  <div class='footer quiet pad2 space-top1 center small'>
203
203
  Code coverage generated by
204
204
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
205
- at 2026-01-18T14:25:37.520Z
205
+ at 2026-01-18T16:53:50.068Z
206
206
  </div>
207
207
  <script src="../../prettify.js"></script>
208
208
  <script>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2026-01-18T14:25:37.520Z
104
+ at 2026-01-18T16:53:50.068Z
105
105
  </div>
106
106
  <script src="../../prettify.js"></script>
107
107
  <script>
@@ -25,28 +25,28 @@
25
25
  <div class='fl pad1y space-right2'>
26
26
  <span class="strong">100% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>121/121</span>
28
+ <span class='fraction'>203/203</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
33
  <span class="strong">100% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>42/42</span>
35
+ <span class='fraction'>70/70</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
40
  <span class="strong">100% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>19/19</span>
42
+ <span class='fraction'>38/38</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
47
  <span class="strong">100% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>119/119</span>
49
+ <span class='fraction'>201/201</span>
50
50
  </div>
51
51
 
52
52
 
@@ -99,13 +99,13 @@
99
99
  <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
100
100
  </td>
101
101
  <td data-value="100" class="pct high">100%</td>
102
- <td data-value="46" class="abs high">46/46</td>
102
+ <td data-value="56" class="abs high">56/56</td>
103
103
  <td data-value="100" class="pct high">100%</td>
104
104
  <td data-value="16" class="abs high">16/16</td>
105
105
  <td data-value="100" class="pct high">100%</td>
106
106
  <td data-value="5" class="abs high">5/5</td>
107
107
  <td data-value="100" class="pct high">100%</td>
108
- <td data-value="44" class="abs high">44/44</td>
108
+ <td data-value="54" class="abs high">54/54</td>
109
109
  </tr>
110
110
 
111
111
  <tr>
@@ -114,13 +114,58 @@
114
114
  <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
115
115
  </td>
116
116
  <td data-value="100" class="pct high">100%</td>
117
- <td data-value="12" class="abs high">12/12</td>
117
+ <td data-value="33" class="abs high">33/33</td>
118
+ <td data-value="100" class="pct high">100%</td>
119
+ <td data-value="14" class="abs high">14/14</td>
120
+ <td data-value="100" class="pct high">100%</td>
121
+ <td data-value="8" class="abs high">8/8</td>
122
+ <td data-value="100" class="pct high">100%</td>
123
+ <td data-value="33" class="abs high">33/33</td>
124
+ </tr>
125
+
126
+ <tr>
127
+ <td class="file high" data-value="TaskRunnerBuilder.ts"><a href="TaskRunnerBuilder.ts.html">TaskRunnerBuilder.ts</a></td>
128
+ <td data-value="100" class="pic high">
129
+ <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
130
+ </td>
131
+ <td data-value="100" class="pct high">100%</td>
132
+ <td data-value="16" class="abs high">16/16</td>
118
133
  <td data-value="100" class="pct high">100%</td>
119
134
  <td data-value="4" class="abs high">4/4</td>
120
135
  <td data-value="100" class="pct high">100%</td>
121
- <td data-value="5" class="abs high">5/5</td>
136
+ <td data-value="6" class="abs high">6/6</td>
122
137
  <td data-value="100" class="pct high">100%</td>
123
- <td data-value="12" class="abs high">12/12</td>
138
+ <td data-value="16" class="abs high">16/16</td>
139
+ </tr>
140
+
141
+ <tr>
142
+ <td class="file empty" data-value="TaskRunnerExecutionConfig.ts"><a href="TaskRunnerExecutionConfig.ts.html">TaskRunnerExecutionConfig.ts</a></td>
143
+ <td data-value="0" class="pic empty">
144
+ <div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
145
+ </td>
146
+ <td data-value="0" class="pct empty">0%</td>
147
+ <td data-value="0" class="abs empty">0/0</td>
148
+ <td data-value="0" class="pct empty">0%</td>
149
+ <td data-value="0" class="abs empty">0/0</td>
150
+ <td data-value="0" class="pct empty">0%</td>
151
+ <td data-value="0" class="abs empty">0/0</td>
152
+ <td data-value="0" class="pct empty">0%</td>
153
+ <td data-value="0" class="abs empty">0/0</td>
154
+ </tr>
155
+
156
+ <tr>
157
+ <td class="file high" data-value="TaskStateManager.ts"><a href="TaskStateManager.ts.html">TaskStateManager.ts</a></td>
158
+ <td data-value="100" class="pic high">
159
+ <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
160
+ </td>
161
+ <td data-value="100" class="pct high">100%</td>
162
+ <td data-value="45" class="abs high">45/45</td>
163
+ <td data-value="100" class="pct high">100%</td>
164
+ <td data-value="16" class="abs high">16/16</td>
165
+ <td data-value="100" class="pct high">100%</td>
166
+ <td data-value="9" class="abs high">9/9</td>
167
+ <td data-value="100" class="pct high">100%</td>
168
+ <td data-value="45" class="abs high">45/45</td>
124
169
  </tr>
125
170
 
126
171
  <tr>
@@ -129,13 +174,13 @@
129
174
  <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
130
175
  </td>
131
176
  <td data-value="100" class="pct high">100%</td>
132
- <td data-value="48" class="abs high">48/48</td>
177
+ <td data-value="38" class="abs high">38/38</td>
133
178
  <td data-value="100" class="pct high">100%</td>
134
- <td data-value="14" class="abs high">14/14</td>
179
+ <td data-value="12" class="abs high">12/12</td>
135
180
  <td data-value="100" class="pct high">100%</td>
136
- <td data-value="5" class="abs high">5/5</td>
181
+ <td data-value="6" class="abs high">6/6</td>
137
182
  <td data-value="100" class="pct high">100%</td>
138
- <td data-value="48" class="abs high">48/48</td>
183
+ <td data-value="38" class="abs high">38/38</td>
139
184
  </tr>
140
185
 
141
186
  </tbody>
@@ -146,7 +191,7 @@
146
191
  <div class='footer quiet pad2 space-top1 center small'>
147
192
  Code coverage generated by
148
193
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
149
- at 2026-01-18T14:25:37.520Z
194
+ at 2026-01-18T16:53:50.068Z
150
195
  </div>
151
196
  <script src="../prettify.js"></script>
152
197
  <script>