@calmo/task-runner 2.0.0 → 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 (83) hide show
  1. package/AGENTS.md +49 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +1 -1
  4. package/coverage/coverage-final.json +8 -4
  5. package/coverage/index.html +24 -9
  6. package/coverage/lcov-report/index.html +24 -9
  7. package/coverage/lcov-report/src/EventBus.ts.html +8 -8
  8. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +53 -53
  9. package/coverage/lcov-report/src/TaskRunner.ts.html +213 -21
  10. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +313 -0
  11. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +124 -0
  12. package/coverage/lcov-report/src/TaskStateManager.ts.html +511 -0
  13. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +119 -137
  14. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  15. package/coverage/lcov-report/src/contracts/index.html +1 -1
  16. package/coverage/lcov-report/src/index.html +57 -12
  17. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  18. package/coverage/lcov-report/src/strategies/index.html +116 -0
  19. package/coverage/lcov.info +381 -204
  20. package/coverage/src/EventBus.ts.html +8 -8
  21. package/coverage/src/TaskGraphValidator.ts.html +53 -53
  22. package/coverage/src/TaskRunner.ts.html +213 -21
  23. package/coverage/src/TaskRunnerBuilder.ts.html +313 -0
  24. package/coverage/src/TaskRunnerExecutionConfig.ts.html +124 -0
  25. package/coverage/src/TaskStateManager.ts.html +511 -0
  26. package/coverage/src/WorkflowExecutor.ts.html +119 -137
  27. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  28. package/coverage/src/contracts/index.html +1 -1
  29. package/coverage/src/index.html +57 -12
  30. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  31. package/coverage/src/strategies/index.html +116 -0
  32. package/dist/TaskRunner.d.ts +12 -2
  33. package/dist/TaskRunner.js +55 -3
  34. package/dist/TaskRunner.js.map +1 -1
  35. package/dist/TaskRunnerBuilder.d.ts +33 -0
  36. package/dist/TaskRunnerBuilder.js +54 -0
  37. package/dist/TaskRunnerBuilder.js.map +1 -0
  38. package/dist/TaskRunnerExecutionConfig.d.ts +13 -0
  39. package/dist/TaskRunnerExecutionConfig.js +2 -0
  40. package/dist/TaskRunnerExecutionConfig.js.map +1 -0
  41. package/dist/TaskStateManager.d.ts +54 -0
  42. package/dist/TaskStateManager.js +130 -0
  43. package/dist/TaskStateManager.js.map +1 -0
  44. package/dist/TaskStatus.d.ts +1 -1
  45. package/dist/TaskStep.d.ts +2 -1
  46. package/dist/WorkflowExecutor.d.ts +11 -17
  47. package/dist/WorkflowExecutor.js +67 -69
  48. package/dist/WorkflowExecutor.js.map +1 -1
  49. package/dist/index.d.ts +4 -0
  50. package/dist/index.js +3 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/strategies/IExecutionStrategy.d.ts +16 -0
  53. package/dist/strategies/IExecutionStrategy.js +2 -0
  54. package/dist/strategies/IExecutionStrategy.js.map +1 -0
  55. package/dist/strategies/StandardExecutionStrategy.d.ts +9 -0
  56. package/dist/strategies/StandardExecutionStrategy.js +25 -0
  57. package/dist/strategies/StandardExecutionStrategy.js.map +1 -0
  58. package/openspec/changes/add-concurrency-control/proposal.md +14 -0
  59. package/openspec/changes/add-concurrency-control/tasks.md +9 -0
  60. package/openspec/changes/add-task-retry-policy/proposal.md +13 -0
  61. package/openspec/changes/add-task-retry-policy/tasks.md +10 -0
  62. package/openspec/changes/add-workflow-preview/proposal.md +12 -0
  63. package/openspec/changes/add-workflow-preview/tasks.md +7 -0
  64. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +10 -0
  65. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +18 -0
  66. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +17 -0
  67. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +14 -0
  68. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +8 -0
  69. package/package.json +1 -1
  70. package/src/TaskRunner.ts +68 -4
  71. package/src/TaskRunnerBuilder.ts +76 -0
  72. package/src/TaskRunnerExecutionConfig.ts +13 -0
  73. package/src/TaskStateManager.ts +142 -0
  74. package/src/TaskStatus.ts +1 -1
  75. package/src/TaskStep.ts +2 -1
  76. package/src/WorkflowExecutor.ts +77 -83
  77. package/src/index.ts +4 -0
  78. package/src/strategies/IExecutionStrategy.ts +21 -0
  79. package/src/strategies/StandardExecutionStrategy.ts +35 -0
  80. package/test-report.xml +119 -45
  81. package/GEMINI.md +0 -48
  82. package/openspec/changes/add-external-task-cancellation/tasks.md +0 -10
  83. /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'>43/43</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'>20/20</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'>11/11</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'>42/42</span>
49
+ <span class='fraction'>38/38</span>
50
50
  </div>
51
51
 
52
52
 
@@ -183,13 +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>
191
- <a name='L126'></a><a href='#L126'>126</a>
192
- <a name='L127'></a><a href='#L127'>127</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>
193
187
  <span class="cline-any cline-neutral">&nbsp;</span>
194
188
  <span class="cline-any cline-neutral">&nbsp;</span>
195
189
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -198,246 +192,234 @@
198
192
  <span class="cline-any cline-neutral">&nbsp;</span>
199
193
  <span class="cline-any cline-neutral">&nbsp;</span>
200
194
  <span class="cline-any cline-neutral">&nbsp;</span>
201
- <span class="cline-any cline-yes">18x</span>
202
195
  <span class="cline-any cline-neutral">&nbsp;</span>
203
196
  <span class="cline-any cline-neutral">&nbsp;</span>
204
197
  <span class="cline-any cline-neutral">&nbsp;</span>
205
198
  <span class="cline-any cline-neutral">&nbsp;</span>
206
199
  <span class="cline-any cline-neutral">&nbsp;</span>
207
200
  <span class="cline-any cline-neutral">&nbsp;</span>
208
- <span class="cline-any cline-yes">18x</span>
209
- <span class="cline-any cline-yes">18x</span>
210
201
  <span class="cline-any cline-neutral">&nbsp;</span>
211
202
  <span class="cline-any cline-neutral">&nbsp;</span>
212
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>
213
208
  <span class="cline-any cline-neutral">&nbsp;</span>
214
209
  <span class="cline-any cline-neutral">&nbsp;</span>
215
210
  <span class="cline-any cline-neutral">&nbsp;</span>
216
211
  <span class="cline-any cline-neutral">&nbsp;</span>
217
212
  <span class="cline-any cline-neutral">&nbsp;</span>
218
- <span class="cline-any cline-yes">18x</span>
219
213
  <span class="cline-any cline-neutral">&nbsp;</span>
220
- <span class="cline-any cline-yes">18x</span>
221
- <span class="cline-any cline-yes">18x</span>
222
214
  <span class="cline-any cline-neutral">&nbsp;</span>
223
215
  <span class="cline-any cline-neutral">&nbsp;</span>
224
- <span class="cline-any cline-yes">18x</span>
225
216
  <span class="cline-any cline-neutral">&nbsp;</span>
226
- <span class="cline-any cline-yes">18x</span>
227
217
  <span class="cline-any cline-neutral">&nbsp;</span>
228
- <span class="cline-any cline-yes">25x</span>
229
218
  <span class="cline-any cline-neutral">&nbsp;</span>
230
- <span class="cline-any cline-yes">25x</span>
231
- <span class="cline-any cline-neutral">&nbsp;</span>
232
- <span class="cline-any cline-neutral">&nbsp;</span>
233
- <span class="cline-any cline-yes">18x</span>
234
- <span class="cline-any cline-yes">18x</span>
235
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>
236
222
  <span class="cline-any cline-neutral">&nbsp;</span>
237
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>
238
229
  <span class="cline-any cline-neutral">&nbsp;</span>
239
230
  <span class="cline-any cline-neutral">&nbsp;</span>
231
+ <span class="cline-any cline-yes">39x</span>
240
232
  <span class="cline-any cline-neutral">&nbsp;</span>
233
+ <span class="cline-any cline-yes">39x</span>
241
234
  <span class="cline-any cline-neutral">&nbsp;</span>
235
+ <span class="cline-any cline-yes">9x</span>
242
236
  <span class="cline-any cline-neutral">&nbsp;</span>
243
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>
244
240
  <span class="cline-any cline-neutral">&nbsp;</span>
245
- <span class="cline-any cline-yes">43x</span>
246
241
  <span class="cline-any cline-neutral">&nbsp;</span>
247
- <span class="cline-any cline-yes">43x</span>
242
+ <span class="cline-any cline-yes">39x</span>
248
243
  <span class="cline-any cline-neutral">&nbsp;</span>
249
- <span class="cline-any cline-yes">43x</span>
250
- <span class="cline-any cline-yes">31x</span>
251
- <span class="cline-any cline-yes">31x</span>
244
+ <span class="cline-any cline-yes">39x</span>
252
245
  <span class="cline-any cline-neutral">&nbsp;</span>
253
- <span class="cline-any cline-yes">31x</span>
246
+ <span class="cline-any cline-yes">39x</span>
254
247
  <span class="cline-any cline-neutral">&nbsp;</span>
255
248
  <span class="cline-any cline-neutral">&nbsp;</span>
256
249
  <span class="cline-any cline-neutral">&nbsp;</span>
257
250
  <span class="cline-any cline-neutral">&nbsp;</span>
258
251
  <span class="cline-any cline-neutral">&nbsp;</span>
259
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>
260
255
  <span class="cline-any cline-neutral">&nbsp;</span>
261
- <span class="cline-any cline-yes">43x</span>
262
- <span class="cline-any cline-yes">114x</span>
263
256
  <span class="cline-any cline-neutral">&nbsp;</span>
257
+ <span class="cline-any cline-yes">74x</span>
264
258
  <span class="cline-any cline-neutral">&nbsp;</span>
265
- <span class="cline-any cline-yes">43x</span>
266
- <span class="cline-any cline-yes">57x</span>
267
- <span class="cline-any cline-yes">57x</span>
268
- <span class="cline-any cline-yes">43x</span>
269
259
  <span class="cline-any cline-neutral">&nbsp;</span>
260
+ <span class="cline-any cline-yes">74x</span>
261
+ <span class="cline-any cline-yes">9x</span>
270
262
  <span class="cline-any cline-neutral">&nbsp;</span>
271
- <span class="cline-any cline-yes">57x</span>
272
- <span class="cline-any cline-yes">7x</span>
273
263
  <span class="cline-any cline-neutral">&nbsp;</span>
264
+ <span class="cline-any cline-yes">65x</span>
274
265
  <span class="cline-any cline-neutral">&nbsp;</span>
275
266
  <span class="cline-any cline-neutral">&nbsp;</span>
276
- <span class="cline-any cline-yes">7x</span>
277
- <span class="cline-any cline-yes">7x</span>
278
267
  <span class="cline-any cline-neutral">&nbsp;</span>
279
268
  <span class="cline-any cline-neutral">&nbsp;</span>
269
+ <span class="cline-any cline-yes">39x</span>
280
270
  <span class="cline-any cline-neutral">&nbsp;</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>
281
274
  <span class="cline-any cline-neutral">&nbsp;</span>
275
+ <span class="cline-any cline-yes">39x</span>
276
+ <span class="cline-any cline-yes">9x</span>
282
277
  <span class="cline-any cline-neutral">&nbsp;</span>
283
278
  <span class="cline-any cline-neutral">&nbsp;</span>
284
279
  <span class="cline-any cline-neutral">&nbsp;</span>
285
280
  <span class="cline-any cline-neutral">&nbsp;</span>
286
- <span class="cline-any cline-yes">43x</span>
287
- <span class="cline-any cline-yes">114x</span>
288
281
  <span class="cline-any cline-neutral">&nbsp;</span>
289
- <span class="cline-any cline-yes">50x</span>
290
- <span class="cline-any cline-yes">114x</span>
291
- <span class="cline-any cline-yes">32x</span>
292
282
  <span class="cline-any cline-neutral">&nbsp;</span>
293
283
  <span class="cline-any cline-neutral">&nbsp;</span>
294
284
  <span class="cline-any cline-neutral">&nbsp;</span>
295
285
  <span class="cline-any cline-neutral">&nbsp;</span>
296
286
  <span class="cline-any cline-neutral">&nbsp;</span>
297
287
  <span class="cline-any cline-neutral">&nbsp;</span>
288
+ <span class="cline-any cline-yes">104x</span>
298
289
  <span class="cline-any cline-neutral">&nbsp;</span>
299
290
  <span class="cline-any cline-neutral">&nbsp;</span>
300
- <span class="cline-any cline-yes">31x</span>
301
- <span class="cline-any cline-yes">31x</span>
291
+ <span class="cline-any cline-yes">104x</span>
292
+ <span class="cline-any cline-yes">101x</span>
302
293
  <span class="cline-any cline-neutral">&nbsp;</span>
303
- <span class="cline-any cline-yes">31x</span>
304
- <span class="cline-any cline-yes">31x</span>
305
- <span class="cline-any cline-yes">29x</span>
294
+ <span class="cline-any cline-yes">101x</span>
306
295
  <span class="cline-any cline-neutral">&nbsp;</span>
307
- <span class="cline-any cline-yes">2x</span>
296
+ <span class="cline-any cline-yes">101x</span>
308
297
  <span class="cline-any cline-neutral">&nbsp;</span>
309
298
  <span class="cline-any cline-neutral">&nbsp;</span>
299
+ <span class="cline-any cline-yes">101x</span>
310
300
  <span class="cline-any cline-neutral">&nbsp;</span>
311
301
  <span class="cline-any cline-neutral">&nbsp;</span>
312
- <span class="cline-any cline-yes">31x</span>
313
- <span class="cline-any cline-yes">31x</span>
314
- <span class="cline-any cline-yes">31x</span>
302
+ <span class="cline-any cline-yes">101x</span>
315
303
  <span class="cline-any cline-neutral">&nbsp;</span>
316
304
  <span class="cline-any cline-neutral">&nbsp;</span>
317
305
  <span class="cline-any cline-neutral">&nbsp;</span>
318
306
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { TaskStep } from "./TaskStep.js";
319
307
  import { TaskResult } from "./TaskResult.js";
320
308
  import { EventBus } from "./EventBus.js";
309
+ import { TaskStateManager } from "./TaskStateManager.js";
310
+ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
321
311
  &nbsp;
322
312
  /**
323
313
  * Handles the execution of the workflow steps.
324
314
  * @template TContext The shape of the shared context object.
325
315
  */
326
316
  export class WorkflowExecutor&lt;TContext&gt; {
327
- private running = new Set&lt;string&gt;();
328
- &nbsp;
329
317
  /**
330
318
  * @param context The shared context object.
331
319
  * @param eventBus The event bus to emit events.
320
+ * @param stateManager Manages execution state.
321
+ * @param strategy Execution strategy.
332
322
  */
333
323
  constructor(
334
324
  private context: TContext,
335
- 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;
336
328
  ) {}
337
329
  &nbsp;
338
330
  /**
339
331
  * Executes the given steps.
340
332
  * @param steps The list of steps to execute.
333
+ * @param signal Optional AbortSignal for cancellation.
341
334
  * @returns A Promise that resolves to a map of task results.
342
335
  */
343
- 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; {
344
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
+ }
345
350
  &nbsp;
346
- const results = new Map&lt;string, TaskResult&gt;();
347
351
  const executingPromises = new Set&lt;Promise&lt;void&gt;&gt;();
348
352
  &nbsp;
349
- // Initial pass
350
- this.processQueue(steps, results, executingPromises);
353
+ const onAbort = () =&gt; {
354
+ // Mark all pending tasks as cancelled
355
+ this.stateManager.cancelAllPending("Workflow cancelled.");
356
+ };
351
357
  &nbsp;
352
- while (results.size &lt; steps.length &amp;&amp; executingPromises.size &gt; 0) {
353
- // Wait for the next task to finish
354
- await Promise.race(executingPromises);
355
- // After a task finishes, check for new work
356
- this.processQueue(steps, results, executingPromises);
358
+ if (signal) {
359
+ signal.addEventListener("abort", onAbort);
357
360
  }
358
361
  &nbsp;
359
- this.eventBus.emit("workflowEnd", { context: this.context, results });
360
- return results;
361
- }
362
- &nbsp;
363
- /**
364
- * Logic to identify tasks that can be started or must be skipped.
365
- */
366
- private processQueue(
367
- steps: TaskStep&lt;TContext&gt;[],
368
- results: Map&lt;string, TaskResult&gt;,
369
- executingPromises: Set&lt;Promise&lt;void&gt;&gt;
370
- ): void {
371
- this.handleSkippedTasks(steps, results);
372
- &nbsp;
373
- const readySteps = this.getReadySteps(steps, results);
362
+ try {
363
+ // Initial pass
364
+ this.processLoop(executingPromises, signal);
374
365
  &nbsp;
375
- for (const step of readySteps) {
376
- const taskPromise = this.runStep(step, results).then(() =&gt; {
377
- executingPromises.delete(taskPromise);
378
- });
379
- executingPromises.add(taskPromise);
380
- }
381
- }
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) {
374
+ break;
375
+ } else {
376
+ // Wait for the next task to finish
377
+ await Promise.race(executingPromises);
378
+ }
382
379
  &nbsp;
383
- /**
384
- * Identifies steps that cannot run because a dependency failed.
385
- */
386
- private handleSkippedTasks(steps: TaskStep&lt;TContext&gt;[], results: Map&lt;string, TaskResult&gt;): void {
387
- const pendingSteps = steps.filter(
388
- (step) =&gt; !results.has(step.name) &amp;&amp; !this.running.has(step.name)
389
- );
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
+ }
386
+ }
390
387
  &nbsp;
391
- for (const step of pendingSteps) {
392
- const deps = step.dependencies ?? [];
393
- const failedDep = deps.find(
394
- (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status !== "success"
395
- );
388
+ // Ensure everything is accounted for (e.g. if loop exited early)
389
+ this.stateManager.cancelAllPending("Workflow cancelled.");
396
390
  &nbsp;
397
- if (failedDep) {
398
- const result: TaskResult = {
399
- status: "skipped",
400
- message: `Skipped due to failed dependency: ${failedDep}`,
401
- };
402
- results.set(step.name, result);
403
- this.eventBus.emit("taskSkipped", { step, result });
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);
404
397
  }
405
398
  }
406
399
  }
407
400
  &nbsp;
408
401
  /**
409
- * Returns steps where all dependencies have finished successfully.
402
+ * Logic to identify tasks that can be started and run them.
410
403
  */
411
- private getReadySteps(steps: TaskStep&lt;TContext&gt;[], results: Map&lt;string, TaskResult&gt;): TaskStep&lt;TContext&gt;[] {
412
- return steps.filter((step) =&gt; {
413
- if (results.has(step.name) || this.running.has(step.name)) return false;
404
+ private processLoop(
405
+ executingPromises: Set&lt;Promise&lt;void&gt;&gt;,
406
+ signal?: AbortSignal
407
+ ): void {
408
+ const toRun = this.stateManager.processDependencies();
414
409
  &nbsp;
415
- const deps = step.dependencies ?? [];
416
- return deps.every(
417
- (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status === "success"
418
- );
419
- });
420
- }
410
+ // Execute ready tasks
411
+ for (const step of toRun) {
412
+ this.stateManager.markRunning(step);
421
413
  &nbsp;
422
- /**
423
- * Handles the lifecycle of a single task execution.
424
- */
425
- private async runStep(step: TaskStep&lt;TContext&gt;, results: Map&lt;string, TaskResult&gt;): Promise&lt;void&gt; {
426
- this.running.add(step.name);
427
- this.eventBus.emit("taskStart", { step });
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
+ });
428
421
  &nbsp;
429
- try {
430
- const result = await step.run(this.context);
431
- results.set(step.name, result);
432
- } catch (e) {
433
- results.set(step.name, {
434
- status: "failure",
435
- error: e instanceof Error ? e.message : String(e),
436
- });
437
- } finally {
438
- this.running.delete(step.name);
439
- const result = results.get(step.name)!;
440
- this.eventBus.emit("taskEnd", { step, result });
422
+ executingPromises.add(taskPromise);
441
423
  }
442
424
  }
443
425
  }
@@ -448,7 +430,7 @@ export class WorkflowExecutor&lt;TContext&gt; {
448
430
  <div class='footer quiet pad2 space-top1 center small'>
449
431
  Code coverage generated by
450
432
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
451
- at 2026-01-18T15:13:46.741Z
433
+ at 2026-01-18T16:53:50.059Z
452
434
  </div>
453
435
  <script src="../prettify.js"></script>
454
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-18T15:13:46.741Z
205
+ at 2026-01-18T16:53:50.059Z
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-18T15:13:46.741Z
104
+ at 2026-01-18T16:53:50.059Z
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'>126/126</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'>48/48</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'>25/25</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'>123/123</span>
49
+ <span class='fraction'>201/201</span>
50
50
  </div>
51
51
 
52
52
 
@@ -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="43" class="abs high">43/43</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="20" class="abs high">20/20</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="11" class="abs high">11/11</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="42" class="abs high">42/42</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-18T15:13:46.741Z
194
+ at 2026-01-18T16:53:50.059Z
150
195
  </div>
151
196
  <script src="../prettify.js"></script>
152
197
  <script>