@dtlabs/ai-toolkit 0.12.0 → 0.13.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 (36) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/cli.js +480 -7
  3. package/lib/agent-registry.js +1018 -0
  4. package/lib/execution-ledger.js +97 -3
  5. package/package.json +1 -1
  6. package/src/claude/agents/{concurrency-safety-assessment.md → gaia-concurrency-safety-assessment.md} +1 -1
  7. package/src/claude/agents/{define-feature.md → gaia-define-feature.md} +1 -1
  8. package/src/claude/agents/{dependency-injection-refactoring.md → gaia-dependency-injection-refactoring.md} +1 -1
  9. package/src/claude/agents/{dependency-supply-chain-security.md → gaia-dependency-supply-chain-security.md} +1 -1
  10. package/src/claude/agents/{developer-backend.md → gaia-developer-backend.md} +1 -1
  11. package/src/claude/agents/{developer-frontend.md → gaia-developer-frontend.md} +1 -1
  12. package/src/claude/agents/{developer-testing.md → gaia-developer-testing.md} +1 -1
  13. package/src/claude/agents/{domain-model-refactoring.md → gaia-domain-model-refactoring.md} +1 -1
  14. package/src/claude/agents/{generate-requirements.md → gaia-generate-requirements.md} +1 -1
  15. package/src/claude/agents/{generate-tech-spec.md → gaia-generate-tech-spec.md} +1 -1
  16. package/src/claude/agents/{generate-work-breakdown.md → gaia-generate-work-breakdown.md} +5 -5
  17. package/src/claude/agents/{generic-software-assessment.md → gaia-generic-software-assessment.md} +1 -1
  18. package/src/claude/agents/{god-class-decomposition.md → gaia-god-class-decomposition.md} +1 -1
  19. package/src/claude/agents/{init-agents-md.md → gaia-init-agents-md.md} +1 -1
  20. package/src/claude/agents/{install-toolkit.md → gaia-install-toolkit.md} +1 -1
  21. package/src/claude/agents/{intervention-documentation-standard.md → gaia-intervention-documentation-standard.md} +1 -1
  22. package/src/claude/agents/{layered-architecture-assessment.md → gaia-layered-architecture-assessment.md} +1 -1
  23. package/src/claude/agents/{review-solution.md → gaia-review-solution.md} +1 -1
  24. package/src/claude/agents/{security-hardening.md → gaia-security-hardening.md} +1 -1
  25. package/src/claude/agents/{validate-feature-docs.md → gaia-validate-feature-docs.md} +2 -2
  26. package/src/claude/agents/{validate-work-breakdown-semantic.md → gaia-validate-work-breakdown-semantic.md} +1 -1
  27. package/src/claude/commands/next-task.md +4 -4
  28. package/src/claude/skills/assess-codebase/SKILL.md +1 -1
  29. package/src/claude/skills/define-feature/SKILL.md +2 -2
  30. package/src/claude/skills/hi-gaia/SKILL.md +1 -1
  31. package/src/claude/skills/implement-feature/SKILL.md +186 -8
  32. package/src/claude/skills/init-agents/SKILL.md +2 -2
  33. package/src/claude/skills/install-toolkit/SKILL.md +2 -2
  34. package/src/claude/workflows/pm-phase1.js +76 -5
  35. package/src/claude/workflows/pm-phase2.js +63 -2
  36. package/src/claude/workflows/pm-phase3.js +66 -9
@@ -14,17 +14,98 @@ with their declared `model:` frontmatter honoured and producing accurate per-age
14
14
 
15
15
  ---
16
16
 
17
+ ## Step 0 — Agent Preflight (fail-closed, AC-19)
18
+
19
+ Before starting the pipeline, verify that every pipeline agent is installed and verified.
20
+
21
+ 1. **Derive the feature prefix** from the `<path-to-feature.md>` argument (e.g. path contains `FTR-017` → prefix = `FTR-017`). Derive the ledger directory as the directory containing the feature.md file.
22
+
23
+ 2. **Open the preflight ledger entry** (fail-closed — if this fails, do NOT proceed):
24
+ ```bash
25
+ ai-toolkit ledger open \
26
+ --prefix {PREFIX} \
27
+ --agent agent-preflight:implement-feature \
28
+ --phase phase3 \
29
+ --dir {LEDGER_DIR}
30
+ ```
31
+ If the command exits non-zero, **STOP IMMEDIATELY** — report the error and do not continue.
32
+
33
+ 3. **Run the preflight check:**
34
+ ```bash
35
+ ai-toolkit agents preflight --project . --pipeline implement-feature
36
+ ```
37
+
38
+ 4. **On success** (exit 0): close the ledger entry:
39
+ ```bash
40
+ ai-toolkit ledger close \
41
+ --prefix {PREFIX} \
42
+ --agent agent-preflight:implement-feature \
43
+ --dir {LEDGER_DIR}
44
+ ```
45
+ Then continue to Step 1.
46
+
47
+ 5. **On failure** (exit non-zero): mark the ledger entry failed and STOP:
48
+ ```bash
49
+ ai-toolkit ledger fail \
50
+ --prefix {PREFIX} \
51
+ --agent agent-preflight:implement-feature \
52
+ --error "agent preflight failed — one or more pipeline agents unverified" \
53
+ --dir {LEDGER_DIR}
54
+ ```
55
+ Report the preflight error to the user. Do NOT continue to Step 1.
56
+
57
+ ---
58
+
17
59
  ## Step 1 — Invoke pm-phase1 (Documentation Phase)
18
60
 
19
- Invoke the `pm-phase1` workflow with the feature path:
61
+ ### Step 1a — Tier 1 dispatch guard (AC-06, AC-07, AC-20)
20
62
 
63
+ Before dispatching, resolve and verify the pm-phase1 orchestrator:
64
+
65
+ ```bash
66
+ ai-toolkit agents resolve \
67
+ --project . \
68
+ --id gaia.orchestrator.feature.phase1 \
69
+ --require-verified
21
70
  ```
22
- subagent_type: pm-phase1
71
+
72
+ If exit non-zero → **HARD STOP**: report the error, do not dispatch pm-phase1.
73
+
74
+ Store the resolution record fields: `nativeName`, `sha256` (definitionHash), `scope` (resolutionScope), `toolkitVersion`.
75
+
76
+ Open the dispatch ledger entry (fail-closed):
77
+
78
+ ```bash
79
+ ai-toolkit ledger open \
80
+ --prefix {PREFIX} \
81
+ --agent pm-phase1:dispatch \
82
+ --phase phase3 \
83
+ --dir {LEDGER_DIR} \
84
+ --metadata-json '{"agentId":"gaia.orchestrator.feature.phase1","nativeAgentName":"{nativeName}","platform":"claude","toolkitVersion":"{toolkitVersion}","resolutionScope":"{scope}","definitionHash":"{sha256}"}'
85
+ ```
86
+
87
+ If exit non-zero → **HARD STOP** (fail-closed): do not dispatch.
88
+
89
+ ### Step 1b — Invoke pm-phase1 using verified nativeName
90
+
91
+ Invoke the workflow using ONLY the resolved `nativeName` (never a hardcoded name):
92
+
93
+ ```
94
+ subagent_type: {nativeName}
23
95
  prompt: <path-to-feature.md>
24
96
  ```
25
97
 
26
98
  Wait for the workflow to complete. Do NOT proceed until it returns.
27
99
 
100
+ After the workflow returns, close the dispatch ledger entry:
101
+
102
+ ```bash
103
+ ai-toolkit ledger close \
104
+ --prefix {PREFIX} --agent pm-phase1:dispatch --dir {LEDGER_DIR}
105
+ ```
106
+
107
+ If ledger close fails → **HARD STOP**: report the error and do not continue.
108
+
28
109
  Extract from the result:
29
110
  - `prefix` — feature prefix (e.g. `FTR-009`)
30
111
  - `requirements.summary` — requirements file summary
@@ -93,10 +174,36 @@ If the file is missing or incomplete, write it again before proceeding.
93
174
 
94
175
  ## Step 3 — Invoke pm-phase2 (Work Breakdown Phase)
95
176
 
96
- Invoke the `pm-phase2` workflow:
177
+ ### Step 3a — Tier 1 dispatch guard
178
+
179
+ Resolve and verify pm-phase2:
180
+
181
+ ```bash
182
+ ai-toolkit agents resolve \
183
+ --project . \
184
+ --id gaia.orchestrator.feature.phase2 \
185
+ --require-verified
186
+ ```
187
+
188
+ If exit non-zero → **HARD STOP**: do not dispatch pm-phase2.
189
+
190
+ Open the dispatch ledger entry (fail-closed):
191
+
192
+ ```bash
193
+ ai-toolkit ledger open \
194
+ --prefix {PREFIX} \
195
+ --agent pm-phase2:dispatch \
196
+ --phase phase3 \
197
+ --dir {LEDGER_DIR} \
198
+ --metadata-json '{"agentId":"gaia.orchestrator.feature.phase2","nativeAgentName":"{nativeName}","platform":"claude","toolkitVersion":"{toolkitVersion}","resolutionScope":"{scope}","definitionHash":"{sha256}"}'
199
+ ```
200
+
201
+ If exit non-zero → **HARD STOP** (fail-closed).
202
+
203
+ ### Step 3b — Invoke pm-phase2 using verified nativeName
97
204
 
98
205
  ```
99
- subagent_type: pm-phase2
206
+ subagent_type: {nativeName}
100
207
  prompt: <path-to-feature.md>
101
208
  ```
102
209
 
@@ -109,6 +216,15 @@ Wait for the workflow to complete. Extract from the result:
109
216
  - `agent_estimate` — agent parallel estimate
110
217
  - `token_ledger` — per-agent token data from phase 2
111
218
 
219
+ Close the dispatch ledger entry after the workflow returns:
220
+
221
+ ```bash
222
+ ai-toolkit ledger close \
223
+ --prefix {PREFIX} --agent pm-phase2:dispatch --dir {LEDGER_DIR}
224
+ ```
225
+
226
+ If ledger close fails → **HARD STOP**: report the error. Do not continue to Gate 2. The error is not swallowed (AC-22).
227
+
112
228
  ---
113
229
 
114
230
  ## Step 4 — Present Gate 2 (HARD STOP — present in main loop)
@@ -179,10 +295,52 @@ git checkout feature/{PREFIX}-{short-slug}
179
295
  Pre-condition check: read `{PREFIX}-Approvals.md` and verify BOTH Gate 1 ✅ and Gate 2 ✅.
180
296
  If either is missing, return to the missing gate.
181
297
 
182
- Invoke the `pm-phase3` workflow:
298
+ ### Step 6a — Tier 1 dispatch guard (Gate 2 post-approval, AC-06, AC-07, AC-08)
299
+
300
+ After Gate 2 approval, resolve and verify pm-phase3 with `--require-verified`. Only a `verified` status permits dispatch:
301
+
302
+ ```bash
303
+ ai-toolkit agents resolve \
304
+ --project . \
305
+ --id gaia.orchestrator.feature.phase3 \
306
+ --require-verified
307
+ ```
308
+
309
+ If exit non-zero → **HARD STOP** (AC-08): pm-phase3 is absent or tampered. Report the error with remediation instructions:
310
+
311
+ ```
312
+ ⛔ HARD STOP — pm-phase3 cannot be dispatched.
313
+
314
+ Remediation:
315
+ 1. Run: ai-toolkit agents resolve --project . --id gaia.orchestrator.feature.phase3
316
+ 2. Check the status field in the output for the failure reason.
317
+ 3. Re-install the toolkit if the status is not-installed or hash-mismatch.
318
+ 4. Contact the toolkit team if the issue persists.
319
+
320
+ Do NOT use an alternative workflow name or any fallback. This pipeline requires verified agents.
321
+ ```
322
+
323
+ Do NOT use any alternative workflow name or fallback.
324
+
325
+ Open the dispatch ledger entry (fail-closed):
326
+
327
+ ```bash
328
+ ai-toolkit ledger open \
329
+ --prefix {PREFIX} \
330
+ --agent pm-phase3:dispatch \
331
+ --phase phase3 \
332
+ --dir {LEDGER_DIR} \
333
+ --metadata-json '{"agentId":"gaia.orchestrator.feature.phase3","nativeAgentName":"{nativeName}","platform":"claude","toolkitVersion":"{toolkitVersion}","resolutionScope":"{scope}","definitionHash":"{sha256}"}'
334
+ ```
335
+
336
+ If exit non-zero → **HARD STOP** (fail-closed): do not dispatch.
337
+
338
+ ### Step 6b — Invoke pm-phase3 using verified nativeName
339
+
340
+ Invoke the workflow using ONLY the resolved `nativeName` (never a hardcoded name):
183
341
 
184
342
  ```
185
- subagent_type: pm-phase3
343
+ subagent_type: {nativeName}
186
344
  prompt: <path-to-feature.md> --branch feature/{PREFIX}-{short-slug}
187
345
  ```
188
346
 
@@ -194,6 +352,26 @@ Wait for the workflow to complete. Capture its full result, including the `<usag
194
352
 
195
353
  If `issues_summary.escalations > 0`, report the escalation details to the user.
196
354
 
355
+ **If pm-phase3 returned an error or escalation:** mark the dispatch ledger entry failed before stopping:
356
+
357
+ ```bash
358
+ ai-toolkit ledger fail \
359
+ --prefix {PREFIX} --agent pm-phase3:dispatch \
360
+ --error "pm-phase3 returned an error or escalation" \
361
+ --dir {LEDGER_DIR}
362
+ ```
363
+
364
+ If `ledger fail` itself exits non-zero → **HARD STOP** (AC-22): report both the original workflow error and the ledger failure. The error is NOT swallowed.
365
+
366
+ **On successful completion:** close the dispatch ledger entry:
367
+
368
+ ```bash
369
+ ai-toolkit ledger close \
370
+ --prefix {PREFIX} --agent pm-phase3:dispatch --dir {LEDGER_DIR}
371
+ ```
372
+
373
+ If ledger close fails → **HARD STOP** (AC-22): report the error. The pipeline does not silently swallow ledger failures after dispatch returns.
374
+
197
375
  ---
198
376
 
199
377
  ## Step 7 — Complete Token Estimate file
@@ -207,7 +385,7 @@ Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
207
385
  > **Null-compatibility — actuals reading and cost calculation:** Treat a token value of `null`, `0`, or `not_available` as **data unavailable** — never as a real, observable zero consumption. Render unavailable values as `—` in the Actual tokens and Actual cost columns; exclude them from sums, averages, and grand totals. Preventing resume clobber and legacy misinterpretation requires that no unavailable measurement is coerced into a real zero.
208
386
 
209
387
  ```markdown
210
- | project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 (estimated) | {subagent_tokens} (actual) | ±{delta} | {duration} |
388
+ | pm-phase3 (orchestrator) | — | sonnet | 80,000 (estimated) | {subagent_tokens} (actual) | ±{delta} | {duration} |
211
389
 
212
390
  ---
213
391
 
@@ -216,7 +394,7 @@ Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
216
394
  | Agent | Task / Scope | Model | Est. tokens | Actual tokens | Delta | Est. cost ($) | Actual cost ($) | Duration |
217
395
  |-------|-------------|-------|------------|---------------|-------|--------------|----------------|----------|
218
396
  {one row per entry in token_ledger — from phase 1, 2, and 3; for any entry where actual tokens is null, 0, or not_available show — in Actual tokens and Actual cost and exclude that row from totals}
219
- | project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
397
+ | pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
220
398
 
221
399
  ## Estimation accuracy by agent type
222
400
 
@@ -4,10 +4,10 @@ argument-hint: "[path-to-project-root]"
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- Spawn the `init-agents-md` agent with the arguments provided by the user:
7
+ Spawn the `gaia-init-agents-md` agent with the arguments provided by the user:
8
8
 
9
9
  ```
10
- subagent_type: init-agents-md
10
+ subagent_type: gaia-init-agents-md
11
11
  prompt: [path-to-project-root]
12
12
  ```
13
13
 
@@ -4,10 +4,10 @@ argument-hint: "[path-to-destination] [--force]"
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
7
- Spawn the `install-toolkit` agent with the arguments provided by the user:
7
+ Spawn the `gaia-install-toolkit` agent with the arguments provided by the user:
8
8
 
9
9
  ```
10
- subagent_type: install-toolkit
10
+ subagent_type: gaia-install-toolkit
11
11
  prompt: <path-to-destination> [--force]
12
12
  ```
13
13
 
@@ -9,11 +9,69 @@ export const meta = {
9
9
  ],
10
10
  }
11
11
 
12
+ // ── Tier 2 resolution guard ───────────────────────────────────────────────────
13
+ // All worker agents must be resolved via the CLI before dispatch.
14
+ // No direct require('lib/agent-registry') — resolution goes through the CLI facade.
15
+ const RESOLVE_SCHEMA = {
16
+ type: 'object',
17
+ properties: {
18
+ nativeName: { type: 'string' },
19
+ status: { type: 'string' },
20
+ agentId: { type: 'string' },
21
+ exitNonZero: { type: 'boolean' },
22
+ error: { type: 'string' },
23
+ },
24
+ }
25
+
26
+ async function resolveAgentTier2(agentId, label, phaseName) {
27
+ const result = await agent(
28
+ `Run this command via Bash:\n\nai-toolkit agents resolve --project . --id ${agentId} --require-verified\n\nIf the command exits 0: parse the JSON from stdout and return it with exitNonZero: false.\nIf the command exits non-zero: return {"exitNonZero": true, "error": "<stderr text>", "agentId": "${agentId}"}.`,
29
+ { label, phase: phaseName, model: 'haiku', schema: RESOLVE_SCHEMA }
30
+ )
31
+ if (!result || result.exitNonZero || !result.nativeName) {
32
+ throw new Error(`HARD STOP — Tier 2 resolution failed for ${agentId}: ${result && result.error ? result.error : 'no nativeName returned'}`)
33
+ }
34
+ return result.nativeName
35
+ }
36
+
37
+ // ── Tier 3 — self-ledger helper ───────────────────────────────────────────────
38
+ const SELF_LEDGER_SCHEMA = {
39
+ type: 'object',
40
+ properties: {
41
+ exitCode: { type: 'number' },
42
+ stdout: { type: 'string' },
43
+ stderr: { type: 'string' },
44
+ },
45
+ required: ['exitCode'],
46
+ }
47
+
48
+ async function selfLedgerOp(cmd, label, phaseName) {
49
+ const result = await agent(
50
+ `Run this shell command via Bash and return the exit code as structured output.\n\nCommand: ${cmd}\n\nCapture: exitCode (integer), stdout (string), stderr (string). Return all three.`,
51
+ { label, phase: phaseName, model: 'haiku', schema: SELF_LEDGER_SCHEMA }
52
+ )
53
+ const status = (result && typeof result.exitCode === 'number') ? result.exitCode : 1
54
+ if (status !== 0) {
55
+ throw new Error(`HARD STOP — self-ledger operation failed. Exit code: ${status}. Command: ${cmd}`)
56
+ }
57
+ }
58
+
12
59
  // ── Parse args ────────────────────────────────────────────────────────────────
13
60
  // args is the raw prompt string: "<path-to-feature.md> [--force]"
14
61
  const featurePath = args.split(/\s+/)[0]
15
62
  const force = typeof args === 'string' && args.includes('--force')
16
63
 
64
+ // Derive prefix and feature dir early (before Discovery agent) for self-registration
65
+ const featureDirEarly = featurePath.replace(/[/\\][^/\\]+$/, '')
66
+ const prefixEarlyMatch = featureDirEarly.match(/([A-Z]+-\d+)/)
67
+ const prefixEarly = prefixEarlyMatch ? prefixEarlyMatch[1] : 'FTR-000'
68
+
69
+ // ── Tier 3 self-registration (open BEFORE any main logic, fail-closed) ────────
70
+ await selfLedgerOp(
71
+ `ai-toolkit ledger open --prefix ${prefixEarly} --agent pm-phase1:self --phase phase1 --dir "${featureDirEarly}" --attempt 1`,
72
+ 'ledger-open-pm-phase1-self', 'Discovery'
73
+ )
74
+
17
75
  // ── Discovery ─────────────────────────────────────────────────────────────────
18
76
  phase('Discovery')
19
77
 
@@ -87,13 +145,14 @@ const tokenLedger = []
87
145
  const errors = []
88
146
 
89
147
  if (discoveryResult.needs_requirements) {
148
+ const reqNativeName = await resolveAgentTier2('gaia.agent.planner.requirements', 'resolve-generate-requirements', 'Requirements')
90
149
  const reqKey = 'generate-requirements:phase1'
91
150
  await agent(
92
151
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${feature_dir} --prefix ${prefix} --agent ${reqKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
93
152
  { label: 'ledger-open-requirements', phase: 'Requirements', model: 'haiku' }
94
153
  )
95
154
  const beforeReq = budget.spent()
96
- const reqResult = await agent(featurePath, { agentType: 'generate-requirements', label: 'generate-requirements', phase: 'Requirements' })
155
+ const reqResult = await agent(featurePath, { agentType: reqNativeName, label: 'generate-requirements', phase: 'Requirements' })
97
156
  const reqTokens = budget.spent() - beforeReq
98
157
  await agent(
99
158
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${reqKey} --tokens ${reqTokens} --attempt 1\n\nReturn no output.`,
@@ -109,13 +168,14 @@ if (discoveryResult.needs_requirements) {
109
168
  phase('Tech-Spec')
110
169
 
111
170
  if (discoveryResult.needs_tech_spec) {
171
+ const specNativeName = await resolveAgentTier2('gaia.agent.planner.tech-spec', 'resolve-generate-tech-spec', 'Tech-Spec')
112
172
  const specKey = 'generate-tech-spec:phase1'
113
173
  await agent(
114
174
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${feature_dir} --prefix ${prefix} --agent ${specKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
115
175
  { label: 'ledger-open-tech-spec', phase: 'Tech-Spec', model: 'haiku' }
116
176
  )
117
177
  const beforeSpec = budget.spent()
118
- const specResult = await agent(featurePath, { agentType: 'generate-tech-spec', label: 'generate-tech-spec', phase: 'Tech-Spec' })
178
+ const specResult = await agent(featurePath, { agentType: specNativeName, label: 'generate-tech-spec', phase: 'Tech-Spec' })
119
179
  const specTokens = budget.spent() - beforeSpec
120
180
  await agent(
121
181
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${specKey} --tokens ${specTokens} --attempt 1\n\nReturn no output.`,
@@ -130,6 +190,11 @@ if (discoveryResult.needs_tech_spec) {
130
190
  // ── validate-feature-docs (revision loop, max 3 cycles) ──────────────────────
131
191
  phase('Validation')
132
192
 
193
+ // Resolve all validation-phase agents once before the loop (Tier 2 guard)
194
+ const valNativeName = await resolveAgentTier2('gaia.agent.planner.validate-feature-docs', 'resolve-validate-feature-docs', 'Validation')
195
+ const reqRevNativeName = await resolveAgentTier2('gaia.agent.planner.requirements', 'resolve-generate-requirements-rev', 'Validation')
196
+ const specRevNativeName = await resolveAgentTier2('gaia.agent.planner.tech-spec', 'resolve-generate-tech-spec-rev', 'Validation')
197
+
133
198
  let validationSummary = 'skipped'
134
199
  let lastValText = ''
135
200
  const MAX_CYCLES = 3
@@ -142,7 +207,7 @@ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
142
207
  { label: `ledger-open-validate-cycle-${cycle}`, phase: 'Validation', model: 'haiku' }
143
208
  )
144
209
  const beforeVal = budget.spent()
145
- const valResult = await agent(featurePath, { agentType: 'validate-feature-docs', label: `validate-feature-docs (cycle ${cycle})`, phase: 'Validation' })
210
+ const valResult = await agent(featurePath, { agentType: valNativeName, label: `validate-feature-docs (cycle ${cycle})`, phase: 'Validation' })
146
211
  const valTokens = budget.spent() - beforeVal
147
212
  await agent(
148
213
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${valKey} --tokens ${valTokens} --attempt 1\n\nReturn no output.`,
@@ -167,10 +232,10 @@ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
167
232
  if (cycle < MAX_CYCLES) {
168
233
  // Re-run failing docs before next validation cycle
169
234
  if (resultText.includes('Requirements')) {
170
- await agent(featurePath, { agentType: 'generate-requirements', label: `generate-requirements (revision ${cycle})`, phase: 'Validation' })
235
+ await agent(featurePath, { agentType: reqRevNativeName, label: `generate-requirements (revision ${cycle})`, phase: 'Validation' })
171
236
  }
172
237
  if (resultText.includes('Tech-Spec')) {
173
- await agent(featurePath, { agentType: 'generate-tech-spec', label: `generate-tech-spec (revision ${cycle})`, phase: 'Validation' })
238
+ await agent(featurePath, { agentType: specRevNativeName, label: `generate-tech-spec (revision ${cycle})`, phase: 'Validation' })
174
239
  }
175
240
  } else {
176
241
  validationSummary = 'gaps remain after 3 cycles'
@@ -289,5 +354,11 @@ const gate1Payload = {
289
354
  errors,
290
355
  }
291
356
 
357
+ // ── Tier 3 self-registration — close on successful completion ─────────────────
358
+ await selfLedgerOp(
359
+ `ai-toolkit ledger close --prefix ${prefix} --agent pm-phase1:self --dir "${feature_dir}" --attempt 1`,
360
+ 'ledger-close-pm-phase1-self', 'Validation'
361
+ )
362
+
292
363
  log(`pm-phase1 complete — gate1_payload ready`)
293
364
  return gate1Payload
@@ -49,6 +49,31 @@ function normalizeError(err) {
49
49
  try { return JSON.stringify(err) } catch (_) { return '<unknown error>' }
50
50
  }
51
51
 
52
+ // ── Tier 2 resolution guard ───────────────────────────────────────────────────
53
+ // All worker agents must be resolved via the CLI before dispatch.
54
+ // No direct require('lib/agent-registry') — resolution goes through the CLI facade.
55
+ const RESOLVE_SCHEMA = {
56
+ type: 'object',
57
+ properties: {
58
+ nativeName: { type: 'string' },
59
+ status: { type: 'string' },
60
+ agentId: { type: 'string' },
61
+ exitNonZero: { type: 'boolean' },
62
+ error: { type: 'string' },
63
+ },
64
+ }
65
+
66
+ async function resolveAgentTier2(agentId, label, phaseName) {
67
+ const result = await agent(
68
+ `Run this command via Bash:\n\nai-toolkit agents resolve --project . --id ${agentId} --require-verified\n\nIf the command exits 0: parse the JSON from stdout and return it with exitNonZero: false.\nIf the command exits non-zero: return {"exitNonZero": true, "error": "<stderr text>", "agentId": "${agentId}"}.`,
69
+ { label, phase: phaseName, model: 'haiku', schema: RESOLVE_SCHEMA }
70
+ )
71
+ if (!result || result.exitNonZero || !result.nativeName) {
72
+ throw new Error(`HARD STOP — Tier 2 resolution failed for ${agentId}: ${result && result.error ? result.error : 'no nativeName returned'}`)
73
+ }
74
+ return result.nativeName
75
+ }
76
+
52
77
  // ── Parse args ────────────────────────────────────────────────────────────────
53
78
 
54
79
  // args: "<path-to-feature.md>"
@@ -72,12 +97,41 @@ const featureDir = deriveFeatureDir(featurePath)
72
97
  const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
73
98
  const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
74
99
 
100
+ // ── Tier 3 — self-ledger helper ───────────────────────────────────────────────
101
+ const SELF_LEDGER_SCHEMA = {
102
+ type: 'object',
103
+ properties: {
104
+ exitCode: { type: 'number' },
105
+ stdout: { type: 'string' },
106
+ stderr: { type: 'string' },
107
+ },
108
+ required: ['exitCode'],
109
+ }
110
+
111
+ async function selfLedgerOp(cmd, label, phaseName) {
112
+ const result = await agent(
113
+ `Run this shell command via Bash and return the exit code as structured output.\n\nCommand: ${cmd}\n\nCapture: exitCode (integer), stdout (string), stderr (string). Return all three.`,
114
+ { label, phase: phaseName, model: 'haiku', schema: SELF_LEDGER_SCHEMA }
115
+ )
116
+ const status = (result && typeof result.exitCode === 'number') ? result.exitCode : 1
117
+ if (status !== 0) {
118
+ throw new Error(`HARD STOP — self-ledger operation failed. Exit code: ${status}. Command: ${cmd}`)
119
+ }
120
+ }
121
+
122
+ // ── Tier 3 self-registration (open BEFORE any main logic, fail-closed) ────────
123
+ await selfLedgerOp(
124
+ `ai-toolkit ledger open --prefix ${prefix} --agent pm-phase2:self --phase phase2 --dir "${featureDir}" --attempt 1`,
125
+ 'ledger-open-pm-phase2-self', 'Work Breakdown'
126
+ )
127
+
75
128
  // ── generate-work-breakdown ───────────────────────────────────────────────────
76
129
  phase('Work Breakdown')
77
130
 
78
131
  const tokenLedger = []
79
132
 
80
133
  log(`Running generate-work-breakdown for ${featurePath}`)
134
+ const wbNativeName = await resolveAgentTier2('gaia.agent.planner.work-breakdown', 'resolve-generate-work-breakdown', 'Work Breakdown')
81
135
  const wbKey = 'generate-work-breakdown:phase2'
82
136
  await agent(
83
137
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${featureDir} --prefix ${prefix} --agent ${wbKey} --phase phase2 --model haiku --attempt 1\n\nReturn no output.`,
@@ -85,7 +139,7 @@ await agent(
85
139
  )
86
140
  const beforeWB = budget.spent()
87
141
  await agent(featurePath, {
88
- agentType: 'generate-work-breakdown',
142
+ agentType: wbNativeName,
89
143
  label: 'generate-work-breakdown',
90
144
  phase: 'Work Breakdown',
91
145
  })
@@ -165,6 +219,7 @@ const semanticKey = 'validate-work-breakdown-semantic:phase2'
165
219
  if (!wbValidatorPassed) {
166
220
  log('validate-work-breakdown-semantic: skipped (wb-validate did not pass)')
167
221
  } else {
222
+ const semanticNativeName = await resolveAgentTier2('gaia.agent.planner.validate-work-breakdown', 'resolve-validate-work-breakdown-semantic', 'Work Breakdown')
168
223
  await agent(
169
224
  `Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${featureDir} --prefix ${prefix} --agent ${semanticKey} --phase phase2 --model sonnet --attempt 1\n\nReturn no output.`,
170
225
  { label: 'ledger-open-semantic-validator', phase: 'Work Breakdown', model: 'haiku' }
@@ -176,7 +231,7 @@ if (!wbValidatorPassed) {
176
231
  {
177
232
  label: 'validate-work-breakdown-semantic',
178
233
  phase: 'Work Breakdown',
179
- agentType: 'validate-work-breakdown-semantic',
234
+ agentType: semanticNativeName,
180
235
  schema: WB_SEMANTIC_SCHEMA,
181
236
  }
182
237
  )
@@ -495,6 +550,12 @@ ${phase2Events}`,
495
550
  { label: 'append-process-log', phase: 'Effort Estimate' }
496
551
  )
497
552
 
553
+ // ── Tier 3 self-registration — close on successful completion ─────────────────
554
+ await selfLedgerOp(
555
+ `ai-toolkit ledger close --prefix ${prefix} --agent pm-phase2:self --dir "${featureDir}" --attempt 1`,
556
+ 'ledger-close-pm-phase2-self', 'Effort Estimate'
557
+ )
558
+
498
559
  return {
499
560
  prefix: metrics.prefix || prefix,
500
561
  feature_path: featurePath,
@@ -11,6 +11,40 @@ export const meta = {
11
11
  ],
12
12
  }
13
13
 
14
+ // ── Tier 2 resolution guard ───────────────────────────────────────────────────
15
+ // All worker agents must be resolved via the CLI before dispatch.
16
+ // No direct require('lib/agent-registry') — resolution goes through the CLI facade.
17
+ const RESOLVE_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ nativeName: { type: 'string' },
21
+ status: { type: 'string' },
22
+ agentId: { type: 'string' },
23
+ exitNonZero: { type: 'boolean' },
24
+ error: { type: 'string' },
25
+ },
26
+ }
27
+
28
+ async function resolveAgentTier2(agentId, label, phaseName) {
29
+ const result = await agent(
30
+ `Run this command via Bash:\n\nai-toolkit agents resolve --project . --id ${agentId} --require-verified\n\nIf the command exits 0: parse the JSON from stdout and return it with exitNonZero: false.\nIf the command exits non-zero: return {"exitNonZero": true, "error": "<stderr text>", "agentId": "${agentId}"}.`,
31
+ { label, phase: phaseName, model: 'haiku', schema: RESOLVE_SCHEMA }
32
+ )
33
+ if (!result || result.exitNonZero || !result.nativeName) {
34
+ throw new Error(`HARD STOP — Tier 2 resolution failed for ${agentId}: ${result && result.error ? result.error : 'no nativeName returned'}`)
35
+ }
36
+ return result.nativeName
37
+ }
38
+
39
+ // Map from Work-Breakdown agent_type values to canonical agent IDs.
40
+ // Used by the Tier 2 guard to resolve before each dispatch.
41
+ const IMPL_AGENT_IDS = {
42
+ 'developer-backend': 'gaia.agent.developer.backend',
43
+ 'developer-frontend': 'gaia.agent.developer.frontend',
44
+ 'developer-testing': 'gaia.agent.developer.testing',
45
+ 'review-solution': 'gaia.agent.review.solution',
46
+ }
47
+
14
48
  // ── Ledger facade helpers ─────────────────────────────────────────────────────
15
49
  // Route all ledger I/O through the ai-toolkit CLI facade.
16
50
  // Open calls fire-and-forget (matching pm-phase1/pm-phase2 pattern).
@@ -64,6 +98,12 @@ const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
64
98
  const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
65
99
  const csvPath = `${featureDir}/${prefix}-Work-Breakdown.csv`
66
100
 
101
+ // ── Tier 3 self-registration (open BEFORE any main logic, fail-closed) ────────
102
+ await ledgerTerminal(
103
+ `ai-toolkit ledger open --prefix ${prefix} --agent pm-phase3:self --phase phase3 --dir "${featureDir}" --attempt 1`,
104
+ 'ledger-open-pm-phase3-self', 'Parse'
105
+ )
106
+
67
107
  log(`Reading CSV: ${csvPath}`)
68
108
 
69
109
  const csvKey = 'read-wb-csv:phase3'
@@ -160,6 +200,13 @@ const buildWaves = (phases) => {
160
200
  const waves = buildWaves(wb.phases)
161
201
  log(`Execution plan: ${waves.length} wave(s) — ${waves.map((w, i) => `wave${i+1}:[${w.map(p => p.phase_id).join(',')}]`).join(' ')}`)
162
202
 
203
+ // Pre-resolve all implementation agent types (Tier 2 guard, fail-closed before any phase runs)
204
+ const resolvedNativeNames = {}
205
+ for (const [legacyName, canonicalId] of Object.entries(IMPL_AGENT_IDS)) {
206
+ resolvedNativeNames[legacyName] = await resolveAgentTier2(canonicalId, `resolve-${legacyName}`, 'Parse')
207
+ }
208
+ log(`Tier 2 resolution complete: ${Object.keys(resolvedNativeNames).join(', ')}`)
209
+
163
210
  let phasesDone = 0
164
211
  const usPassed = []
165
212
  const usEscalated = []
@@ -203,16 +250,18 @@ const executePhase = async (implPhase) => {
203
250
  let implFailed = false
204
251
  let implErrMsg = ''
205
252
  try {
206
- await parallel(implPhase.impl_groups.map(group => () =>
207
- agent(
253
+ await parallel(implPhase.impl_groups.map(group => () => {
254
+ const nativeName = resolvedNativeNames[group.agent_type]
255
+ if (!nativeName) throw new Error(`HARD STOP — Tier 2: no resolved nativeName for agent_type: ${group.agent_type}`)
256
+ return agent(
208
257
  `${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
209
258
  {
210
- agentType: group.agent_type,
259
+ agentType: nativeName,
211
260
  label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
212
261
  phase: 'Implementation',
213
262
  }
214
263
  )
215
- ))
264
+ }))
216
265
  } catch (err) {
217
266
  implFailed = true
218
267
  implErrMsg = err && err.message ? err.message.slice(0, 120) : 'impl dispatch failed'
@@ -244,16 +293,18 @@ const executePhase = async (implPhase) => {
244
293
  let testFailed = false
245
294
  let testErrMsg = ''
246
295
  try {
247
- await parallel(implPhase.test_groups.map(group => () =>
248
- agent(
296
+ await parallel(implPhase.test_groups.map(group => () => {
297
+ const nativeName = resolvedNativeNames[group.agent_type]
298
+ if (!nativeName) throw new Error(`HARD STOP — Tier 2: no resolved nativeName for agent_type: ${group.agent_type}`)
299
+ return agent(
249
300
  `${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
250
301
  {
251
- agentType: group.agent_type,
302
+ agentType: nativeName,
252
303
  label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
253
304
  phase: 'Implementation',
254
305
  }
255
306
  )
256
- ))
307
+ }))
257
308
  } catch (err) {
258
309
  testFailed = true
259
310
  testErrMsg = err && err.message ? err.message.slice(0, 120) : 'test dispatch failed'
@@ -284,7 +335,7 @@ const executePhase = async (implPhase) => {
284
335
  const review = await agent(
285
336
  `${featurePath} --scope ${implPhase.phase_id}`,
286
337
  {
287
- agentType: 'review-solution',
338
+ agentType: resolvedNativeNames['review-solution'],
288
339
  label: `review-solution:${implPhase.phase_id}`,
289
340
  phase: 'Implementation',
290
341
  schema: REVIEW_SCHEMA,
@@ -759,6 +810,12 @@ If there is nothing to commit (all files already committed), that is fine — re
759
810
  { label: 'commit-actuals', phase: 'Actuals' }
760
811
  )
761
812
 
813
+ // ── Tier 3 self-registration — close on successful completion ─────────────────
814
+ await ledgerTerminal(
815
+ `ai-toolkit ledger close --prefix ${prefix} --agent pm-phase3:self --dir "${featureDir}" --attempt 1`,
816
+ 'ledger-close-pm-phase3-self', 'Actuals'
817
+ )
818
+
762
819
  return {
763
820
  pr_url: prResult.pr_url,
764
821
  token_ledger: tokenLedger,