@calmo/task-runner 1.2.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.jules/sentinel.md +4 -0
  2. package/AGENTS.md +53 -1
  3. package/CHANGELOG.md +41 -15
  4. package/README.md +1 -1
  5. package/coverage/coverage-final.json +8 -4
  6. package/coverage/index.html +24 -9
  7. package/coverage/lcov-report/index.html +24 -9
  8. package/coverage/lcov-report/src/EventBus.ts.html +8 -8
  9. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +143 -62
  10. package/coverage/lcov-report/src/TaskRunner.ts.html +213 -21
  11. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +313 -0
  12. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +124 -0
  13. package/coverage/lcov-report/src/TaskStateManager.ts.html +511 -0
  14. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +125 -137
  15. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  16. package/coverage/lcov-report/src/contracts/index.html +1 -1
  17. package/coverage/lcov-report/src/index.html +59 -14
  18. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  19. package/coverage/lcov-report/src/strategies/index.html +116 -0
  20. package/coverage/lcov.info +383 -184
  21. package/coverage/src/EventBus.ts.html +8 -8
  22. package/coverage/src/TaskGraphValidator.ts.html +143 -62
  23. package/coverage/src/TaskRunner.ts.html +213 -21
  24. package/coverage/src/TaskRunnerBuilder.ts.html +313 -0
  25. package/coverage/src/TaskRunnerExecutionConfig.ts.html +124 -0
  26. package/coverage/src/TaskStateManager.ts.html +511 -0
  27. package/coverage/src/WorkflowExecutor.ts.html +125 -137
  28. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  29. package/coverage/src/contracts/index.html +1 -1
  30. package/coverage/src/index.html +59 -14
  31. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  32. package/coverage/src/strategies/index.html +116 -0
  33. package/dist/TaskGraphValidator.js +39 -16
  34. package/dist/TaskGraphValidator.js.map +1 -1
  35. package/dist/TaskRunner.d.ts +12 -2
  36. package/dist/TaskRunner.js +55 -3
  37. package/dist/TaskRunner.js.map +1 -1
  38. package/dist/TaskRunnerBuilder.d.ts +33 -0
  39. package/dist/TaskRunnerBuilder.js +54 -0
  40. package/dist/TaskRunnerBuilder.js.map +1 -0
  41. package/dist/TaskRunnerExecutionConfig.d.ts +13 -0
  42. package/dist/TaskRunnerExecutionConfig.js +2 -0
  43. package/dist/TaskRunnerExecutionConfig.js.map +1 -0
  44. package/dist/TaskStateManager.d.ts +54 -0
  45. package/dist/TaskStateManager.js +130 -0
  46. package/dist/TaskStateManager.js.map +1 -0
  47. package/dist/TaskStatus.d.ts +1 -1
  48. package/dist/TaskStep.d.ts +2 -1
  49. package/dist/WorkflowExecutor.d.ts +11 -10
  50. package/dist/WorkflowExecutor.js +64 -73
  51. package/dist/WorkflowExecutor.js.map +1 -1
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +3 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/strategies/IExecutionStrategy.d.ts +16 -0
  56. package/dist/strategies/IExecutionStrategy.js +2 -0
  57. package/dist/strategies/IExecutionStrategy.js.map +1 -0
  58. package/dist/strategies/StandardExecutionStrategy.d.ts +9 -0
  59. package/dist/strategies/StandardExecutionStrategy.js +25 -0
  60. package/dist/strategies/StandardExecutionStrategy.js.map +1 -0
  61. package/openspec/changes/add-concurrency-control/proposal.md +14 -0
  62. package/openspec/changes/add-concurrency-control/tasks.md +9 -0
  63. package/openspec/changes/add-task-retry-policy/proposal.md +13 -0
  64. package/openspec/changes/add-task-retry-policy/tasks.md +10 -0
  65. package/openspec/changes/add-workflow-preview/proposal.md +12 -0
  66. package/openspec/changes/add-workflow-preview/tasks.md +7 -0
  67. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +10 -0
  68. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +18 -0
  69. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +17 -0
  70. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +14 -0
  71. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +8 -0
  72. package/package.json +1 -1
  73. package/src/TaskGraphValidator.ts +46 -19
  74. package/src/TaskRunner.ts +68 -4
  75. package/src/TaskRunnerBuilder.ts +76 -0
  76. package/src/TaskRunnerExecutionConfig.ts +13 -0
  77. package/src/TaskStateManager.ts +142 -0
  78. package/src/TaskStatus.ts +1 -1
  79. package/src/TaskStep.ts +2 -1
  80. package/src/WorkflowExecutor.ts +75 -79
  81. package/src/index.ts +4 -0
  82. package/src/strategies/IExecutionStrategy.ts +21 -0
  83. package/src/strategies/StandardExecutionStrategy.ts +35 -0
  84. package/test-report.xml +121 -43
  85. package/GEMINI.md +0 -46
  86. package/openspec/changes/add-external-task-cancellation/tasks.md +0 -10
  87. /package/openspec/changes/{add-external-task-cancellation → archive/2026-01-18-add-external-task-cancellation}/proposal.md +0 -0
@@ -0,0 +1,54 @@
1
+ import { TaskStep } from "./TaskStep.js";
2
+ import { TaskResult } from "./TaskResult.js";
3
+ import { EventBus } from "./EventBus.js";
4
+ /**
5
+ * Manages the state of the task execution, including results, pending steps, and running tasks.
6
+ * Handles dependency resolution and event emission for state changes.
7
+ */
8
+ export declare class TaskStateManager<TContext> {
9
+ private eventBus;
10
+ private results;
11
+ private pendingSteps;
12
+ private running;
13
+ constructor(eventBus: EventBus<TContext>);
14
+ /**
15
+ * Initializes the state with the given steps.
16
+ * @param steps The steps to execute.
17
+ */
18
+ initialize(steps: TaskStep<TContext>[]): void;
19
+ /**
20
+ * Processes the pending steps to identify tasks that can be started or must be skipped.
21
+ * Emits `taskSkipped` for skipped tasks.
22
+ * @returns An array of tasks that are ready to run.
23
+ */
24
+ processDependencies(): TaskStep<TContext>[];
25
+ /**
26
+ * Marks a task as running and emits `taskStart`.
27
+ * @param step The task that is starting.
28
+ */
29
+ markRunning(step: TaskStep<TContext>): void;
30
+ /**
31
+ * Marks a task as completed (success, failure, or cancelled during execution)
32
+ * and emits `taskEnd`.
33
+ * @param step The task that completed.
34
+ * @param result The result of the task.
35
+ */
36
+ markCompleted(step: TaskStep<TContext>, result: TaskResult): void;
37
+ /**
38
+ * Cancels all pending tasks that haven't started yet.
39
+ * @param message The cancellation message.
40
+ */
41
+ cancelAllPending(message: string): void;
42
+ /**
43
+ * Returns the current results map.
44
+ */
45
+ getResults(): Map<string, TaskResult>;
46
+ /**
47
+ * Checks if there are any tasks currently running.
48
+ */
49
+ hasRunningTasks(): boolean;
50
+ /**
51
+ * Checks if there are any pending tasks.
52
+ */
53
+ hasPendingTasks(): boolean;
54
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Manages the state of the task execution, including results, pending steps, and running tasks.
3
+ * Handles dependency resolution and event emission for state changes.
4
+ */
5
+ export class TaskStateManager {
6
+ eventBus;
7
+ results = new Map();
8
+ pendingSteps = new Set();
9
+ running = new Set();
10
+ constructor(eventBus) {
11
+ this.eventBus = eventBus;
12
+ }
13
+ /**
14
+ * Initializes the state with the given steps.
15
+ * @param steps The steps to execute.
16
+ */
17
+ initialize(steps) {
18
+ this.pendingSteps = new Set(steps);
19
+ this.results.clear();
20
+ this.running.clear();
21
+ }
22
+ /**
23
+ * Processes the pending steps to identify tasks that can be started or must be skipped.
24
+ * Emits `taskSkipped` for skipped tasks.
25
+ * @returns An array of tasks that are ready to run.
26
+ */
27
+ 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.results.set(step.name, result);
53
+ this.eventBus.emit("taskSkipped", { step, result });
54
+ toRemove.push(step);
55
+ }
56
+ else if (!blocked) {
57
+ toRun.push(step);
58
+ toRemove.push(step);
59
+ }
60
+ }
61
+ // Cleanup pending set
62
+ for (const step of toRemove) {
63
+ this.pendingSteps.delete(step);
64
+ }
65
+ return toRun;
66
+ }
67
+ /**
68
+ * Marks a task as running and emits `taskStart`.
69
+ * @param step The task that is starting.
70
+ */
71
+ markRunning(step) {
72
+ this.running.add(step.name);
73
+ this.eventBus.emit("taskStart", { step });
74
+ }
75
+ /**
76
+ * Marks a task as completed (success, failure, or cancelled during execution)
77
+ * and emits `taskEnd`.
78
+ * @param step The task that completed.
79
+ * @param result The result of the task.
80
+ */
81
+ markCompleted(step, result) {
82
+ this.running.delete(step.name);
83
+ this.results.set(step.name, result);
84
+ this.eventBus.emit("taskEnd", { step, result });
85
+ }
86
+ /**
87
+ * Cancels all pending tasks that haven't started yet.
88
+ * @param message The cancellation message.
89
+ */
90
+ cancelAllPending(message) {
91
+ // Iterate over pendingSteps to cancel them
92
+ for (const step of this.pendingSteps) {
93
+ // Also check running? No, running tasks are handled by AbortSignal in Executor.
94
+ // We only cancel what is pending and hasn't started.
95
+ /* v8 ignore next 1 */
96
+ if (!this.results.has(step.name) && !this.running.has(step.name)) {
97
+ const result = {
98
+ status: "cancelled",
99
+ message,
100
+ };
101
+ this.results.set(step.name, result);
102
+ }
103
+ }
104
+ // Clear pending set as they are now "done" (cancelled)
105
+ // Wait, if we clear pending steps, processDependencies won't pick them up.
106
+ // The loop in Executor relies on results.size or pendingSteps.
107
+ // The previous implementation iterated `steps` (all steps) to cancel.
108
+ // Here we iterate `pendingSteps`.
109
+ this.pendingSteps.clear();
110
+ }
111
+ /**
112
+ * Returns the current results map.
113
+ */
114
+ getResults() {
115
+ return this.results;
116
+ }
117
+ /**
118
+ * Checks if there are any tasks currently running.
119
+ */
120
+ hasRunningTasks() {
121
+ return this.running.size > 0;
122
+ }
123
+ /**
124
+ * Checks if there are any pending tasks.
125
+ */
126
+ hasPendingTasks() {
127
+ return this.pendingSteps.size > 0;
128
+ }
129
+ }
130
+ //# sourceMappingURL=TaskStateManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Represents the completion status of a task.
3
3
  */
4
- export type TaskStatus = "success" | "failure" | "skipped";
4
+ export type TaskStatus = "success" | "failure" | "skipped" | "cancelled";
@@ -11,7 +11,8 @@ export interface TaskStep<TContext> {
11
11
  /**
12
12
  * The core logic of the task.
13
13
  * @param context The shared context object, allowing for state to be passed between tasks.
14
+ * @param signal An optional AbortSignal to listen for cancellation.
14
15
  * @returns A Promise that resolves to a TaskResult.
15
16
  */
16
- run(context: TContext): Promise<TaskResult>;
17
+ run(context: TContext, signal?: AbortSignal): Promise<TaskResult>;
17
18
  }
@@ -1,6 +1,8 @@
1
1
  import { TaskStep } from "./TaskStep.js";
2
2
  import { TaskResult } from "./TaskResult.js";
3
3
  import { EventBus } from "./EventBus.js";
4
+ import { TaskStateManager } from "./TaskStateManager.js";
5
+ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
4
6
  /**
5
7
  * Handles the execution of the workflow steps.
6
8
  * @template TContext The shape of the shared context object.
@@ -8,25 +10,24 @@ import { EventBus } from "./EventBus.js";
8
10
  export declare class WorkflowExecutor<TContext> {
9
11
  private context;
10
12
  private eventBus;
11
- private running;
13
+ private stateManager;
14
+ private strategy;
12
15
  /**
13
16
  * @param context The shared context object.
14
17
  * @param eventBus The event bus to emit events.
18
+ * @param stateManager Manages execution state.
19
+ * @param strategy Execution strategy.
15
20
  */
16
- constructor(context: TContext, eventBus: EventBus<TContext>);
21
+ constructor(context: TContext, eventBus: EventBus<TContext>, stateManager: TaskStateManager<TContext>, strategy: IExecutionStrategy<TContext>);
17
22
  /**
18
23
  * Executes the given steps.
19
24
  * @param steps The list of steps to execute.
25
+ * @param signal Optional AbortSignal for cancellation.
20
26
  * @returns A Promise that resolves to a map of task results.
21
27
  */
22
- execute(steps: TaskStep<TContext>[]): Promise<Map<string, TaskResult>>;
28
+ execute(steps: TaskStep<TContext>[], signal?: AbortSignal): Promise<Map<string, TaskResult>>;
23
29
  /**
24
- * Logic to identify tasks that can be started or must be skipped.
25
- * Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
30
+ * Logic to identify tasks that can be started and run them.
26
31
  */
27
- private processQueue;
28
- /**
29
- * Handles the lifecycle of a single task execution.
30
- */
31
- private runStep;
32
+ private processLoop;
32
33
  }
@@ -5,105 +5,96 @@
5
5
  export class WorkflowExecutor {
6
6
  context;
7
7
  eventBus;
8
- running = new Set();
8
+ stateManager;
9
+ strategy;
9
10
  /**
10
11
  * @param context The shared context object.
11
12
  * @param eventBus The event bus to emit events.
13
+ * @param stateManager Manages execution state.
14
+ * @param strategy Execution strategy.
12
15
  */
13
- constructor(context, eventBus) {
16
+ constructor(context, eventBus, stateManager, strategy) {
14
17
  this.context = context;
15
18
  this.eventBus = eventBus;
19
+ this.stateManager = stateManager;
20
+ this.strategy = strategy;
16
21
  }
17
22
  /**
18
23
  * Executes the given steps.
19
24
  * @param steps The list of steps to execute.
25
+ * @param signal Optional AbortSignal for cancellation.
20
26
  * @returns A Promise that resolves to a map of task results.
21
27
  */
22
- async execute(steps) {
28
+ async execute(steps, signal) {
23
29
  this.eventBus.emit("workflowStart", { context: this.context, steps });
24
- const results = new Map();
30
+ this.stateManager.initialize(steps);
31
+ // Check if already aborted
32
+ if (signal?.aborted) {
33
+ this.stateManager.cancelAllPending("Workflow cancelled before execution started.");
34
+ const results = this.stateManager.getResults();
35
+ this.eventBus.emit("workflowEnd", { context: this.context, results });
36
+ return results;
37
+ }
25
38
  const executingPromises = new Set();
26
- const pendingSteps = new Set(steps);
27
- // Initial pass
28
- this.processQueue(pendingSteps, results, executingPromises);
29
- while (results.size < steps.length && executingPromises.size > 0) {
30
- // Wait for the next task to finish
31
- await Promise.race(executingPromises);
32
- // After a task finishes, check for new work
33
- this.processQueue(pendingSteps, results, executingPromises);
39
+ const onAbort = () => {
40
+ // Mark all pending tasks as cancelled
41
+ this.stateManager.cancelAllPending("Workflow cancelled.");
42
+ };
43
+ if (signal) {
44
+ signal.addEventListener("abort", onAbort);
34
45
  }
35
- this.eventBus.emit("workflowEnd", { context: this.context, results });
36
- return results;
37
- }
38
- /**
39
- * Logic to identify tasks that can be started or must be skipped.
40
- * Iterate only over pending steps to avoid O(N^2) checks on completed tasks.
41
- */
42
- processQueue(pendingSteps, results, executingPromises) {
43
- const toRemove = [];
44
- const toRun = [];
45
- for (const step of pendingSteps) {
46
- const deps = step.dependencies ?? [];
47
- let blocked = false;
48
- let failedDep;
49
- for (const dep of deps) {
50
- const depResult = results.get(dep);
51
- if (!depResult) {
52
- // Dependency not finished yet
53
- blocked = true;
54
- }
55
- else if (depResult.status !== "success") {
56
- failedDep = dep;
46
+ try {
47
+ // Initial pass
48
+ this.processLoop(executingPromises, signal);
49
+ while (this.stateManager.hasPendingTasks() ||
50
+ this.stateManager.hasRunningTasks()) {
51
+ // Safety check: if no tasks are running and we still have pending tasks,
52
+ // it means we are stuck (e.g. cycle or unhandled dependency).
53
+ // Since valid graphs shouldn't have this, we break to avoid infinite loop.
54
+ if (executingPromises.size === 0) {
57
55
  break;
58
56
  }
57
+ else {
58
+ // Wait for the next task to finish
59
+ await Promise.race(executingPromises);
60
+ }
61
+ if (signal?.aborted) {
62
+ this.stateManager.cancelAllPending("Workflow cancelled.");
63
+ }
64
+ else {
65
+ // After a task finishes, check for new work
66
+ this.processLoop(executingPromises, signal);
67
+ }
59
68
  }
60
- if (failedDep) {
61
- const result = {
62
- status: "skipped",
63
- message: `Skipped due to failed dependency: ${failedDep}`,
64
- };
65
- results.set(step.name, result);
66
- this.eventBus.emit("taskSkipped", { step, result });
67
- toRemove.push(step);
68
- }
69
- else if (!blocked) {
70
- toRun.push(step);
71
- toRemove.push(step);
72
- }
69
+ // Ensure everything is accounted for (e.g. if loop exited early)
70
+ this.stateManager.cancelAllPending("Workflow cancelled.");
71
+ const results = this.stateManager.getResults();
72
+ this.eventBus.emit("workflowEnd", { context: this.context, results });
73
+ return results;
73
74
  }
74
- // Cleanup pending set
75
- for (const step of toRemove) {
76
- pendingSteps.delete(step);
75
+ finally {
76
+ if (signal) {
77
+ signal.removeEventListener("abort", onAbort);
78
+ }
77
79
  }
80
+ }
81
+ /**
82
+ * Logic to identify tasks that can be started and run them.
83
+ */
84
+ processLoop(executingPromises, signal) {
85
+ const toRun = this.stateManager.processDependencies();
78
86
  // Execute ready tasks
79
87
  for (const step of toRun) {
80
- const taskPromise = this.runStep(step, results).then(() => {
88
+ this.stateManager.markRunning(step);
89
+ const taskPromise = this.strategy.execute(step, this.context, signal)
90
+ .then((result) => {
91
+ this.stateManager.markCompleted(step, result);
92
+ })
93
+ .finally(() => {
81
94
  executingPromises.delete(taskPromise);
82
95
  });
83
96
  executingPromises.add(taskPromise);
84
97
  }
85
98
  }
86
- /**
87
- * Handles the lifecycle of a single task execution.
88
- */
89
- async runStep(step, results) {
90
- this.running.add(step.name);
91
- this.eventBus.emit("taskStart", { step });
92
- try {
93
- const result = await step.run(this.context);
94
- results.set(step.name, result);
95
- }
96
- catch (e) {
97
- results.set(step.name, {
98
- status: "failure",
99
- error: e instanceof Error ? e.message : String(e),
100
- });
101
- }
102
- finally {
103
- this.running.delete(step.name);
104
- const result = results.get(step.name);
105
- this.eventBus.emit("taskEnd", { step, result });
106
- }
107
- }
108
99
  }
109
100
  //# sourceMappingURL=WorkflowExecutor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export { TaskRunner } from "./TaskRunner.js";
2
+ export { TaskRunnerBuilder } from "./TaskRunnerBuilder.js";
3
+ export { TaskStateManager } from "./TaskStateManager.js";
4
+ export { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
5
+ export type { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
2
6
  export type { TaskStep } from "./TaskStep.js";
3
7
  export type { TaskResult } from "./TaskResult.js";
4
8
  export type { TaskStatus } from "./TaskStatus.js";
package/dist/index.js CHANGED
@@ -1,2 +1,5 @@
1
1
  export { TaskRunner } from "./TaskRunner.js";
2
+ export { TaskRunnerBuilder } from "./TaskRunnerBuilder.js";
3
+ export { TaskStateManager } from "./TaskStateManager.js";
4
+ export { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
2
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { TaskStep } from "../TaskStep.js";
2
+ import { TaskResult } from "../TaskResult.js";
3
+ /**
4
+ * Interface for task execution strategies.
5
+ * Allows different execution behaviors (e.g., standard, dry-run, debugging).
6
+ */
7
+ export interface IExecutionStrategy<TContext> {
8
+ /**
9
+ * Executes a single task step.
10
+ * @param step The task step to execute.
11
+ * @param context The shared context.
12
+ * @param signal Optional abort signal.
13
+ * @returns A promise resolving to the task result.
14
+ */
15
+ execute(step: TaskStep<TContext>, context: TContext, signal?: AbortSignal): Promise<TaskResult>;
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IExecutionStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/IExecutionStrategy.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { IExecutionStrategy } from "./IExecutionStrategy.js";
2
+ import { TaskStep } from "../TaskStep.js";
3
+ import { TaskResult } from "../TaskResult.js";
4
+ /**
5
+ * Standard execution strategy that runs the task's run method.
6
+ */
7
+ export declare class StandardExecutionStrategy<TContext> implements IExecutionStrategy<TContext> {
8
+ execute(step: TaskStep<TContext>, context: TContext, signal?: AbortSignal): Promise<TaskResult>;
9
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Standard execution strategy that runs the task's run method.
3
+ */
4
+ export class StandardExecutionStrategy {
5
+ async execute(step, context, signal) {
6
+ try {
7
+ return await step.run(context, signal);
8
+ }
9
+ catch (e) {
10
+ // Check if error is due to abort
11
+ if (signal?.aborted &&
12
+ (e instanceof Error && e.name === "AbortError" || signal.reason === e)) {
13
+ return {
14
+ status: "cancelled",
15
+ message: "Task cancelled during execution",
16
+ };
17
+ }
18
+ return {
19
+ status: "failure",
20
+ error: e instanceof Error ? e.message : String(e),
21
+ };
22
+ }
23
+ }
24
+ }
25
+ //# sourceMappingURL=StandardExecutionStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StandardExecutionStrategy.js","sourceRoot":"","sources":["../../src/strategies/StandardExecutionStrategy.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,yBAAyB;IAGpC,KAAK,CAAC,OAAO,CACX,IAAwB,EACxB,OAAiB,EACjB,MAAoB;QAEpB,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,iCAAiC;YACjC,IACE,MAAM,EAAE,OAAO;gBACf,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EACtE,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,WAAW;oBACnB,OAAO,EAAE,iCAAiC;iBAC3C,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,SAAS;gBACjB,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;aAClD,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ # Change: Add Concurrency Control
2
+
3
+ ## Why
4
+ The current implementation executes *all* independent tasks in parallel. In large graphs with many independent nodes, this could overwhelm system resources (CPU, memory) or trigger external API rate limits.
5
+
6
+ ## What Changes
7
+ - Add a concurrency control mechanism to the `TaskRunner`.
8
+ - Add `concurrency: number` to the `TaskRunnerExecutionConfig`.
9
+ - Implement a task queue to hold tasks that are ready but waiting for a free slot.
10
+ - Update `WorkflowExecutor` to respect the concurrency limit.
11
+
12
+ ## Impact
13
+ - Affected specs: `task-runner`
14
+ - Affected code: `src/TaskRunner.ts`, `src/WorkflowExecutor.ts`, `src/TaskRunnerExecutionConfig.ts`
@@ -0,0 +1,9 @@
1
+ ## Implementation
2
+ - [ ] 1.1 Update `TaskRunnerExecutionConfig` to include an optional `concurrency` property.
3
+ - [ ] 1.2 Update `WorkflowExecutor` to accept the `concurrency` limit.
4
+ - [ ] 1.3 Implement a task queueing mechanism in `WorkflowExecutor` to manage pending tasks.
5
+ - [ ] 1.4 Update execution logic to check available concurrency slots before starting a task.
6
+ - [ ] 1.5 Ensure task completion triggers the execution of queued tasks.
7
+ - [ ] 1.6 Verify that unlimited concurrency (default behavior) is preserved when no limit is set.
8
+ - [ ] 1.7 Add unit tests for concurrency limits (e.g., ensure no more than N tasks run at once).
9
+ - [ ] 1.8 Add integration tests with mixed dependencies and concurrency limits.
@@ -0,0 +1,13 @@
1
+ # Change: Add Task Retry Policy
2
+
3
+ ## Why
4
+ Tasks currently run once and fail immediately if they throw an error or return a failure status. Network glitches or transient issues can therefore cause an entire workflow to fail unnecessarily.
5
+
6
+ ## What Changes
7
+ - Allow tasks to define a retry policy.
8
+ - Update `TaskStep` interface to include optional `retry` configuration.
9
+ - Implement logic in `TaskRunner` (or `WorkflowExecutor`) to catch failures, check the retry policy, and re-execute the task after the specified delay.
10
+
11
+ ## Impact
12
+ - Affected specs: `task-runner`
13
+ - Affected code: `src/TaskStep.ts`, `src/WorkflowExecutor.ts` (or `TaskRunner.ts`), `src/contracts/TaskRetryConfig.ts`
@@ -0,0 +1,10 @@
1
+ ## Implementation
2
+ - [ ] 1.1 Create `TaskRetryConfig` interface with `attempts`, `delay`, and `backoff`.
3
+ - [ ] 1.2 Update `TaskStep` interface to include optional `retry: TaskRetryConfig`.
4
+ - [ ] 1.3 Update execution logic to catch task failures.
5
+ - [ ] 1.4 Implement retry loop/recursion checking `attempts` count.
6
+ - [ ] 1.5 Implement delay logic with support for `fixed` and `exponential` backoff.
7
+ - [ ] 1.6 Ensure `TaskResult` reflects the final status after retries (success if eventually succeeds, failure if all attempts fail).
8
+ - [ ] 1.7 Add unit tests for successful retry after failure.
9
+ - [ ] 1.8 Add unit tests for exhaustion of retry attempts (final failure).
10
+ - [ ] 1.9 Add unit tests for backoff timing (mock timers).
@@ -0,0 +1,12 @@
1
+ # Change: Add Workflow Preview
2
+
3
+ ## Why
4
+ It can be difficult to understand the execution flow of complex dependency graphs just by looking at the code. Users also currently cannot easily verify the execution plan without running the side effects, which carries risk.
5
+
6
+ ## What Changes
7
+ - Add a `dryRun` execution mode to `TaskRunner`.
8
+ - Add a helper method `getMermaidGraph(steps)` to generate a Mermaid.js diagram of the dependency graph.
9
+
10
+ ## Impact
11
+ - Affected specs: `task-runner`
12
+ - Affected code: `src/TaskRunner.ts`, `src/TaskRunnerExecutionConfig.ts`
@@ -0,0 +1,7 @@
1
+ ## Implementation
2
+ - [ ] 1.1 Update `TaskRunnerExecutionConfig` to include an optional `dryRun: boolean` property.
3
+ - [ ] 1.2 Implement `dryRun` logic in `WorkflowExecutor` (traverse graph, validate order, skip `step.run()`, return `skipped` or `success` pseudo-status).
4
+ - [ ] 1.3 Implement `getMermaidGraph(steps: TaskStep[])` method (can be static or instance method on `TaskRunner`).
5
+ - [ ] 1.4 Ensure `dryRun` respects other configs like `concurrency` (if applicable) to simulate actual timing/order if possible, or just strict topological order.
6
+ - [ ] 1.5 Add unit tests for `dryRun` ensuring no side effects occur.
7
+ - [ ] 1.6 Add unit tests for `getMermaidGraph` output correctness (nodes and edges).
@@ -0,0 +1,10 @@
1
+ ## Implementation
2
+ - [x] 1.1 Update `TaskRunner.execute` signature to accept an optional config object.
3
+ - [x] 1.2 Implement logic within `TaskRunner` to listen for `AbortSignal` and initiate cancellation.
4
+ - [x] 1.3 Implement global timeout mechanism using `AbortController` and `setTimeout`.
5
+ - [x] 1.4 Propagate `AbortSignal` to individual `TaskStep` executions.
6
+ - [x] 1.5 Ensure `TaskRunner` correctly handles tasks that are already aborted before execution.
7
+ - [x] 1.6 Update `TaskResult` and `RunnerEvents` to reflect cancellation status.
8
+ - [x] 1.7 Add unit tests for `AbortSignal` cancellation scenarios.
9
+ - [x] 1.8 Add unit tests for global timeout scenarios.
10
+ - [x] 1.9 Add integration tests covering both `AbortSignal` and timeout interactions.
@@ -0,0 +1,18 @@
1
+ # Change: Add Comprehensive Integration Tests
2
+
3
+ ## Why
4
+ The current test suite relies heavily on unit tests and mocks. To ensure robust behavior in real-world scenarios, we need comprehensive integration tests that execute full task graphs without mocks, validating complex configurations and interactions.
5
+
6
+ ## What Changes
7
+ - Create a dedicated `tests/integration-tests/` directory.
8
+ - Implement 10-20 integration test scenarios covering:
9
+ - Linear and branching dependencies.
10
+ - Failure handling and propagation.
11
+ - Context mutation and sharing.
12
+ - Timing and concurrency (real execution).
13
+ - Cancellation and timeouts.
14
+ - Error recovery (if retry policy is implemented, otherwise standard error states).
15
+
16
+ ## Impact
17
+ - Affected specs: `task-runner` (no functional changes to the runtime, but validates existing specs)
18
+ - Affected code: `tests/integration-tests/*`