@dtlabs/ai-toolkit 0.12.0 → 0.14.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/CHANGELOG.md +72 -0
  2. package/bin/cli.js +851 -9
  3. package/docs/task-executor-bootstrap.md +195 -0
  4. package/docs/task-executor-cli.md +319 -0
  5. package/docs/workflow-control-plane.md +95 -0
  6. package/lib/agent-registry.js +1018 -0
  7. package/lib/execution-ledger.js +283 -7
  8. package/lib/task-executor/claude-process.js +563 -0
  9. package/lib/task-executor/git.js +1362 -0
  10. package/lib/task-executor/index.js +3729 -0
  11. package/lib/task-executor/ownership.js +590 -0
  12. package/lib/task-executor/plan.js +725 -0
  13. package/lib/task-executor/store.js +1164 -0
  14. package/lib/workflow-artifacts.js +102 -0
  15. package/lib/workflow-control.js +219 -0
  16. package/lib/workflow-runner.js +39 -0
  17. package/package.json +1 -1
  18. package/src/claude/agents/{concurrency-safety-assessment.md → gaia-concurrency-safety-assessment.md} +1 -1
  19. package/src/claude/agents/{define-feature.md → gaia-define-feature.md} +1 -1
  20. package/src/claude/agents/{dependency-injection-refactoring.md → gaia-dependency-injection-refactoring.md} +1 -1
  21. package/src/claude/agents/{dependency-supply-chain-security.md → gaia-dependency-supply-chain-security.md} +1 -1
  22. package/src/claude/agents/{developer-backend.md → gaia-developer-backend.md} +1 -1
  23. package/src/claude/agents/{developer-frontend.md → gaia-developer-frontend.md} +1 -1
  24. package/src/claude/agents/{developer-testing.md → gaia-developer-testing.md} +1 -1
  25. package/src/claude/agents/{domain-model-refactoring.md → gaia-domain-model-refactoring.md} +1 -1
  26. package/src/claude/agents/{generate-requirements.md → gaia-generate-requirements.md} +1 -1
  27. package/src/claude/agents/{generate-tech-spec.md → gaia-generate-tech-spec.md} +1 -1
  28. package/src/claude/agents/{generate-work-breakdown.md → gaia-generate-work-breakdown.md} +5 -5
  29. package/src/claude/agents/{generic-software-assessment.md → gaia-generic-software-assessment.md} +1 -1
  30. package/src/claude/agents/{god-class-decomposition.md → gaia-god-class-decomposition.md} +1 -1
  31. package/src/claude/agents/{init-agents-md.md → gaia-init-agents-md.md} +1 -1
  32. package/src/claude/agents/{install-toolkit.md → gaia-install-toolkit.md} +1 -1
  33. package/src/claude/agents/{intervention-documentation-standard.md → gaia-intervention-documentation-standard.md} +10 -10
  34. package/src/claude/agents/{layered-architecture-assessment.md → gaia-layered-architecture-assessment.md} +1 -1
  35. package/src/claude/agents/{review-solution.md → gaia-review-solution.md} +1 -1
  36. package/src/claude/agents/{security-hardening.md → gaia-security-hardening.md} +1 -1
  37. package/src/claude/agents/{validate-feature-docs.md → gaia-validate-feature-docs.md} +2 -2
  38. package/src/claude/agents/{validate-work-breakdown-semantic.md → gaia-validate-work-breakdown-semantic.md} +1 -1
  39. package/src/claude/commands/next-task.md +4 -4
  40. package/src/claude/skills/assess-codebase/SKILL.md +9 -103
  41. package/src/claude/skills/define-feature/SKILL.md +2 -2
  42. package/src/claude/skills/hi-gaia/SKILL.md +1 -1
  43. package/src/claude/skills/implement-feature/SKILL.md +131 -38
  44. package/src/claude/skills/init-agents/SKILL.md +2 -2
  45. package/src/claude/skills/install-toolkit/SKILL.md +2 -2
  46. package/src/claude/workflows/am-phase1.js +43 -294
  47. package/src/claude/workflows/am-phase2.js +10 -164
  48. package/src/claude/workflows/pm-phase1.js +45 -287
  49. package/src/claude/workflows/pm-phase2.js +51 -511
  50. package/src/claude/workflows/pm-phase3.js +5 -767
@@ -0,0 +1,195 @@
1
+ # Task Executor Bootstrap
2
+
3
+ > Companion to [`docs/task-executor-cli.md`](task-executor-cli.md), which documents the CLI
4
+ > contract (commands, flags, exit codes, JSON schema) in detail and is not repeated here. This
5
+ > document covers the **operational** side: how a real operator/coordinator starts a run after
6
+ > Gates 1/2, the Windows-only qualification that gates whether `start` will run at all, and the
7
+ > persistence caveats an operator must know about before trusting a run's state — all pulled
8
+ > from FTR-018's own approved artifacts and test findings, not restated from memory.
9
+
10
+ ---
11
+
12
+ ## 1. The supervised delivery procedure after Gates 1/2
13
+
14
+ Once a feature's Requirements, Tech-Spec and Work Breakdown are approved (Gate 1 and Gate 2 both
15
+ recorded in `{PREFIX}-Approvals.md`), the `implement-feature` skill's **Step 6 — Launch the task
16
+ executor (Implementation Phase)** (`src/claude/skills/implement-feature/SKILL.md`) takes over.
17
+
18
+ The key behavioral change from the retired `pm-phase3` model, as stated in Step 6 itself:
19
+
20
+ - Implementation is **no longer dispatched as a subagent**. There is no `pm-phase3` workflow, no
21
+ Tier-1 dispatch guard, and no `pm-phase3:dispatch` ledger entry for this step.
22
+ - Once Gate 2 is approved, the skill's job is limited to **presenting or launching one explicit
23
+ `task-executor` start command** — the executor process itself then owns the repo, plans,
24
+ executes, checkpoints and resumes tasks deterministically, independent of the chat.
25
+ - **No subagent orchestrates the run, and no assistant or agent involvement is required for each
26
+ task transition once the run has started.** A single durable command launch is enough, and the
27
+ run may keep going, checkpointing and resuming, even after the launching conversation ends.
28
+
29
+ Concretely (Step 6a), the skill builds:
30
+
31
+ ```bash
32
+ ai-toolkit executor start \
33
+ --project . \
34
+ --feature <path-to-feature.md> \
35
+ --max-concurrency 1 \
36
+ --claude-path <resolved-claude-cli-path> \
37
+ --task-timeout-ms <task-timeout-ms, e.g. 900000> \
38
+ --agent-budget-usd <per-agent budget in USD, e.g. 5.00>
39
+ ```
40
+
41
+ If the host's ordinary terminal capability can launch a durable external command, the skill
42
+ launches it directly so the process persists independently of the chat. If not, it shows the
43
+ exact command for the user to run themselves in a terminal — it does **not** fake a durable
44
+ launch by running it as a blocking in-chat command.
45
+
46
+ In both cases, the `run-id` printed by `start` must be captured — it is required for every
47
+ subsequent `status`, `diagnose`, `stop`, `reconcile`, `resume`, and `replan` call; there is no
48
+ implicit last-run selection.
49
+
50
+ **The run may outlive the launching conversation.** The skill explicitly does not block on the
51
+ run reaching a terminal state; it proceeds to its own Step 7 with whatever the latest status
52
+ reports at that point. To check on a run later — from the same conversation, a new one, or no
53
+ conversation at all — the operator uses the read-only `status` subcommand documented in
54
+ `docs/task-executor-cli.md`:
55
+
56
+ ```bash
57
+ ai-toolkit executor status --project <path> --run-id <uuid>
58
+ ```
59
+
60
+ Note, per the CLI reference, that `status` (like `diagnose`) is currently a stub that always
61
+ throws `NOT_IMPLEMENTED` (exit 1) — the command surface and its intended read-only summary shape
62
+ (task status counts, run status, total cost from the ledger) are defined, but the real body has
63
+ not been built yet (attributed there to `US-06-TASK-BE-01`). Until it lands, checking on a
64
+ running or finished executor run means reading the ledger and per-run state directly, or using
65
+ `reconcile`/`resume`, both of which are fully implemented and report each task's classification.
66
+
67
+ For the full flag reference, exit-code table, and JSON result schemas of every subcommand
68
+ (`start`, `status`, `diagnose`, `stop`, `reconcile`, `resume`, `replan`), see
69
+ `docs/task-executor-cli.md` — this document intentionally does not restate that contract.
70
+
71
+ ---
72
+
73
+ ## 2. Windows-only E-02 qualification
74
+
75
+ The executor's process-supervision guarantees — tree-kill of in-flight attempts, tag-based
76
+ worker-liveness scanning, and immediate-stop confirmation — are proven for **Windows only**. This
77
+ is FTR-018's Gate-1 binding constraint #3 (`FTR-018-Approvals.md`):
78
+
79
+ > **E-02 is Windows-only qualified.** Other platforms require dedicated proofs before their
80
+ > execution is enabled; no implicit cross-platform guarantee.
81
+
82
+ This is not a paper constraint: `execute()` in `lib/task-executor/index.js` checks
83
+ `process.platform !== 'win32'` as its **very first validation step** — before any other argument
84
+ validation, before deriving the execution root, before touching the plan snapshot, and before
85
+ acquiring the ownership lease — and throws `PLATFORM_NOT_QUALIFIED` (CLI exit code 7) on any
86
+ non-Windows platform (US-08-TASK-BE-05, behaviorally tested by US-08-TASK-TEST-02's
87
+ `platform-guard.test.js`, which asserts no agent dispatch and no worktree creation occur on a
88
+ simulated non-win32 platform).
89
+
90
+ In other words: there is **no implicit cross-platform guarantee, and no partial or best-effort
91
+ execution on other platforms**. On any platform other than Windows, `start` refuses to run at
92
+ all rather than degrading silently.
93
+
94
+ ---
95
+
96
+ ## 3. Persistence caveats
97
+
98
+ These are the real, already-confirmed caveats and limitations discovered during this feature's
99
+ own delivery. Each one is cited to its source rather than paraphrased from memory.
100
+
101
+ ### a. E-01 residual: worktree persistence not re-reproduced in the isolated proof run
102
+
103
+ Per `FTR-018-Approvals.md`, Gate 1 section (OQ-01a / E-01 sub-point), the 2026-09-28 binding
104
+ closure of the agent-provenance proof carries an explicit residual:
105
+
106
+ > **Residual:** persistence not re-reproduced in this run (Write permission-denied); proven
107
+ > separately by the original Test B.
108
+
109
+ That is: the isolated 2026-09-28 proof run that closed the *definition-binding* question (verified
110
+ agent-hash → loaded definition) did **not** itself re-demonstrate worktree persistence, because
111
+ the Write tool was permission-denied in that isolated cwd. Persistence is proven by a separate,
112
+ earlier proof (the original Test B referenced in the same evidence record), not by this run. An
113
+ operator relying only on the E-01 dossier for "the whole chain is proven end-to-end in one run"
114
+ should know that persistence and definition-binding were proven in two separate runs, not one.
115
+
116
+ ### b. Windows filesystem-race flake on atomic write (operational, not a code defect)
117
+
118
+ `store.js`'s state persistence uses a temp-write-then-rename atomic-write protocol
119
+ (`_atomicWriteFileSync` in `lib/task-executor/store.js`: open a same-directory exclusive temp
120
+ file, write, `fsyncSync`, close, then `fs.renameSync(tmpPath, filePath)`). This is the same
121
+ atomic-write discipline the executor's own production state persistence (`writeState`) relies on.
122
+
123
+ **Operational note:** under heavy concurrent I/O load on Windows (e.g. many parallel Jest workers
124
+ writing state files simultaneously), this rename step can occasionally hit a transient
125
+ filesystem error (EPERM / rename race) that is environment-load-dependent rather than
126
+ deterministic — it has not reproduced when run in isolation, and is not a defect in the
127
+ atomic-write logic itself. Anyone operating or testing the executor under high Windows I/O
128
+ contention should expect this as a known environmental characteristic, not treat an occasional
129
+ transient failure of this shape as evidence of a code bug.
130
+
131
+ ### c. `resume()`/`reconcile()` only inspect and report — no automatic continuation
132
+
133
+ Confirmed by US-07-TASK-TEST-01 (`tests/task-executor/sequential.test.js`, commit `25b2663`):
134
+
135
+ > Finding, confirmed and NOT worked around: resume()/reconcile() only inspect and report - there
136
+ > is currently no way to actually continue/finish an interrupted run (execute() always mints a
137
+ > fresh runId and resets all tasks to pending). This is a known, explicit architecture gap, not a
138
+ > bug - documented in the test file for any future work that wants to add real continuation.
139
+
140
+ **Operator implication:** "resuming" a run today means safely inspecting what has already been
141
+ durably completed and getting an evidence-based classification per task — it does **not** mean
142
+ the interrupted run will automatically finish its remaining tasks. `execute()` always starts a
143
+ brand-new run (a fresh `runId`, all tasks reset to pending); there is no run loop wired into
144
+ `resume()`.
145
+
146
+ ### d. Dormant concurrency gap in `dispatchTaskAttempt` (must be resolved before N>1 wiring)
147
+
148
+ Confirmed by US-08-TASK-TEST-01 (`tests/task-executor/parallel.test.js`, commit `2f06883`),
149
+ Finding B:
150
+
151
+ > a real, currently-dormant concurrency gap: two concurrent dispatchTaskAttempt calls requesting
152
+ > the IDENTICAL, not-yet-existing attemptNumber for the same task both dispatch real subprocesses
153
+ > - neither is rejected, only one attempt record persists, and its processIdentity is
154
+ > byte-identical for both calls (derived only from runId/taskId/attemptNumber), so persisted state
155
+ > cannot even reveal two real processes ran. Currently unreachable via any real production entry
156
+ > point (execute()'s only caller dispatches one attempt at a time sequentially, and no N>1
157
+ > orchestrator exists), but flagged prominently for whoever eventually wires real N>1 dispatch.
158
+
159
+ **Operator implication:** this race cannot fire today because `execute()` is strictly
160
+ sequential (`maxConcurrency=1` only; see docs/task-executor-cli.md — any other value is
161
+ rejected as `UNSUPPORTED_CONCURRENCY`, never silently downgraded) and no orchestrator calls
162
+ `dispatchTaskAttempt` concurrently. It is a **constraint that must be resolved before any future
163
+ N>1 wiring work** lands, not something safe to rely on or ignore once that work begins.
164
+
165
+ ### e. Verification receipts already on disk are not consulted on resume (efficiency gap)
166
+
167
+ Confirmed by US-09-TASK-TEST-01 (`tests/task-executor/fault-injection.test.js`, commit `e601e3d`),
168
+ Finding 1:
169
+
170
+ > there is no distinct durable state for "verification passed, review not yet dispatched" - a
171
+ > crash in that exact window leaves reconcile() unable to tell it apart from "verification never
172
+ > ran", so a resume may safely but redundantly re-run verification commands whose receipts are
173
+ > already sitting on disk. Not an AC-10 violation (nothing is ever wrongly marked complete) - a
174
+ > design/efficiency observation for a future decision, not fixed here.
175
+
176
+ **Operator implication:** a resume after a crash in that narrow window is always *safe* (nothing
177
+ is ever falsely marked complete), but may be redundant — it can re-run verification commands,
178
+ including potentially expensive ones, that already passed and whose receipts already exist on
179
+ disk.
180
+
181
+ ### f. N>1 parallel-execution primitives exist and are tested, but are not wired into `execute()`
182
+
183
+ `createTaskWorktree`, `createSlotPool` (US-08-TASK-BE-01/02), `integrateAttempt`
184
+ (`lib/task-executor/git.js`, US-08-TASK-BE-03) and `registerIntegratedSHA`
185
+ (`lib/task-executor/store.js`, US-08-TASK-BE-04) are all real, already-implemented functions, and
186
+ US-08-TASK-TEST-01's `parallel.test.js` exercises them together end-to-end for N=2/3 scenarios.
187
+ But as that same commit's message states plainly:
188
+
189
+ > there is no real "execute() with maxConcurrency=N" path yet (execute() still rejects anything
190
+ > but N=1), so this proves the standalone primitives compose correctly rather than exercising a
191
+ > not-yet-existing orchestrator.
192
+
193
+ **Operator implication:** these primitives are proven to compose correctly in isolation, but
194
+ `execute()` remains N=1 / sequential only. Do not expect `--max-concurrency` values other than
195
+ `1` to do anything but fail fast with `UNSUPPORTED_CONCURRENCY` today.
@@ -0,0 +1,319 @@
1
+ # Task Executor CLI
2
+
3
+ > Documents the **real, currently-implemented** behavior of `ai-toolkit executor <subcommand>`
4
+ > (`bin/cli.js`'s `handleExecutorCommand`, wired to `lib/task-executor/index.js`), part of
5
+ > FTR-018 (deterministic task execution, checkpoints and resume).
6
+ >
7
+ > This file describes what the code actually does today, not the aspirational CLI contract in
8
+ > `FTR-018-Tech-Spec.md` section 10. Every discrepancy between the two is called out explicitly
9
+ > below rather than silently reconciled.
10
+
11
+ ---
12
+
13
+ ## Invocation
14
+
15
+ ```bash
16
+ ai-toolkit executor <subcommand> [flags]
17
+ # or, inside this repo:
18
+ node bin/cli.js executor <subcommand> [flags]
19
+ ```
20
+
21
+ `--project <path>` is accepted by every subcommand and defaults to `process.cwd()` when omitted.
22
+ All other flags are per-subcommand (see below). Any flag not explicitly recognized for a
23
+ subcommand is silently ignored — there is no generic flag-forwarding path, so nothing reaches a
24
+ filesystem path or lock primitive except the whitelisted flags read via `getFlag()`/`hasFlag()`.
25
+
26
+ **Discrepancy vs. Tech-Spec:** section 10 documents `--format json or text (default)` as a
27
+ common flag. The real CLI has no `--format` flag at all — `handleExecutorCommand` always emits
28
+ JSON (`JSON.stringify(result, null, 2)`) on success and always emits JSON on error. There is no
29
+ text-mode output in the current implementation.
30
+
31
+ ---
32
+
33
+ ## Commands
34
+
35
+ ### `start`
36
+
37
+ ```bash
38
+ ai-toolkit executor start --project <path> --feature <path> [--max-concurrency N] [--claude-path <path>] [--task-timeout-ms <ms>] [--agent-budget-usd <amount>]
39
+ ```
40
+
41
+ | Flag | Required | Default | Notes |
42
+ |---|---|---|---|
43
+ | `--project` | no | `process.cwd()` | Repo root; also resolves the common Git directory |
44
+ | `--feature` | **yes** | — | Path to the feature's `feature.md`; the CLI usage-errors (exit 2, no JSON) if omitted |
45
+ | `--max-concurrency` | no | `1` | Forwarded to `execute()` as a Number; only `1` is currently supported (see below) |
46
+ | `--claude-path` | no | — | Absolute path to the qualified Claude CLI executable |
47
+ | `--task-timeout-ms` | no | — | Forwarded as a Number |
48
+ | `--agent-budget-usd` | no | — | Forwarded as a Number; persisted into run config, not itself enforced |
49
+
50
+ Backs `lib/task-executor/index.js`'s `execute()`. Validates gates/config, acquires the
51
+ repo-wide execution lease for a brand-new run, builds an immutable plan snapshot from the
52
+ feature's own `{PREFIX}-Work-Breakdown.md/.csv`, and drives a sequential (slot count 1)
53
+ dispatch → verify → review → checkpoint loop to completion or block.
54
+
55
+ Only `maxConcurrency=1` is implemented — any other requested value is **rejected**
56
+ (`UNSUPPORTED_CONCURRENCY`), never silently downgraded. `execute()` also refuses to run at all
57
+ on any platform other than `win32` (see "Platform qualification" below), before touching any
58
+ other validation.
59
+
60
+ ### `status`
61
+
62
+ ```bash
63
+ ai-toolkit executor status --project <path> --run-id <uuid>
64
+ ```
65
+
66
+ `--run-id` is required (usage error, exit 2, if omitted). Read-only summary of a run: task
67
+ status counts, current run status, and total cost from the ledger.
68
+
69
+ **Not implemented.** `status()` in `lib/task-executor/index.js` is currently a stub that always
70
+ throws `NOT_IMPLEMENTED` (attributed to US-06-TASK-BE-01, which has not built its real body
71
+ yet). Calling `ai-toolkit executor status` today always fails with exit code 1 and an error
72
+ JSON body — it does not return the summary shape documented in its own JSDoc until that task is
73
+ implemented.
74
+
75
+ ### `diagnose`
76
+
77
+ ```bash
78
+ ai-toolkit executor diagnose --project <path> --run-id <uuid>
79
+ ```
80
+
81
+ `--run-id` is required. Intended as a read-only detailed evidence dump (per-task/attempt
82
+ evidence, ownership/lease state, repair suggestions).
83
+
84
+ **Not implemented.** Same situation as `status`: `diagnose()` is a stub that always throws
85
+ `NOT_IMPLEMENTED` (attributed to US-06-TASK-BE-02). Calling it today always exits 1 with an
86
+ error JSON body.
87
+
88
+ ### `stop`
89
+
90
+ ```bash
91
+ ai-toolkit executor stop --project <path> --run-id <uuid> [--mode graceful|immediate]
92
+ ```
93
+
94
+ | Flag | Required | Default | Notes |
95
+ |---|---|---|---|
96
+ | `--run-id` | **yes** | — | |
97
+ | `--mode` | no | `'graceful'` | Only `'graceful'` or `'immediate'` are accepted; anything else throws `STOP_VALIDATION_ERROR` |
98
+
99
+ Persists a stop request and acknowledges receipt only — it never itself claims the run has
100
+ stopped.
101
+
102
+ - `graceful` (default): persists the request; the already-running `execute()` loop is the only
103
+ thing that actually enforces the block, at its next natural iteration boundary.
104
+ - `immediate`: additionally attempts real termination of every in-flight attempt (stage
105
+ `dispatching`/`running` with a recorded process tag), via the same Windows-only tagged-process
106
+ tree-kill technique used elsewhere in this module, then polls for confirmed termination.
107
+
108
+ Idempotent: if the run's current status is already `stopping` or settled
109
+ (`completed`/`blocked`/`superseded`/`paused`), this call is a pure no-op and reports the
110
+ originally-recorded mode.
111
+
112
+ ### `reconcile`
113
+
114
+ ```bash
115
+ ai-toolkit executor reconcile --run-id <uuid> [--project <path>]
116
+ ```
117
+
118
+ `--run-id` is required. `--project` resolves the execution root and the current-branch task ref
119
+ automatically (via `git rev-parse --abbrev-ref HEAD` in the given `--project`, or its default,
120
+ the current directory) — there is no separate flag for these; the CLI derives them itself.
121
+
122
+ Reconciles persisted state and evidence for a run and classifies every task's current attempt.
123
+ **Only repairs proven gaps — it never dispatches new work and never creates new integrations.**
124
+ The only two repairs it can apply are: registering a commit SHA when a checkpoint-prepared
125
+ attempt's tip exactly matches its own persisted checkpoint intent, and finalizing a task
126
+ checkpoint when a committed attempt's registered SHA is confirmed reachable but the task's
127
+ status was not yet advanced. Refuses to proceed (`COMPETING_COORDINATOR_LIVE` /
128
+ `COMPETING_COORDINATOR_LIVENESS_UNKNOWN`) if a different, live-or-unknown-liveness coordinator
129
+ holds the repo-wide execution lease.
130
+
131
+ ### `resume`
132
+
133
+ ```bash
134
+ ai-toolkit executor resume --run-id <uuid> [--project <path>]
135
+ ```
136
+
137
+ Same flags/derivation as `reconcile` (in fact the CLI dispatches both `reconcile` and `resume`
138
+ through the identical flag-parsing branch, selecting the target function by subcommand name).
139
+ Enforces the same "no live competing coordinator" precondition, then calls `reconcile()` to
140
+ apply its repairs, then reports each task's current status plus the next-safe-action
141
+ classification `reconcile()` already computed. Does **not** dispatch new work itself — there is
142
+ no run loop wired into `resume()` yet (the real dispatch loop lives in `execute()`); its scope
143
+ ends at "reconcile, then report what is safe to continue."
144
+
145
+ ### `replan`
146
+
147
+ ```bash
148
+ ai-toolkit executor replan --run-id <uuid> --feature <path> [--project <path>] [--task-mapping-json '<json>']
149
+ ```
150
+
151
+ | Flag | Required | Default | Notes |
152
+ |---|---|---|---|
153
+ | `--run-id` | **yes** | — | The original run being replanned |
154
+ | `--feature` | **yes** | — | Path to the **successor** feature's `feature.md` (the approved successor plan) |
155
+ | `--project` | no | `process.cwd()` | Resolves execution root + current-branch task ref, same as `reconcile`/`resume` |
156
+ | `--task-mapping-json` | no | `'[]'` (empty array) | JSON array of `{oldTaskId, newTaskId}` objects |
157
+
158
+ **Discrepancy vs. Tech-Spec:** the table in section 10 lists only `--run-id`/`--feature` for
159
+ `replan`. The real `replan()` in `lib/task-executor/index.js` requires `args.taskMapping` to be
160
+ an array (throwing `DISPATCH_VALIDATION_ERROR` otherwise) and never invents one itself. The CLI
161
+ therefore added `--task-mapping-json` as a necessary extension beyond the literal table, using
162
+ the same `--*-json` structured-input convention the `ledger` subcommand already established
163
+ (`--metadata-json`). This is documented here as an intentional, flagged gap — not silently
164
+ papered over — per the implementing agent's own note at the flag's parse site in `bin/cli.js`.
165
+ Omitting the flag defaults to an empty mapping (a valid "no task carries forward" array), never
166
+ a rejection.
167
+
168
+ Grants **no approval itself** — the successor's Gate 2 approval must already exist on disk
169
+ (`## Gate 2` heading in its `Approvals.md`, produced entirely outside the executor by the normal
170
+ `implement-feature` gate protocol). Does not dispatch, start, or touch the successor run in any
171
+ way. Validates the caller-supplied old→new task mapping against both the original run's recorded
172
+ tasks and the successor's plan snapshot, computes carry-completion eligibility per pair
173
+ (read-only), refuses if any original-run attempt's worker liveness is not confirmed dead
174
+ (`LIVE_WORKER_BLOCKS_REPLAN`), then records the mapping as a durable intent and marks the
175
+ original run `superseded`. Never deletes or rewrites any task/attempt evidence.
176
+
177
+ ---
178
+
179
+ ## Exit codes
180
+
181
+ | Exit code | Meaning |
182
+ |---|---|
183
+ | 0 | Successful operation/request |
184
+ | 1 | Unexpected I/O (also the default for any unmapped/unknown error code) |
185
+ | 2 | Invalid config/input |
186
+ | 3 | Ownership/live worker conflict |
187
+ | 4 | Evidence/plan/approval mismatch |
188
+ | 5 | Worker/verification/review failure |
189
+ | 6 | Integration conflict |
190
+ | 7 | Runtime not qualified |
191
+ | 8 | Paused awaiting human decision |
192
+
193
+ This table matches `bin/cli.js`'s `EXECUTOR_EXIT_CODE_BY_ERROR_CODE` mapping (plus a
194
+ suffix-based catch-all: any error code ending in `_VALIDATION_ERROR` maps to exit 2). A few
195
+ notable groupings from that mapping:
196
+
197
+ - **3** — every lease/ownership code: `LEASE_HELD`, `LEASE_NOT_OWNER`, `LEASE_STILL_LIVE`,
198
+ `LEASE_LIVENESS_UNKNOWN`, `LEASE_NOT_FOUND`, `GUARD_HELD`, `NO_LOCK_HELD`,
199
+ `COMPETING_COORDINATOR_LIVE`, `COMPETING_COORDINATOR_LIVENESS_UNKNOWN`,
200
+ `LIVE_WORKER_BLOCKS_REPLAN`.
201
+ - **4** — every "plan/evidence/approval doesn't match" code, including
202
+ `SUCCESSOR_NOT_APPROVED`, `PLAN_NOT_FOUND`, `PLAN_PARSE_ERROR`, `REPLAN_UNKNOWN_TASK_MAPPING`,
203
+ `STATE_NOT_FOUND`/`STATE_TASK_NOT_FOUND`/`STATE_ATTEMPT_NOT_FOUND`, `TREE_MISMATCH`,
204
+ `RECEIPT_CONFLICT`, `INTENT_CONFLICT`, and many more (see the source comment for the full
205
+ list).
206
+ - **5** — `CLAUDE_SPAWN_FAILED`, `AGENT_NOT_VERIFIED`, `RESOLUTION_FAILED`.
207
+ - **6** — `INTEGRATION_CONFLICT`.
208
+ - **7** — `PLATFORM_NOT_QUALIFIED` (see "Platform qualification" below).
209
+ - **1 (default)** — includes `NOT_IMPLEMENTED` explicitly, so `status`/`diagnose` calls exit 1
210
+ today, not some special code.
211
+
212
+ **Exit code 8 is a success-path outcome, never a thrown error** — unlike every other code
213
+ above, which is assigned only on the error path via `mapExecutorErrorToExitCode`. Per the
214
+ Tech-Spec, 8 means "paused awaiting human decision": `execute()`'s and `resume()`'s result
215
+ objects can both legitimately carry `runStatus: 'paused'` (set when a `stop()` request — either
216
+ mode — was observed at a loop boundary; `resume()` simply passes through whatever `runStatus`
217
+ was last persisted, including `'paused'`). `handleExecutorCommand`'s shared `emitSuccess()`
218
+ inspects `result.runStatus` and sets exit code **8** when it is `'paused'`, else **0** — this is
219
+ a no-op check for `reconcile`/`stop`/`replan` (their result shapes never carry a `runStatus`
220
+ field at all) and never applies to `status`/`diagnose` (still `NOT_IMPLEMENTED`, so they never
221
+ reach the success path).
222
+
223
+ ---
224
+
225
+ ## JSON result schema
226
+
227
+ ### Success (stdout, exit matches the table above)
228
+
229
+ Every command that has a real implementation returns a **versioned result object** —
230
+ `{ protocolVersion, ... }` — written to stdout as pretty-printed JSON. Per
231
+ `lib/task-executor/index.js`'s own JSDoc/return statements:
232
+
233
+ **`start` (`execute()`) and `resume`:**
234
+ ```json
235
+ {
236
+ "protocolVersion": 1,
237
+ "runId": "<uuid>",
238
+ "runStatus": "completed | blocked | paused",
239
+ "tasks": [ { "taskId": "US-01-T01", "status": "checkpointed" } ]
240
+ }
241
+ ```
242
+ `resume`'s object additionally carries `tasks[].nextAction` (the next-safe-action classification
243
+ from `reconcile()`) and a top-level `repairsApplied` array — both additive beyond the minimum
244
+ shape above.
245
+
246
+ **`stop`:**
247
+ ```json
248
+ {
249
+ "protocolVersion": 1,
250
+ "runId": "<uuid>",
251
+ "requestAccepted": true,
252
+ "mode": "graceful | immediate",
253
+ "idempotent": true,
254
+ "terminationConfirmed": true,
255
+ "killAttempts": [
256
+ { "taskId": "US-01-T01", "attemptNumber": 1, "processIdentity": "ai-toolkit-task:...", "confirmed": true }
257
+ ]
258
+ }
259
+ ```
260
+ `idempotent` is present only on an idempotent replay. `terminationConfirmed`/`killAttempts` are
261
+ present only for a newly-accepted `immediate` request — never for `graceful`, never for an
262
+ idempotent replay.
263
+
264
+ **`reconcile`:**
265
+ ```json
266
+ {
267
+ "protocolVersion": 1,
268
+ "runId": "<uuid>",
269
+ "repairsApplied": [ { "taskId": "US-01-T01", "action": "registerCommitSHA" } ],
270
+ "classifications": [ { "taskId": "US-01-T01", "classification": "...", "action": "..." } ]
271
+ }
272
+ ```
273
+
274
+ **`replan`:**
275
+ ```json
276
+ {
277
+ "protocolVersion": 1,
278
+ "originalRunId": "<uuid>",
279
+ "successorPlanDigest": "<digest>",
280
+ "taskMapping": [
281
+ { "oldTaskId": "US-01-T01", "newTaskId": "US-01-T01", "carriesCompletion": true }
282
+ ]
283
+ }
284
+ ```
285
+
286
+ **`status`/`diagnose`:** no success shape exists yet — both are `NOT_IMPLEMENTED` stubs (see
287
+ above); every call surfaces the error shape below with `code: "NOT_IMPLEMENTED"`.
288
+
289
+ ### Error (stderr, exit per the table above)
290
+
291
+ ```json
292
+ {
293
+ "status": "error",
294
+ "code": "NO_LOCK_HELD",
295
+ "message": "dispatchTaskAttempt: no repo-wide execution lease held by runId \"...\" at ..."
296
+ }
297
+ ```
298
+
299
+ This is `bin/cli.js`'s real `emitError()` shape: `code` falls back to `'UNKNOWN'` if the thrown
300
+ error carries no `.code`; `message` falls back to `String(err)` if the error carries no
301
+ `.message`.
302
+
303
+ A malformed CLI invocation (e.g. missing a required flag) is a **usage error**, not one of the
304
+ JSON error shapes above: it writes a plain `Error: <message>` line to stderr and sets exit code 2
305
+ directly, without going through `mapExecutorErrorToExitCode`.
306
+
307
+ ---
308
+
309
+ ## Platform qualification
310
+
311
+ `execute()` checks `process.platform !== 'win32'` as its very first validation step — before any
312
+ other argument validation, before deriving the execution root, before touching the plan
313
+ snapshot, and before acquiring the ownership lease — and throws `PLATFORM_NOT_QUALIFIED` (exit
314
+ 7) on any non-Windows platform. This reflects FTR-018's Gate-1 binding constraint: the E-02
315
+ process-supervision evidence (tree-kill via `taskkill`, tag-based liveness scanning via
316
+ `Win32_Process` CIM queries) that the executor's crash-recovery/stop/worker-liveness machinery
317
+ depends on is qualified for Windows only (US-08-TASK-BE-05). There is no implicit
318
+ cross-platform guarantee — other platforms require their own dedicated qualification proof
319
+ before `start` will run on them at all.
@@ -0,0 +1,95 @@
1
+ # Deterministic workflow control plane
2
+
3
+ The toolkit owns workflow execution in Node. An LLM is used only for document
4
+ generation, review, and assessment. A workflow script is not a registered worker
5
+ agent: never pass `pm-phase1`, `pm-phase2`, `pm-phase3`, `am-phase1`, or `am-phase2`
6
+ to `agentType`, `subagent_type`, or Claude's `--agent`.
7
+
8
+ ## Invocation
9
+
10
+ Reinstall the toolkit runtime assets after upgrading the package. The host rejects
11
+ older installed workflows without `controlPlaneVersion: 1`, even if their old
12
+ manifest hashes match. Use one installation scope (local or global); ambiguous or
13
+ unverified agent installations fail closed.
14
+
15
+ ```text
16
+ ai-toolkit workflow run pm-phase1 --project . --claude-path "C:\path\claude.exe" -- "internal_docs/features/FTR-123-example/feature.md"
17
+ ai-toolkit workflow run pm-phase2 --project . --claude-path "C:\path\claude.exe" -- "internal_docs/features/FTR-123-example/feature.md"
18
+ ai-toolkit workflow run am-phase1 --project . --claude-path "C:\path\claude.exe" -- . --scope=security,devops --prefix ASSESS-123
19
+ ai-toolkit workflow run am-phase2 --project . -- --prefix ASSESS-123 --flagged INT-001 --ack "Reviewed by the user"
20
+ ```
21
+
22
+ Resolve the actual Claude executable as described in `task-executor-bootstrap.md`.
23
+ `--home DIR` is available for explicit installation-scope resolution. Paths are
24
+ individual argv elements, never interpolated shell commands. Raw string arguments
25
+ in the JavaScript API support quoted paths and preserve Windows backslashes.
26
+
27
+ Exit 0 returns a JSON payload on stdout. A nonzero exit stops the pipeline; do not
28
+ present the next approval gate. Gate 1/Gate 2 and Findings Gate decisions remain in
29
+ the main loop. `am-phase2` requires explicit acknowledgement and a validated
30
+ intervention selection; it does not invent approval.
31
+
32
+ `pm-phase3` is a retired compatibility entry point and refuses execution. Since
33
+ FTR-018, implementation belongs to `ai-toolkit tasks start/run`, with its task
34
+ ownership, checkpoints, verification, review and resume protocol. The old workflow
35
+ must not provide a fallback execution path. No JobScheduler feature is involved
36
+ in this migration.
37
+
38
+ ## Audit and replacements
39
+
40
+ | Original location | Deterministic operation formerly delegated to `agent()` | Replacement |
41
+ |---|---|---|
42
+ | pm-phase1 | agent resolution; self/worker ledger open/close | Verified host dispatch and owned activity lifecycle |
43
+ | pm-phase1 | feature discovery, prefix, existence/mtime checks, ledger creation | Direct filesystem operations; freshness includes upstream documents; corrupt ledgers are preserved and rejected |
44
+ | pm-phase1 | validation report existence/fabrication, process log writes | Require a nonempty fresh report from the validator; append log directly; structured validation verdict |
45
+ | pm-phase2 | resolution and ledger operations | Same host lifecycle; unsuccessful results cannot close the owner as done |
46
+ | pm-phase2 | validator/renderer execution, exit/output parsing and file checks | Toolkit CLI subprocess, `shell: false`, argv arrays, actual exit status, JSON parsing and disk reads |
47
+ | pm-phase2 | raw pricing read, WB metrics, Token/Effort estimates, process log | Direct JSON reads and deterministic templates; unavailable usage/cost stays unavailable |
48
+ | pm-phase3 | resolution, all ledger mutations, raw CSV/ledger/pricing reads, issue counts, test/git/PR commands, actuals writes | Legacy execution removed; implementation and CSV parsing already belong to the FTR-018 executor |
49
+ | am-phase1 | discovery CLI, catalog/file reads, scope filtering, estimates/index counts | Canonical assessor map, verified resolution, strict scope filtering and deterministic Markdown parsing/templates |
50
+ | am-phase2 | approval file, intervention selection, registry read/write/counts | Deterministic recorder with explicit input and readback |
51
+ | FTR-018 process helper | worker identity cache and dispatch identity | Reject orchestrators; require requested canonical ID/native name agreement; cache by project + home + ID |
52
+
53
+ Architecture, concurrency and quality have dedicated assessors. Security, DevOps,
54
+ domain-model and dependencies use the generic assessor with explicit scope
55
+ instructions. Multiple generic scopes are combined into one dispatch to avoid
56
+ parallel writers overwriting the same report. Remediation agents are recommendations
57
+ in intervention documents only; they are never used as assessors.
58
+
59
+ ## Identity, state and ownership
60
+
61
+ Worker resolution checks the canonical catalog ID, verified status, exact native
62
+ name, current definition hash, agent frontmatter name/model, and worker kind.
63
+ Workflow resolution is separate and can never authorize worker dispatch.
64
+
65
+ Every invocation has a unique owner and activity IDs. A per-prefix lock prevents
66
+ two workflow runs from writing the same feature/assessment concurrently. The owner
67
+ opens before work, drains parallel workers, validates outputs and serializes the
68
+ result before its terminal transition. Worker usage and provenance are persisted.
69
+ Legacy `open/close/fail/skip/finalizeActivity` calls cannot mutate owned entries;
70
+ owned finalization requires the matching owner and a running entry. No name-only
71
+ fallback is used for owned transitions. Later invocations cannot reopen an old run.
72
+
73
+ The ownership token prevents accidental cross-finalization, not hostile filesystem
74
+ access: workers and the host still run under the user's OS account. This is not a
75
+ security sandbox.
76
+
77
+ A caught failure produces `failed`. A hard OS/process kill can leave `running`,
78
+ never a speculative `done`; workflow automatic resume/reconciliation is not added
79
+ by this change. Retrying uses a new owner and preserves the interrupted record.
80
+ If persistence itself fails, the error propagates rather than reporting success.
81
+
82
+ ## Verification and limits
83
+
84
+ Regression tests execute the installed workflow source through the real host,
85
+ resolver, filesystem and ledger. Integration tests invoke the actual structural
86
+ validator/renderer and workflow CLI; LLM workers are deterministic test doubles.
87
+ Coverage includes worker failure, missing outputs, corrupt ledger, late terminal
88
+ calls, parallel rejection/drain, identity mismatch, cross-project cache leakage,
89
+ negative semantic verdict, unknown scopes, paths with spaces and explicit approval.
90
+
91
+ The live paid Claude model/permission flow has not been exercised by these tests.
92
+ The subprocess adapter reuses FTR-018's existing qualification; no new cross-platform
93
+ process-supervision guarantee is claimed. On Windows, the full executor test suite
94
+ requires Git Bash ahead of the WSL launcher in PATH. User-level installations are
95
+ not modified by running the isolated test fixtures.