@calmo/task-runner 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +49 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +1 -1
  4. package/coverage/coverage-final.json +8 -4
  5. package/coverage/index.html +24 -9
  6. package/coverage/lcov-report/index.html +24 -9
  7. package/coverage/lcov-report/src/EventBus.ts.html +8 -8
  8. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +53 -53
  9. package/coverage/lcov-report/src/TaskRunner.ts.html +213 -21
  10. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +313 -0
  11. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +124 -0
  12. package/coverage/lcov-report/src/TaskStateManager.ts.html +511 -0
  13. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +119 -137
  14. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  15. package/coverage/lcov-report/src/contracts/index.html +1 -1
  16. package/coverage/lcov-report/src/index.html +57 -12
  17. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  18. package/coverage/lcov-report/src/strategies/index.html +116 -0
  19. package/coverage/lcov.info +381 -204
  20. package/coverage/src/EventBus.ts.html +8 -8
  21. package/coverage/src/TaskGraphValidator.ts.html +53 -53
  22. package/coverage/src/TaskRunner.ts.html +213 -21
  23. package/coverage/src/TaskRunnerBuilder.ts.html +313 -0
  24. package/coverage/src/TaskRunnerExecutionConfig.ts.html +124 -0
  25. package/coverage/src/TaskStateManager.ts.html +511 -0
  26. package/coverage/src/WorkflowExecutor.ts.html +119 -137
  27. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  28. package/coverage/src/contracts/index.html +1 -1
  29. package/coverage/src/index.html +57 -12
  30. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  31. package/coverage/src/strategies/index.html +116 -0
  32. package/dist/TaskRunner.d.ts +12 -2
  33. package/dist/TaskRunner.js +55 -3
  34. package/dist/TaskRunner.js.map +1 -1
  35. package/dist/TaskRunnerBuilder.d.ts +33 -0
  36. package/dist/TaskRunnerBuilder.js +54 -0
  37. package/dist/TaskRunnerBuilder.js.map +1 -0
  38. package/dist/TaskRunnerExecutionConfig.d.ts +13 -0
  39. package/dist/TaskRunnerExecutionConfig.js +2 -0
  40. package/dist/TaskRunnerExecutionConfig.js.map +1 -0
  41. package/dist/TaskStateManager.d.ts +54 -0
  42. package/dist/TaskStateManager.js +130 -0
  43. package/dist/TaskStateManager.js.map +1 -0
  44. package/dist/TaskStatus.d.ts +1 -1
  45. package/dist/TaskStep.d.ts +2 -1
  46. package/dist/WorkflowExecutor.d.ts +11 -17
  47. package/dist/WorkflowExecutor.js +67 -69
  48. package/dist/WorkflowExecutor.js.map +1 -1
  49. package/dist/index.d.ts +4 -0
  50. package/dist/index.js +3 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/strategies/IExecutionStrategy.d.ts +16 -0
  53. package/dist/strategies/IExecutionStrategy.js +2 -0
  54. package/dist/strategies/IExecutionStrategy.js.map +1 -0
  55. package/dist/strategies/StandardExecutionStrategy.d.ts +9 -0
  56. package/dist/strategies/StandardExecutionStrategy.js +25 -0
  57. package/dist/strategies/StandardExecutionStrategy.js.map +1 -0
  58. package/openspec/changes/add-concurrency-control/proposal.md +14 -0
  59. package/openspec/changes/add-concurrency-control/tasks.md +9 -0
  60. package/openspec/changes/add-task-retry-policy/proposal.md +13 -0
  61. package/openspec/changes/add-task-retry-policy/tasks.md +10 -0
  62. package/openspec/changes/add-workflow-preview/proposal.md +12 -0
  63. package/openspec/changes/add-workflow-preview/tasks.md +7 -0
  64. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +10 -0
  65. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +18 -0
  66. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +17 -0
  67. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +14 -0
  68. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +8 -0
  69. package/package.json +1 -1
  70. package/src/TaskRunner.ts +68 -4
  71. package/src/TaskRunnerBuilder.ts +76 -0
  72. package/src/TaskRunnerExecutionConfig.ts +13 -0
  73. package/src/TaskStateManager.ts +142 -0
  74. package/src/TaskStatus.ts +1 -1
  75. package/src/TaskStep.ts +2 -1
  76. package/src/WorkflowExecutor.ts +77 -83
  77. package/src/index.ts +4 -0
  78. package/src/strategies/IExecutionStrategy.ts +21 -0
  79. package/src/strategies/StandardExecutionStrategy.ts +35 -0
  80. package/test-report.xml +119 -45
  81. package/GEMINI.md +0 -48
  82. package/openspec/changes/add-external-task-cancellation/tasks.md +0 -10
  83. /package/openspec/changes/{add-external-task-cancellation → archive/2026-01-18-add-external-task-cancellation}/proposal.md +0 -0
@@ -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,32 +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.
30
+ * Logic to identify tasks that can be started and run them.
25
31
  */
26
- private processQueue;
27
- /**
28
- * Identifies steps that cannot run because a dependency failed.
29
- */
30
- private handleSkippedTasks;
31
- /**
32
- * Returns steps where all dependencies have finished successfully.
33
- */
34
- private getReadySteps;
35
- /**
36
- * Handles the lifecycle of a single task execution.
37
- */
38
- private runStep;
32
+ private processLoop;
39
33
  }
@@ -5,97 +5,95 @@
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
- // Initial pass
27
- this.processQueue(steps, results, executingPromises);
28
- while (results.size < steps.length && executingPromises.size > 0) {
29
- // Wait for the next task to finish
30
- await Promise.race(executingPromises);
31
- // After a task finishes, check for new work
32
- this.processQueue(steps, 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);
33
45
  }
34
- this.eventBus.emit("workflowEnd", { context: this.context, results });
35
- return results;
36
- }
37
- /**
38
- * Logic to identify tasks that can be started or must be skipped.
39
- */
40
- processQueue(steps, results, executingPromises) {
41
- this.handleSkippedTasks(steps, results);
42
- const readySteps = this.getReadySteps(steps, results);
43
- for (const step of readySteps) {
44
- const taskPromise = this.runStep(step, results).then(() => {
45
- executingPromises.delete(taskPromise);
46
- });
47
- executingPromises.add(taskPromise);
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) {
55
+ break;
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
+ }
68
+ }
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;
48
74
  }
49
- }
50
- /**
51
- * Identifies steps that cannot run because a dependency failed.
52
- */
53
- handleSkippedTasks(steps, results) {
54
- const pendingSteps = steps.filter((step) => !results.has(step.name) && !this.running.has(step.name));
55
- for (const step of pendingSteps) {
56
- const deps = step.dependencies ?? [];
57
- const failedDep = deps.find((dep) => results.has(dep) && results.get(dep)?.status !== "success");
58
- if (failedDep) {
59
- const result = {
60
- status: "skipped",
61
- message: `Skipped due to failed dependency: ${failedDep}`,
62
- };
63
- results.set(step.name, result);
64
- this.eventBus.emit("taskSkipped", { step, result });
75
+ finally {
76
+ if (signal) {
77
+ signal.removeEventListener("abort", onAbort);
65
78
  }
66
79
  }
67
80
  }
68
81
  /**
69
- * Returns steps where all dependencies have finished successfully.
70
- */
71
- getReadySteps(steps, results) {
72
- return steps.filter((step) => {
73
- if (results.has(step.name) || this.running.has(step.name))
74
- return false;
75
- const deps = step.dependencies ?? [];
76
- return deps.every((dep) => results.has(dep) && results.get(dep)?.status === "success");
77
- });
78
- }
79
- /**
80
- * Handles the lifecycle of a single task execution.
82
+ * Logic to identify tasks that can be started and run them.
81
83
  */
82
- async runStep(step, results) {
83
- this.running.add(step.name);
84
- this.eventBus.emit("taskStart", { step });
85
- try {
86
- const result = await step.run(this.context);
87
- results.set(step.name, result);
88
- }
89
- catch (e) {
90
- results.set(step.name, {
91
- status: "failure",
92
- error: e instanceof Error ? e.message : String(e),
84
+ processLoop(executingPromises, signal) {
85
+ const toRun = this.stateManager.processDependencies();
86
+ // Execute ready tasks
87
+ for (const step of toRun) {
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(() => {
94
+ executingPromises.delete(taskPromise);
93
95
  });
94
- }
95
- finally {
96
- this.running.delete(step.name);
97
- const result = results.get(step.name);
98
- this.eventBus.emit("taskEnd", { step, result });
96
+ executingPromises.add(taskPromise);
99
97
  }
100
98
  }
101
99
  }
@@ -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/*`
@@ -0,0 +1,17 @@
1
+ ## Implementation
2
+ - [x] 1.1 Setup `tests/integration-tests/` directory and test runner config if needed.
3
+ - [x] 1.2 Implement Scenario 1: Basic linear workflow (A -> B -> C) success.
4
+ - [x] 1.3 Implement Scenario 2: Branching workflow (A -> [B, C] -> D) success.
5
+ - [x] 1.4 Implement Scenario 3: Task failure and downstream skipping (A -> B(fail) -> C(skip)).
6
+ - [x] 1.5 Implement Scenario 4: Shared context mutation (A writes, B reads).
7
+ - [x] 1.6 Implement Scenario 5: Large graph execution (e.g., 20+ nodes).
8
+ - [x] 1.7 Implement Scenario 6: Mixed duration tasks (verifying parallel efficiency).
9
+ - [x] 1.8 Implement Scenario 7: Cancellation via AbortSignal in mid-execution.
10
+ - [x] 1.9 Implement Scenario 8: Global timeout interrupting long tasks.
11
+ - [x] 1.10 Implement Scenario 9: Dynamic context validation (tasks validating context state).
12
+ - [x] 1.11 Implement Scenario 10: Circular dependency detection (at runtime validation).
13
+ - [x] 1.12 Implement Scenario 11: Missing dependency handling.
14
+ - [x] 1.13 Implement Scenario 12: Complex "Diamond" dependency graph.
15
+ - [x] 1.14 Implement Scenario 13: Tasks with side-effects (e.g., file I/O or simulated network).
16
+ - [x] 1.15 Implement Scenario 14: Zero-dependency parallel burst (all run at once).
17
+ - [x] 1.16 Verification: Ensure all integration tests pass consistently.
@@ -0,0 +1,14 @@
1
+ # Change: Refactor Core Architecture
2
+
3
+ ## Why
4
+ When multiple developers work on the project, conflicts arise due to tight coupling and poor separation of concerns. Large classes like `WorkflowExecutor` are taking on too many responsibilities, and there is duplicated logic around graph traversal and state management.
5
+
6
+ ## What Changes
7
+ - Decouple `WorkflowExecutor` from `EventBus` (pass a listener interface or use a mediating controller).
8
+ - Extract `TaskExecutionStrategy` to allow pluggable execution modes (e.g., standard, dry-run, debug).
9
+ - Centralize state management for task results and context, moving it out of the executor loop.
10
+ - Standardize error handling and logging (QoL improvements).
11
+
12
+ ## Impact
13
+ - Affected specs: `task-runner` (no behavior change, but structural refactor)
14
+ - Affected code: `src/WorkflowExecutor.ts`, `src/TaskRunner.ts`, new files for extracted logic.