@calmo/task-runner 3.4.1 → 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 (50) hide show
  1. package/AGENTS.md +15 -16
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +89 -82
  4. package/coverage/coverage-final.json +4 -4
  5. package/coverage/index.html +9 -9
  6. package/coverage/lcov-report/index.html +9 -9
  7. package/coverage/lcov-report/src/EventBus.ts.html +4 -4
  8. package/coverage/lcov-report/src/TaskGraphValidationError.ts.html +1 -1
  9. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +1 -1
  10. package/coverage/lcov-report/src/TaskRunner.ts.html +1 -1
  11. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +1 -1
  12. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +1 -1
  13. package/coverage/lcov-report/src/TaskStateManager.ts.html +82 -52
  14. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +197 -62
  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 +12 -12
  18. package/coverage/lcov-report/src/strategies/DryRunExecutionStrategy.ts.html +1 -1
  19. package/coverage/lcov-report/src/strategies/RetryingExecutionStrategy.ts.html +1 -1
  20. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +3 -3
  21. package/coverage/lcov-report/src/strategies/index.html +1 -1
  22. package/coverage/lcov.info +202 -163
  23. package/coverage/src/EventBus.ts.html +4 -4
  24. package/coverage/src/TaskGraphValidationError.ts.html +1 -1
  25. package/coverage/src/TaskGraphValidator.ts.html +1 -1
  26. package/coverage/src/TaskRunner.ts.html +1 -1
  27. package/coverage/src/TaskRunnerBuilder.ts.html +1 -1
  28. package/coverage/src/TaskRunnerExecutionConfig.ts.html +1 -1
  29. package/coverage/src/TaskStateManager.ts.html +82 -52
  30. package/coverage/src/WorkflowExecutor.ts.html +197 -62
  31. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  32. package/coverage/src/contracts/index.html +1 -1
  33. package/coverage/src/index.html +12 -12
  34. package/coverage/src/strategies/DryRunExecutionStrategy.ts.html +1 -1
  35. package/coverage/src/strategies/RetryingExecutionStrategy.ts.html +1 -1
  36. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +3 -3
  37. package/coverage/src/strategies/index.html +1 -1
  38. package/dist/TaskStateManager.d.ts +6 -0
  39. package/dist/TaskStateManager.js +11 -2
  40. package/dist/TaskStateManager.js.map +1 -1
  41. package/dist/TaskStep.d.ts +5 -0
  42. package/dist/WorkflowExecutor.js +49 -8
  43. package/dist/WorkflowExecutor.js.map +1 -1
  44. package/openspec/changes/archive/2026-01-18-feat-conditional-execution/proposal.md +35 -0
  45. package/openspec/changes/archive/2026-01-18-feat-conditional-execution/tasks.md +32 -0
  46. package/package.json +2 -1
  47. package/src/TaskStateManager.ts +12 -2
  48. package/src/TaskStep.ts +6 -0
  49. package/src/WorkflowExecutor.ts +57 -12
  50. package/test-report.xml +132 -108
package/AGENTS.md CHANGED
@@ -27,6 +27,21 @@ Keep this managed block so 'openspec update' can refresh the instructions.
27
27
  - Always prefer to add more tests instead of simply bypassing coverage validation with comments.
28
28
  - Its forbidden to have coverage drop below 100%, thats non negotiable.
29
29
 
30
+ ## Operational Protocols
31
+
32
+ - **Critic-First Generation**: Every code block you generate must undergo an internal "Reflection" pass. Explicitly flag potential race conditions, security flaws (OWASP Top 10), or architectural debt.
33
+ - **Verification Gatekeeping**: You are STRICTLY FORBIDDEN from claiming a task is "finished" until you provide terminal output of a passing test suite, a successful build log and a successful lint log.
34
+ - **Planning Sparring**: For any task >20 lines, you must first output a blueprint and validate it. You will not write implementation code until the user approves the blueprint.
35
+ - **Tool-Augmented Research**: Use `/search` and `/read` to understand the entire context of the project. Do not assume; verify existing utility functions to ensure DRY (Don't Repeat Yourself) compliance.
36
+ - **The Confession Rule**: If you hit a logic error or a hallucination, you must immediately halt and state: "I have identified an inconsist- - **Atomic Commits for Complex Features:** When working on complex multi-task features, you MUST commit after completing each distinct task. Before each commit, ensure that `pnpm build`, `pnpm lint`, and `pnpm test` pass. This creates safe rollback points and prevents restarting the entire feature if issues arise.
37
+
38
+ ## Style & Tone
39
+
40
+ - **Tone**: Professional, technical, and high-density.
41
+ - **Zero Politeness Fluff**: No "I'd be happy to," "Great question," etc.
42
+ - **Architectural Comparisons**: Use markdown tables for comparing architectural trade-offs.
43
+ - **Hypothesis Labeling**: Label speculative thoughts clearly as `[ARCHITECTURAL_HYPOTHESIS]`.
44
+
30
45
  # task-runner Development Guidelines
31
46
 
32
47
  Auto-generated from all feature plans. Last updated: 2026-01-17
@@ -62,19 +77,3 @@ tests/
62
77
  - 005-concurrency-control: Added TypeScript 5.9.3 + vitest 4.0.17
63
78
  - 002-task-cancellation: Added TypeScript 5.9.3 + vitest 4.0.17, AbortSignal/AbortController (standard Web APIs)
64
79
  - 004-pre-execution-validation: Added TypeScript 5.9.3 + vitest 4.0.17 (for testing)
65
-
66
- <!-- MANUAL ADDITIONS START -->
67
-
68
- - **Atomic Commits for Complex Features:** When working on complex multi-task features, you MUST commit after completing each distinct task. Before each commit, ensure that `pnpm build`, `pnpm lint`, and `pnpm test` pass. This creates safe rollback points and prevents restarting the entire feature if issues arise.
69
- - Never edit CHANGELOG.md manually. This file is for semantic release and is filled automatically.
70
-
71
- Before marking a task as concluded, YOU MUST:
72
-
73
- 1. run pnpm install
74
- 2. run pnpm build
75
- 3. run pnpm test
76
- 4. run pnpm lint
77
-
78
- If any of those command fail, review your changes.
79
-
80
- <!-- MANUAL ADDITIONS END -->
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 3.5.0 (2026-01-18)
2
+
3
+ * Merge pull request #69 from thalesraymond/feat/conditional-execution ([dd1dd8d](https://github.com/thalesraymond/task-runner/commit/dd1dd8d)), closes [#69](https://github.com/thalesraymond/task-runner/issues/69)
4
+ * Update README.md ([8808f94](https://github.com/thalesraymond/task-runner/commit/8808f94))
5
+ * docs: ✏️ archive conditional specs since its implemented ([b3db067](https://github.com/thalesraymond/task-runner/commit/b3db067))
6
+ * docs: ✏️ conditional execution spec ([5a670c4](https://github.com/thalesraymond/task-runner/commit/5a670c4))
7
+ * docs: ✏️ fix typo ([d969ff5](https://github.com/thalesraymond/task-runner/commit/d969ff5))
8
+ * docs: ✏️ rewriting readme with latest changes ([c968fc3](https://github.com/thalesraymond/task-runner/commit/c968fc3))
9
+ * docs: ✏️ update readme with example of conditional execution ([da9a818](https://github.com/thalesraymond/task-runner/commit/da9a818))
10
+ * feat: 🎸 new conditional execution feature ([e936ad7](https://github.com/thalesraymond/task-runner/commit/e936ad7))
11
+ * chore: 🤖 add AI skills ([5feacc6](https://github.com/thalesraymond/task-runner/commit/5feacc6))
12
+ * chore: 🤖 removed skill md file, general skills moved to workspa ([5a8bd0e](https://github.com/thalesraymond/task-runner/commit/5a8bd0e))
13
+ * chore: 🤖 removed workspace skills ([e819e00](https://github.com/thalesraymond/task-runner/commit/e819e00))
14
+
1
15
  ## <small>3.4.1 (2026-01-18)</small>
2
16
 
3
17
  * Merge pull request #65 from thalesraymond/perf/async-event-listeners-3002139613350975332 ([77edbc7](https://github.com/thalesraymond/task-runner/commit/77edbc7)), closes [#65](https://github.com/thalesraymond/task-runner/issues/65)
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
- - **Error Handling & Skipping**: robustly handles failures and automatically skips dependent steps.
17
- - **Event System**: Subscribe to lifecycle events (`workflowStart`, `taskStart`, `taskEnd`, etc.) for logging or monitoring.
18
- - **Runtime Validation**: Automatically detects circular dependencies and missing dependencies before execution loops.
19
-
20
- ## Event System
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
- Here is a simple example showing how to define a context, create steps, and execute them.
25
+ The library now provides a fluent `TaskRunnerBuilder` for easy configuration.
37
26
 
38
27
  ```typescript
39
- import { TaskRunner, TaskStep } from "./src";
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 {
@@ -51,16 +45,21 @@ interface ValidationContext {
51
45
  const UrlFormatStep: TaskStep<ValidationContext> = {
52
46
  name: "UrlFormatStep",
53
47
  run: async (ctx) => {
54
- const valid = ctx.issueBody.includes("github.com");
55
- return valid
56
- ? { status: "success" }
57
- : { status: "failure", error: "Invalid URL" };
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
56
  name: "DataLoaderStep",
63
57
  dependencies: ["UrlFormatStep"],
58
+ retry: {
59
+ attempts: 3,
60
+ delay: 1000,
61
+ backoff: "exponential"
62
+ },
64
63
  run: async (ctx) => {
65
64
  // Simulate API call
66
65
  ctx.prData = { additions: 20, ciStatus: "success" };
@@ -68,29 +67,25 @@ const DataLoaderStep: TaskStep<ValidationContext> = {
68
67
  },
69
68
  };
70
69
 
71
- const MaxChangesStep: TaskStep<ValidationContext> = {
72
- name: "MaxChangesStep",
73
- dependencies: ["DataLoaderStep"],
74
- run: async (ctx) => {
75
- // Safe access because dependencies ensured execution order
76
- if (!ctx.prData) return { status: "failure", error: "Missing PR Data" };
77
-
78
- return ctx.prData.additions < 50
79
- ? { status: "success" }
80
- : { status: "failure", error: "Too many changes" };
81
- },
82
- };
83
-
84
- // 3. Execute the runner
70
+ // 3. Configure and Build the Runner
85
71
  async function main() {
86
72
  const context: ValidationContext = {
87
73
  issueBody: "https://github.com/org/repo/pull/1",
88
74
  };
89
75
 
90
- const runner = new TaskRunner(context);
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, MaxChangesStep];
93
- const results = await runner.execute(steps);
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
- ## Skip Propagation
96
+ ## Advanced Configuration
102
97
 
103
- 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, saving resources and preventing cascading errors.
98
+ ### Execution Strategies
104
99
 
105
- ## Context Hydration
100
+ The `TaskRunner` is built on top of composable execution strategies.
106
101
 
107
- One nice thing to do is to avoid optional parameters and excessive use of `!` operator, with task dependencies we can chain our steps and context usages to make sure steps are executed only when pre requisites are met.
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
- This decouples **Data Loading** from **Business Logic**.
106
+ You can set a strategy globally using the `TaskRunnerBuilder`:
110
107
 
111
- ### Scenario: User Profile Validation
108
+ ```typescript
109
+ runnerBuilder.useStrategy(new DryRunExecutionStrategy());
110
+ ```
111
+
112
+ ### Execution Options
112
113
 
113
- Imagine you need to validate if a user is a "Pro" member. You shouldn't mix the API fetching logic with the validation logic.
114
+ When calling `execute`, you can provide a configuration object:
114
115
 
115
- 1. **Initial State**: The context starts with only a `userId`.
116
- 2. **Hydration Step**: A `UserLoaderStep` runs first. It fetches data from an API and attaches it to the context.
117
- 3. **Logic Step**: A `PremiumCheckStep` runs next. It doesn't need to know _how_ the data was fetched; it simply checks the `isPro` flag in the context.
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
- interface MyProjectContext {
121
- rawInput: string;
122
- }
122
+ await runner.execute(steps, {
123
+ concurrency: 2,
124
+ dryRun: true
125
+ });
126
+ ```
123
127
 
124
- interface MyProjectFullContext extends MyProjectContext {
125
- apiData: {
126
- user: string;
127
- isPro: boolean;
128
- };
129
- }
128
+ ## Visualization
130
129
 
131
- // Step 1: Hydrate the context
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
- // Step 2: Use the hydrated data
142
- class PremiumCheckStep implements TaskStep<MyProjectContext> {
143
- name = "PremiumCheckStep";
144
- dependencies = ["UserLoaderStep"]; // Ensures data is ready
132
+ ```typescript
133
+ import { TaskRunner } from "@calmo/task-runner";
145
134
 
146
- async run(ctx: MyProjectFullContext) {
147
- return ctx.apiData.isPro
148
- ? { status: "success" }
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
- This approach allows `PremiumCheckStep` to be easily tested with mock data, as it doesn't depend on the actual API loader.
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 company 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 niche case. I don't know if someone will find it useful but here it is.
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
- ## What is .gemini and .specify
167
+ ## Contributing and General Usage
161
168
 
162
- One of the reasons this project exists is to test 'code vibing' tools, so yes, this is vibe coded. My goal is to not touch the code and see if works.
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
  ---