@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.
- package/CHANGELOG.md +65 -0
- package/bin/cli.js +757 -68
- package/lib/agent-registry.js +1018 -0
- package/lib/execution-ledger.js +609 -0
- 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} +37 -61
- 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/scripts/wb-render.js +101 -2
- package/src/claude/scripts/wb-validate.js +32 -2
- 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 +190 -10
- 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 +94 -73
- package/src/claude/workflows/pm-phase2.js +118 -108
- package/src/claude/workflows/pm-phase3.js +219 -75
|
@@ -11,38 +11,73 @@ export const meta = {
|
|
|
11
11
|
],
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// ──
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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
|
+
}
|
|
17
27
|
|
|
18
|
-
async function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`Append a JSON object to the ledger array at: ${ledgerPath}\n\n` +
|
|
23
|
-
`1. Run: date -u +"%Y-%m-%dT%H:%M:%SZ" and capture the output as NOW.\n` +
|
|
24
|
-
`2. Read the file. If it does not exist or cannot be parsed as a JSON array, start with [].\n` +
|
|
25
|
-
`3. Push this object onto the array, replacing "__TS__" in started_at with NOW: ${entryWithoutTs}\n` +
|
|
26
|
-
`4. Write the full array back (JSON, 2-space indent). Return no output.`,
|
|
27
|
-
{ label: 'append-ledger', phase: 'Implementation', model: 'haiku' }
|
|
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 }
|
|
28
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
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
|
|
48
|
+
// ── Ledger facade helpers ─────────────────────────────────────────────────────
|
|
49
|
+
// Route all ledger I/O through the ai-toolkit CLI facade.
|
|
50
|
+
// Open calls fire-and-forget (matching pm-phase1/pm-phase2 pattern).
|
|
51
|
+
// Terminal calls (close, fail, skip) are fail-closed: a non-zero exit status
|
|
52
|
+
// hard-stops the workflow because the terminal state was not persisted.
|
|
53
|
+
|
|
54
|
+
const LEDGER_TERMINAL_SCHEMA = {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
exitCode: { type: 'number' },
|
|
58
|
+
stdout: { type: 'string' },
|
|
59
|
+
stderr: { type: 'string' },
|
|
60
|
+
},
|
|
61
|
+
required: ['exitCode'],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function ledgerTerminal(cmd, label, phaseName) {
|
|
65
|
+
const result = await agent(
|
|
66
|
+
`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.`,
|
|
67
|
+
{ label, phase: phaseName, model: 'haiku', schema: LEDGER_TERMINAL_SCHEMA }
|
|
43
68
|
)
|
|
69
|
+
const status = (result && typeof result.exitCode === 'number') ? result.exitCode : 1
|
|
70
|
+
if (status !== 0) {
|
|
71
|
+
throw new Error(`HARD STOP — ledger terminal state not persisted. Exit code: ${status}. Command: ${cmd}`)
|
|
72
|
+
}
|
|
44
73
|
}
|
|
45
74
|
|
|
75
|
+
// null/0/not_available => data unavailable, never an observable real zero.
|
|
76
|
+
// A cached or resumed agent returns a budget delta of 0, which is not a real
|
|
77
|
+
// measurement — omit --tokens from close so the module preserves any existing
|
|
78
|
+
// positive phase_delta_tokens on disk rather than clobbering it with zero.
|
|
79
|
+
function tokensAvailable(t) { return Number.isInteger(t) && t > 0 }
|
|
80
|
+
|
|
46
81
|
// ── Parse args ────────────────────────────────────────────────────────────────
|
|
47
82
|
|
|
48
83
|
// args: "<path-to-feature.md> --branch feature/FTR-NNN-slug"
|
|
@@ -63,15 +98,29 @@ const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
|
|
|
63
98
|
const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
|
|
64
99
|
const csvPath = `${featureDir}/${prefix}-Work-Breakdown.csv`
|
|
65
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
|
+
|
|
66
107
|
log(`Reading CSV: ${csvPath}`)
|
|
67
108
|
|
|
68
|
-
|
|
109
|
+
const csvKey = 'read-wb-csv:phase3'
|
|
110
|
+
await agent(
|
|
111
|
+
`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 ${csvKey} --phase phase3 --model haiku --attempt 1\n\nReturn no output.`,
|
|
112
|
+
{ label: 'ledger-open-read-wb-csv', phase: 'Parse', model: 'haiku' }
|
|
113
|
+
)
|
|
69
114
|
const beforeCsv = budget.spent()
|
|
70
115
|
const csvContent = await agent(
|
|
71
116
|
`Read the file at path: ${csvPath}\nReturn ONLY the raw file contents, nothing else. No explanation, no formatting, no JSON wrapping — just the raw text of the file.`,
|
|
72
117
|
{ label: 'read-wb-csv', phase: 'Parse', model: 'haiku' }
|
|
73
118
|
)
|
|
74
|
-
|
|
119
|
+
const csvTokens = budget.spent() - beforeCsv
|
|
120
|
+
await ledgerTerminal(
|
|
121
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${csvKey}${tokensAvailable(csvTokens) ? ` --tokens ${csvTokens}` : ''} --attempt 1`,
|
|
122
|
+
'ledger-close-read-wb-csv', 'Parse'
|
|
123
|
+
)
|
|
75
124
|
|
|
76
125
|
// Parse CSV into structured phases — pure JS, no AI
|
|
77
126
|
const rows = csvContent
|
|
@@ -151,6 +200,13 @@ const buildWaves = (phases) => {
|
|
|
151
200
|
const waves = buildWaves(wb.phases)
|
|
152
201
|
log(`Execution plan: ${waves.length} wave(s) — ${waves.map((w, i) => `wave${i+1}:[${w.map(p => p.phase_id).join(',')}]`).join(' ')}`)
|
|
153
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
|
+
|
|
154
210
|
let phasesDone = 0
|
|
155
211
|
const usPassed = []
|
|
156
212
|
const usEscalated = []
|
|
@@ -186,20 +242,42 @@ const executePhase = async (implPhase) => {
|
|
|
186
242
|
// Step 1 — impl groups in parallel (INFRA / BE / FE)
|
|
187
243
|
if (implPhase.impl_groups.length > 0) {
|
|
188
244
|
const implKey = `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`
|
|
189
|
-
await
|
|
245
|
+
await agent(
|
|
246
|
+
`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 ${implKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
247
|
+
{ label: `ledger-open-impl:${implPhase.phase_id}`, phase: 'Implementation', model: 'haiku' }
|
|
248
|
+
)
|
|
190
249
|
const beforeImpl = budget.spent()
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
250
|
+
let implFailed = false
|
|
251
|
+
let implErrMsg = ''
|
|
252
|
+
try {
|
|
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(
|
|
257
|
+
`${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
|
|
258
|
+
{
|
|
259
|
+
agentType: nativeName,
|
|
260
|
+
label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
|
|
261
|
+
phase: 'Implementation',
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
}))
|
|
265
|
+
} catch (err) {
|
|
266
|
+
implFailed = true
|
|
267
|
+
implErrMsg = err && err.message ? err.message.slice(0, 120) : 'impl dispatch failed'
|
|
268
|
+
}
|
|
201
269
|
const implTokens = budget.spent() - beforeImpl
|
|
202
|
-
|
|
270
|
+
if (implFailed) {
|
|
271
|
+
await ledgerTerminal(
|
|
272
|
+
`ai-toolkit ledger fail --dir ${featureDir} --prefix ${prefix} --agent ${implKey} --error "impl dispatch failed" --attempt 1`,
|
|
273
|
+
`ledger-fail-impl:${implPhase.phase_id}`, 'Implementation'
|
|
274
|
+
)
|
|
275
|
+
throw new Error(`Phase ${implPhase.phase_id} impl dispatch failed: ${implErrMsg}`)
|
|
276
|
+
}
|
|
277
|
+
await ledgerTerminal(
|
|
278
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${implKey}${tokensAvailable(implTokens) ? ` --tokens ${implTokens}` : ''} --attempt 1`,
|
|
279
|
+
`ledger-close-impl:${implPhase.phase_id}`, 'Implementation'
|
|
280
|
+
)
|
|
203
281
|
tokenLedger.push({ agent: implKey, model: 'sonnet', phase_delta_tokens: implTokens })
|
|
204
282
|
log(`Phase ${implPhase.phase_id} impl groups done — ${implTokens} tokens`)
|
|
205
283
|
}
|
|
@@ -207,39 +285,67 @@ const executePhase = async (implPhase) => {
|
|
|
207
285
|
// Step 2 — test groups in parallel (TEST) — after impl groups
|
|
208
286
|
if (implPhase.test_groups.length > 0) {
|
|
209
287
|
const testKey = `developer-testing:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`
|
|
210
|
-
await
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
288
|
+
await agent(
|
|
289
|
+
`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 ${testKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
290
|
+
{ label: `ledger-open-test:${implPhase.phase_id}`, phase: 'Implementation', model: 'haiku' }
|
|
291
|
+
)
|
|
292
|
+
const beforeTestGroup = budget.spent()
|
|
293
|
+
let testFailed = false
|
|
294
|
+
let testErrMsg = ''
|
|
295
|
+
try {
|
|
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(
|
|
300
|
+
`${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
|
|
301
|
+
{
|
|
302
|
+
agentType: nativeName,
|
|
303
|
+
label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
|
|
304
|
+
phase: 'Implementation',
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
}))
|
|
308
|
+
} catch (err) {
|
|
309
|
+
testFailed = true
|
|
310
|
+
testErrMsg = err && err.message ? err.message.slice(0, 120) : 'test dispatch failed'
|
|
311
|
+
}
|
|
312
|
+
const testGroupTokens = budget.spent() - beforeTestGroup
|
|
313
|
+
if (testFailed) {
|
|
314
|
+
await ledgerTerminal(
|
|
315
|
+
`ai-toolkit ledger fail --dir ${featureDir} --prefix ${prefix} --agent ${testKey} --error "test dispatch failed" --attempt 1`,
|
|
316
|
+
`ledger-fail-test:${implPhase.phase_id}`, 'Implementation'
|
|
220
317
|
)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await
|
|
224
|
-
|
|
225
|
-
|
|
318
|
+
throw new Error(`Phase ${implPhase.phase_id} test dispatch failed: ${testErrMsg}`)
|
|
319
|
+
}
|
|
320
|
+
await ledgerTerminal(
|
|
321
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${testKey}${tokensAvailable(testGroupTokens) ? ` --tokens ${testGroupTokens}` : ''} --attempt 1`,
|
|
322
|
+
`ledger-close-test:${implPhase.phase_id}`, 'Implementation'
|
|
323
|
+
)
|
|
324
|
+
tokenLedger.push({ agent: testKey, model: 'sonnet', phase_delta_tokens: testGroupTokens })
|
|
325
|
+
log(`Phase ${implPhase.phase_id} test groups done — ${testGroupTokens} tokens`)
|
|
226
326
|
}
|
|
227
327
|
|
|
228
328
|
// Step 3 — review-solution
|
|
229
329
|
const reviewKey = `review-solution:${implPhase.phase_id}`
|
|
230
|
-
await
|
|
330
|
+
await agent(
|
|
331
|
+
`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 ${reviewKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
332
|
+
{ label: `ledger-open-review:${implPhase.phase_id}`, phase: 'Implementation', model: 'haiku' }
|
|
333
|
+
)
|
|
231
334
|
const beforeReview = budget.spent()
|
|
232
335
|
const review = await agent(
|
|
233
336
|
`${featurePath} --scope ${implPhase.phase_id}`,
|
|
234
337
|
{
|
|
235
|
-
agentType: 'review-solution',
|
|
338
|
+
agentType: resolvedNativeNames['review-solution'],
|
|
236
339
|
label: `review-solution:${implPhase.phase_id}`,
|
|
237
340
|
phase: 'Implementation',
|
|
238
341
|
schema: REVIEW_SCHEMA,
|
|
239
342
|
}
|
|
240
343
|
)
|
|
241
344
|
const reviewTokens = budget.spent() - beforeReview
|
|
242
|
-
await
|
|
345
|
+
await ledgerTerminal(
|
|
346
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${reviewKey}${tokensAvailable(reviewTokens) ? ` --tokens ${reviewTokens}` : ''} --attempt 1`,
|
|
347
|
+
`ledger-close-review:${implPhase.phase_id}`, 'Implementation'
|
|
348
|
+
)
|
|
243
349
|
tokenLedger.push({
|
|
244
350
|
agent: reviewKey,
|
|
245
351
|
model: 'sonnet',
|
|
@@ -313,8 +419,12 @@ log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US pas
|
|
|
313
419
|
// contention down to a single centralized run with one consolidated failure report.
|
|
314
420
|
phase('Test')
|
|
315
421
|
|
|
316
|
-
|
|
317
|
-
|
|
422
|
+
const testRunKey = 'final-test-run'
|
|
423
|
+
await agent(
|
|
424
|
+
`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 ${testRunKey} --phase phase3 --model haiku --attempt 1\n\nReturn no output.`,
|
|
425
|
+
{ label: 'ledger-open-final-test-run', phase: 'Test', model: 'haiku' }
|
|
426
|
+
)
|
|
427
|
+
const beforeFinalTest = budget.spent()
|
|
318
428
|
await agent(
|
|
319
429
|
`Run the full test suite in the repository root and report the result.
|
|
320
430
|
|
|
@@ -325,10 +435,13 @@ await agent(
|
|
|
325
435
|
Do NOT fix failing tests — only report them.`,
|
|
326
436
|
{ label: 'final-test-run', phase: 'Test', model: 'haiku' }
|
|
327
437
|
)
|
|
328
|
-
const
|
|
329
|
-
await
|
|
330
|
-
|
|
331
|
-
|
|
438
|
+
const finalTestTokens = budget.spent() - beforeFinalTest
|
|
439
|
+
await ledgerTerminal(
|
|
440
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${testRunKey}${tokensAvailable(finalTestTokens) ? ` --tokens ${finalTestTokens}` : ''} --attempt 1`,
|
|
441
|
+
'ledger-close-final-test-run', 'Test'
|
|
442
|
+
)
|
|
443
|
+
tokenLedger.push({ agent: testRunKey, model: 'haiku', phase_delta_tokens: finalTestTokens })
|
|
444
|
+
log(`Test run complete — ${finalTestTokens} tokens`)
|
|
332
445
|
|
|
333
446
|
// ── Remediation ───────────────────────────────────────────────────────────────
|
|
334
447
|
phase('Remediation')
|
|
@@ -345,9 +458,13 @@ const REM_SCHEMA = {
|
|
|
345
458
|
let issuesFixed = 0
|
|
346
459
|
let issuesDeferred = 0
|
|
347
460
|
|
|
461
|
+
const remKey = 'remediation'
|
|
348
462
|
if (issuesOpen > 0 && issuesPath) {
|
|
349
463
|
log(`Remediation: ${issuesOpen} OPEN issues`)
|
|
350
|
-
await
|
|
464
|
+
await agent(
|
|
465
|
+
`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 ${remKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
466
|
+
{ label: 'ledger-open-remediation', phase: 'Remediation', model: 'haiku' }
|
|
467
|
+
)
|
|
351
468
|
const beforeRem = budget.spent()
|
|
352
469
|
|
|
353
470
|
const remResult = await agent(
|
|
@@ -370,13 +487,20 @@ Update the Issues Register Status column in place for each resolved/deferred iss
|
|
|
370
487
|
)
|
|
371
488
|
|
|
372
489
|
const remTokens = budget.spent() - beforeRem
|
|
373
|
-
await
|
|
374
|
-
|
|
490
|
+
await ledgerTerminal(
|
|
491
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${remKey}${tokensAvailable(remTokens) ? ` --tokens ${remTokens}` : ''} --attempt 1`,
|
|
492
|
+
'ledger-close-remediation', 'Remediation'
|
|
493
|
+
)
|
|
494
|
+
tokenLedger.push({ agent: remKey, model: 'sonnet', phase_delta_tokens: remTokens })
|
|
375
495
|
issuesFixed = remResult.issues_fixed || 0
|
|
376
496
|
issuesDeferred = remResult.issues_deferred || 0
|
|
377
497
|
log(`Remediation done — fixed: ${issuesFixed}, deferred: ${issuesDeferred} — ${remTokens} tokens`)
|
|
378
498
|
} else {
|
|
379
499
|
log('Remediation: no OPEN issues — skipped')
|
|
500
|
+
await ledgerTerminal(
|
|
501
|
+
`ai-toolkit ledger skip --dir ${featureDir} --prefix ${prefix} --agent ${remKey} --phase phase3 --model sonnet --attempt 1`,
|
|
502
|
+
'ledger-skip-remediation', 'Remediation'
|
|
503
|
+
)
|
|
380
504
|
}
|
|
381
505
|
|
|
382
506
|
// ── PR ────────────────────────────────────────────────────────────────────────
|
|
@@ -391,7 +515,11 @@ const PR_SCHEMA = {
|
|
|
391
515
|
required: ['pr_url'],
|
|
392
516
|
}
|
|
393
517
|
|
|
394
|
-
|
|
518
|
+
const prKey = 'pr-and-registry'
|
|
519
|
+
await agent(
|
|
520
|
+
`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 ${prKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
521
|
+
{ label: 'ledger-open-pr-and-registry', phase: 'PR', model: 'haiku' }
|
|
522
|
+
)
|
|
395
523
|
const beforePR = budget.spent()
|
|
396
524
|
|
|
397
525
|
const prResult = await agent(
|
|
@@ -441,8 +569,11 @@ If the PR creation fails, return { "pr_url": "(PR creation failed)", "registry_u
|
|
|
441
569
|
)
|
|
442
570
|
|
|
443
571
|
const prTokens = budget.spent() - beforePR
|
|
444
|
-
await
|
|
445
|
-
|
|
572
|
+
await ledgerTerminal(
|
|
573
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${prKey}${tokensAvailable(prTokens) ? ` --tokens ${prTokens}` : ''} --attempt 1`,
|
|
574
|
+
'ledger-close-pr-and-registry', 'PR'
|
|
575
|
+
)
|
|
576
|
+
tokenLedger.push({ agent: prKey, model: 'sonnet', phase_delta_tokens: prTokens })
|
|
446
577
|
log(`PR done: ${prResult.pr_url} — ${prTokens} tokens`)
|
|
447
578
|
|
|
448
579
|
// ── Actuals ───────────────────────────────────────────────────────────────────
|
|
@@ -468,7 +599,7 @@ try {
|
|
|
468
599
|
tokenLedger.push(entry)
|
|
469
600
|
inMemoryByAgent.set(entry.agent, tokenLedger.length - 1)
|
|
470
601
|
log(`Recovered ledger entry from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
|
|
471
|
-
} else if (tokenLedger[idx].phase_delta_tokens
|
|
602
|
+
} else if (!tokensAvailable(tokenLedger[idx].phase_delta_tokens) && tokensAvailable(entry.phase_delta_tokens)) {
|
|
472
603
|
// in-memory entry has delta=0 (cached agent on resume) but disk has real data — prefer disk
|
|
473
604
|
tokenLedger[idx] = entry
|
|
474
605
|
log(`Restored real token count from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
|
|
@@ -479,7 +610,7 @@ try {
|
|
|
479
610
|
log(`Could not parse persisted token ledger — using in-memory ledger only`)
|
|
480
611
|
}
|
|
481
612
|
|
|
482
|
-
const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens
|
|
613
|
+
const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (tokensAvailable(e.phase_delta_tokens) ? e.phase_delta_tokens : 0), 0)
|
|
483
614
|
|
|
484
615
|
// ── Load token pricing for cost columns ──────────────────────────────────────
|
|
485
616
|
const pricingRaw3 = await agent(
|
|
@@ -509,7 +640,7 @@ const roleTotals = {}
|
|
|
509
640
|
const roleModels = {}
|
|
510
641
|
for (const e of tokenLedger) {
|
|
511
642
|
const role = String(e.agent).split(':')[0]
|
|
512
|
-
roleTotals[role] = (roleTotals[role] || 0) + (e.phase_delta_tokens
|
|
643
|
+
roleTotals[role] = (roleTotals[role] || 0) + (tokensAvailable(e.phase_delta_tokens) ? e.phase_delta_tokens : 0)
|
|
513
644
|
roleModels[role] = e.model || 'sonnet'
|
|
514
645
|
}
|
|
515
646
|
const roleRows = Object.entries(roleTotals)
|
|
@@ -528,7 +659,11 @@ const ACTUALS_SCHEMA = {
|
|
|
528
659
|
required: [],
|
|
529
660
|
}
|
|
530
661
|
|
|
531
|
-
|
|
662
|
+
const actualsKey = 'write-actuals'
|
|
663
|
+
await agent(
|
|
664
|
+
`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 ${actualsKey} --phase phase3 --model sonnet --attempt 1\n\nReturn no output.`,
|
|
665
|
+
{ label: 'ledger-open-write-actuals', phase: 'Actuals', model: 'haiku' }
|
|
666
|
+
)
|
|
532
667
|
const beforeActuals = budget.spent()
|
|
533
668
|
|
|
534
669
|
await agent(
|
|
@@ -622,8 +757,11 @@ Return { "token_estimate_path": "<path>", "effort_estimate_path": "<path>" }.`,
|
|
|
622
757
|
)
|
|
623
758
|
|
|
624
759
|
const actualsTokens = budget.spent() - beforeActuals
|
|
625
|
-
await
|
|
626
|
-
|
|
760
|
+
await ledgerTerminal(
|
|
761
|
+
`ai-toolkit ledger close --dir ${featureDir} --prefix ${prefix} --agent ${actualsKey}${tokensAvailable(actualsTokens) ? ` --tokens ${actualsTokens}` : ''} --attempt 1`,
|
|
762
|
+
'ledger-close-write-actuals', 'Actuals'
|
|
763
|
+
)
|
|
764
|
+
tokenLedger.push({ agent: actualsKey, model: 'sonnet', phase_delta_tokens: actualsTokens })
|
|
627
765
|
log(`Actuals written — ${actualsTokens} tokens`)
|
|
628
766
|
|
|
629
767
|
// ── Append to process-log ─────────────────────────────────────────────────────
|
|
@@ -672,6 +810,12 @@ If there is nothing to commit (all files already committed), that is fine — re
|
|
|
672
810
|
{ label: 'commit-actuals', phase: 'Actuals' }
|
|
673
811
|
)
|
|
674
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
|
+
|
|
675
819
|
return {
|
|
676
820
|
pr_url: prResult.pr_url,
|
|
677
821
|
token_ledger: tokenLedger,
|