@calmo/task-runner 3.4.0 → 3.5.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 (95) hide show
  1. package/.github/dependabot.yml +7 -7
  2. package/.github/workflows/ci.yml +4 -4
  3. package/.jules/backlog_maniac.md +1 -0
  4. package/.jules/nexus.md +1 -0
  5. package/.jules/sentinel.md +1 -0
  6. package/.releaserc.json +2 -7
  7. package/AGENTS.md +21 -16
  8. package/CHANGELOG.md +192 -174
  9. package/README.md +95 -88
  10. package/coverage/coverage-final.json +9 -9
  11. package/coverage/index.html +9 -9
  12. package/coverage/lcov-report/index.html +9 -9
  13. package/coverage/lcov-report/src/EventBus.ts.html +30 -24
  14. package/coverage/lcov-report/src/TaskGraphValidationError.ts.html +12 -3
  15. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +152 -137
  16. package/coverage/lcov-report/src/TaskRunner.ts.html +48 -45
  17. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +29 -5
  18. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +1 -1
  19. package/coverage/lcov-report/src/TaskStateManager.ts.html +82 -52
  20. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +210 -66
  21. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  22. package/coverage/lcov-report/src/contracts/index.html +1 -1
  23. package/coverage/lcov-report/src/index.html +16 -16
  24. package/coverage/lcov-report/src/strategies/DryRunExecutionStrategy.ts.html +4 -4
  25. package/coverage/lcov-report/src/strategies/RetryingExecutionStrategy.ts.html +29 -11
  26. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +7 -7
  27. package/coverage/lcov-report/src/strategies/index.html +1 -1
  28. package/coverage/lcov.info +426 -383
  29. package/coverage/src/EventBus.ts.html +30 -24
  30. package/coverage/src/TaskGraphValidationError.ts.html +12 -3
  31. package/coverage/src/TaskGraphValidator.ts.html +152 -137
  32. package/coverage/src/TaskRunner.ts.html +48 -45
  33. package/coverage/src/TaskRunnerBuilder.ts.html +29 -5
  34. package/coverage/src/TaskRunnerExecutionConfig.ts.html +1 -1
  35. package/coverage/src/TaskStateManager.ts.html +82 -52
  36. package/coverage/src/WorkflowExecutor.ts.html +210 -66
  37. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  38. package/coverage/src/contracts/index.html +1 -1
  39. package/coverage/src/index.html +16 -16
  40. package/coverage/src/strategies/DryRunExecutionStrategy.ts.html +4 -4
  41. package/coverage/src/strategies/RetryingExecutionStrategy.ts.html +29 -11
  42. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +7 -7
  43. package/coverage/src/strategies/index.html +1 -1
  44. package/dist/EventBus.js +13 -11
  45. package/dist/EventBus.js.map +1 -1
  46. package/dist/TaskGraphValidationError.js.map +1 -1
  47. package/dist/TaskGraphValidator.js +9 -9
  48. package/dist/TaskGraphValidator.js.map +1 -1
  49. package/dist/TaskRunner.js.map +1 -1
  50. package/dist/TaskRunnerBuilder.js.map +1 -1
  51. package/dist/TaskStateManager.d.ts +6 -0
  52. package/dist/TaskStateManager.js +11 -2
  53. package/dist/TaskStateManager.js.map +1 -1
  54. package/dist/TaskStep.d.ts +5 -0
  55. package/dist/WorkflowExecutor.js +49 -7
  56. package/dist/WorkflowExecutor.js.map +1 -1
  57. package/dist/strategies/RetryingExecutionStrategy.js +3 -1
  58. package/dist/strategies/RetryingExecutionStrategy.js.map +1 -1
  59. package/dist/strategies/StandardExecutionStrategy.js +1 -1
  60. package/dist/strategies/StandardExecutionStrategy.js.map +1 -1
  61. package/openspec/AGENTS.md +81 -15
  62. package/openspec/changes/archive/2026-01-18-add-concurrency-control/proposal.md +7 -4
  63. package/openspec/changes/archive/2026-01-18-add-concurrency-control/tasks.md +1 -0
  64. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/proposal.md +4 -1
  65. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +2 -1
  66. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +3 -0
  67. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +1 -0
  68. package/openspec/changes/archive/2026-01-18-add-task-retry-policy/proposal.md +3 -0
  69. package/openspec/changes/archive/2026-01-18-add-task-retry-policy/tasks.md +1 -0
  70. package/openspec/changes/archive/2026-01-18-add-workflow-preview/proposal.md +3 -0
  71. package/openspec/changes/archive/2026-01-18-add-workflow-preview/tasks.md +1 -0
  72. package/openspec/changes/archive/2026-01-18-feat-conditional-execution/proposal.md +35 -0
  73. package/openspec/changes/archive/2026-01-18-feat-conditional-execution/tasks.md +32 -0
  74. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +3 -0
  75. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +1 -0
  76. package/openspec/changes/feat-per-task-timeout/proposal.md +11 -6
  77. package/openspec/changes/feat-per-task-timeout/tasks.md +1 -1
  78. package/openspec/project.md +21 -15
  79. package/package.json +2 -1
  80. package/src/EventBus.ts +18 -16
  81. package/src/TaskGraph.ts +8 -8
  82. package/src/TaskGraphValidationError.ts +4 -1
  83. package/src/TaskGraphValidator.ts +148 -143
  84. package/src/TaskRunner.ts +42 -41
  85. package/src/TaskRunnerBuilder.ts +11 -3
  86. package/src/TaskStateManager.ts +12 -2
  87. package/src/TaskStep.ts +6 -0
  88. package/src/WorkflowExecutor.ts +63 -15
  89. package/src/contracts/ITaskGraphValidator.ts +12 -12
  90. package/src/contracts/ValidationError.ts +6 -6
  91. package/src/contracts/ValidationResult.ts +4 -4
  92. package/src/strategies/DryRunExecutionStrategy.ts +3 -3
  93. package/src/strategies/RetryingExecutionStrategy.ts +15 -9
  94. package/src/strategies/StandardExecutionStrategy.ts +4 -4
  95. package/test-report.xml +132 -108
@@ -41,7 +41,9 @@ export class WorkflowExecutor<TContext> {
41
41
 
42
42
  // Check if already aborted
43
43
  if (signal?.aborted) {
44
- this.stateManager.cancelAllPending("Workflow cancelled before execution started.");
44
+ this.stateManager.cancelAllPending(
45
+ "Workflow cancelled before execution started."
46
+ );
45
47
  const results = this.stateManager.getResults();
46
48
  this.eventBus.emit("workflowEnd", { context: this.context, results });
47
49
  return results;
@@ -55,7 +57,7 @@ export class WorkflowExecutor<TContext> {
55
57
  };
56
58
 
57
59
  if (signal) {
58
- signal.addEventListener("abort", onAbort);
60
+ signal.addEventListener("abort", onAbort);
59
61
  }
60
62
 
61
63
  try {
@@ -64,7 +66,8 @@ export class WorkflowExecutor<TContext> {
64
66
 
65
67
  while (
66
68
  this.stateManager.hasPendingTasks() ||
67
- this.stateManager.hasRunningTasks()
69
+ this.stateManager.hasRunningTasks() ||
70
+ executingPromises.size > 0
68
71
  ) {
69
72
  // Safety check: if no tasks are running and we still have pending tasks,
70
73
  // it means we are stuck (e.g. cycle or unhandled dependency).
@@ -77,10 +80,10 @@ export class WorkflowExecutor<TContext> {
77
80
  }
78
81
 
79
82
  if (signal?.aborted) {
80
- this.stateManager.cancelAllPending("Workflow cancelled.");
83
+ this.stateManager.cancelAllPending("Workflow cancelled.");
81
84
  } else {
82
- // After a task finishes, check for new work
83
- this.processLoop(executingPromises, signal);
85
+ // After a task finishes, check for new work
86
+ this.processLoop(executingPromises, signal);
84
87
  }
85
88
  }
86
89
 
@@ -122,17 +125,62 @@ export class WorkflowExecutor<TContext> {
122
125
 
123
126
  const step = this.readyQueue.shift()!;
124
127
 
125
- this.stateManager.markRunning(step);
128
+ const taskPromise = (async () => {
129
+ try {
130
+ if (step.condition) {
131
+ const check = step.condition(this.context);
132
+ const shouldRun = check instanceof Promise ? await check : check;
133
+
134
+ if (signal?.aborted) {
135
+ this.stateManager.markCompleted(step, {
136
+ status: "cancelled",
137
+ message: "Cancelled during condition evaluation.",
138
+ });
139
+ return;
140
+ }
141
+
142
+ if (!shouldRun) {
143
+ const result: TaskResult = {
144
+ status: "skipped",
145
+ message: "Skipped by condition evaluation.",
146
+ };
147
+ this.stateManager.markSkipped(step, result);
148
+ return;
149
+ }
150
+ }
151
+ } catch (error) {
152
+ const result: TaskResult = {
153
+ status: "failure",
154
+ message:
155
+ error instanceof Error
156
+ ? error.message
157
+ : "Condition evaluation failed",
158
+ error: error instanceof Error ? error.message : String(error),
159
+ };
160
+ this.stateManager.markCompleted(step, result);
161
+ return;
162
+ }
163
+
164
+ if (signal?.aborted) {
165
+ this.stateManager.markCompleted(step, {
166
+ status: "cancelled",
167
+ message: "Cancelled before execution started.",
168
+ });
169
+ return;
170
+ }
171
+
172
+ this.stateManager.markRunning(step);
126
173
 
127
- const taskPromise = this.strategy.execute(step, this.context, signal)
128
- .then((result) => {
174
+ await this.strategy
175
+ .execute(step, this.context, signal)
176
+ .then((result) => {
129
177
  this.stateManager.markCompleted(step, result);
130
- })
131
- .finally(() => {
132
- executingPromises.delete(taskPromise);
133
- // When a task finishes, we try to run more
134
- this.processLoop(executingPromises, signal);
135
- });
178
+ });
179
+ })().finally(() => {
180
+ executingPromises.delete(taskPromise);
181
+ // When a task finishes, we try to run more
182
+ this.processLoop(executingPromises, signal);
183
+ });
136
184
 
137
185
  executingPromises.add(taskPromise);
138
186
  }
@@ -5,17 +5,17 @@ import { ValidationResult } from "./ValidationResult.js";
5
5
  * Defines the interface for a task graph validator.
6
6
  */
7
7
  export interface ITaskGraphValidator {
8
- /**
9
- * Validates a given task graph for structural integrity.
10
- * @param taskGraph The task graph to validate.
11
- * @returns A ValidationResult object indicating the outcome of the validation.
12
- */
13
- validate(taskGraph: TaskGraph): ValidationResult;
8
+ /**
9
+ * Validates a given task graph for structural integrity.
10
+ * @param taskGraph The task graph to validate.
11
+ * @returns A ValidationResult object indicating the outcome of the validation.
12
+ */
13
+ validate(taskGraph: TaskGraph): ValidationResult;
14
14
 
15
- /**
16
- * Creates a human-readable error message from a validation result.
17
- * @param result The validation result containing errors.
18
- * @returns A formatted error string.
19
- */
20
- createErrorMessage(result: ValidationResult): string;
15
+ /**
16
+ * Creates a human-readable error message from a validation result.
17
+ * @param result The validation result containing errors.
18
+ * @returns A formatted error string.
19
+ */
20
+ createErrorMessage(result: ValidationResult): string;
21
21
  }
@@ -2,10 +2,10 @@
2
2
  * Describes a specific validation error found in the task graph.
3
3
  */
4
4
  export interface ValidationError {
5
- /** The type of validation error. */
6
- type: "cycle" | "missing_dependency" | "duplicate_task";
7
- /** A human-readable message describing the error. */
8
- message: string;
9
- /** Optional detailed information about the error, e.g., the cycle path, or the task with a missing dependency. */
10
- details?: unknown;
5
+ /** The type of validation error. */
6
+ type: "cycle" | "missing_dependency" | "duplicate_task";
7
+ /** A human-readable message describing the error. */
8
+ message: string;
9
+ /** Optional detailed information about the error, e.g., the cycle path, or the task with a missing dependency. */
10
+ details?: unknown;
11
11
  }
@@ -4,8 +4,8 @@ import { ValidationError } from "./ValidationError.js";
4
4
  * The result of a task graph validation operation.
5
5
  */
6
6
  export interface ValidationResult {
7
- /** True if the graph is valid, false otherwise. */
8
- isValid: boolean;
9
- /** An array of ValidationError objects if the graph is not valid. Empty if isValid is true. */
10
- errors: ValidationError[];
7
+ /** True if the graph is valid, false otherwise. */
8
+ isValid: boolean;
9
+ /** An array of ValidationError objects if the graph is not valid. Empty if isValid is true. */
10
+ errors: ValidationError[];
11
11
  }
@@ -5,9 +5,9 @@ import { TaskResult } from "../TaskResult.js";
5
5
  /**
6
6
  * Execution strategy that simulates task execution without running the actual logic.
7
7
  */
8
- export class DryRunExecutionStrategy<TContext>
9
- implements IExecutionStrategy<TContext>
10
- {
8
+ export class DryRunExecutionStrategy<
9
+ TContext,
10
+ > implements IExecutionStrategy<TContext> {
11
11
  /**
12
12
  * Simulates execution by returning a success result immediately.
13
13
  * @param step The task step (ignored).
@@ -5,7 +5,9 @@ import { TaskResult } from "../TaskResult.js";
5
5
  /**
6
6
  * Execution strategy that retries tasks upon failure based on their retry configuration.
7
7
  */
8
- export class RetryingExecutionStrategy<TContext> implements IExecutionStrategy<TContext> {
8
+ export class RetryingExecutionStrategy<
9
+ TContext,
10
+ > implements IExecutionStrategy<TContext> {
9
11
  constructor(private innerStrategy: IExecutionStrategy<TContext>) {}
10
12
 
11
13
  async execute(
@@ -30,7 +32,11 @@ export class RetryingExecutionStrategy<TContext> implements IExecutionStrategy<T
30
32
 
31
33
  const result = await this.innerStrategy.execute(step, context, signal);
32
34
 
33
- if (result.status === "success" || result.status === "cancelled" || result.status === "skipped") {
35
+ if (
36
+ result.status === "success" ||
37
+ result.status === "cancelled" ||
38
+ result.status === "skipped"
39
+ ) {
34
40
  return result;
35
41
  }
36
42
 
@@ -51,13 +57,13 @@ export class RetryingExecutionStrategy<TContext> implements IExecutionStrategy<T
51
57
  try {
52
58
  await this.sleep(delay, signal);
53
59
  } catch (e) {
54
- if (signal?.aborted) {
55
- return {
56
- status: "cancelled",
57
- message: "Task cancelled during retry delay",
58
- };
59
- }
60
- throw e;
60
+ if (signal?.aborted) {
61
+ return {
62
+ status: "cancelled",
63
+ message: "Task cancelled during retry delay",
64
+ };
65
+ }
66
+ throw e;
61
67
  }
62
68
  }
63
69
  }
@@ -5,9 +5,9 @@ import { TaskResult } from "../TaskResult.js";
5
5
  /**
6
6
  * Standard execution strategy that runs the task's run method.
7
7
  */
8
- export class StandardExecutionStrategy<TContext>
9
- implements IExecutionStrategy<TContext>
10
- {
8
+ export class StandardExecutionStrategy<
9
+ TContext,
10
+ > implements IExecutionStrategy<TContext> {
11
11
  async execute(
12
12
  step: TaskStep<TContext>,
13
13
  context: TContext,
@@ -19,7 +19,7 @@ export class StandardExecutionStrategy<TContext>
19
19
  // Check if error is due to abort
20
20
  if (
21
21
  signal?.aborted &&
22
- (e instanceof Error && e.name === "AbortError" || signal.reason === e)
22
+ ((e instanceof Error && e.name === "AbortError") || signal.reason === e)
23
23
  ) {
24
24
  return {
25
25
  status: "cancelled",