@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.
- package/.github/dependabot.yml +7 -7
- package/.github/workflows/ci.yml +4 -4
- package/.jules/backlog_maniac.md +1 -0
- package/.jules/nexus.md +1 -0
- package/.jules/sentinel.md +1 -0
- package/.releaserc.json +2 -7
- package/AGENTS.md +21 -16
- package/CHANGELOG.md +192 -174
- package/README.md +95 -88
- package/coverage/coverage-final.json +9 -9
- package/coverage/index.html +9 -9
- package/coverage/lcov-report/index.html +9 -9
- package/coverage/lcov-report/src/EventBus.ts.html +30 -24
- package/coverage/lcov-report/src/TaskGraphValidationError.ts.html +12 -3
- package/coverage/lcov-report/src/TaskGraphValidator.ts.html +152 -137
- package/coverage/lcov-report/src/TaskRunner.ts.html +48 -45
- package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +29 -5
- package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +1 -1
- package/coverage/lcov-report/src/TaskStateManager.ts.html +82 -52
- package/coverage/lcov-report/src/WorkflowExecutor.ts.html +210 -66
- package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/lcov-report/src/contracts/index.html +1 -1
- package/coverage/lcov-report/src/index.html +16 -16
- package/coverage/lcov-report/src/strategies/DryRunExecutionStrategy.ts.html +4 -4
- package/coverage/lcov-report/src/strategies/RetryingExecutionStrategy.ts.html +29 -11
- package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +7 -7
- package/coverage/lcov-report/src/strategies/index.html +1 -1
- package/coverage/lcov.info +426 -383
- package/coverage/src/EventBus.ts.html +30 -24
- package/coverage/src/TaskGraphValidationError.ts.html +12 -3
- package/coverage/src/TaskGraphValidator.ts.html +152 -137
- package/coverage/src/TaskRunner.ts.html +48 -45
- package/coverage/src/TaskRunnerBuilder.ts.html +29 -5
- package/coverage/src/TaskRunnerExecutionConfig.ts.html +1 -1
- package/coverage/src/TaskStateManager.ts.html +82 -52
- package/coverage/src/WorkflowExecutor.ts.html +210 -66
- package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/src/contracts/index.html +1 -1
- package/coverage/src/index.html +16 -16
- package/coverage/src/strategies/DryRunExecutionStrategy.ts.html +4 -4
- package/coverage/src/strategies/RetryingExecutionStrategy.ts.html +29 -11
- package/coverage/src/strategies/StandardExecutionStrategy.ts.html +7 -7
- package/coverage/src/strategies/index.html +1 -1
- package/dist/EventBus.js +13 -11
- package/dist/EventBus.js.map +1 -1
- package/dist/TaskGraphValidationError.js.map +1 -1
- package/dist/TaskGraphValidator.js +9 -9
- package/dist/TaskGraphValidator.js.map +1 -1
- package/dist/TaskRunner.js.map +1 -1
- package/dist/TaskRunnerBuilder.js.map +1 -1
- package/dist/TaskStateManager.d.ts +6 -0
- package/dist/TaskStateManager.js +11 -2
- package/dist/TaskStateManager.js.map +1 -1
- package/dist/TaskStep.d.ts +5 -0
- package/dist/WorkflowExecutor.js +49 -7
- package/dist/WorkflowExecutor.js.map +1 -1
- package/dist/strategies/RetryingExecutionStrategy.js +3 -1
- package/dist/strategies/RetryingExecutionStrategy.js.map +1 -1
- package/dist/strategies/StandardExecutionStrategy.js +1 -1
- package/dist/strategies/StandardExecutionStrategy.js.map +1 -1
- package/openspec/AGENTS.md +81 -15
- package/openspec/changes/archive/2026-01-18-add-concurrency-control/proposal.md +7 -4
- package/openspec/changes/archive/2026-01-18-add-concurrency-control/tasks.md +1 -0
- package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/proposal.md +4 -1
- package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +2 -1
- package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +3 -0
- package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +1 -0
- package/openspec/changes/archive/2026-01-18-add-task-retry-policy/proposal.md +3 -0
- package/openspec/changes/archive/2026-01-18-add-task-retry-policy/tasks.md +1 -0
- package/openspec/changes/archive/2026-01-18-add-workflow-preview/proposal.md +3 -0
- package/openspec/changes/archive/2026-01-18-add-workflow-preview/tasks.md +1 -0
- package/openspec/changes/archive/2026-01-18-feat-conditional-execution/proposal.md +35 -0
- package/openspec/changes/archive/2026-01-18-feat-conditional-execution/tasks.md +32 -0
- package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +3 -0
- package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +1 -0
- package/openspec/changes/feat-per-task-timeout/proposal.md +11 -6
- package/openspec/changes/feat-per-task-timeout/tasks.md +1 -1
- package/openspec/project.md +21 -15
- package/package.json +2 -1
- package/src/EventBus.ts +18 -16
- package/src/TaskGraph.ts +8 -8
- package/src/TaskGraphValidationError.ts +4 -1
- package/src/TaskGraphValidator.ts +148 -143
- package/src/TaskRunner.ts +42 -41
- package/src/TaskRunnerBuilder.ts +11 -3
- package/src/TaskStateManager.ts +12 -2
- package/src/TaskStep.ts +6 -0
- package/src/WorkflowExecutor.ts +63 -15
- package/src/contracts/ITaskGraphValidator.ts +12 -12
- package/src/contracts/ValidationError.ts +6 -6
- package/src/contracts/ValidationResult.ts +4 -4
- package/src/strategies/DryRunExecutionStrategy.ts +3 -3
- package/src/strategies/RetryingExecutionStrategy.ts +15 -9
- package/src/strategies/StandardExecutionStrategy.ts +4 -4
- package/test-report.xml +132 -108
package/README.md
CHANGED
|
@@ -13,30 +13,24 @@ A lightweight, type-safe, and domain-agnostic task orchestration engine. It reso
|
|
|
13
13
|
- **Type-Safe Context**: Fully typed shared state using TypeScript Generics.
|
|
14
14
|
- **Parallel Execution**: Automatically identifies and runs independent steps concurrently.
|
|
15
15
|
- **Dependency Management**: Enforces execution order based on dependencies.
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
The `TaskRunner` implements an Observer Pattern, allowing you to subscribe to various lifecycle events.
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
runner.on("taskStart", ({ step }) => {
|
|
26
|
-
console.log(`Starting step: ${step.name}`);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
runner.on("taskEnd", ({ step, result }) => {
|
|
30
|
-
console.log(`Step ${step.name} finished with status: ${result.status}`);
|
|
31
|
-
});
|
|
32
|
-
```
|
|
16
|
+
- **Automatic Retries**: Configurable retry logic for flaky tasks.
|
|
17
|
+
- **Dry Run Mode**: Simulate workflow execution to verify dependency graphs.
|
|
18
|
+
- **Visualization**: Generate Mermaid.js diagrams of your task graph.
|
|
19
|
+
- **Event System**: Subscribe to lifecycle events for logging or monitoring.
|
|
20
|
+
- **Runtime Validation**: Automatically detects circular dependencies and missing dependencies.
|
|
21
|
+
- **Conditional Execution**: Skip tasks dynamically based on context state.
|
|
33
22
|
|
|
34
23
|
## Usage Example
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
The library now provides a fluent `TaskRunnerBuilder` for easy configuration.
|
|
37
26
|
|
|
38
27
|
```typescript
|
|
39
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
TaskRunnerBuilder,
|
|
30
|
+
TaskStep,
|
|
31
|
+
RetryingExecutionStrategy,
|
|
32
|
+
StandardExecutionStrategy
|
|
33
|
+
} from "@calmo/task-runner";
|
|
40
34
|
|
|
41
35
|
// 1. Define your domain-specific context
|
|
42
36
|
interface ValidationContext {
|
|
@@ -49,48 +43,49 @@ interface ValidationContext {
|
|
|
49
43
|
|
|
50
44
|
// 2. Define your steps
|
|
51
45
|
const UrlFormatStep: TaskStep<ValidationContext> = {
|
|
52
|
-
name:
|
|
46
|
+
name: "UrlFormatStep",
|
|
53
47
|
run: async (ctx) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
if (!ctx.issueBody.includes("github.com")) {
|
|
49
|
+
return { status: "failure", error: "Invalid URL" };
|
|
50
|
+
}
|
|
51
|
+
return { status: "success" };
|
|
58
52
|
},
|
|
59
53
|
};
|
|
60
54
|
|
|
61
55
|
const DataLoaderStep: TaskStep<ValidationContext> = {
|
|
62
|
-
name:
|
|
63
|
-
dependencies: [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
name: "DataLoaderStep",
|
|
57
|
+
dependencies: ["UrlFormatStep"],
|
|
58
|
+
retry: {
|
|
59
|
+
attempts: 3,
|
|
60
|
+
delay: 1000,
|
|
61
|
+
backoff: "exponential"
|
|
68
62
|
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const MaxChangesStep: TaskStep<ValidationContext> = {
|
|
72
|
-
name: 'MaxChangesStep',
|
|
73
|
-
dependencies: ['DataLoaderStep'],
|
|
74
63
|
run: async (ctx) => {
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return ctx.prData.additions < 50
|
|
79
|
-
? { status: 'success' }
|
|
80
|
-
: { status: 'failure', error: 'Too many changes' };
|
|
64
|
+
// Simulate API call
|
|
65
|
+
ctx.prData = { additions: 20, ciStatus: "success" };
|
|
66
|
+
return { status: "success", message: "Data fetched" };
|
|
81
67
|
},
|
|
82
68
|
};
|
|
83
69
|
|
|
84
|
-
// 3.
|
|
70
|
+
// 3. Configure and Build the Runner
|
|
85
71
|
async function main() {
|
|
86
72
|
const context: ValidationContext = {
|
|
87
|
-
issueBody:
|
|
73
|
+
issueBody: "https://github.com/org/repo/pull/1",
|
|
88
74
|
};
|
|
89
75
|
|
|
90
|
-
const runner = new
|
|
76
|
+
const runner = new TaskRunnerBuilder(context)
|
|
77
|
+
.useStrategy(new RetryingExecutionStrategy(new StandardExecutionStrategy()))
|
|
78
|
+
.on("taskStart", ({ step }) => console.log(`Starting: ${step.name}`))
|
|
79
|
+
.on("taskEnd", ({ step, result }) => console.log(`Finished: ${step.name} -> ${result.status}`))
|
|
80
|
+
.build();
|
|
91
81
|
|
|
92
|
-
const steps = [UrlFormatStep, DataLoaderStep
|
|
93
|
-
|
|
82
|
+
const steps = [UrlFormatStep, DataLoaderStep];
|
|
83
|
+
|
|
84
|
+
// 4. Execute with options
|
|
85
|
+
const results = await runner.execute(steps, {
|
|
86
|
+
concurrency: 5, // Run up to 5 tasks in parallel
|
|
87
|
+
timeout: 30000 // 30s timeout for the whole workflow
|
|
88
|
+
});
|
|
94
89
|
|
|
95
90
|
console.table(Object.fromEntries(results));
|
|
96
91
|
}
|
|
@@ -98,67 +93,79 @@ async function main() {
|
|
|
98
93
|
main();
|
|
99
94
|
```
|
|
100
95
|
|
|
101
|
-
##
|
|
96
|
+
## Advanced Configuration
|
|
102
97
|
|
|
103
|
-
|
|
98
|
+
### Execution Strategies
|
|
104
99
|
|
|
105
|
-
|
|
100
|
+
The `TaskRunner` is built on top of composable execution strategies.
|
|
106
101
|
|
|
107
|
-
|
|
102
|
+
- **StandardExecutionStrategy**: The default strategy that simply runs the task.
|
|
103
|
+
- **RetryingExecutionStrategy**: Wraps another strategy to add retry logic. Configured via the `retry` property on `TaskStep`.
|
|
104
|
+
- **DryRunExecutionStrategy**: Simulates execution without running the actual task logic. Useful for validating your graph or testing conditions.
|
|
108
105
|
|
|
109
|
-
|
|
106
|
+
You can set a strategy globally using the `TaskRunnerBuilder`:
|
|
110
107
|
|
|
111
|
-
|
|
108
|
+
```typescript
|
|
109
|
+
runnerBuilder.useStrategy(new DryRunExecutionStrategy());
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Execution Options
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
When calling `execute`, you can provide a configuration object:
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
- **concurrency**: Limits the number of tasks running in parallel. Defaults to unlimited.
|
|
117
|
+
- **timeout**: Sets a maximum execution time for the entire workflow.
|
|
118
|
+
- **signal**: Accepts an `AbortSignal` to cancel the workflow programmatically.
|
|
119
|
+
- **dryRun**: Overrides the current strategy with `DryRunExecutionStrategy` for this execution.
|
|
118
120
|
|
|
119
121
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
await runner.execute(steps, {
|
|
123
|
+
concurrency: 2,
|
|
124
|
+
dryRun: true
|
|
125
|
+
});
|
|
126
|
+
```
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
apiData: {
|
|
126
|
-
user: string;
|
|
127
|
-
isPro: boolean;
|
|
128
|
-
};
|
|
129
|
-
}
|
|
128
|
+
## Visualization
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
class UserLoaderStep implements TaskStep<MyProjectContext> {
|
|
133
|
-
name = 'UserLoaderStep';
|
|
134
|
-
async run(ctx: MyProjectContext & Partial<MyProjectFullContext>) {
|
|
135
|
-
// Fetch data and update context
|
|
136
|
-
ctx.apiData = { user: 'john_doe', isPro: true };
|
|
137
|
-
return { status: 'success' };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
130
|
+
You can generate a [Mermaid.js](https://mermaid.js.org/) diagram to visualize your task dependencies.
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
name = 'PremiumCheckStep';
|
|
144
|
-
dependencies = ['UserLoaderStep']; // Ensures data is ready
|
|
132
|
+
```typescript
|
|
133
|
+
import { TaskRunner } from "@calmo/task-runner";
|
|
145
134
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
: { status: 'failure', error: 'User is not a Pro member' };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
135
|
+
const graph = TaskRunner.getMermaidGraph(steps);
|
|
136
|
+
console.log(graph);
|
|
137
|
+
// Output: graph TD; A-->B; ...
|
|
152
138
|
```
|
|
153
139
|
|
|
154
|
-
|
|
140
|
+
## Skip Propagation
|
|
141
|
+
|
|
142
|
+
If a task fails or is skipped, the `TaskRunner` automatically marks all subsequent tasks that depend on it as `skipped`. This ensures that your pipeline doesn't attempt to run steps with missing prerequisites.
|
|
143
|
+
|
|
144
|
+
## Conditional Execution
|
|
145
|
+
|
|
146
|
+
You can define a `condition` function for a task. If it returns `false`, the task is marked as `skipped`, and its dependencies are also skipped.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const deployStep: TaskStep<MyContext> = {
|
|
150
|
+
name: "deploy",
|
|
151
|
+
condition: (ctx) => ctx.env === "production",
|
|
152
|
+
run: async () => {
|
|
153
|
+
// Deploy logic
|
|
154
|
+
return { status: "success" };
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
```
|
|
155
158
|
|
|
156
159
|
## Why I did this?
|
|
157
160
|
|
|
158
|
-
In my
|
|
161
|
+
In my current job I have a Github Issue validation engine that checks **a lot** of stuff and I wanted to make a package that encapsulates the "validation engine" logic for use outside that use case. I also wanted to try to make a package that is not tied to a specific scenario. I don't know if someone will find it useful but here it is.
|
|
162
|
+
|
|
163
|
+
## AI Usage
|
|
164
|
+
|
|
165
|
+
One of the reasons this project exists is to test 'vibe coding' tools, so yes, this is vibe coded (like, A LOT, I've added myself only specs and some conflict resolutions). This repository serves as a testbed for these tools. It's a way to create a real life scenario showcasing the capabilities of agentic AI development.
|
|
159
166
|
|
|
160
|
-
##
|
|
167
|
+
## Contributing and General Usage
|
|
161
168
|
|
|
162
|
-
|
|
169
|
+
Feel free to open issues and PRs. I'm open to feedback and suggestions. I can't promise to act on them but I'll try my best. If you want to play with it, feel free to fork it, change it and use it in your own projects.
|
|
163
170
|
|
|
164
171
|
---
|