@dtlabs/ai-toolkit 0.7.0 → 0.9.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/.claude/agents/define-feature.md +67 -1
- package/.claude/agents/install-toolkit.md +34 -0
- package/.claude/workflows/pm-phase1.js +87 -0
- package/.claude/workflows/pm-phase2.js +114 -26
- package/.claude/workflows/pm-phase3.js +155 -41
- package/CHANGELOG.md +57 -0
- package/bin/cli.js +428 -3
- package/docs/token-pricing.json +21 -0
- package/package.json +1 -1
- package/docs/token-optimization-backlog.md +0 -115
- package/docs/token-optimization.md +0 -274
|
@@ -11,6 +11,40 @@ export const meta = {
|
|
|
11
11
|
],
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// ── Ledger helper functions ───────────────────────────────────────────────────
|
|
15
|
+
// Route all ledger I/O through agent() — fs and require() are not available in
|
|
16
|
+
// the Workflow runtime. Mirrors the pattern used by pm-phase2.js.
|
|
17
|
+
|
|
18
|
+
async function appendLedgerEntry(featureDir, prefix, entry) {
|
|
19
|
+
const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
|
|
20
|
+
const entryWithoutTs = JSON.stringify({ ...entry, started_at: '__TS__', completed_at: null })
|
|
21
|
+
await agent(
|
|
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
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
|
|
32
|
+
const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
|
|
33
|
+
const updatesWithoutTs = JSON.stringify({ ...updates, completed_at: '__TS__' })
|
|
34
|
+
await agent(
|
|
35
|
+
`Update an entry in the ledger array at: ${ledgerPath}\n\n` +
|
|
36
|
+
`1. Run: date -u +"%Y-%m-%dT%H:%M:%SZ" and capture the output as NOW.\n` +
|
|
37
|
+
`2. Read the file. If it does not exist or cannot be parsed as a JSON array, do nothing.\n` +
|
|
38
|
+
`3. Search from the end for the last entry where agent === "${agentKey}".\n` +
|
|
39
|
+
`4. If found, merge these fields into that entry, replacing "__TS__" in completed_at with NOW: ${updatesWithoutTs}\n` +
|
|
40
|
+
`5. Write the full array back (JSON, 2-space indent). Return no output.\n` +
|
|
41
|
+
`6. If not found, do nothing.`,
|
|
42
|
+
{ label: 'update-ledger', phase: 'Implementation', model: 'haiku' }
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Parse args ────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
14
48
|
// args: "<path-to-feature.md> --branch feature/FTR-NNN-slug"
|
|
15
49
|
const argStr = typeof args === 'string' ? args : ''
|
|
16
50
|
const featurePath = argStr.trim().split(/\s+/)[0]
|
|
@@ -31,10 +65,13 @@ const csvPath = `${featureDir}/${prefix}-Work-Breakdown.csv`
|
|
|
31
65
|
|
|
32
66
|
log(`Reading CSV: ${csvPath}`)
|
|
33
67
|
|
|
68
|
+
await appendLedgerEntry(featureDir, prefix, { agent: 'read-wb-csv:phase3', phase: 'phase3', model: 'haiku', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
69
|
+
const beforeCsv = budget.spent()
|
|
34
70
|
const csvContent = await agent(
|
|
35
71
|
`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.`,
|
|
36
72
|
{ label: 'read-wb-csv', phase: 'Parse', model: 'haiku' }
|
|
37
73
|
)
|
|
74
|
+
await updateLedgerEntry(featureDir, prefix, 'read-wb-csv:phase3', { status: 'done', completed_at: '__TS__', phase_delta_tokens: budget.spent() - beforeCsv })
|
|
38
75
|
|
|
39
76
|
// Parse CSV into structured phases — pure JS, no AI
|
|
40
77
|
const rows = csvContent
|
|
@@ -148,6 +185,8 @@ const executePhase = async (implPhase) => {
|
|
|
148
185
|
|
|
149
186
|
// Step 1 — impl groups in parallel (INFRA / BE / FE)
|
|
150
187
|
if (implPhase.impl_groups.length > 0) {
|
|
188
|
+
const implKey = `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`
|
|
189
|
+
await appendLedgerEntry(featureDir, prefix, { agent: implKey, phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
151
190
|
const beforeImpl = budget.spent()
|
|
152
191
|
await parallel(implPhase.impl_groups.map(group => () =>
|
|
153
192
|
agent(
|
|
@@ -160,16 +199,15 @@ const executePhase = async (implPhase) => {
|
|
|
160
199
|
)
|
|
161
200
|
))
|
|
162
201
|
const implTokens = budget.spent() - beforeImpl
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
model: 'sonnet',
|
|
166
|
-
phase_delta_tokens: implTokens,
|
|
167
|
-
})
|
|
202
|
+
await updateLedgerEntry(featureDir, prefix, implKey, { status: 'done', completed_at: '__TS__', phase_delta_tokens: implTokens })
|
|
203
|
+
tokenLedger.push({ agent: implKey, model: 'sonnet', phase_delta_tokens: implTokens })
|
|
168
204
|
log(`Phase ${implPhase.phase_id} impl groups done — ${implTokens} tokens`)
|
|
169
205
|
}
|
|
170
206
|
|
|
171
207
|
// Step 2 — test groups in parallel (TEST) — after impl groups
|
|
172
208
|
if (implPhase.test_groups.length > 0) {
|
|
209
|
+
const testKey = `developer-testing:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`
|
|
210
|
+
await appendLedgerEntry(featureDir, prefix, { agent: testKey, phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
173
211
|
const beforeTest = budget.spent()
|
|
174
212
|
await parallel(implPhase.test_groups.map(group => () =>
|
|
175
213
|
agent(
|
|
@@ -182,15 +220,14 @@ const executePhase = async (implPhase) => {
|
|
|
182
220
|
)
|
|
183
221
|
))
|
|
184
222
|
const testTokens = budget.spent() - beforeTest
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
model: 'sonnet',
|
|
188
|
-
phase_delta_tokens: testTokens,
|
|
189
|
-
})
|
|
223
|
+
await updateLedgerEntry(featureDir, prefix, testKey, { status: 'done', completed_at: '__TS__', phase_delta_tokens: testTokens })
|
|
224
|
+
tokenLedger.push({ agent: testKey, model: 'sonnet', phase_delta_tokens: testTokens })
|
|
190
225
|
log(`Phase ${implPhase.phase_id} test groups done — ${testTokens} tokens`)
|
|
191
226
|
}
|
|
192
227
|
|
|
193
228
|
// Step 3 — review-solution
|
|
229
|
+
const reviewKey = `review-solution:${implPhase.phase_id}`
|
|
230
|
+
await appendLedgerEntry(featureDir, prefix, { agent: reviewKey, phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
194
231
|
const beforeReview = budget.spent()
|
|
195
232
|
const review = await agent(
|
|
196
233
|
`${featurePath} --scope ${implPhase.phase_id}`,
|
|
@@ -202,8 +239,9 @@ const executePhase = async (implPhase) => {
|
|
|
202
239
|
}
|
|
203
240
|
)
|
|
204
241
|
const reviewTokens = budget.spent() - beforeReview
|
|
242
|
+
await updateLedgerEntry(featureDir, prefix, reviewKey, { status: 'done', completed_at: '__TS__', phase_delta_tokens: reviewTokens })
|
|
205
243
|
tokenLedger.push({
|
|
206
|
-
agent:
|
|
244
|
+
agent: reviewKey,
|
|
207
245
|
model: 'sonnet',
|
|
208
246
|
phase_delta_tokens: reviewTokens,
|
|
209
247
|
})
|
|
@@ -238,16 +276,6 @@ const executePhase = async (implPhase) => {
|
|
|
238
276
|
}
|
|
239
277
|
|
|
240
278
|
phasesDone++
|
|
241
|
-
|
|
242
|
-
// Persist the cumulative token ledger to disk after every completed phase.
|
|
243
|
-
// budget.spent() is an in-memory counter — if the workflow is stopped (manual
|
|
244
|
-
// stop, crash, timeout) before the Actuals phase, all token data would be lost.
|
|
245
|
-
// Writing the full ledger here means the file always reflects real consumption
|
|
246
|
-
// up to the last completed phase, recoverable manually or by a resumed run.
|
|
247
|
-
await agent(
|
|
248
|
-
`Write the following JSON to ${featureDir}/${prefix}-token-ledger.json (overwrite the file if it exists):\n${JSON.stringify(tokenLedger, null, 2)}`,
|
|
249
|
-
{ label: `persist-ledger:${implPhase.phase_id}`, phase: 'Implementation', model: 'haiku' }
|
|
250
|
-
)
|
|
251
279
|
}
|
|
252
280
|
|
|
253
281
|
// Execute waves: phases in the same wave run in parallel, waves are sequential
|
|
@@ -285,6 +313,7 @@ log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US pas
|
|
|
285
313
|
// contention down to a single centralized run with one consolidated failure report.
|
|
286
314
|
phase('Test')
|
|
287
315
|
|
|
316
|
+
await appendLedgerEntry(featureDir, prefix, { agent: 'final-test-run', phase: 'phase3', model: 'haiku', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
288
317
|
const beforeTest = budget.spent()
|
|
289
318
|
await agent(
|
|
290
319
|
`Run the full test suite in the repository root and report the result.
|
|
@@ -297,6 +326,7 @@ Do NOT fix failing tests — only report them.`,
|
|
|
297
326
|
{ label: 'final-test-run', phase: 'Test', model: 'haiku' }
|
|
298
327
|
)
|
|
299
328
|
const testTokens = budget.spent() - beforeTest
|
|
329
|
+
await updateLedgerEntry(featureDir, prefix, 'final-test-run', { status: 'done', completed_at: '__TS__', phase_delta_tokens: testTokens })
|
|
300
330
|
tokenLedger.push({ agent: 'final-test-run', model: 'haiku', phase_delta_tokens: testTokens })
|
|
301
331
|
log(`Test run complete — ${testTokens} tokens`)
|
|
302
332
|
|
|
@@ -317,6 +347,7 @@ let issuesDeferred = 0
|
|
|
317
347
|
|
|
318
348
|
if (issuesOpen > 0 && issuesPath) {
|
|
319
349
|
log(`Remediation: ${issuesOpen} OPEN issues`)
|
|
350
|
+
await appendLedgerEntry(featureDir, prefix, { agent: 'remediation', phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
320
351
|
const beforeRem = budget.spent()
|
|
321
352
|
|
|
322
353
|
const remResult = await agent(
|
|
@@ -339,6 +370,7 @@ Update the Issues Register Status column in place for each resolved/deferred iss
|
|
|
339
370
|
)
|
|
340
371
|
|
|
341
372
|
const remTokens = budget.spent() - beforeRem
|
|
373
|
+
await updateLedgerEntry(featureDir, prefix, 'remediation', { status: 'done', completed_at: '__TS__', phase_delta_tokens: remTokens })
|
|
342
374
|
tokenLedger.push({ agent: 'remediation', model: 'sonnet', phase_delta_tokens: remTokens })
|
|
343
375
|
issuesFixed = remResult.issues_fixed || 0
|
|
344
376
|
issuesDeferred = remResult.issues_deferred || 0
|
|
@@ -359,6 +391,7 @@ const PR_SCHEMA = {
|
|
|
359
391
|
required: ['pr_url'],
|
|
360
392
|
}
|
|
361
393
|
|
|
394
|
+
await appendLedgerEntry(featureDir, prefix, { agent: 'pr-and-registry', phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
362
395
|
const beforePR = budget.spent()
|
|
363
396
|
|
|
364
397
|
const prResult = await agent(
|
|
@@ -408,6 +441,7 @@ If the PR creation fails, return { "pr_url": "(PR creation failed)", "registry_u
|
|
|
408
441
|
)
|
|
409
442
|
|
|
410
443
|
const prTokens = budget.spent() - beforePR
|
|
444
|
+
await updateLedgerEntry(featureDir, prefix, 'pr-and-registry', { status: 'done', completed_at: '__TS__', phase_delta_tokens: prTokens })
|
|
411
445
|
tokenLedger.push({ agent: 'pr-and-registry', model: 'sonnet', phase_delta_tokens: prTokens })
|
|
412
446
|
log(`PR done: ${prResult.pr_url} — ${prTokens} tokens`)
|
|
413
447
|
|
|
@@ -425,12 +459,19 @@ const persistedLedgerRaw = await agent(
|
|
|
425
459
|
try {
|
|
426
460
|
const persisted = JSON.parse(typeof persistedLedgerRaw === 'string' ? persistedLedgerRaw.trim() : '[]')
|
|
427
461
|
if (Array.isArray(persisted)) {
|
|
428
|
-
const
|
|
462
|
+
const inMemoryByAgent = new Map(tokenLedger.map((e, i) => [e.agent, i]))
|
|
429
463
|
for (const entry of persisted) {
|
|
430
|
-
if (entry
|
|
464
|
+
if (!entry || !entry.agent) continue
|
|
465
|
+
const idx = inMemoryByAgent.get(entry.agent)
|
|
466
|
+
if (idx === undefined) {
|
|
467
|
+
// entry exists on disk but not in memory — recover it (e.g. from a prior interrupted run)
|
|
431
468
|
tokenLedger.push(entry)
|
|
432
|
-
|
|
469
|
+
inMemoryByAgent.set(entry.agent, tokenLedger.length - 1)
|
|
433
470
|
log(`Recovered ledger entry from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
|
|
471
|
+
} else if (tokenLedger[idx].phase_delta_tokens === 0 && (entry.phase_delta_tokens || 0) > 0) {
|
|
472
|
+
// in-memory entry has delta=0 (cached agent on resume) but disk has real data — prefer disk
|
|
473
|
+
tokenLedger[idx] = entry
|
|
474
|
+
log(`Restored real token count from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
|
|
434
475
|
}
|
|
435
476
|
}
|
|
436
477
|
}
|
|
@@ -440,6 +481,44 @@ try {
|
|
|
440
481
|
|
|
441
482
|
const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens || 0), 0)
|
|
442
483
|
|
|
484
|
+
// ── Load token pricing for cost columns ──────────────────────────────────────
|
|
485
|
+
const pricingRaw3 = await agent(
|
|
486
|
+
`Read the file docs/token-pricing.json and return its raw JSON contents as a string, nothing else.`,
|
|
487
|
+
{ label: 'read-pricing', phase: 'Actuals', model: 'haiku' }
|
|
488
|
+
)
|
|
489
|
+
let pricing3 = null
|
|
490
|
+
try { pricing3 = JSON.parse(typeof pricingRaw3 === 'string' ? pricingRaw3.trim() : '{}') } catch (_) {}
|
|
491
|
+
|
|
492
|
+
function tokenCostEur3(tokens, model) {
|
|
493
|
+
if (!pricing3 || !pricing3.models) return null
|
|
494
|
+
const m = pricing3.models[model] || pricing3.models['sonnet']
|
|
495
|
+
const usdPer1M = 0.8 * m.input_per_1m_usd + 0.2 * m.output_per_1m_usd
|
|
496
|
+
return (tokens * usdPer1M / 1_000_000) * (pricing3.usd_to_eur || 1)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function formatEur3(val) {
|
|
500
|
+
if (val === null || val === undefined) return '—'
|
|
501
|
+
return '€' + val.toFixed(4)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Aggregate the per-agent ledger into per-role totals so the actuals line up with the
|
|
505
|
+
// per-role estimate rows pm-phase2 wrote (developer-backend, developer-testing,
|
|
506
|
+
// review-solution, pr-and-registry, write-actuals, plus final-test-run). Precomputing
|
|
507
|
+
// here keeps the write-actuals agent from having to reason about grouping.
|
|
508
|
+
const roleTotals = {}
|
|
509
|
+
const roleModels = {}
|
|
510
|
+
for (const e of tokenLedger) {
|
|
511
|
+
const role = String(e.agent).split(':')[0]
|
|
512
|
+
roleTotals[role] = (roleTotals[role] || 0) + (e.phase_delta_tokens || 0)
|
|
513
|
+
roleModels[role] = e.model || 'sonnet'
|
|
514
|
+
}
|
|
515
|
+
const roleRows = Object.entries(roleTotals)
|
|
516
|
+
.map(([role, tok]) => `| ${role} | ${tok} | ${formatEur3(tokenCostEur3(tok, roleModels[role]))} |`)
|
|
517
|
+
.join('\n')
|
|
518
|
+
const totalPhase3CostEur = formatEur3(
|
|
519
|
+
Object.entries(roleTotals).reduce((s, [role, tok]) => s + (tokenCostEur3(tok, roleModels[role]) || 0), 0)
|
|
520
|
+
)
|
|
521
|
+
|
|
443
522
|
const ACTUALS_SCHEMA = {
|
|
444
523
|
type: 'object',
|
|
445
524
|
properties: {
|
|
@@ -449,6 +528,7 @@ const ACTUALS_SCHEMA = {
|
|
|
449
528
|
required: [],
|
|
450
529
|
}
|
|
451
530
|
|
|
531
|
+
await appendLedgerEntry(featureDir, prefix, { agent: 'write-actuals', phase: 'phase3', model: 'sonnet', status: 'running', phase_delta_tokens: 0, started_at: '__TS__', completed_at: null })
|
|
452
532
|
const beforeActuals = budget.spent()
|
|
453
533
|
|
|
454
534
|
await agent(
|
|
@@ -462,30 +542,50 @@ ${JSON.stringify(tokenLedger, null, 2)}
|
|
|
462
542
|
|
|
463
543
|
Total tokens consumed by pm-phase3 workflow: ${totalPhase3Tokens} (exact, from budget tracking)
|
|
464
544
|
|
|
465
|
-
TASK 1 —
|
|
466
|
-
|
|
545
|
+
TASK 1 — Fill in the Phase 3 actuals in ${prefix}-Token-Estimate.md IN PLACE.
|
|
546
|
+
Read the file in the feature directory first (it was written by pm-phase2 and already contains
|
|
547
|
+
Phase 1/Phase 2 actuals and the Phase 3 estimate table). If it does not exist, create it.
|
|
467
548
|
|
|
468
|
-
|
|
549
|
+
Do NOT append a second Phase 3 table. Instead:
|
|
469
550
|
|
|
470
|
-
|
|
551
|
+
1. Locate the existing "## Phase 3 — Implementation (Estimates)" section. Replace its heading
|
|
552
|
+
with "## Phase 3 — Implementation (Est. vs Actual, by role)" and REPLACE its table with the
|
|
553
|
+
per-role reconciliation below — filling the "Tokens Actual" and "Actual cost €" columns of the
|
|
554
|
+
existing per-role estimate rows (keep the existing Tokens Est. and Est. cost € values; add a Delta column):
|
|
555
|
+
|
|
556
|
+
| Role | Model | Tokens Actual | Actual cost € |
|
|
557
|
+
|------|-------|--------------|--------------|
|
|
558
|
+
${roleRows}
|
|
559
|
+
| **Phase 3 total** | | **${totalPhase3Tokens}** | **${totalPhase3CostEur}** |
|
|
560
|
+
|
|
561
|
+
Match each existing estimate row (developer-backend, developer-testing, review-solution,
|
|
562
|
+
remediation, pr-and-registry, write-actuals) to the actual role total above by name, write the
|
|
563
|
+
actual into its "Tokens Actual" and "Actual cost €" cells, and compute Delta = Actual − Est (tokens).
|
|
564
|
+
Roles present in the actuals but absent from the estimate rows (e.g. final-test-run) get a new row with Est. = —.
|
|
471
565
|
|
|
472
|
-
|
|
566
|
+
2. Immediately after that table, add the per-agent detail as a sub-table (source of truth):
|
|
473
567
|
|
|
474
|
-
|
|
475
|
-
> Per-agent values are exact where each agent ran separately; proportional where merged.
|
|
568
|
+
### Phase 3 — per-agent detail (actuals)
|
|
476
569
|
|
|
477
|
-
| Agent |
|
|
478
|
-
|
|
479
|
-
${tokenLedger.map(e => `| ${e.agent} |
|
|
480
|
-
| **
|
|
570
|
+
| Agent | Model | Tokens Actual | Actual cost € |
|
|
571
|
+
|-------|-------|--------------|--------------|
|
|
572
|
+
${tokenLedger.map(e => `| ${e.agent} | ${e.model} | ${e.phase_delta_tokens} | ${formatEur3(tokenCostEur3(e.phase_delta_tokens, e.model))} |`).join('\n')}
|
|
573
|
+
| **Detail total** | | **${totalPhase3Tokens}** | **${totalPhase3CostEur}** |
|
|
481
574
|
|
|
482
|
-
## Grand Total
|
|
575
|
+
3. Update the existing "## Grand Total" table. Read the Phase 1 and Phase 2 actual totals from
|
|
576
|
+
the top of THIS SAME file (the "Phase 1 total" and "Phase 2 total" rows already present) and
|
|
577
|
+
copy them verbatim — never the literal text "(see above)". If a value there is "—" (Phase 1 is
|
|
578
|
+
written as a placeholder by pm-phase2 and filled later by the orchestrator), keep it as "—";
|
|
579
|
+
do NOT invent a number. The table must read:
|
|
483
580
|
|
|
484
|
-
| Phase | Tokens Est. | Tokens Actual |
|
|
485
|
-
|
|
486
|
-
| Phase 1 — Documentation | — |
|
|
487
|
-
| Phase 2 — Work Breakdown | — |
|
|
488
|
-
| Phase 3 — Implementation |
|
|
581
|
+
| Phase | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € | Delta (tokens) |
|
|
582
|
+
|-------|------------|------------|--------------|--------------|---------------|
|
|
583
|
+
| Phase 1 — Documentation | — | — | {Phase 1 total from this file, or — if placeholder} | — | — |
|
|
584
|
+
| Phase 2 — Work Breakdown | — | — | {Phase 2 total from this file} | {Phase 2 cost from this file} | — |
|
|
585
|
+
| Phase 3 — Implementation | {existing Phase 3 estimate total} | {existing Phase 3 est. cost} | ${totalPhase3Tokens} | ${totalPhase3CostEur} | {Actual − Est} |
|
|
586
|
+
| **Total** | **{sum of numeric Est}** | **{sum of numeric Est. cost}** | **{sum of numeric Actual}** | **{sum of numeric Actual cost}** | **{Actual − Est}** |
|
|
587
|
+
|
|
588
|
+
4. Append (or update if present) the Implementation Summary section:
|
|
489
589
|
|
|
490
590
|
## Implementation Summary
|
|
491
591
|
|
|
@@ -495,6 +595,8 @@ ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_
|
|
|
495
595
|
| US passed | ${usPassed.join(', ') || 'N/A'} |
|
|
496
596
|
| US escalated | ${usEscalated.join(', ') || 'none'} |
|
|
497
597
|
|
|
598
|
+
Remove the stale "*Actuals will be appended by pm-phase3...*" footer line if it is still present.
|
|
599
|
+
|
|
498
600
|
TASK 2 — Update actuals in ${prefix}-Effort-Estimate.md:
|
|
499
601
|
Find the file in the feature directory. Read the existing Per-Phase Breakdown table and update the "Actual Agent" column for each phase that was implemented. Use the token ledger to infer which phases completed:
|
|
500
602
|
- Phases in usPassed: ${usPassed.join(', ') || 'none'} — mark Actual Agent as completed (write the wall-clock duration if available, otherwise write "done (agent)")
|
|
@@ -520,6 +622,7 @@ Return { "token_estimate_path": "<path>", "effort_estimate_path": "<path>" }.`,
|
|
|
520
622
|
)
|
|
521
623
|
|
|
522
624
|
const actualsTokens = budget.spent() - beforeActuals
|
|
625
|
+
await updateLedgerEntry(featureDir, prefix, 'write-actuals', { status: 'done', completed_at: '__TS__', phase_delta_tokens: actualsTokens })
|
|
523
626
|
tokenLedger.push({ agent: 'write-actuals', model: 'sonnet', phase_delta_tokens: actualsTokens })
|
|
524
627
|
log(`Actuals written — ${actualsTokens} tokens`)
|
|
525
628
|
|
|
@@ -558,6 +661,17 @@ ${phase3Events}`,
|
|
|
558
661
|
{ label: 'finalize-process-log', phase: 'Actuals' }
|
|
559
662
|
)
|
|
560
663
|
|
|
664
|
+
await agent(
|
|
665
|
+
`Commit the actuals files for this feature delivery run.
|
|
666
|
+
|
|
667
|
+
Run these exact git commands in the repository root:
|
|
668
|
+
git add "${featureDir3}/${prefix}-Token-Estimate.md" "${featureDir3}/${prefix}-Effort-Estimate.md" "${featureDir3}/${prefix}-process-log.txt"
|
|
669
|
+
git commit -m "docs(${prefix}): add token/effort actuals and process log"
|
|
670
|
+
|
|
671
|
+
If there is nothing to commit (all files already committed), that is fine — report success.`,
|
|
672
|
+
{ label: 'commit-actuals', phase: 'Actuals' }
|
|
673
|
+
)
|
|
674
|
+
|
|
561
675
|
return {
|
|
562
676
|
pr_url: prResult.pr_url,
|
|
563
677
|
token_ledger: tokenLedger,
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,61 @@
|
|
|
1
1
|
## 0.1.4 (2026-07-21)
|
|
2
|
+
## [0.9.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.8.0...v0.9.0) (2026-08-05)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **FTR-020:** add initial documentation for Atomic Work Breakdown, Execution Ledger, and Task Checkpoints and Resume ([c5f724a](https://github.com/dtlab-tech/ai-toolkit/commit/c5f724af068e003d36ec196cdec3502fe0c47333))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* **pm-phase3:** replace require/fs/execSync with agent()-based ledger helpers ([1166d6f](https://github.com/dtlab-tech/ai-toolkit/commit/1166d6f17e913405ea737e74236742569740cd0a))
|
|
13
|
+
* **pm-phase3:** replace require/fs/execSync with agent()-based ledger helpers ([bf4077c](https://github.com/dtlab-tech/ai-toolkit/commit/bf4077cca28920876185535aa553ec91c6ffc08b))
|
|
14
|
+
* **pm-phase3:** replace require/fs/execSync with agent()-based ledger helpers ([bb4c88d](https://github.com/dtlab-tech/ai-toolkit/commit/bb4c88df20c62db09de327975ab57e045247dc51))
|
|
15
|
+
* **workflows:** remove new Date() from all workflow call sites ([343b604](https://github.com/dtlab-tech/ai-toolkit/commit/343b604dba2d4d0b12e5590c1b5323b2a9e888f2))
|
|
16
|
+
|
|
17
|
+
## [0.8.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.7.0...v0.8.0) (2026-08-04)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **FTR-011:** implement shared infrastructure (INFRA) ([718b0eb](https://github.com/dtlab-tech/ai-toolkit/commit/718b0ebe684814028b0457351a40b171ea80a197))
|
|
23
|
+
* **FTR-011:** implement US-01 — Implement readManifest() Function ([aad7de1](https://github.com/dtlab-tech/ai-toolkit/commit/aad7de19aa6aaf48578c46eebd3bb236ebb4fadb))
|
|
24
|
+
* **FTR-011:** implement US-02 — Implement computeOrphans() Function ([3a70517](https://github.com/dtlab-tech/ai-toolkit/commit/3a705173e649aa7782d036b5790c830c88a1d797))
|
|
25
|
+
* **FTR-011:** implement US-03 — Implement moveToTrash() Function ([a6ac7d2](https://github.com/dtlab-tech/ai-toolkit/commit/a6ac7d2d841905bba24f5349a7d763c9f43527e8))
|
|
26
|
+
* **FTR-011:** implement US-04 — Implement writeManifest() Function ([dc43ea1](https://github.com/dtlab-tech/ai-toolkit/commit/dc43ea1e35c2e4981876ad5f46b3138890851fcd))
|
|
27
|
+
* **FTR-011:** implement US-05 — Integrate Prune Phase into runInstall() and Add UI Display ([bca87ea](https://github.com/dtlab-tech/ai-toolkit/commit/bca87ea8f8843f6a8a07371eaef072b1fc004453))
|
|
28
|
+
* **FTR-011:** implement US-06 — Add CI Safety Net — Agent Name-to-Filename Alignment Check ([06db318](https://github.com/dtlab-tech/ai-toolkit/commit/06db3180b4867b611a2793bf2c74e8510e66827c))
|
|
29
|
+
* **FTR-011:** Installer Manifest and Orphan Pruning ([764eff4](https://github.com/dtlab-tech/ai-toolkit/commit/764eff4dd3b13ee34e7e4be7159c08168e49fe1d))
|
|
30
|
+
* **FTR-011:** update effort and token estimates, add implementation summary, and log completion details ([ce5660d](https://github.com/dtlab-tech/ai-toolkit/commit/ce5660dec691b75bb501bcadb149c2f4eaabdb5a))
|
|
31
|
+
* **FTR-012:** implement shared infrastructure (INFRA) ([da14ae1](https://github.com/dtlab-tech/ai-toolkit/commit/da14ae15d19301c47231e73872ecba75f180e739))
|
|
32
|
+
* **FTR-012:** implement US-01 — Fresh Installation with Allowlist Opt-In ([803018a](https://github.com/dtlab-tech/ai-toolkit/commit/803018aafe77abb06390849540f7fb214fccbc40))
|
|
33
|
+
* **FTR-012:** implement US-02 — Merge Allowlist into Existing Settings ([9855abc](https://github.com/dtlab-tech/ai-toolkit/commit/9855abc940443d4e75b7a1b3340715d8e1403fcf))
|
|
34
|
+
* **FTR-012:** implement US-03 — Ask-Beats-Allow Conflict Resolution ([9c51984](https://github.com/dtlab-tech/ai-toolkit/commit/9c5198490824647bac7b1850b09e624d9f00e883))
|
|
35
|
+
* **FTR-012:** implement US-04 — Reinstall with Idempotent Merge ([8e9a49f](https://github.com/dtlab-tech/ai-toolkit/commit/8e9a49f69d7bb761a698b2ac7f7a405d5d2d833e))
|
|
36
|
+
* **FTR-012:** implement US-05 — .gitignore Creation and Idempotent Update ([20e19de](https://github.com/dtlab-tech/ai-toolkit/commit/20e19de3c38cbbfcd5dd27c8b8b5ab629036b101))
|
|
37
|
+
* **FTR-012:** Installer Bash Allowlist ([2deb73e](https://github.com/dtlab-tech/ai-toolkit/commit/2deb73ef4ad4189c6eea1ae46ec0f0dc6ece469e))
|
|
38
|
+
* **FTR-012:** mark feature as completed in registry ([ab90d58](https://github.com/dtlab-tech/ai-toolkit/commit/ab90d5878a4091ccaef38863c14634ebd2082623))
|
|
39
|
+
* **FTR-013:** add additional agent entries for US-03 and US-04 in ledger ([6f756ed](https://github.com/dtlab-tech/ai-toolkit/commit/6f756edfaa0d75b48809aa3964261efa65fd69f9))
|
|
40
|
+
* **FTR-013:** add registry entry and update ledger actuals ([23f5138](https://github.com/dtlab-tech/ai-toolkit/commit/23f513801486c36e95782ea513e15fcfcb755d06))
|
|
41
|
+
* **FTR-013:** implement shared infrastructure (INFRA) ([99d564f](https://github.com/dtlab-tech/ai-toolkit/commit/99d564f8ca543b7cd95c5cf1a5687961ef402921))
|
|
42
|
+
* **FTR-013:** implement US-01 — Initialize and Track Ledger in define-feature Agent ([ce0b010](https://github.com/dtlab-tech/ai-toolkit/commit/ce0b010afd40e94f9e9e2618eca1fbcc60ede7ae))
|
|
43
|
+
* **FTR-013:** implement US-01 — Initialize and Track Ledger in define-feature Agent ([ca84538](https://github.com/dtlab-tech/ai-toolkit/commit/ca84538d6980828313d331e4a07e10bde5cb34c7))
|
|
44
|
+
* **FTR-013:** implement US-02 — Track Phase 1 Agent Invocations ([a4e3b10](https://github.com/dtlab-tech/ai-toolkit/commit/a4e3b1001bb0b6201c873c4608c23c94c37ca5e0))
|
|
45
|
+
* **FTR-013:** implement US-02 — Track Phase 1 Agent Invocations ([85d86e4](https://github.com/dtlab-tech/ai-toolkit/commit/85d86e406d9ace96b22214de5aef14a6b0cde0b2))
|
|
46
|
+
* **FTR-013:** implement US-03 — Track Phase 2 Agent Invocations ([819f3c3](https://github.com/dtlab-tech/ai-toolkit/commit/819f3c33a954ce89bb221461ee21092b9429c085))
|
|
47
|
+
* **FTR-013:** implement US-03 — Track Phase 2 Agent Invocations ([7d6f9bb](https://github.com/dtlab-tech/ai-toolkit/commit/7d6f9bba8a794e83a12b31a306e27a5d8505c3f7))
|
|
48
|
+
* **FTR-013:** Ledger Pipeline Activity Tracker ([f1bf49a](https://github.com/dtlab-tech/ai-toolkit/commit/f1bf49a4c7496fba8f0c04c5770fe2ad08a6b4ac))
|
|
49
|
+
* **pm:** add EUR cost columns to Token-Estimate ([b0faf0d](https://github.com/dtlab-tech/ai-toolkit/commit/b0faf0dc15c40358bf64b55a0a34987be8db19a2))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* **FTR-011:** remediate review issues ([c34d824](https://github.com/dtlab-tech/ai-toolkit/commit/c34d8246fff5548401cb50cc300043679ede4bba))
|
|
55
|
+
* **FTR-012:** remediate review issues ([f9ea280](https://github.com/dtlab-tech/ai-toolkit/commit/f9ea280c394a6436fa6a0840f0110b1b620b6025))
|
|
56
|
+
* **pm:** commit actuals after write-actuals; restore zero-delta ledger entries on resume ([83bbaec](https://github.com/dtlab-tech/ai-toolkit/commit/83bbaecdbbbbf9aa436af8d56a6e823500868b1b))
|
|
57
|
+
* **pm:** correct write-actuals token estimate output ([d45653a](https://github.com/dtlab-tech/ai-toolkit/commit/d45653a2c9f999ec8478836e01128428295fa95a))
|
|
58
|
+
|
|
2
59
|
## [0.7.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.6.0...v0.7.0) (2026-07-29)
|
|
3
60
|
|
|
4
61
|
|