@calmo/task-runner 4.0.4 → 4.1.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 (46) hide show
  1. package/.github/workflows/codeql.yml +1 -1
  2. package/.github/workflows/release-please.yml +2 -2
  3. package/.jules/nexus.md +5 -0
  4. package/.release-please-manifest.json +1 -1
  5. package/AGENTS.md +1 -0
  6. package/CHANGELOG.md +19 -0
  7. package/CODE_OF_CONDUCT.md +131 -0
  8. package/CONTRIBUTING.md +89 -0
  9. package/dist/TaskResult.d.ts +9 -0
  10. package/dist/TaskRunner.js +47 -34
  11. package/dist/TaskRunner.js.map +1 -1
  12. package/dist/TaskStateManager.d.ts +22 -6
  13. package/dist/TaskStateManager.js +101 -45
  14. package/dist/TaskStateManager.js.map +1 -1
  15. package/dist/WorkflowExecutor.js +17 -10
  16. package/dist/WorkflowExecutor.js.map +1 -1
  17. package/dist/strategies/DryRunExecutionStrategy.d.ts +1 -1
  18. package/dist/strategies/DryRunExecutionStrategy.js +2 -4
  19. package/dist/strategies/DryRunExecutionStrategy.js.map +1 -1
  20. package/dist/utils/PriorityQueue.d.ts +13 -0
  21. package/dist/utils/PriorityQueue.js +82 -0
  22. package/dist/utils/PriorityQueue.js.map +1 -0
  23. package/openspec/changes/add-resource-concurrency/proposal.md +18 -0
  24. package/openspec/changes/add-resource-concurrency/specs/task-runner/spec.md +25 -0
  25. package/openspec/changes/add-resource-concurrency/tasks.md +9 -0
  26. package/openspec/changes/{feat-task-metrics → archive/2026-01-22-feat-task-metrics}/proposal.md +1 -1
  27. package/openspec/changes/archive/2026-01-22-feat-task-metrics/tasks.md +6 -0
  28. package/openspec/changes/feat-conditional-retries/proposal.md +18 -0
  29. package/openspec/changes/feat-conditional-retries/specs/task-runner/spec.md +23 -0
  30. package/openspec/changes/feat-conditional-retries/tasks.md +37 -0
  31. package/openspec/changes/feat-state-persistence/specs/task-runner/spec.md +47 -0
  32. package/openspec/specs/release-pr/spec.md +31 -0
  33. package/openspec/specs/task-runner/spec.md +12 -0
  34. package/package.json +1 -1
  35. package/src/TaskResult.ts +9 -0
  36. package/src/TaskRunner.ts +55 -36
  37. package/src/TaskStateManager.ts +114 -46
  38. package/src/WorkflowExecutor.ts +21 -11
  39. package/src/strategies/DryRunExecutionStrategy.ts +2 -3
  40. package/src/utils/PriorityQueue.ts +101 -0
  41. package/openspec/changes/feat-task-metrics/tasks.md +0 -6
  42. /package/openspec/changes/{adopt-release-pr → archive/2026-01-22-adopt-release-pr}/design.md +0 -0
  43. /package/openspec/changes/{adopt-release-pr → archive/2026-01-22-adopt-release-pr}/proposal.md +0 -0
  44. /package/openspec/changes/{adopt-release-pr → archive/2026-01-22-adopt-release-pr}/specs/release-pr/spec.md +0 -0
  45. /package/openspec/changes/{adopt-release-pr → archive/2026-01-22-adopt-release-pr}/tasks.md +0 -0
  46. /package/openspec/changes/{feat-task-metrics → archive/2026-01-22-feat-task-metrics}/specs/001-generic-task-runner/spec.md +0 -0
@@ -7,6 +7,10 @@ export class TaskStateManager {
7
7
  results = new Map();
8
8
  pendingSteps = new Set();
9
9
  running = new Set();
10
+ // Optimization structures
11
+ dependencyGraph = new Map();
12
+ dependencyCounts = new Map();
13
+ readyQueue = [];
10
14
  constructor(eventBus) {
11
15
  this.eventBus = eventBus;
12
16
  }
@@ -18,47 +22,35 @@ export class TaskStateManager {
18
22
  this.pendingSteps = new Set(steps);
19
23
  this.results.clear();
20
24
  this.running.clear();
25
+ this.readyQueue = [];
26
+ this.dependencyGraph.clear();
27
+ this.dependencyCounts.clear();
28
+ for (const step of steps) {
29
+ const deps = step.dependencies ?? [];
30
+ this.dependencyCounts.set(step.name, deps.length);
31
+ if (deps.length === 0) {
32
+ this.readyQueue.push(step);
33
+ }
34
+ else {
35
+ for (const dep of deps) {
36
+ if (!this.dependencyGraph.has(dep)) {
37
+ this.dependencyGraph.set(dep, []);
38
+ }
39
+ this.dependencyGraph.get(dep).push(step);
40
+ }
41
+ }
42
+ }
21
43
  }
22
44
  /**
23
- * Processes the pending steps to identify tasks that can be started or must be skipped.
24
- * Emits `taskSkipped` for skipped tasks.
45
+ * Processes the pending steps to identify tasks that can be started.
46
+ * Emits `taskSkipped` for skipped tasks (handled during cascade).
25
47
  * @returns An array of tasks that are ready to run.
26
48
  */
27
49
  processDependencies() {
28
- const toRemove = [];
29
- const toRun = [];
30
- for (const step of this.pendingSteps) {
31
- const deps = step.dependencies ?? [];
32
- let blocked = false;
33
- let failedDep;
34
- for (const dep of deps) {
35
- const depResult = this.results.get(dep);
36
- if (!depResult) {
37
- // Dependency not finished yet
38
- blocked = true;
39
- }
40
- else if (depResult.status !== "success") {
41
- failedDep = dep;
42
- break;
43
- }
44
- }
45
- if (failedDep) {
46
- const depResult = this.results.get(failedDep);
47
- const depError = depResult?.error ? `: ${depResult.error}` : "";
48
- const result = {
49
- status: "skipped",
50
- message: `Skipped because dependency '${failedDep}' failed${depError}`,
51
- };
52
- this.markSkipped(step, result);
53
- toRemove.push(step);
54
- }
55
- else if (!blocked) {
56
- toRun.push(step);
57
- toRemove.push(step);
58
- }
59
- }
60
- // Cleanup pending set
61
- for (const step of toRemove) {
50
+ const toRun = [...this.readyQueue];
51
+ this.readyQueue = [];
52
+ // Remove them from pendingSteps as they are now handed off to the executor
53
+ for (const step of toRun) {
62
54
  this.pendingSteps.delete(step);
63
55
  }
64
56
  return toRun;
@@ -81,16 +73,45 @@ export class TaskStateManager {
81
73
  this.running.delete(step.name);
82
74
  this.results.set(step.name, result);
83
75
  this.eventBus.emit("taskEnd", { step, result });
76
+ if (result.status === "success") {
77
+ this.handleSuccess(step.name);
78
+ }
79
+ else {
80
+ this.cascadeFailure(step.name);
81
+ }
82
+ }
83
+ /**
84
+ * Marks a task as skipped and emits `taskSkipped`.
85
+ * @param step The task that was skipped.
86
+ * @param result The result object (status: skipped).
87
+ */
88
+ markSkipped(step, result) {
89
+ if (this.internalMarkSkipped(step, result)) {
90
+ this.cascadeFailure(step.name);
91
+ }
92
+ }
93
+ /**
94
+ * Internal method to mark skipped without triggering cascade (to be used inside cascade loop).
95
+ * Returns true if the task was actually marked skipped (was not already finished).
96
+ */
97
+ internalMarkSkipped(step, result) {
98
+ if (this.results.has(step.name)) {
99
+ return false;
100
+ }
101
+ this.running.delete(step.name);
102
+ this.results.set(step.name, result);
103
+ this.pendingSteps.delete(step);
104
+ this.eventBus.emit("taskSkipped", { step, result });
105
+ return true;
84
106
  }
85
107
  /**
86
108
  * Cancels all pending tasks that haven't started yet.
87
109
  * @param message The cancellation message.
88
110
  */
89
111
  cancelAllPending(message) {
112
+ this.readyQueue = []; // Clear ready queue
90
113
  // Iterate over pendingSteps to cancel them
91
114
  for (const step of this.pendingSteps) {
92
- // Also check running? No, running tasks are handled by AbortSignal in Executor.
93
- // We only cancel what is pending and hasn't started.
94
115
  if (!this.results.has(step.name) && !this.running.has(step.name)) {
95
116
  const result = {
96
117
  status: "cancelled",
@@ -122,14 +143,49 @@ export class TaskStateManager {
122
143
  return this.pendingSteps.size > 0;
123
144
  }
124
145
  /**
125
- * Marks a task as skipped and emits `taskSkipped`.
126
- * @param step The task that was skipped.
127
- * @param result The result object (status: skipped).
146
+ * Handles successful completion of a task by updating dependents.
128
147
  */
129
- markSkipped(step, result) {
130
- this.running.delete(step.name);
131
- this.results.set(step.name, result);
132
- this.eventBus.emit("taskSkipped", { step, result });
148
+ handleSuccess(stepName) {
149
+ const dependents = this.dependencyGraph.get(stepName);
150
+ if (!dependents)
151
+ return;
152
+ for (const dependent of dependents) {
153
+ const currentCount = this.dependencyCounts.get(dependent.name);
154
+ const newCount = currentCount - 1;
155
+ this.dependencyCounts.set(dependent.name, newCount);
156
+ if (newCount === 0) {
157
+ // Task is ready. Ensure it's still pending.
158
+ if (this.pendingSteps.has(dependent)) {
159
+ this.readyQueue.push(dependent);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Cascades failure/skipping to dependents.
166
+ */
167
+ cascadeFailure(failedStepName) {
168
+ const queue = [failedStepName];
169
+ // Use a set to track visited nodes in this cascade pass to avoid redundant processing,
170
+ // although checking results.has() in internalMarkSkipped also prevents it.
171
+ while (queue.length > 0) {
172
+ const currentName = queue.shift();
173
+ const dependents = this.dependencyGraph.get(currentName);
174
+ if (!dependents)
175
+ continue;
176
+ // Get the result of the failed/skipped dependency to propagate error info if available
177
+ const currentResult = this.results.get(currentName);
178
+ const depError = currentResult?.error ? `: ${currentResult.error}` : "";
179
+ for (const dependent of dependents) {
180
+ const result = {
181
+ status: "skipped",
182
+ message: `Skipped because dependency '${currentName}' failed${depError}`,
183
+ };
184
+ if (this.internalMarkSkipped(dependent, result)) {
185
+ queue.push(dependent.name);
186
+ }
187
+ }
188
+ }
133
189
  }
134
190
  }
135
191
  //# sourceMappingURL=TaskStateManager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAUP;IATZ,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IACxC,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC7C,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,0BAA0B;IAClB,eAAe,GAAG,IAAI,GAAG,EAAgC,CAAC;IAC1D,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,UAAU,GAAyB,EAAE,CAAC;IAE9C,YAAoB,QAA4B;QAA5B,aAAQ,GAAR,QAAQ,CAAoB;IAAG,CAAC;IAEpD;;;OAGG;IACH,UAAU,CAAC,KAA2B;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QAErB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAE9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;YACrC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAElD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBACnC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;oBACpC,CAAC;oBACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5C,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,mBAAmB;QACjB,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QACnC,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QAErB,2EAA2E;QAC3E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,IAAwB;QAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,IAAwB,EAAE,MAAkB;QACxD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAEhD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,IAAwB,EAAE,MAAkB;QACtD,IAAI,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,mBAAmB,CAAC,IAAwB,EAAE,MAAkB;QACtE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAe;QAC9B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC,oBAAoB;QAE1C,2CAA2C;QAC3C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjE,MAAM,MAAM,GAAe;oBACzB,MAAM,EAAE,WAAW;oBACnB,OAAO;iBACR,CAAC;gBACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QACD,uDAAuD;QACvD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,QAAgB;QACpC,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAE,CAAC;YAChE,MAAM,QAAQ,GAAG,YAAY,GAAG,CAAC,CAAC;YAClC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAEpD,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,4CAA4C;gBAC5C,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAClC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,cAAsB;QAC3C,MAAM,KAAK,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/B,uFAAuF;QACvF,2EAA2E;QAE3E,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;YACnC,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAEzD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAE1B,uFAAuF;YACvF,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAExE,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,MAAM,MAAM,GAAe;oBACzB,MAAM,EAAE,SAAS;oBACjB,OAAO,EAAE,+BAA+B,WAAW,WAAW,QAAQ,EAAE;iBACzE,CAAC;gBAEF,IAAI,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;oBAChD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -1,4 +1,5 @@
1
1
  import { ExecutionConstants } from "./ExecutionConstants.js";
2
+ import { PriorityQueue } from "./utils/PriorityQueue.js";
2
3
  /**
3
4
  * Handles the execution of the workflow steps.
4
5
  * @template TContext The shape of the shared context object.
@@ -9,7 +10,7 @@ export class WorkflowExecutor {
9
10
  stateManager;
10
11
  strategy;
11
12
  concurrency;
12
- readyQueue = [];
13
+ readyQueue = new PriorityQueue();
13
14
  /**
14
15
  * @param context The shared context object.
15
16
  * @param eventBus The event bus to emit events.
@@ -91,17 +92,15 @@ export class WorkflowExecutor {
91
92
  const newlyReady = this.stateManager.processDependencies();
92
93
  // Add newly ready tasks to the queue
93
94
  for (const task of newlyReady) {
94
- this.readyQueue.push(task);
95
+ this.readyQueue.push(task, task.priority ?? 0);
95
96
  }
96
- // Sort by priority (descending) once after adding new tasks
97
- this.readyQueue.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
98
97
  // Execute ready tasks while respecting concurrency limit
99
- while (this.readyQueue.length > 0) {
98
+ while (!this.readyQueue.isEmpty()) {
100
99
  if (this.concurrency !== undefined &&
101
100
  executingPromises.size >= this.concurrency) {
102
101
  break;
103
102
  }
104
- const step = this.readyQueue.shift();
103
+ const step = this.readyQueue.pop();
105
104
  const taskPromise = this.executeTaskStep(step, signal)
106
105
  .finally(() => {
107
106
  executingPromises.delete(taskPromise);
@@ -155,18 +154,26 @@ export class WorkflowExecutor {
155
154
  return;
156
155
  }
157
156
  this.stateManager.markRunning(step);
157
+ const startTime = performance.now();
158
+ let result;
158
159
  try {
159
- const result = await this.strategy.execute(step, this.context, signal);
160
- this.stateManager.markCompleted(step, result);
160
+ result = await this.strategy.execute(step, this.context, signal);
161
161
  }
162
162
  catch (error) {
163
- const result = {
163
+ result = {
164
164
  status: "failure",
165
165
  message: ExecutionConstants.EXECUTION_STRATEGY_FAILED,
166
166
  error: error instanceof Error ? error.message : String(error),
167
167
  };
168
- this.stateManager.markCompleted(step, result);
169
168
  }
169
+ const endTime = performance.now();
170
+ // Always inject metrics to ensure accuracy
171
+ result.metrics = {
172
+ startTime,
173
+ endTime,
174
+ duration: endTime - startTime,
175
+ };
176
+ this.stateManager.markCompleted(step, result);
170
177
  }
171
178
  }
172
179
  //# sourceMappingURL=WorkflowExecutor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAWjB;IACA;IACA;IACA;IACA;IAdF,UAAU,GAAyB,EAAE,CAAC;IAE9C;;;;;;OAMG;IACH,YACU,OAAiB,EACjB,QAA4B,EAC5B,YAAwC,EACxC,QAAsC,EACtC,WAAoB;QAJpB,YAAO,GAAP,OAAO,CAAU;QACjB,aAAQ,GAAR,QAAQ,CAAoB;QAC5B,iBAAY,GAAZ,YAAY,CAA4B;QACxC,aAAQ,GAAR,QAAQ,CAA8B;QACtC,gBAAW,GAAX,WAAW,CAAS;IAC3B,CAAC;IAEJ;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,KAA2B,EAC3B,MAAoB;QAEpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAEpC,2BAA2B;QAC3B,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAChC,kBAAkB,CAAC,sBAAsB,CAC1C,CAAC;YACF,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YACtE,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAiB,CAAC;QAEnD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,sCAAsC;YACtC,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;QAC5E,CAAC,CAAC;QAEF,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC;YACH,eAAe;YACf,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAE5C,OACE,IAAI,CAAC,YAAY,CAAC,eAAe,EAAE;gBACnC,IAAI,CAAC,YAAY,CAAC,eAAe,EAAE;gBACnC,iBAAiB,CAAC,IAAI,GAAG,CAAC,EAC1B,CAAC;gBACD,yEAAyE;gBACzE,8DAA8D;gBAC9D,2EAA2E;gBAC3E,IAAI,iBAAiB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBACjC,MAAM;gBACR,CAAC;qBAAM,CAAC;oBACN,mCAAmC;oBACnC,MAAM,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;gBACxC,CAAC;gBAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;oBACpB,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAChC,kBAAkB,CAAC,kBAAkB,CACtC,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,4CAA4C;oBAC5C,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YAED,iEAAiE;YACjE,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;YAE1E,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;YACtE,OAAO,OAAO,CAAC;QACjB,CAAC;gBAAS,CAAC;YACT,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,WAAW,CACjB,iBAAqC,EACrC,MAAoB;QAEpB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,mBAAmB,EAAE,CAAC;QAE3D,qCAAqC;QACrC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QAED,4DAA4D;QAC5D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;QAEtE,yDAAyD;QACzD,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,IACE,IAAI,CAAC,WAAW,KAAK,SAAS;gBAC9B,iBAAiB,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAC1C,CAAC;gBACD,MAAM;YACR,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAG,CAAC;YAEtC,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC;iBACnD,OAAO,CAAC,GAAG,EAAE;gBACZ,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBACtC,2CAA2C;gBAC3C,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC;YAEL,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,eAAe,CAC3B,IAAwB,EACxB,MAAoB;QAEpB,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC3C,MAAM,SAAS,GAAG,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;gBAEjE,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;oBACpB,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE;wBACpC,MAAM,EAAE,WAAW;wBACnB,OAAO,EAAE,kBAAkB,CAAC,0BAA0B;qBACvD,CAAC,CAAC;oBACH,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,MAAM,MAAM,GAAe;wBACzB,MAAM,EAAE,SAAS;wBACjB,OAAO,EAAE,kBAAkB,CAAC,oBAAoB;qBACjD,CAAC;oBACF,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAC5C,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAe;gBACzB,MAAM,EAAE,SAAS;gBACjB,OAAO,EACL,KAAK,YAAY,KAAK;oBACpB,CAAC,CAAC,KAAK,CAAC,OAAO;oBACf,CAAC,CAAC,kBAAkB,CAAC,2BAA2B;gBACpD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;YACF,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE;gBACpC,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,kBAAkB,CAAC,2BAA2B;aACxD,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAEpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACvE,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAe;gBACzB,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,kBAAkB,CAAC,yBAAyB;gBACrD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;YACF,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"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"}
@@ -12,5 +12,5 @@ export declare class DryRunExecutionStrategy<TContext> implements IExecutionStra
12
12
  * @param signal Optional abort signal (ignored).
13
13
  * @returns A promise resolving to a success result.
14
14
  */
15
- execute(_step: TaskStep<TContext>, _context: TContext, _signal?: AbortSignal): Promise<TaskResult>;
15
+ execute(step: TaskStep<TContext>, _context: TContext, _signal?: AbortSignal): Promise<TaskResult>;
16
16
  }
@@ -9,16 +9,14 @@ export class DryRunExecutionStrategy {
9
9
  * @param signal Optional abort signal (ignored).
10
10
  * @returns A promise resolving to a success result.
11
11
  */
12
- async execute(
13
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
- _step,
12
+ async execute(step,
15
13
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
14
  _context,
17
15
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
18
16
  _signal) {
19
17
  return Promise.resolve({
20
18
  status: "success",
21
- message: "Dry run: simulated success",
19
+ message: "Dry run: simulated success " + step.name,
22
20
  });
23
21
  }
24
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DryRunExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/DryRunExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAGlC;;;;;;OAMG;IACH,KAAK,CAAC,OAAO;IACX,6DAA6D;IAC7D,KAAyB;IACzB,6DAA6D;IAC7D,QAAkB;IAClB,6DAA6D;IAC7D,OAAqB;QAErB,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,4BAA4B;SACtC,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"DryRunExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/DryRunExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,uBAAuB;IAGlC;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,IAAwB;IACxB,6DAA6D;IAC7D,QAAkB;IAClB,6DAA6D;IAC7D,OAAqB;QAErB,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAAC,IAAI;SACnD,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ export declare class PriorityQueue<T> {
2
+ private heap;
3
+ private sequenceCounter;
4
+ push(item: T, priority: number): void;
5
+ pop(): T | undefined;
6
+ peek(): T | undefined;
7
+ size(): number;
8
+ isEmpty(): boolean;
9
+ clear(): void;
10
+ private bubbleUp;
11
+ private sinkDown;
12
+ private compare;
13
+ }
@@ -0,0 +1,82 @@
1
+ export class PriorityQueue {
2
+ heap = [];
3
+ sequenceCounter = 0;
4
+ push(item, priority) {
5
+ const node = { item, priority, sequenceId: this.sequenceCounter++ };
6
+ this.heap.push(node);
7
+ this.bubbleUp();
8
+ }
9
+ pop() {
10
+ if (this.heap.length === 0)
11
+ return undefined;
12
+ if (this.heap.length === 1)
13
+ return this.heap.pop().item;
14
+ const top = this.heap[0];
15
+ this.heap[0] = this.heap.pop();
16
+ this.sinkDown();
17
+ return top.item;
18
+ }
19
+ peek() {
20
+ return this.heap[0]?.item;
21
+ }
22
+ size() {
23
+ return this.heap.length;
24
+ }
25
+ isEmpty() {
26
+ return this.heap.length === 0;
27
+ }
28
+ clear() {
29
+ this.heap = [];
30
+ this.sequenceCounter = 0;
31
+ }
32
+ bubbleUp() {
33
+ let index = this.heap.length - 1;
34
+ const element = this.heap[index];
35
+ while (index > 0) {
36
+ const parentIndex = Math.floor((index - 1) / 2);
37
+ const parent = this.heap[parentIndex];
38
+ if (this.compare(element, parent) <= 0)
39
+ break;
40
+ this.heap[index] = parent;
41
+ this.heap[parentIndex] = element;
42
+ index = parentIndex;
43
+ }
44
+ }
45
+ sinkDown() {
46
+ let index = 0;
47
+ const length = this.heap.length;
48
+ const element = this.heap[0];
49
+ while (true) {
50
+ const leftChildIndex = 2 * index + 1;
51
+ const rightChildIndex = 2 * index + 2;
52
+ let swapIndex = null;
53
+ if (leftChildIndex < length) {
54
+ if (this.compare(this.heap[leftChildIndex], element) > 0) {
55
+ swapIndex = leftChildIndex;
56
+ }
57
+ }
58
+ if (rightChildIndex < length) {
59
+ const rightChild = this.heap[rightChildIndex];
60
+ const leftChild = this.heap[leftChildIndex];
61
+ if ((swapIndex === null && this.compare(rightChild, element) > 0) ||
62
+ (swapIndex !== null && this.compare(rightChild, leftChild) > 0)) {
63
+ swapIndex = rightChildIndex;
64
+ }
65
+ }
66
+ if (swapIndex === null)
67
+ break;
68
+ this.heap[index] = this.heap[swapIndex];
69
+ this.heap[swapIndex] = element;
70
+ index = swapIndex;
71
+ }
72
+ }
73
+ // Returns positive if a > b (a should come before b)
74
+ compare(a, b) {
75
+ if (a.priority !== b.priority) {
76
+ return a.priority - b.priority;
77
+ }
78
+ // Lower sequenceId means earlier insertion, so it has higher priority
79
+ return b.sequenceId - a.sequenceId;
80
+ }
81
+ }
82
+ //# sourceMappingURL=PriorityQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriorityQueue.js","sourceRoot":"","sources":["../../src/utils/PriorityQueue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ # Change: Add Resource-Based Concurrency Control
2
+
3
+ ## Why
4
+
5
+ Global concurrency limits (`concurrency: 5`) are too blunt for workflows accessing heterogeneous resources. A workflow might bottleneck on a slow API (e.g., Jira) while underutilizing a fast one (e.g., Redis). Users need to define concurrency limits *per resource type* (e.g., "max 2 Jira requests", "max 50 DB writes") to optimize throughput without overloading specific downstream services.
6
+
7
+ ## What Changes
8
+
9
+ - **TaskStep Interface**: Add `resources` property (e.g., `{ cpu: 1, github_api: 1 }`).
10
+ - **TaskRunnerExecutionConfig**: Add `resourceLimits` property (e.g., `{ github_api: 5 }`).
11
+ - **WorkflowExecutor**: Update the `processLoop` to check if *both* global concurrency AND specific resource limits are satisfied before starting a task.
12
+ - **State Management**: Track currently consumed resources in `WorkflowExecutor`.
13
+
14
+ ## Impact
15
+
16
+ - **Affected Specs**: `task-runner`
17
+ - **Affected Code**: `src/TaskStep.ts`, `src/TaskRunnerExecutionConfig.ts`, `src/WorkflowExecutor.ts`.
18
+ - **Breaking Changes**: None. Optional properties added.
@@ -0,0 +1,25 @@
1
+ ## ADDED Requirements
2
+
3
+ ### Requirement: Resource-Based Concurrency Control
4
+
5
+ The system SHALL support limiting concurrency based on abstract resources defined by tasks.
6
+
7
+ #### Scenario: Task defines resource usage
8
+ - **GIVEN** a `TaskStep` with `resources: { "api_call": 1 }`
9
+ - **WHEN** the task is executed
10
+ - **THEN** the system SHALL account for 1 unit of "api_call" consumption.
11
+
12
+ #### Scenario: Execution limited by resource availability
13
+ - **GIVEN** `resourceLimits` is configured with `{ "db_connection": 2 }`
14
+ - **AND** 3 tasks are ready, each requiring 1 "db_connection"
15
+ - **WHEN** execution proceeds
16
+ - **THEN** only 2 tasks SHALL execute concurrently.
17
+ - **AND** the 3rd task SHALL wait until resources are released.
18
+
19
+ #### Scenario: Global and Resource limits combined
20
+ - **GIVEN** `concurrency` is set to 5
21
+ - **AND** `resourceLimits` is `{ "heavy_job": 2 }`
22
+ - **AND** 10 tasks are ready: 5 "heavy_job" tasks and 5 "light" tasks (no resources)
23
+ - **WHEN** execution proceeds
24
+ - **THEN** at most 2 "heavy_job" tasks SHALL run.
25
+ - **AND** up to 3 "light" tasks MAY run concurrently (totaling 5).
@@ -0,0 +1,9 @@
1
+ ## 1. Implementation
2
+
3
+ - [ ] 1.1 Update `TaskStep` interface in `src/TaskStep.ts` to include `resources?: Record<string, number>`.
4
+ - [ ] 1.2 Update `TaskRunnerExecutionConfig` in `src/TaskRunnerExecutionConfig.ts` to include `resourceLimits?: Record<string, number>`.
5
+ - [ ] 1.3 Update `WorkflowExecutor` to track active resource usage.
6
+ - [ ] 1.4 Update `WorkflowExecutor.processLoop` to validate resource availability before scheduling tasks.
7
+ - [ ] 1.5 Update `WorkflowExecutor.executeTaskStep` (or equivalent completion logic) to release resources when a task finishes.
8
+ - [ ] 1.6 Add unit tests for resource limiting in `tests/WorkflowExecutor.test.ts`.
9
+ - [ ] 1.7 Add integration test for mixed resource usage in `tests/integration/resource-concurrency.test.ts`.
@@ -7,7 +7,7 @@ Users currently lack visibility into the performance of individual tasks within
7
7
  ## What Changes
8
8
 
9
9
  - Update `TaskResult` interface to include an optional `metrics` property containing `startTime`, `endTime`, and `duration`.
10
- - Update `WorkflowExecutor` to capture these timestamps during task execution and populate the `metrics` property.
10
+ - Update `WorkflowExecutor` to capture these timestamps using `performance.now()` for high-precision timing during task execution and populate the `metrics` property.
11
11
  - Ensure these metrics are available in the final `TaskResult` map returned by `TaskRunner.execute`.
12
12
 
13
13
  ## Impact
@@ -0,0 +1,6 @@
1
+ ## 1. Implementation
2
+
3
+ - [x] 1.1 Update `TaskResult` interface in `src/TaskResult.ts` to include `metrics`.
4
+ - [x] 1.2 Update `WorkflowExecutor.ts` to capture start/end times using `performance.now()` and calculate duration.
5
+ - [x] 1.3 Update `WorkflowExecutor.ts` to inject metrics into the `TaskResult`.
6
+ - [x] 1.4 Add unit tests in `tests/TaskMetrics.test.ts` to verify metrics are present and correct.
@@ -0,0 +1,18 @@
1
+ # Change: Conditional Retries
2
+
3
+ ## Why
4
+
5
+ Currently, the `RetryingExecutionStrategy` treats all task failures equally. If a task has retry attempts configured, it will blindly retry even if the error is permanent (e.g., syntax error, invalid configuration) or logic-based (e.g., validation failure). This wastes resources and execution time. Users need a way to specify *which* errors should trigger a retry, allowing them to fail fast on critical errors while retrying on transient ones (e.g., network timeouts).
6
+
7
+ ## What Changes
8
+
9
+ - Update `TaskRetryConfig` interface in `src/contracts/TaskRetryConfig.ts` to include an optional `shouldRetry` predicate.
10
+ - Update `RetryingExecutionStrategy` in `src/strategies/RetryingExecutionStrategy.ts` to evaluate this predicate (if present) before deciding to retry.
11
+ - If `shouldRetry` returns `false`, the retry loop is broken immediately, and the failure result is returned.
12
+ - If `shouldRetry` is undefined, the existing behavior (retry on any failure) is preserved.
13
+
14
+ ## Impact
15
+
16
+ - **Affected specs**: `001-generic-task-runner` (or simply `task-runner`)
17
+ - **Affected code**: `src/contracts/TaskRetryConfig.ts`, `src/strategies/RetryingExecutionStrategy.ts`
18
+ - **Non-breaking change**: The `shouldRetry` property is optional. Existing retry configurations will continue to work as before.
@@ -0,0 +1,23 @@
1
+ ## ADDED Requirements
2
+
3
+ ### Requirement: Conditional Retries
4
+
5
+ The system SHALL support conditional retries where a user-defined predicate determines if a task failure warrants a retry attempt.
6
+
7
+ #### Scenario: Retry allowed by predicate
8
+ - **GIVEN** a task configured with retries and a `shouldRetry` predicate
9
+ - **WHEN** the task fails with an error
10
+ - **AND** the `shouldRetry` predicate returns `true` for that error
11
+ - **THEN** the system SHALL proceed with the retry logic (respecting attempt limits and delays).
12
+
13
+ #### Scenario: Retry denied by predicate
14
+ - **GIVEN** a task configured with retries and a `shouldRetry` predicate
15
+ - **WHEN** the task fails with an error
16
+ - **AND** the `shouldRetry` predicate returns `false` for that error
17
+ - **THEN** the system SHALL NOT retry the task.
18
+ - **AND** the task status SHALL be immediately marked as 'failure'.
19
+
20
+ #### Scenario: Default retry behavior
21
+ - **GIVEN** a task configured with retries but NO `shouldRetry` predicate
22
+ - **WHEN** the task fails with an error
23
+ - **THEN** the system SHALL retry the task (respecting attempt limits and delays), preserving backward compatibility.
@@ -0,0 +1,37 @@
1
+ # Engineering Tasks
2
+
3
+ - [ ] **Task 1: Update TaskRetryConfig Interface**
4
+ - Modify `src/contracts/TaskRetryConfig.ts`.
5
+ - Add `shouldRetry?: (error: unknown) => boolean;` to the `TaskRetryConfig` interface.
6
+ - Document the property with JSDoc explaining its purpose (return true to retry, false to abort).
7
+
8
+ - [ ] **Task 2: Update RetryingExecutionStrategy Logic**
9
+ - Modify `src/strategies/RetryingExecutionStrategy.ts`.
10
+ - Inside the `execute` loop, after receiving a "failure" result:
11
+ - Check if `config.shouldRetry` is defined.
12
+ - If defined, call it with `result.error`.
13
+ - If it returns `false`, break the loop and return the failure result immediately.
14
+ - If it returns `true` (or is undefined), proceed with the existing retry logic (check attempts, delay).
15
+
16
+ - [ ] **Task 3: Unit Tests**
17
+ - Create `tests/strategies/RetryingExecutionStrategy.conditional.test.ts` (or add to existing test file if small).
18
+ - **Scenario 1**: `shouldRetry` returns `true`.
19
+ - Setup a task that fails 2 times then succeeds.
20
+ - Configure `shouldRetry: () => true`.
21
+ - Verify it retries and eventually succeeds.
22
+ - **Scenario 2**: `shouldRetry` returns `false`.
23
+ - Setup a task that fails with a specific error "FatalError".
24
+ - Configure `shouldRetry: (err) => err !== "FatalError"`.
25
+ - Verify it does *not* retry and returns failure immediately after the first attempt.
26
+ - **Scenario 3**: `shouldRetry` is undefined (Legacy behavior).
27
+ - Setup a failing task.
28
+ - Verify it retries up to max attempts.
29
+
30
+ - [ ] **Task 4: Integration Test**
31
+ - Create `tests/integration-tests/conditional-retries.test.ts`.
32
+ - Define a workflow with two tasks:
33
+ - Task A: Fails with "Transient" error (retries allowed).
34
+ - Task B: Fails with "Permanent" error (retries blocked by predicate).
35
+ - Execute workflow.
36
+ - Verify Task A consumed its retries (or succeeded).
37
+ - Verify Task B failed immediately (did not consume retries).