@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.
- package/CHANGELOG.md +28 -0
- package/bin/cli.js +480 -7
- package/lib/agent-registry.js +1018 -0
- package/lib/execution-ledger.js +97 -3
- package/package.json +1 -1
- package/src/claude/agents/{concurrency-safety-assessment.md → gaia-concurrency-safety-assessment.md} +1 -1
- package/src/claude/agents/{define-feature.md → gaia-define-feature.md} +1 -1
- package/src/claude/agents/{dependency-injection-refactoring.md → gaia-dependency-injection-refactoring.md} +1 -1
- package/src/claude/agents/{dependency-supply-chain-security.md → gaia-dependency-supply-chain-security.md} +1 -1
- package/src/claude/agents/{developer-backend.md → gaia-developer-backend.md} +1 -1
- package/src/claude/agents/{developer-frontend.md → gaia-developer-frontend.md} +1 -1
- package/src/claude/agents/{developer-testing.md → gaia-developer-testing.md} +1 -1
- package/src/claude/agents/{domain-model-refactoring.md → gaia-domain-model-refactoring.md} +1 -1
- package/src/claude/agents/{generate-requirements.md → gaia-generate-requirements.md} +1 -1
- package/src/claude/agents/{generate-tech-spec.md → gaia-generate-tech-spec.md} +1 -1
- package/src/claude/agents/{generate-work-breakdown.md → gaia-generate-work-breakdown.md} +5 -5
- package/src/claude/agents/{generic-software-assessment.md → gaia-generic-software-assessment.md} +1 -1
- package/src/claude/agents/{god-class-decomposition.md → gaia-god-class-decomposition.md} +1 -1
- package/src/claude/agents/{init-agents-md.md → gaia-init-agents-md.md} +1 -1
- package/src/claude/agents/{install-toolkit.md → gaia-install-toolkit.md} +1 -1
- package/src/claude/agents/{intervention-documentation-standard.md → gaia-intervention-documentation-standard.md} +1 -1
- package/src/claude/agents/{layered-architecture-assessment.md → gaia-layered-architecture-assessment.md} +1 -1
- package/src/claude/agents/{review-solution.md → gaia-review-solution.md} +1 -1
- package/src/claude/agents/{security-hardening.md → gaia-security-hardening.md} +1 -1
- package/src/claude/agents/{validate-feature-docs.md → gaia-validate-feature-docs.md} +2 -2
- package/src/claude/agents/{validate-work-breakdown-semantic.md → gaia-validate-work-breakdown-semantic.md} +1 -1
- package/src/claude/commands/next-task.md +4 -4
- package/src/claude/skills/assess-codebase/SKILL.md +1 -1
- package/src/claude/skills/define-feature/SKILL.md +2 -2
- package/src/claude/skills/hi-gaia/SKILL.md +1 -1
- package/src/claude/skills/implement-feature/SKILL.md +186 -8
- package/src/claude/skills/init-agents/SKILL.md +2 -2
- package/src/claude/skills/install-toolkit/SKILL.md +2 -2
- package/src/claude/workflows/pm-phase1.js +76 -5
- package/src/claude/workflows/pm-phase2.js +63 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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,
|