@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.
@@ -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
- tokenLedger.push({
164
- agent: `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
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
- tokenLedger.push({
186
- agent: `developer-testing:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
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: `review-solution:${implPhase.phase_id}`,
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 seen = new Set(tokenLedger.map(e => e.agent))
462
+ const inMemoryByAgent = new Map(tokenLedger.map((e, i) => [e.agent, i]))
429
463
  for (const entry of persisted) {
430
- if (entry && entry.agent && !seen.has(entry.agent)) {
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
- seen.add(entry.agent)
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 — Append actuals to ${prefix}-Token-Estimate.md:
466
- Find the file in the feature directory. If it does not exist, create it.
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
- Append this section at the end of the file:
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
- ## Phase 3 — Implementation (Actuals)
566
+ 2. Immediately after that table, add the per-agent detail as a sub-table (source of truth):
473
567
 
474
- > Phase totals are exact measurements from budget tracking.
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 | Phase | Model | Tokens Est. | Tokens Actual |
478
- |-------|-------|-------|------------|--------------|
479
- ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_tokens} |`).join('\n')}
480
- | **Phase 3 total** | | | **—** | **${totalPhase3Tokens}** |
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 (updated)
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 | — | *(see above)* |
487
- | Phase 2 — Work Breakdown | — | *(see above)* |
488
- | Phase 3 — Implementation | *(see above)* | ${totalPhase3Tokens} |
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