@dtlabs/ai-toolkit 0.11.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 (38) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/cli.js +757 -68
  3. package/lib/agent-registry.js +1018 -0
  4. package/lib/execution-ledger.js +609 -0
  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} +37 -61
  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/scripts/wb-render.js +101 -2
  29. package/src/claude/scripts/wb-validate.js +32 -2
  30. package/src/claude/skills/assess-codebase/SKILL.md +1 -1
  31. package/src/claude/skills/define-feature/SKILL.md +2 -2
  32. package/src/claude/skills/hi-gaia/SKILL.md +1 -1
  33. package/src/claude/skills/implement-feature/SKILL.md +190 -10
  34. package/src/claude/skills/init-agents/SKILL.md +2 -2
  35. package/src/claude/skills/install-toolkit/SKILL.md +2 -2
  36. package/src/claude/workflows/pm-phase1.js +94 -73
  37. package/src/claude/workflows/pm-phase2.js +118 -108
  38. package/src/claude/workflows/pm-phase3.js +219 -75
@@ -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:
183
310
 
184
311
  ```
185
- subagent_type: pm-phase3
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):
341
+
342
+ ```
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
@@ -204,8 +382,10 @@ From the `<usage>` block of the pm-phase3 result, read:
204
382
 
205
383
  Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
206
384
 
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.
386
+
207
387
  ```markdown
208
- | 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} |
209
389
 
210
390
  ---
211
391
 
@@ -213,14 +393,14 @@ Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
213
393
 
214
394
  | Agent | Task / Scope | Model | Est. tokens | Actual tokens | Delta | Est. cost ($) | Actual cost ($) | Duration |
215
395
  |-------|-------------|-------|------------|---------------|-------|--------------|----------------|----------|
216
- {one row per entry in token_ledger — from phase 1, 2, and 3}
217
- | project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
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}
397
+ | pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
218
398
 
219
399
  ## Estimation accuracy by agent type
220
400
 
221
401
  | Model | Count | Avg est. tokens | Avg actual tokens | Avg delta | Trend |
222
402
  |-------|-------|----------------|------------------|-----------|-------|
223
- {one row per model tier (haiku, sonnet) — exclude N/A rows from averages}
403
+ {one row per model tier (haiku, sonnet) — exclude rows where actual tokens is null, 0, or not_available from averages}
224
404
 
225
405
  ## Grand Total
226
406
 
@@ -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,36 +9,51 @@ export const meta = {
9
9
  ],
10
10
  }
11
11
 
12
- // ── Ledger helper functions ───────────────────────────────────────────────────
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
+ }
13
25
 
14
- async function appendLedgerEntry(featureDir, prefix, entry) {
15
- const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
16
- // started_at is generated via Bash inside the agent to get a real UTC timestamp with time
17
- const entryWithoutTs = JSON.stringify({ ...entry, started_at: '__TS__', completed_at: null })
18
- await agent(
19
- `Append a JSON object to the ledger array at: ${ledgerPath}\n\n` +
20
- `1. Run: date -u +"%Y-%m-%dT%H:%M:%SZ" and capture the output as NOW.\n` +
21
- `2. Read the file. If it does not exist or cannot be parsed as a JSON array, start with [].\n` +
22
- `3. Push this object onto the array, replacing "__TS__" in started_at with NOW: ${entryWithoutTs}\n` +
23
- `4. Write the full array back (JSON, 2-space indent). Return no output.`,
24
- { label: 'append-ledger', phase: 'Requirements', model: 'haiku' }
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 }
25
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
26
35
  }
27
36
 
28
- async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
29
- const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
30
- // completed_at is generated via Bash inside the agent to get a real UTC timestamp with time
31
- const updatesWithoutTs = JSON.stringify({ ...updates, completed_at: '__TS__' })
32
- await agent(
33
- `Update an entry in the ledger array at: ${ledgerPath}\n\n` +
34
- `1. Run: date -u +"%Y-%m-%dT%H:%M:%SZ" and capture the output as NOW.\n` +
35
- `2. Read the file. If it does not exist or cannot be parsed as a JSON array, do nothing.\n` +
36
- `3. Search from the end for the last entry where agent === "${agentKey}".\n` +
37
- `4. If found, merge these fields into that entry, replacing "__TS__" in completed_at with NOW: ${updatesWithoutTs}\n` +
38
- `5. Write the full array back (JSON, 2-space indent). Return no output.\n` +
39
- `6. If not found, do nothing.`,
40
- { label: 'update-ledger', phase: 'Requirements', model: 'haiku' }
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 }
41
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
+ }
42
57
  }
43
58
 
44
59
  // ── Parse args ────────────────────────────────────────────────────────────────
@@ -46,6 +61,17 @@ async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
46
61
  const featurePath = args.split(/\s+/)[0]
47
62
  const force = typeof args === 'string' && args.includes('--force')
48
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
+
49
75
  // ── Discovery ─────────────────────────────────────────────────────────────────
50
76
  phase('Discovery')
51
77
 
@@ -104,7 +130,7 @@ log(`needs_requirements=${discoveryResult.needs_requirements} needs_tech_spec=${
104
130
 
105
131
  // ── Ensure ledger file exists (US-02-T02) ─────────────────────────────────────
106
132
  // If define-feature was not used, the ledger file will not exist yet.
107
- // Touch it with an empty array so appendLedgerEntry can always assume a valid base.
133
+ // Touch it with an empty array so the ledger facade can always assume a valid base.
108
134
  const ledgerFilePath = `${feature_dir}/${prefix}-token-ledger.json`
109
135
  await agent(
110
136
  `Check whether the file ${ledgerFilePath} exists.\n\n1. Try to read the file using the Read tool.\n2. If the file does NOT exist: write it now using the Write tool with contents: []\n3. If the file already exists and is a valid JSON array: do nothing.\n4. If the file exists but is not valid JSON: overwrite it with: []\n5. Return no output.`,
@@ -119,24 +145,19 @@ const tokenLedger = []
119
145
  const errors = []
120
146
 
121
147
  if (discoveryResult.needs_requirements) {
148
+ const reqNativeName = await resolveAgentTier2('gaia.agent.planner.requirements', 'resolve-generate-requirements', 'Requirements')
122
149
  const reqKey = 'generate-requirements:phase1'
123
- await appendLedgerEntry(feature_dir, prefix, {
124
- agent: reqKey,
125
- phase: 'phase1',
126
- model: 'haiku',
127
- status: 'running',
128
- phase_delta_tokens: 0,
129
- started_at: '__TS__',
130
- completed_at: null,
131
- })
150
+ await agent(
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.`,
152
+ { label: 'ledger-open-requirements', phase: 'Requirements', model: 'haiku' }
153
+ )
132
154
  const beforeReq = budget.spent()
133
- 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' })
134
156
  const reqTokens = budget.spent() - beforeReq
135
- await updateLedgerEntry(feature_dir, prefix, reqKey, {
136
- status: 'done',
137
- completed_at: '__TS__',
138
- phase_delta_tokens: reqTokens,
139
- })
157
+ await agent(
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.`,
159
+ { label: 'ledger-close-requirements', phase: 'Requirements', model: 'haiku' }
160
+ )
140
161
  tokenLedger.push({ agent: 'generate-requirements', model: 'haiku', phase_delta_tokens: reqTokens })
141
162
  log(`generate-requirements done — phase delta: ${reqTokens} tokens`)
142
163
  } else {
@@ -147,24 +168,19 @@ if (discoveryResult.needs_requirements) {
147
168
  phase('Tech-Spec')
148
169
 
149
170
  if (discoveryResult.needs_tech_spec) {
171
+ const specNativeName = await resolveAgentTier2('gaia.agent.planner.tech-spec', 'resolve-generate-tech-spec', 'Tech-Spec')
150
172
  const specKey = 'generate-tech-spec:phase1'
151
- await appendLedgerEntry(feature_dir, prefix, {
152
- agent: specKey,
153
- phase: 'phase1',
154
- model: 'haiku',
155
- status: 'running',
156
- phase_delta_tokens: 0,
157
- started_at: '__TS__',
158
- completed_at: null,
159
- })
173
+ await agent(
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.`,
175
+ { label: 'ledger-open-tech-spec', phase: 'Tech-Spec', model: 'haiku' }
176
+ )
160
177
  const beforeSpec = budget.spent()
161
- 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' })
162
179
  const specTokens = budget.spent() - beforeSpec
163
- await updateLedgerEntry(feature_dir, prefix, specKey, {
164
- status: 'done',
165
- completed_at: '__TS__',
166
- phase_delta_tokens: specTokens,
167
- })
180
+ await agent(
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.`,
182
+ { label: 'ledger-close-tech-spec', phase: 'Tech-Spec', model: 'haiku' }
183
+ )
168
184
  tokenLedger.push({ agent: 'generate-tech-spec', model: 'haiku', phase_delta_tokens: specTokens })
169
185
  log(`generate-tech-spec done — phase delta: ${specTokens} tokens`)
170
186
  } else {
@@ -174,6 +190,11 @@ if (discoveryResult.needs_tech_spec) {
174
190
  // ── validate-feature-docs (revision loop, max 3 cycles) ──────────────────────
175
191
  phase('Validation')
176
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
+
177
198
  let validationSummary = 'skipped'
178
199
  let lastValText = ''
179
200
  const MAX_CYCLES = 3
@@ -181,23 +202,17 @@ const MAX_CYCLES = 3
181
202
  for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
182
203
  log(`validate-feature-docs cycle ${cycle}`)
183
204
  const valKey = `validate-feature-docs:phase1:cycle${cycle}`
184
- await appendLedgerEntry(feature_dir, prefix, {
185
- agent: valKey,
186
- phase: 'phase1',
187
- model: 'haiku',
188
- status: 'running',
189
- phase_delta_tokens: 0,
190
- started_at: '__TS__',
191
- completed_at: null,
192
- })
205
+ await agent(
206
+ `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 ${valKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
207
+ { label: `ledger-open-validate-cycle-${cycle}`, phase: 'Validation', model: 'haiku' }
208
+ )
193
209
  const beforeVal = budget.spent()
194
- 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' })
195
211
  const valTokens = budget.spent() - beforeVal
196
- await updateLedgerEntry(feature_dir, prefix, valKey, {
197
- status: 'done',
198
- completed_at: '__TS__',
199
- phase_delta_tokens: valTokens,
200
- })
212
+ await agent(
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.`,
214
+ { label: `ledger-close-validate-cycle-${cycle}`, phase: 'Validation', model: 'haiku' }
215
+ )
201
216
  tokenLedger.push({ agent: `validate-feature-docs (cycle ${cycle})`, model: 'haiku', phase_delta_tokens: valTokens })
202
217
 
203
218
  // valResult is the agent's text output — check for gap indicators
@@ -217,10 +232,10 @@ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
217
232
  if (cycle < MAX_CYCLES) {
218
233
  // Re-run failing docs before next validation cycle
219
234
  if (resultText.includes('Requirements')) {
220
- 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' })
221
236
  }
222
237
  if (resultText.includes('Tech-Spec')) {
223
- 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' })
224
239
  }
225
240
  } else {
226
241
  validationSummary = 'gaps remain after 3 cycles'
@@ -339,5 +354,11 @@ const gate1Payload = {
339
354
  errors,
340
355
  }
341
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
+
342
363
  log(`pm-phase1 complete — gate1_payload ready`)
343
364
  return gate1Payload