@calmo/task-runner 1.2.3 → 2.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.
@@ -25,7 +25,7 @@
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'>46/46</span>
28
+ <span class='fraction'>56/56</span>
29
29
  </div>
30
30
 
31
31
 
@@ -46,7 +46,7 @@
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'>44/44</span>
49
+ <span class='fraction'>54/54</span>
50
50
  </div>
51
51
 
52
52
 
@@ -199,7 +199,34 @@
199
199
  <a name='L134'></a><a href='#L134'>134</a>
200
200
  <a name='L135'></a><a href='#L135'>135</a>
201
201
  <a name='L136'></a><a href='#L136'>136</a>
202
- <a name='L137'></a><a href='#L137'>137</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
203
230
  <span class="cline-any cline-neutral">&nbsp;</span>
204
231
  <span class="cline-any cline-neutral">&nbsp;</span>
205
232
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -216,26 +243,26 @@
216
243
  <span class="cline-any cline-neutral">&nbsp;</span>
217
244
  <span class="cline-any cline-neutral">&nbsp;</span>
218
245
  <span class="cline-any cline-neutral">&nbsp;</span>
219
- <span class="cline-any cline-yes">29x</span>
246
+ <span class="cline-any cline-yes">30x</span>
220
247
  <span class="cline-any cline-neutral">&nbsp;</span>
221
248
  <span class="cline-any cline-neutral">&nbsp;</span>
222
- <span class="cline-any cline-yes">29x</span>
223
- <span class="cline-any cline-yes">29x</span>
224
- <span class="cline-any cline-yes">61x</span>
249
+ <span class="cline-any cline-yes">30x</span>
250
+ <span class="cline-any cline-yes">30x</span>
251
+ <span class="cline-any cline-yes">20061x</span>
225
252
  <span class="cline-any cline-yes">3x</span>
226
253
  <span class="cline-any cline-neutral">&nbsp;</span>
227
254
  <span class="cline-any cline-neutral">&nbsp;</span>
228
255
  <span class="cline-any cline-neutral">&nbsp;</span>
229
256
  <span class="cline-any cline-neutral">&nbsp;</span>
230
257
  <span class="cline-any cline-neutral">&nbsp;</span>
231
- <span class="cline-any cline-yes">58x</span>
258
+ <span class="cline-any cline-yes">20058x</span>
232
259
  <span class="cline-any cline-neutral">&nbsp;</span>
233
260
  <span class="cline-any cline-neutral">&nbsp;</span>
234
261
  <span class="cline-any cline-neutral">&nbsp;</span>
235
262
  <span class="cline-any cline-neutral">&nbsp;</span>
236
- <span class="cline-any cline-yes">29x</span>
237
- <span class="cline-any cline-yes">61x</span>
238
- <span class="cline-any cline-yes">41x</span>
263
+ <span class="cline-any cline-yes">30x</span>
264
+ <span class="cline-any cline-yes">20061x</span>
265
+ <span class="cline-any cline-yes">20040x</span>
239
266
  <span class="cline-any cline-yes">5x</span>
240
267
  <span class="cline-any cline-neutral">&nbsp;</span>
241
268
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -247,9 +274,9 @@
247
274
  <span class="cline-any cline-neutral">&nbsp;</span>
248
275
  <span class="cline-any cline-neutral">&nbsp;</span>
249
276
  <span class="cline-any cline-neutral">&nbsp;</span>
250
- <span class="cline-any cline-yes">29x</span>
277
+ <span class="cline-any cline-yes">30x</span>
251
278
  <span class="cline-any cline-neutral">&nbsp;</span>
252
- <span class="cline-any cline-yes">29x</span>
279
+ <span class="cline-any cline-yes">30x</span>
253
280
  <span class="cline-any cline-yes">4x</span>
254
281
  <span class="cline-any cline-neutral">&nbsp;</span>
255
282
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -257,21 +284,21 @@
257
284
  <span class="cline-any cline-neutral">&nbsp;</span>
258
285
  <span class="cline-any cline-neutral">&nbsp;</span>
259
286
  <span class="cline-any cline-neutral">&nbsp;</span>
260
- <span class="cline-any cline-yes">25x</span>
261
- <span class="cline-any cline-yes">25x</span>
262
- <span class="cline-any cline-yes">54x</span>
287
+ <span class="cline-any cline-yes">26x</span>
288
+ <span class="cline-any cline-yes">26x</span>
289
+ <span class="cline-any cline-yes">20054x</span>
263
290
  <span class="cline-any cline-neutral">&nbsp;</span>
264
291
  <span class="cline-any cline-neutral">&nbsp;</span>
265
- <span class="cline-any cline-yes">25x</span>
266
- <span class="cline-any cline-yes">25x</span>
292
+ <span class="cline-any cline-yes">26x</span>
293
+ <span class="cline-any cline-yes">26x</span>
267
294
  <span class="cline-any cline-neutral">&nbsp;</span>
268
- <span class="cline-any cline-yes">25x</span>
269
- <span class="cline-any cline-yes">49x</span>
295
+ <span class="cline-any cline-yes">26x</span>
296
+ <span class="cline-any cline-yes">20049x</span>
270
297
  <span class="cline-any cline-yes">3x</span>
271
298
  <span class="cline-any cline-neutral">&nbsp;</span>
272
299
  <span class="cline-any cline-neutral">&nbsp;</span>
273
- <span class="cline-any cline-yes">46x</span>
274
- <span class="cline-any cline-yes">46x</span>
300
+ <span class="cline-any cline-yes">20046x</span>
301
+ <span class="cline-any cline-yes">20046x</span>
275
302
  <span class="cline-any cline-neutral">&nbsp;</span>
276
303
  <span class="cline-any cline-neutral">&nbsp;</span>
277
304
  <span class="cline-any cline-yes">4x</span>
@@ -288,7 +315,7 @@
288
315
  <span class="cline-any cline-neutral">&nbsp;</span>
289
316
  <span class="cline-any cline-neutral">&nbsp;</span>
290
317
  <span class="cline-any cline-neutral">&nbsp;</span>
291
- <span class="cline-any cline-yes">25x</span>
318
+ <span class="cline-any cline-yes">26x</span>
292
319
  <span class="cline-any cline-neutral">&nbsp;</span>
293
320
  <span class="cline-any cline-neutral">&nbsp;</span>
294
321
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -311,28 +338,55 @@
311
338
  <span class="cline-any cline-neutral">&nbsp;</span>
312
339
  <span class="cline-any cline-neutral">&nbsp;</span>
313
340
  <span class="cline-any cline-neutral">&nbsp;</span>
314
- <span class="cline-any cline-yes">51x</span>
315
- <span class="cline-any cline-yes">51x</span>
316
- <span class="cline-any cline-yes">51x</span>
317
341
  <span class="cline-any cline-neutral">&nbsp;</span>
318
- <span class="cline-any cline-yes">51x</span>
319
- <span class="cline-any cline-yes">51x</span>
320
- <span class="cline-any cline-yes">33x</span>
342
+ <span class="cline-any cline-yes">20046x</span>
343
+ <span class="cline-any cline-neutral">&nbsp;</span>
344
+ <span class="cline-any cline-yes">20046x</span>
345
+ <span class="cline-any cline-yes">20046x</span>
346
+ <span class="cline-any cline-yes">20046x</span>
347
+ <span class="cline-any cline-neutral">&nbsp;</span>
348
+ <span class="cline-any cline-yes">20046x</span>
349
+ <span class="cline-any cline-neutral">&nbsp;</span>
321
350
  <span class="cline-any cline-neutral">&nbsp;</span>
322
351
  <span class="cline-any cline-neutral">&nbsp;</span>
323
352
  <span class="cline-any cline-neutral">&nbsp;</span>
324
- <span class="cline-any cline-yes">5x</span>
325
- <span class="cline-any cline-yes">28x</span>
326
353
  <span class="cline-any cline-neutral">&nbsp;</span>
327
354
  <span class="cline-any cline-neutral">&nbsp;</span>
355
+ <span class="cline-any cline-yes">20046x</span>
356
+ <span class="cline-any cline-yes">40074x</span>
357
+ <span class="cline-any cline-yes">40074x</span>
358
+ <span class="cline-any cline-neutral">&nbsp;</span>
359
+ <span class="cline-any cline-yes">40074x</span>
360
+ <span class="cline-any cline-yes">20032x</span>
361
+ <span class="cline-any cline-yes">20032x</span>
362
+ <span class="cline-any cline-neutral">&nbsp;</span>
363
+ <span class="cline-any cline-yes">20032x</span>
364
+ <span class="cline-any cline-neutral">&nbsp;</span>
328
365
  <span class="cline-any cline-yes">4x</span>
329
366
  <span class="cline-any cline-yes">4x</span>
330
367
  <span class="cline-any cline-neutral">&nbsp;</span>
331
368
  <span class="cline-any cline-neutral">&nbsp;</span>
369
+ <span class="cline-any cline-yes">20028x</span>
370
+ <span class="cline-any cline-yes">5x</span>
371
+ <span class="cline-any cline-yes">5x</span>
372
+ <span class="cline-any cline-yes">5x</span>
373
+ <span class="cline-any cline-neutral">&nbsp;</span>
374
+ <span class="cline-any cline-yes">5x</span>
375
+ <span class="cline-any cline-neutral">&nbsp;</span>
376
+ <span class="cline-any cline-neutral">&nbsp;</span>
377
+ <span class="cline-any cline-neutral">&nbsp;</span>
378
+ <span class="cline-any cline-neutral">&nbsp;</span>
332
379
  <span class="cline-any cline-neutral">&nbsp;</span>
333
- <span class="cline-any cline-yes">42x</span>
334
- <span class="cline-any cline-yes">42x</span>
335
- <span class="cline-any cline-yes">42x</span>
380
+ <span class="cline-any cline-neutral">&nbsp;</span>
381
+ <span class="cline-any cline-neutral">&nbsp;</span>
382
+ <span class="cline-any cline-neutral">&nbsp;</span>
383
+ <span class="cline-any cline-yes">20042x</span>
384
+ <span class="cline-any cline-yes">20042x</span>
385
+ <span class="cline-any cline-yes">20042x</span>
386
+ <span class="cline-any cline-neutral">&nbsp;</span>
387
+ <span class="cline-any cline-neutral">&nbsp;</span>
388
+ <span class="cline-any cline-neutral">&nbsp;</span>
389
+ <span class="cline-any cline-yes">20042x</span>
336
390
  <span class="cline-any cline-neutral">&nbsp;</span>
337
391
  <span class="cline-any cline-neutral">&nbsp;</span>
338
392
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { ITaskGraphValidator } from "./contracts/ITaskGraphValidator.js";
@@ -441,33 +495,60 @@ export class TaskGraphValidator implements ITaskGraphValidator {
441
495
  }
442
496
  &nbsp;
443
497
  private detectCycle(
444
- taskId: string,
498
+ startTaskId: string,
445
499
  path: string[],
446
500
  visited: Set&lt;string&gt;,
447
501
  recursionStack: Set&lt;string&gt;,
448
502
  adjacencyList: Map&lt;string, string[]&gt;
449
503
  ): boolean {
450
- visited.add(taskId);
451
- recursionStack.add(taskId);
452
- path.push(taskId);
504
+ // Use an explicit stack to avoid maximum call stack size exceeded errors
505
+ const stack: { taskId: string; index: number; dependencies: string[] }[] = [];
506
+ &nbsp;
507
+ visited.add(startTaskId);
508
+ recursionStack.add(startTaskId);
509
+ path.push(startTaskId);
510
+ &nbsp;
511
+ stack.push({
512
+ taskId: startTaskId,
513
+ index: 0,
514
+ /* v8 ignore next */
515
+ dependencies: adjacencyList.get(startTaskId) ?? []
516
+ });
453
517
  &nbsp;
454
- const dependencies = adjacencyList.get(taskId)!;
455
- for (const dependenceId of dependencies) {
456
- if (
457
- !visited.has(dependenceId) &amp;&amp;
458
- this.detectCycle(dependenceId, path, visited, recursionStack, adjacencyList)
459
- ) {
460
- return true;
461
- } else if (recursionStack.has(dependenceId)) {
462
- // Cycle detected
463
- // Add the dependency to complete the visual cycle
464
- path.push(dependenceId);
465
- return true;
518
+ while (stack.length &gt; 0) {
519
+ const frame = stack[stack.length - 1];
520
+ const { taskId, dependencies } = frame;
521
+ &nbsp;
522
+ if (frame.index &lt; dependencies.length) {
523
+ const dependenceId = dependencies[frame.index];
524
+ frame.index++;
525
+ &nbsp;
526
+ if (recursionStack.has(dependenceId)) {
527
+ // Cycle detected
528
+ path.push(dependenceId);
529
+ return true;
530
+ }
531
+ &nbsp;
532
+ if (!visited.has(dependenceId)) {
533
+ visited.add(dependenceId);
534
+ recursionStack.add(dependenceId);
535
+ path.push(dependenceId);
536
+ &nbsp;
537
+ stack.push({
538
+ taskId: dependenceId,
539
+ index: 0,
540
+ /* v8 ignore next */
541
+ dependencies: adjacencyList.get(dependenceId) ?? []
542
+ });
543
+ }
544
+ } else {
545
+ // Finished all dependencies for this node
546
+ recursionStack.delete(taskId);
547
+ path.pop();
548
+ stack.pop();
466
549
  }
467
550
  }
468
551
  &nbsp;
469
- recursionStack.delete(taskId);
470
- path.pop();
471
552
  return false;
472
553
  }
473
554
  }
@@ -478,7 +559,7 @@ export class TaskGraphValidator implements ITaskGraphValidator {
478
559
  <div class='footer quiet pad2 space-top1 center small'>
479
560
  Code coverage generated by
480
561
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
481
- at 2026-01-18T14:25:37.520Z
562
+ at 2026-01-18T15:13:46.748Z
482
563
  </div>
483
564
  <script src="../prettify.js"></script>
484
565
  <script>
@@ -292,7 +292,7 @@ export class TaskRunner&lt;TContext&gt; {
292
292
  <div class='footer quiet pad2 space-top1 center small'>
293
293
  Code coverage generated by
294
294
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
295
- at 2026-01-18T14:25:37.520Z
295
+ at 2026-01-18T15:13:46.748Z
296
296
  </div>
297
297
  <script src="../prettify.js"></script>
298
298
  <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'>48/48</span>
28
+ <span class='fraction'>43/43</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'>20/20</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'>11/11</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'>42/42</span>
50
50
  </div>
51
51
 
52
52
 
@@ -187,7 +187,9 @@
187
187
  <a name='L122'></a><a href='#L122'>122</a>
188
188
  <a name='L123'></a><a href='#L123'>123</a>
189
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>
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>
191
193
  <span class="cline-any cline-neutral">&nbsp;</span>
192
194
  <span class="cline-any cline-neutral">&nbsp;</span>
193
195
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -217,7 +219,6 @@
217
219
  <span class="cline-any cline-neutral">&nbsp;</span>
218
220
  <span class="cline-any cline-yes">18x</span>
219
221
  <span class="cline-any cline-yes">18x</span>
220
- <span class="cline-any cline-yes">18x</span>
221
222
  <span class="cline-any cline-neutral">&nbsp;</span>
222
223
  <span class="cline-any cline-neutral">&nbsp;</span>
223
224
  <span class="cline-any cline-yes">18x</span>
@@ -241,24 +242,30 @@
241
242
  <span class="cline-any cline-neutral">&nbsp;</span>
242
243
  <span class="cline-any cline-neutral">&nbsp;</span>
243
244
  <span class="cline-any cline-neutral">&nbsp;</span>
244
- <span class="cline-any cline-neutral">&nbsp;</span>
245
245
  <span class="cline-any cline-yes">43x</span>
246
+ <span class="cline-any cline-neutral">&nbsp;</span>
246
247
  <span class="cline-any cline-yes">43x</span>
247
248
  <span class="cline-any cline-neutral">&nbsp;</span>
248
249
  <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>
250
+ <span class="cline-any cline-yes">31x</span>
251
+ <span class="cline-any cline-yes">31x</span>
252
+ <span class="cline-any cline-neutral">&nbsp;</span>
253
+ <span class="cline-any cline-yes">31x</span>
254
+ <span class="cline-any cline-neutral">&nbsp;</span>
255
+ <span class="cline-any cline-neutral">&nbsp;</span>
256
+ <span class="cline-any cline-neutral">&nbsp;</span>
257
+ <span class="cline-any cline-neutral">&nbsp;</span>
258
+ <span class="cline-any cline-neutral">&nbsp;</span>
251
259
  <span class="cline-any cline-neutral">&nbsp;</span>
252
260
  <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
261
  <span class="cline-any cline-yes">43x</span>
262
+ <span class="cline-any cline-yes">114x</span>
256
263
  <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
264
  <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>
262
269
  <span class="cline-any cline-neutral">&nbsp;</span>
263
270
  <span class="cline-any cline-neutral">&nbsp;</span>
264
271
  <span class="cline-any cline-yes">57x</span>
@@ -268,24 +275,21 @@
268
275
  <span class="cline-any cline-neutral">&nbsp;</span>
269
276
  <span class="cline-any cline-yes">7x</span>
270
277
  <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>
275
278
  <span class="cline-any cline-neutral">&nbsp;</span>
276
279
  <span class="cline-any cline-neutral">&nbsp;</span>
277
280
  <span class="cline-any cline-neutral">&nbsp;</span>
278
281
  <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>
282
+ <span class="cline-any cline-neutral">&nbsp;</span>
281
283
  <span class="cline-any cline-neutral">&nbsp;</span>
282
284
  <span class="cline-any cline-neutral">&nbsp;</span>
283
285
  <span class="cline-any cline-neutral">&nbsp;</span>
284
286
  <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>
287
+ <span class="cline-any cline-yes">114x</span>
288
+ <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>
287
292
  <span class="cline-any cline-neutral">&nbsp;</span>
288
- <span class="cline-any cline-yes">31x</span>
289
293
  <span class="cline-any cline-neutral">&nbsp;</span>
290
294
  <span class="cline-any cline-neutral">&nbsp;</span>
291
295
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -341,16 +345,15 @@ export class WorkflowExecutor&lt;TContext&gt; {
341
345
  &nbsp;
342
346
  const results = new Map&lt;string, TaskResult&gt;();
343
347
  const executingPromises = new Set&lt;Promise&lt;void&gt;&gt;();
344
- const pendingSteps = new Set(steps);
345
348
  &nbsp;
346
349
  // Initial pass
347
- this.processQueue(pendingSteps, results, executingPromises);
350
+ this.processQueue(steps, results, executingPromises);
348
351
  &nbsp;
349
352
  while (results.size &lt; steps.length &amp;&amp; executingPromises.size &gt; 0) {
350
353
  // Wait for the next task to finish
351
354
  await Promise.race(executingPromises);
352
355
  // After a task finishes, check for new work
353
- this.processQueue(pendingSteps, results, executingPromises);
356
+ this.processQueue(steps, results, executingPromises);
354
357
  }
355
358
  &nbsp;
356
359
  this.eventBus.emit("workflowEnd", { context: this.context, results });
@@ -359,31 +362,37 @@ export class WorkflowExecutor&lt;TContext&gt; {
359
362
  &nbsp;
360
363
  /**
361
364
  * 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
365
  */
364
366
  private processQueue(
365
- pendingSteps: Set&lt;TaskStep&lt;TContext&gt;&gt;,
367
+ steps: TaskStep&lt;TContext&gt;[],
366
368
  results: Map&lt;string, TaskResult&gt;,
367
369
  executingPromises: Set&lt;Promise&lt;void&gt;&gt;
368
370
  ): void {
369
- const toRemove: TaskStep&lt;TContext&gt;[] = [];
370
- const toRun: TaskStep&lt;TContext&gt;[] = [];
371
+ this.handleSkippedTasks(steps, results);
372
+ &nbsp;
373
+ const readySteps = this.getReadySteps(steps, results);
374
+ &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
+ }
382
+ &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
+ );
371
390
  &nbsp;
372
391
  for (const step of pendingSteps) {
373
392
  const deps = step.dependencies ?? [];
374
- let blocked = false;
375
- let failedDep: string | undefined;
376
- &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;
384
- break;
385
- }
386
- }
393
+ const failedDep = deps.find(
394
+ (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status !== "success"
395
+ );
387
396
  &nbsp;
388
397
  if (failedDep) {
389
398
  const result: TaskResult = {
@@ -392,25 +401,22 @@ export class WorkflowExecutor&lt;TContext&gt; {
392
401
  };
393
402
  results.set(step.name, result);
394
403
  this.eventBus.emit("taskSkipped", { step, result });
395
- toRemove.push(step);
396
- } else if (!blocked) {
397
- toRun.push(step);
398
- toRemove.push(step);
399
404
  }
400
405
  }
406
+ }
401
407
  &nbsp;
402
- // Cleanup pending set
403
- for (const step of toRemove) {
404
- pendingSteps.delete(step);
405
- }
408
+ /**
409
+ * Returns steps where all dependencies have finished successfully.
410
+ */
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;
406
414
  &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);
413
- }
415
+ const deps = step.dependencies ?? [];
416
+ return deps.every(
417
+ (dep) =&gt; results.has(dep) &amp;&amp; results.get(dep)?.status === "success"
418
+ );
419
+ });
414
420
  }
415
421
  &nbsp;
416
422
  /**
@@ -442,7 +448,7 @@ export class WorkflowExecutor&lt;TContext&gt; {
442
448
  <div class='footer quiet pad2 space-top1 center small'>
443
449
  Code coverage generated by
444
450
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
445
- at 2026-01-18T14:25:37.520Z
451
+ at 2026-01-18T15:13:46.748Z
446
452
  </div>
447
453
  <script src="../prettify.js"></script>
448
454
  <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-18T15:13:46.748Z
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-18T15:13:46.748Z
105
105
  </div>
106
106
  <script src="../../prettify.js"></script>
107
107
  <script>