@dtlabs/ai-toolkit 0.6.0 → 0.8.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.
@@ -4,6 +4,7 @@ export const meta = {
4
4
  phases: [
5
5
  { title: 'Parse', detail: 'Parse Work Breakdown into structured implementation phases' },
6
6
  { title: 'Implementation', detail: 'Dispatch developer agents per US, review, commit' },
7
+ { title: 'Test', detail: 'Single test run after all phases complete' },
7
8
  { title: 'Remediation', detail: 'Fix OPEN issues from Issues Register' },
8
9
  { title: 'PR', detail: 'Push branch, create PR, update registry' },
9
10
  { title: 'Actuals', detail: 'Write Token-Estimate and Effort-Estimate actuals' },
@@ -237,6 +238,16 @@ const executePhase = async (implPhase) => {
237
238
  }
238
239
 
239
240
  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
+ )
240
251
  }
241
252
 
242
253
  // Execute waves: phases in the same wave run in parallel, waves are sequential
@@ -267,6 +278,28 @@ issuesPath = issuesResult?.path || ''
267
278
  issuesOpen = issuesResult?.open_count || 0
268
279
  log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US passed, ${usEscalated.length} escalated, ${issuesOpen} open issues`)
269
280
 
281
+ // ── Final test run ────────────────────────────────────────────────────────────
282
+ // Developer agents no longer run the test suite themselves (they do a syntax-check
283
+ // build at most). Running the suite once here, after all files are written, replaces
284
+ // N parallel per-agent test runs — cutting ~1h of duplicated builds and artifact
285
+ // contention down to a single centralized run with one consolidated failure report.
286
+ phase('Test')
287
+
288
+ const beforeTest = budget.spent()
289
+ await agent(
290
+ `Run the full test suite in the repository root and report the result.
291
+
292
+ 1. Read AGENTS.md in the repository root to find the exact test command for this project.
293
+ 2. Run that command (e.g. \`dotnet test --no-restore --logger "console;verbosity=normal"\`, \`npm test\`).
294
+ 3. Report the outcome: total passed/failed, and for any failures the test name and first error line.
295
+
296
+ Do NOT fix failing tests — only report them.`,
297
+ { label: 'final-test-run', phase: 'Test', model: 'haiku' }
298
+ )
299
+ const testTokens = budget.spent() - beforeTest
300
+ tokenLedger.push({ agent: 'final-test-run', model: 'haiku', phase_delta_tokens: testTokens })
301
+ log(`Test run complete — ${testTokens} tokens`)
302
+
270
303
  // ── Remediation ───────────────────────────────────────────────────────────────
271
304
  phase('Remediation')
272
305
 
@@ -381,8 +414,77 @@ log(`PR done: ${prResult.pr_url} — ${prTokens} tokens`)
381
414
  // ── Actuals ───────────────────────────────────────────────────────────────────
382
415
  phase('Actuals')
383
416
 
417
+ // Fallback: merge in any ledger entries that were persisted to disk but are missing
418
+ // from the in-memory ledger (e.g. this is a resumed run and earlier phases ran in a
419
+ // previous, interrupted invocation). The in-memory ledger has priority for entries
420
+ // present in both — it reflects the most recent measurements.
421
+ const persistedLedgerRaw = await agent(
422
+ `Read ${featureDir}/${prefix}-token-ledger.json and return its contents as a raw JSON string, nothing else. If the file does not exist, return exactly "[]".`,
423
+ { label: 'read-ledger', phase: 'Actuals', model: 'haiku' }
424
+ )
425
+ try {
426
+ const persisted = JSON.parse(typeof persistedLedgerRaw === 'string' ? persistedLedgerRaw.trim() : '[]')
427
+ if (Array.isArray(persisted)) {
428
+ const inMemoryByAgent = new Map(tokenLedger.map((e, i) => [e.agent, i]))
429
+ for (const entry of persisted) {
430
+ if (!entry || !entry.agent) continue
431
+ const idx = inMemoryByAgent.get(entry.agent)
432
+ if (idx === undefined) {
433
+ // entry exists on disk but not in memory — recover it (e.g. from a prior interrupted run)
434
+ tokenLedger.push(entry)
435
+ inMemoryByAgent.set(entry.agent, tokenLedger.length - 1)
436
+ log(`Recovered ledger entry from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
437
+ } else if (tokenLedger[idx].phase_delta_tokens === 0 && (entry.phase_delta_tokens || 0) > 0) {
438
+ // in-memory entry has delta=0 (cached agent on resume) but disk has real data — prefer disk
439
+ tokenLedger[idx] = entry
440
+ log(`Restored real token count from disk: ${entry.agent} (${entry.phase_delta_tokens} tokens)`)
441
+ }
442
+ }
443
+ }
444
+ } catch (e) {
445
+ log(`Could not parse persisted token ledger — using in-memory ledger only`)
446
+ }
447
+
384
448
  const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens || 0), 0)
385
449
 
450
+ // ── Load token pricing for cost columns ──────────────────────────────────────
451
+ const pricingRaw3 = await agent(
452
+ `Read the file docs/token-pricing.json and return its raw JSON contents as a string, nothing else.`,
453
+ { label: 'read-pricing', phase: 'Actuals', model: 'haiku' }
454
+ )
455
+ let pricing3 = null
456
+ try { pricing3 = JSON.parse(typeof pricingRaw3 === 'string' ? pricingRaw3.trim() : '{}') } catch (_) {}
457
+
458
+ function tokenCostEur3(tokens, model) {
459
+ if (!pricing3 || !pricing3.models) return null
460
+ const m = pricing3.models[model] || pricing3.models['sonnet']
461
+ const usdPer1M = 0.8 * m.input_per_1m_usd + 0.2 * m.output_per_1m_usd
462
+ return (tokens * usdPer1M / 1_000_000) * (pricing3.usd_to_eur || 1)
463
+ }
464
+
465
+ function formatEur3(val) {
466
+ if (val === null || val === undefined) return '—'
467
+ return '€' + val.toFixed(4)
468
+ }
469
+
470
+ // Aggregate the per-agent ledger into per-role totals so the actuals line up with the
471
+ // per-role estimate rows pm-phase2 wrote (developer-backend, developer-testing,
472
+ // review-solution, pr-and-registry, write-actuals, plus final-test-run). Precomputing
473
+ // here keeps the write-actuals agent from having to reason about grouping.
474
+ const roleTotals = {}
475
+ const roleModels = {}
476
+ for (const e of tokenLedger) {
477
+ const role = String(e.agent).split(':')[0]
478
+ roleTotals[role] = (roleTotals[role] || 0) + (e.phase_delta_tokens || 0)
479
+ roleModels[role] = e.model || 'sonnet'
480
+ }
481
+ const roleRows = Object.entries(roleTotals)
482
+ .map(([role, tok]) => `| ${role} | ${tok} | ${formatEur3(tokenCostEur3(tok, roleModels[role]))} |`)
483
+ .join('\n')
484
+ const totalPhase3CostEur = formatEur3(
485
+ Object.entries(roleTotals).reduce((s, [role, tok]) => s + (tokenCostEur3(tok, roleModels[role]) || 0), 0)
486
+ )
487
+
386
488
  const ACTUALS_SCHEMA = {
387
489
  type: 'object',
388
490
  properties: {
@@ -405,30 +507,50 @@ ${JSON.stringify(tokenLedger, null, 2)}
405
507
 
406
508
  Total tokens consumed by pm-phase3 workflow: ${totalPhase3Tokens} (exact, from budget tracking)
407
509
 
408
- TASK 1 — Append actuals to ${prefix}-Token-Estimate.md:
409
- Find the file in the feature directory. If it does not exist, create it.
510
+ TASK 1 — Fill in the Phase 3 actuals in ${prefix}-Token-Estimate.md IN PLACE.
511
+ Read the file in the feature directory first (it was written by pm-phase2 and already contains
512
+ Phase 1/Phase 2 actuals and the Phase 3 estimate table). If it does not exist, create it.
410
513
 
411
- Append this section at the end of the file:
514
+ Do NOT append a second Phase 3 table. Instead:
412
515
 
413
- ---
516
+ 1. Locate the existing "## Phase 3 — Implementation (Estimates)" section. Replace its heading
517
+ with "## Phase 3 — Implementation (Est. vs Actual, by role)" and REPLACE its table with the
518
+ per-role reconciliation below — filling the "Tokens Actual" and "Actual cost €" columns of the
519
+ existing per-role estimate rows (keep the existing Tokens Est. and Est. cost € values; add a Delta column):
520
+
521
+ | Role | Model | Tokens Actual | Actual cost € |
522
+ |------|-------|--------------|--------------|
523
+ ${roleRows}
524
+ | **Phase 3 total** | | **${totalPhase3Tokens}** | **${totalPhase3CostEur}** |
414
525
 
415
- ## Phase 3 — Implementation (Actuals)
526
+ Match each existing estimate row (developer-backend, developer-testing, review-solution,
527
+ remediation, pr-and-registry, write-actuals) to the actual role total above by name, write the
528
+ actual into its "Tokens Actual" and "Actual cost €" cells, and compute Delta = Actual − Est (tokens).
529
+ Roles present in the actuals but absent from the estimate rows (e.g. final-test-run) get a new row with Est. = —.
416
530
 
417
- > Phase totals are exact measurements from budget tracking.
418
- > Per-agent values are exact where each agent ran separately; proportional where merged.
531
+ 2. Immediately after that table, add the per-agent detail as a sub-table (source of truth):
419
532
 
420
- | Agent | Phase | Model | Tokens Est. | Tokens Actual |
421
- |-------|-------|-------|------------|--------------|
422
- ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_tokens} |`).join('\n')}
423
- | **Phase 3 total** | | | **—** | **${totalPhase3Tokens}** |
533
+ ### Phase 3 — per-agent detail (actuals)
424
534
 
425
- ## Grand Total (updated)
535
+ | Agent | Model | Tokens Actual | Actual cost € |
536
+ |-------|-------|--------------|--------------|
537
+ ${tokenLedger.map(e => `| ${e.agent} | ${e.model} | ${e.phase_delta_tokens} | ${formatEur3(tokenCostEur3(e.phase_delta_tokens, e.model))} |`).join('\n')}
538
+ | **Detail total** | | **${totalPhase3Tokens}** | **${totalPhase3CostEur}** |
426
539
 
427
- | Phase | Tokens Est. | Tokens Actual |
428
- |-------|------------|--------------|
429
- | Phase 1 — Documentation | — | *(see above)* |
430
- | Phase 2 — Work Breakdown | — | *(see above)* |
431
- | Phase 3 — Implementation | *(see above)* | ${totalPhase3Tokens} |
540
+ 3. Update the existing "## Grand Total" table. Read the Phase 1 and Phase 2 actual totals from
541
+ the top of THIS SAME file (the "Phase 1 total" and "Phase 2 total" rows already present) and
542
+ copy them verbatim — never the literal text "(see above)". If a value there is "—" (Phase 1 is
543
+ written as a placeholder by pm-phase2 and filled later by the orchestrator), keep it as "—";
544
+ do NOT invent a number. The table must read:
545
+
546
+ | Phase | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € | Delta (tokens) |
547
+ |-------|------------|------------|--------------|--------------|---------------|
548
+ | Phase 1 — Documentation | — | — | {Phase 1 total from this file, or — if placeholder} | — | — |
549
+ | Phase 2 — Work Breakdown | — | — | {Phase 2 total from this file} | {Phase 2 cost from this file} | — |
550
+ | Phase 3 — Implementation | {existing Phase 3 estimate total} | {existing Phase 3 est. cost} | ${totalPhase3Tokens} | ${totalPhase3CostEur} | {Actual − Est} |
551
+ | **Total** | **{sum of numeric Est}** | **{sum of numeric Est. cost}** | **{sum of numeric Actual}** | **{sum of numeric Actual cost}** | **{Actual − Est}** |
552
+
553
+ 4. Append (or update if present) the Implementation Summary section:
432
554
 
433
555
  ## Implementation Summary
434
556
 
@@ -438,6 +560,8 @@ ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_
438
560
  | US passed | ${usPassed.join(', ') || 'N/A'} |
439
561
  | US escalated | ${usEscalated.join(', ') || 'none'} |
440
562
 
563
+ Remove the stale "*Actuals will be appended by pm-phase3...*" footer line if it is still present.
564
+
441
565
  TASK 2 — Update actuals in ${prefix}-Effort-Estimate.md:
442
566
  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:
443
567
  - Phases in usPassed: ${usPassed.join(', ') || 'none'} — mark Actual Agent as completed (write the wall-clock duration if available, otherwise write "done (agent)")
@@ -501,6 +625,17 @@ ${phase3Events}`,
501
625
  { label: 'finalize-process-log', phase: 'Actuals' }
502
626
  )
503
627
 
628
+ await agent(
629
+ `Commit the actuals files for this feature delivery run.
630
+
631
+ Run these exact git commands in the repository root:
632
+ git add "${featureDir3}/${prefix}-Token-Estimate.md" "${featureDir3}/${prefix}-Effort-Estimate.md" "${featureDir3}/${prefix}-process-log.txt"
633
+ git commit -m "docs(${prefix}): add token/effort actuals and process log"
634
+
635
+ If there is nothing to commit (all files already committed), that is fine — report success.`,
636
+ { label: 'commit-actuals', phase: 'Actuals' }
637
+ )
638
+
504
639
  return {
505
640
  pr_url: prResult.pr_url,
506
641
  token_ledger: tokenLedger,
package/CHANGELOG.md CHANGED
@@ -1,4 +1,55 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.8.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.7.0...v0.8.0) (2026-08-04)
3
+
4
+
5
+ ### Features
6
+
7
+ * **FTR-011:** implement shared infrastructure (INFRA) ([718b0eb](https://github.com/dtlab-tech/ai-toolkit/commit/718b0ebe684814028b0457351a40b171ea80a197))
8
+ * **FTR-011:** implement US-01 — Implement readManifest() Function ([aad7de1](https://github.com/dtlab-tech/ai-toolkit/commit/aad7de19aa6aaf48578c46eebd3bb236ebb4fadb))
9
+ * **FTR-011:** implement US-02 — Implement computeOrphans() Function ([3a70517](https://github.com/dtlab-tech/ai-toolkit/commit/3a705173e649aa7782d036b5790c830c88a1d797))
10
+ * **FTR-011:** implement US-03 — Implement moveToTrash() Function ([a6ac7d2](https://github.com/dtlab-tech/ai-toolkit/commit/a6ac7d2d841905bba24f5349a7d763c9f43527e8))
11
+ * **FTR-011:** implement US-04 — Implement writeManifest() Function ([dc43ea1](https://github.com/dtlab-tech/ai-toolkit/commit/dc43ea1e35c2e4981876ad5f46b3138890851fcd))
12
+ * **FTR-011:** implement US-05 — Integrate Prune Phase into runInstall() and Add UI Display ([bca87ea](https://github.com/dtlab-tech/ai-toolkit/commit/bca87ea8f8843f6a8a07371eaef072b1fc004453))
13
+ * **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))
14
+ * **FTR-011:** Installer Manifest and Orphan Pruning ([764eff4](https://github.com/dtlab-tech/ai-toolkit/commit/764eff4dd3b13ee34e7e4be7159c08168e49fe1d))
15
+ * **FTR-011:** update effort and token estimates, add implementation summary, and log completion details ([ce5660d](https://github.com/dtlab-tech/ai-toolkit/commit/ce5660dec691b75bb501bcadb149c2f4eaabdb5a))
16
+ * **FTR-012:** implement shared infrastructure (INFRA) ([da14ae1](https://github.com/dtlab-tech/ai-toolkit/commit/da14ae15d19301c47231e73872ecba75f180e739))
17
+ * **FTR-012:** implement US-01 — Fresh Installation with Allowlist Opt-In ([803018a](https://github.com/dtlab-tech/ai-toolkit/commit/803018aafe77abb06390849540f7fb214fccbc40))
18
+ * **FTR-012:** implement US-02 — Merge Allowlist into Existing Settings ([9855abc](https://github.com/dtlab-tech/ai-toolkit/commit/9855abc940443d4e75b7a1b3340715d8e1403fcf))
19
+ * **FTR-012:** implement US-03 — Ask-Beats-Allow Conflict Resolution ([9c51984](https://github.com/dtlab-tech/ai-toolkit/commit/9c5198490824647bac7b1850b09e624d9f00e883))
20
+ * **FTR-012:** implement US-04 — Reinstall with Idempotent Merge ([8e9a49f](https://github.com/dtlab-tech/ai-toolkit/commit/8e9a49f69d7bb761a698b2ac7f7a405d5d2d833e))
21
+ * **FTR-012:** implement US-05 — .gitignore Creation and Idempotent Update ([20e19de](https://github.com/dtlab-tech/ai-toolkit/commit/20e19de3c38cbbfcd5dd27c8b8b5ab629036b101))
22
+ * **FTR-012:** Installer Bash Allowlist ([2deb73e](https://github.com/dtlab-tech/ai-toolkit/commit/2deb73ef4ad4189c6eea1ae46ec0f0dc6ece469e))
23
+ * **FTR-012:** mark feature as completed in registry ([ab90d58](https://github.com/dtlab-tech/ai-toolkit/commit/ab90d5878a4091ccaef38863c14634ebd2082623))
24
+ * **FTR-013:** add additional agent entries for US-03 and US-04 in ledger ([6f756ed](https://github.com/dtlab-tech/ai-toolkit/commit/6f756edfaa0d75b48809aa3964261efa65fd69f9))
25
+ * **FTR-013:** add registry entry and update ledger actuals ([23f5138](https://github.com/dtlab-tech/ai-toolkit/commit/23f513801486c36e95782ea513e15fcfcb755d06))
26
+ * **FTR-013:** implement shared infrastructure (INFRA) ([99d564f](https://github.com/dtlab-tech/ai-toolkit/commit/99d564f8ca543b7cd95c5cf1a5687961ef402921))
27
+ * **FTR-013:** implement US-01 — Initialize and Track Ledger in define-feature Agent ([ce0b010](https://github.com/dtlab-tech/ai-toolkit/commit/ce0b010afd40e94f9e9e2618eca1fbcc60ede7ae))
28
+ * **FTR-013:** implement US-01 — Initialize and Track Ledger in define-feature Agent ([ca84538](https://github.com/dtlab-tech/ai-toolkit/commit/ca84538d6980828313d331e4a07e10bde5cb34c7))
29
+ * **FTR-013:** implement US-02 — Track Phase 1 Agent Invocations ([a4e3b10](https://github.com/dtlab-tech/ai-toolkit/commit/a4e3b1001bb0b6201c873c4608c23c94c37ca5e0))
30
+ * **FTR-013:** implement US-02 — Track Phase 1 Agent Invocations ([85d86e4](https://github.com/dtlab-tech/ai-toolkit/commit/85d86e406d9ace96b22214de5aef14a6b0cde0b2))
31
+ * **FTR-013:** implement US-03 — Track Phase 2 Agent Invocations ([819f3c3](https://github.com/dtlab-tech/ai-toolkit/commit/819f3c33a954ce89bb221461ee21092b9429c085))
32
+ * **FTR-013:** implement US-03 — Track Phase 2 Agent Invocations ([7d6f9bb](https://github.com/dtlab-tech/ai-toolkit/commit/7d6f9bba8a794e83a12b31a306e27a5d8505c3f7))
33
+ * **FTR-013:** Ledger Pipeline Activity Tracker ([f1bf49a](https://github.com/dtlab-tech/ai-toolkit/commit/f1bf49a4c7496fba8f0c04c5770fe2ad08a6b4ac))
34
+ * **pm:** add EUR cost columns to Token-Estimate ([b0faf0d](https://github.com/dtlab-tech/ai-toolkit/commit/b0faf0dc15c40358bf64b55a0a34987be8db19a2))
35
+
36
+
37
+ ### Bug Fixes
38
+
39
+ * **FTR-011:** remediate review issues ([c34d824](https://github.com/dtlab-tech/ai-toolkit/commit/c34d8246fff5548401cb50cc300043679ede4bba))
40
+ * **FTR-012:** remediate review issues ([f9ea280](https://github.com/dtlab-tech/ai-toolkit/commit/f9ea280c394a6436fa6a0840f0110b1b620b6025))
41
+ * **pm:** commit actuals after write-actuals; restore zero-delta ledger entries on resume ([83bbaec](https://github.com/dtlab-tech/ai-toolkit/commit/83bbaecdbbbbf9aa436af8d56a6e823500868b1b))
42
+ * **pm:** correct write-actuals token estimate output ([d45653a](https://github.com/dtlab-tech/ai-toolkit/commit/d45653a2c9f999ec8478836e01128428295fa95a))
43
+
44
+ ## [0.7.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.6.0...v0.7.0) (2026-07-29)
45
+
46
+
47
+ ### Features
48
+
49
+ * **pipeline:** centralized test run + persistent token ledger + validation report fix + smarter define-feature ([c9fad6e](https://github.com/dtlab-tech/ai-toolkit/commit/c9fad6e4e5dfe25823df1d6534b4e597baa97001))
50
+ * **pm-phase3:** add centralized test run phase to streamline testing process ([972fac5](https://github.com/dtlab-tech/ai-toolkit/commit/972fac55d0754743859a7d75379a41acd49a890d))
51
+ * **pm-phase3:** persist token ledger to disk after every phase ([b6ad681](https://github.com/dtlab-tech/ai-toolkit/commit/b6ad6814770fae31cda60f25298dab9b2739eeaa))
52
+
2
53
  ## [0.6.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.5.0...v0.6.0) (2026-07-29)
3
54
 
4
55