@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.
@@ -96,6 +96,41 @@ Ask the user: **"What is the name of this feature?"** (short, descriptive, in th
96
96
 
97
97
  Example: "Supplier Onboarding" → `FTR-003-supplier-onboarding`
98
98
 
99
+ ### 1c. Create feature directory and initialize ledger
100
+
101
+ Once you know the PREFIX (from 1a) and the feature slug (from 1b), do the following **before asking any grilling questions**:
102
+
103
+ 1. Compute the paths:
104
+ - `featureDir` = `docs/features/{PREFIX}-{slug}` (e.g. `docs/features/FTR-003-supplier-onboarding`)
105
+ - `ledgerPath` = `{featureDir}/{PREFIX}-token-ledger.json`
106
+
107
+ 2. Create the feature directory via Bash:
108
+ ```bash
109
+ mkdir -p {featureDir}
110
+ ```
111
+
112
+ 3. Capture the current UTC timestamp via Bash:
113
+ ```bash
114
+ started_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ") && echo $started_at
115
+ ```
116
+
117
+ 4. Write the initial ledger file at `{ledgerPath}` using the Write tool with exactly this content (substitute the actual PREFIX and timestamp):
118
+ ```json
119
+ [
120
+ {
121
+ "agent": "define-feature:define",
122
+ "phase": "define",
123
+ "model": "sonnet",
124
+ "status": "running",
125
+ "phase_delta_tokens": 0,
126
+ "started_at": "<started_at from step 3>",
127
+ "completed_at": null
128
+ }
129
+ ]
130
+ ```
131
+
132
+ The ledger records that the define-feature agent is now running. Proceed to Phase 2.
133
+
99
134
  ---
100
135
 
101
136
  ## Phase 2 — Grilling (only the gaps)
@@ -224,7 +259,7 @@ docs/features/{PREFIX}-{slug}/feature.md
224
259
 
225
260
  Example: `docs/features/FTR-003-supplier-onboarding/feature.md`
226
261
 
227
- Create the directory if it doesn't exist.
262
+ The directory was created in Phase 1c. If for any reason it doesn't exist, create it now.
228
263
 
229
264
  ### feature.md template
230
265
 
@@ -315,6 +350,37 @@ For each entity: name, key fields (name, type, constraints), relationships.
315
350
  - Acceptance criteria must be **testable** (Given/When/Then) — for technical features, express them as observable/verifiable checks (e.g. "grep returns nothing", "build passes", "output file exists")
316
351
  - Be concrete and specific — avoid vague statements like "the system should be fast"
317
352
 
353
+ ### 4b. Finalize ledger entry
354
+
355
+ After writing `feature.md`, update the ledger entry to record successful completion:
356
+
357
+ 1. Capture the current UTC timestamp via Bash:
358
+ ```bash
359
+ completed_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ") && echo $completed_at
360
+ ```
361
+
362
+ 2. Read the ledger file at `{featureDir}/{PREFIX}-token-ledger.json` using the Read tool.
363
+
364
+ 3. Write the updated ledger file using the Write tool, replacing the entry's fields:
365
+ - `"status"`: `"done"`
366
+ - `"completed_at"`: the timestamp captured in step 1
367
+ - `"phase_delta_tokens"`: leave as `0` — token measurement is not available to the agent directly
368
+
369
+ Example final ledger content:
370
+ ```json
371
+ [
372
+ {
373
+ "agent": "define-feature:define",
374
+ "phase": "define",
375
+ "model": "sonnet",
376
+ "status": "done",
377
+ "phase_delta_tokens": 0,
378
+ "started_at": "<original started_at>",
379
+ "completed_at": "<completed_at from step 1>"
380
+ }
381
+ ]
382
+ ```
383
+
318
384
  ---
319
385
 
320
386
  ## Phase 5 — Confirm and handoff
@@ -78,7 +78,7 @@ Follow all conventions EXACTLY.
78
78
  4. **Create test project/file** if it doesn't exist (following project conventions)
79
79
  5. **Write test helpers** (custom factories, builders, mocks) if needed
80
80
  6. **Implement tests** following AAA pattern
81
- 7. **Verify** by running the test command from AGENTS.md
81
+ 7. **Syntax check only (optional)** — you MAY run a build to confirm the test files compile (e.g. `dotnet build`, `npx tsc --noEmit`). **Do NOT run the test suite** (`dotnet test`, `npm test`, etc.) — the orchestrator runs the full suite once, after all agents finish. Running it here duplicates a multi-minute build per agent and contends for build artifacts.
82
82
 
83
83
  ---
84
84
 
@@ -102,13 +102,13 @@ When all assigned tasks are complete, return a **structured summary** to the orc
102
102
  Tasks completed: [list]
103
103
  Files created: [list with path only]
104
104
  Files modified: [list with path only]
105
- Test result: ✅ N/N passed / ❌ N failed — [failing test names if any]
105
+ Build check: ✅ compiles / ❌ compile error — [first error line if any] / ⏭️ not run
106
106
  Notes: [blocking issues, skipped cases, TODO items]
107
107
  ─────────────────────────────────────────────
108
108
  ```
109
109
 
110
110
  - **Never dump test code** into the summary
111
- - If tests FAILED, include only the failing test names and first error line
111
+ - Do NOT report test-run results — you do not run the suite; the orchestrator runs it once after all agents finish. Report only whether the test files compile.
112
112
 
113
113
  ---
114
114
 
@@ -119,4 +119,4 @@ Notes: [blocking issues, skipped cases, TODO items]
119
119
  - **No testing of trivial code** — getters/setters, data classes, config files
120
120
  - **Mock external dependencies** — databases, HTTP clients, file systems
121
121
  - **Use the project's preferred mocking approach** — as defined in AGENTS.md
122
- - **After implementation**: run the test command to verify all tests pass
122
+ - **Do NOT run the test suite** — the orchestrator runs the full suite once, after every agent has finished writing its files. A build for a quick syntax check is fine; running the tests here wastes minutes on a duplicate build and can lock build artifacts.
@@ -251,6 +251,39 @@ COMPACT_SECTION
251
251
 
252
252
  ---
253
253
 
254
+ ## Step 6 — Bash Permission Allowlist opt-in
255
+
256
+ After the Compact Instructions step, offer an opt-in to create or update `.claude/settings.local.json` in the destination project with a pre-approved Bash command allowlist.
257
+
258
+ 1. **Offer the opt-in** — inform the user and ask:
259
+
260
+ > "Would you like to create or update `.claude/settings.local.json` in this project with a pre-approved Bash command allowlist? This lets pm-phase3 worker agents run read-only commands (ls, git status, git log, npm test, dotnet build, etc.) without prompting you. Dangerous commands (git push, gh pr create, rm, git reset, git clean) will always require confirmation."
261
+ >
262
+ > Options: "Yes — write allowlist" / "No — skip"
263
+
264
+ 2. If the user selects **No**: skip silently; set `allowlist_status = "skipped (user said No)"`.
265
+
266
+ 3. If the user selects **Yes**:
267
+
268
+ a. **Invoke the merge CLI** from the toolkit root directory:
269
+ ```bash
270
+ node bin/cli.js merge-allowlist "{dest}"
271
+ ```
272
+ Capture stdout and the exit code.
273
+ - If exit code is non-zero: set `allowlist_status = "failed — see above"` and continue to Step 7 (allowlist is advisory, not blocking).
274
+ - If exit code is 0: the stdout line will be one of:
275
+ - `Allowlist: written` → set `allowlist_status = "written"`
276
+ - `Allowlist: merged (N rules preserved)` → set `allowlist_status = "merged (N rules preserved)"`
277
+ - `Allowlist: reset` → set `allowlist_status = "reset (file was malformed)"`
278
+
279
+ b. **Check and update `.gitignore`**:
280
+ ```bash
281
+ node bin/cli.js update-gitignore "{dest}"
282
+ ```
283
+ This ensures `.claude/settings.local.json` is gitignored in the destination project. The CLI is idempotent — it appends the line only if it is not already present.
284
+
285
+ ---
286
+
254
287
  ## Step 7 — Report
255
288
 
256
289
  ```
@@ -262,6 +295,7 @@ Modified/kept: N (user chose to keep existing version)
262
295
  Unchanged (same): N (skipped silently)
263
296
  Spawn depth: ✅ CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=2+ present / ⚠️ MISSING — see action above
264
297
  Compact instructions: {compact_instructions_status}
298
+ Allowlist: {allowlist_status}
265
299
  ──────────────────────────────────────────────────
266
300
  Next steps:
267
301
  1. Run /init-agents to generate AGENTS.md for this project
@@ -9,6 +9,26 @@ export const meta = {
9
9
  ],
10
10
  }
11
11
 
12
+ // ── Ledger helper functions ───────────────────────────────────────────────────
13
+
14
+ async function appendLedgerEntry(featureDir, prefix, entry) {
15
+ const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
16
+ const entryJson = JSON.stringify(entry)
17
+ await agent(
18
+ `Append a JSON object to the ledger array at: ${ledgerPath}\n\n1. Read the file. If it does not exist or cannot be parsed as a JSON array, start with [].\n2. Push this object onto the array: ${entryJson}\n3. Write the full array back (JSON, 2-space indent). Return no output.`,
19
+ { label: 'append-ledger', phase: 'Requirements', model: 'haiku' }
20
+ )
21
+ }
22
+
23
+ async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
24
+ const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
25
+ const updatesJson = JSON.stringify(updates)
26
+ await agent(
27
+ `Update an entry in the ledger array at: ${ledgerPath}\n\n1. Read the file. If it does not exist or cannot be parsed as a JSON array, do nothing.\n2. Search from the end for the last entry where agent === "${agentKey}".\n3. If found, merge these fields into that entry: ${updatesJson}\n4. Write the full array back (JSON, 2-space indent). Return no output.\n5. If not found, do nothing.`,
28
+ { label: 'update-ledger', phase: 'Requirements', model: 'haiku' }
29
+ )
30
+ }
31
+
12
32
  // ── Parse args ────────────────────────────────────────────────────────────────
13
33
  // args is the raw prompt string: "<path-to-feature.md> [--force]"
14
34
  const featurePath = args.split(/\s+/)[0]
@@ -70,6 +90,16 @@ const { prefix, feature_dir } = discoveryResult
70
90
  log(`Prefix: ${prefix} | Dir: ${feature_dir}`)
71
91
  log(`needs_requirements=${discoveryResult.needs_requirements} needs_tech_spec=${discoveryResult.needs_tech_spec}`)
72
92
 
93
+ // ── Ensure ledger file exists (US-02-T02) ─────────────────────────────────────
94
+ // If define-feature was not used, the ledger file will not exist yet.
95
+ // Touch it with an empty array so appendLedgerEntry can always assume a valid base.
96
+ const ledgerFilePath = `${feature_dir}/${prefix}-token-ledger.json`
97
+ await agent(
98
+ `Check whether the file ${ledgerFilePath} exists.\n\n1. Try to read the file using the Read tool.\n2. If the file does NOT exist: write it now using the Write tool with contents: []\n3. If the file already exists and is a valid JSON array: do nothing.\n4. If the file exists but is not valid JSON: overwrite it with: []\n5. Return no output.`,
99
+ { label: 'ensure-ledger', phase: 'Discovery', model: 'haiku' }
100
+ )
101
+ log(`Ledger ensured at ${ledgerFilePath}`)
102
+
73
103
  // ── generate-requirements ─────────────────────────────────────────────────────
74
104
  phase('Requirements')
75
105
 
@@ -77,9 +107,25 @@ const tokenLedger = []
77
107
  const errors = []
78
108
 
79
109
  if (discoveryResult.needs_requirements) {
110
+ const reqKey = 'generate-requirements:phase1'
111
+ const reqStartedAt = new Date().toISOString()
112
+ await appendLedgerEntry(feature_dir, prefix, {
113
+ agent: reqKey,
114
+ phase: 'phase1',
115
+ model: 'haiku',
116
+ status: 'running',
117
+ phase_delta_tokens: 0,
118
+ started_at: reqStartedAt,
119
+ completed_at: null,
120
+ })
80
121
  const beforeReq = budget.spent()
81
122
  const reqResult = await agent(featurePath, { agentType: 'generate-requirements', label: 'generate-requirements', phase: 'Requirements' })
82
123
  const reqTokens = budget.spent() - beforeReq
124
+ await updateLedgerEntry(feature_dir, prefix, reqKey, {
125
+ status: 'done',
126
+ completed_at: new Date().toISOString(),
127
+ phase_delta_tokens: reqTokens,
128
+ })
83
129
  tokenLedger.push({ agent: 'generate-requirements', model: 'haiku', phase_delta_tokens: reqTokens })
84
130
  log(`generate-requirements done — phase delta: ${reqTokens} tokens`)
85
131
  } else {
@@ -90,9 +136,25 @@ if (discoveryResult.needs_requirements) {
90
136
  phase('Tech-Spec')
91
137
 
92
138
  if (discoveryResult.needs_tech_spec) {
139
+ const specKey = 'generate-tech-spec:phase1'
140
+ const specStartedAt = new Date().toISOString()
141
+ await appendLedgerEntry(feature_dir, prefix, {
142
+ agent: specKey,
143
+ phase: 'phase1',
144
+ model: 'haiku',
145
+ status: 'running',
146
+ phase_delta_tokens: 0,
147
+ started_at: specStartedAt,
148
+ completed_at: null,
149
+ })
93
150
  const beforeSpec = budget.spent()
94
151
  const specResult = await agent(featurePath, { agentType: 'generate-tech-spec', label: 'generate-tech-spec', phase: 'Tech-Spec' })
95
152
  const specTokens = budget.spent() - beforeSpec
153
+ await updateLedgerEntry(feature_dir, prefix, specKey, {
154
+ status: 'done',
155
+ completed_at: new Date().toISOString(),
156
+ phase_delta_tokens: specTokens,
157
+ })
96
158
  tokenLedger.push({ agent: 'generate-tech-spec', model: 'haiku', phase_delta_tokens: specTokens })
97
159
  log(`generate-tech-spec done — phase delta: ${specTokens} tokens`)
98
160
  } else {
@@ -108,9 +170,25 @@ const MAX_CYCLES = 3
108
170
 
109
171
  for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
110
172
  log(`validate-feature-docs cycle ${cycle}`)
173
+ const valKey = `validate-feature-docs:phase1:cycle${cycle}`
174
+ const valStartedAt = new Date().toISOString()
175
+ await appendLedgerEntry(feature_dir, prefix, {
176
+ agent: valKey,
177
+ phase: 'phase1',
178
+ model: 'haiku',
179
+ status: 'running',
180
+ phase_delta_tokens: 0,
181
+ started_at: valStartedAt,
182
+ completed_at: null,
183
+ })
111
184
  const beforeVal = budget.spent()
112
185
  const valResult = await agent(featurePath, { agentType: 'validate-feature-docs', label: `validate-feature-docs (cycle ${cycle})`, phase: 'Validation' })
113
186
  const valTokens = budget.spent() - beforeVal
187
+ await updateLedgerEntry(feature_dir, prefix, valKey, {
188
+ status: 'done',
189
+ completed_at: new Date().toISOString(),
190
+ phase_delta_tokens: valTokens,
191
+ })
114
192
  tokenLedger.push({ agent: `validate-feature-docs (cycle ${cycle})`, model: 'haiku', phase_delta_tokens: valTokens })
115
193
 
116
194
  // valResult is the agent's text output — check for gap indicators
@@ -7,15 +7,54 @@ export const meta = {
7
7
  ],
8
8
  }
9
9
 
10
+ // ── Ledger helper functions ───────────────────────────────────────────────────
11
+ // Route all ledger I/O through agent() — fs is not available in the workflow runtime.
12
+ // Mirrors appendLedgerEntry / updateLedgerEntry from bin/cli.js (same contract).
13
+
14
+ async function appendLedgerEntry(featureDir, prefix, entry) {
15
+ const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
16
+ const entryJson = JSON.stringify(entry)
17
+ await agent(
18
+ `Append a JSON object to the ledger array at: ${ledgerPath}\n\n1. Read the file. If it does not exist or cannot be parsed as a JSON array, start with [].\n2. Push this object onto the array: ${entryJson}\n3. Write the full array back (JSON, 2-space indent). Return no output.`,
19
+ { label: 'append-ledger', phase: 'Work Breakdown', model: 'haiku' }
20
+ )
21
+ }
22
+
23
+ async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
24
+ const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
25
+ const updatesJson = JSON.stringify(updates)
26
+ await agent(
27
+ `Update an entry in the ledger array at: ${ledgerPath}\n\n1. Read the file. If it does not exist or cannot be parsed as a JSON array, do nothing.\n2. Search from the end for the last entry where agent === "${agentKey}".\n3. If found, merge these fields into that entry: ${updatesJson}\n4. Write the full array back (JSON, 2-space indent). Return no output.\n5. If not found, do nothing.`,
28
+ { label: 'update-ledger', phase: 'Work Breakdown', model: 'haiku' }
29
+ )
30
+ }
31
+
32
+ // ── Parse args ────────────────────────────────────────────────────────────────
33
+
10
34
  // args: "<path-to-feature.md>"
11
35
  const featurePath = (typeof args === 'string' ? args : '').trim().split(/\s+/)[0]
12
36
 
37
+ // Derive feature directory and prefix early (needed for ledger writes before metrics agent runs)
38
+ const featureDir = featurePath.replace(/\/[^/]+$/, '')
39
+ const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
40
+ const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
41
+
13
42
  // ── generate-work-breakdown ───────────────────────────────────────────────────
14
43
  phase('Work Breakdown')
15
44
 
16
45
  const tokenLedger = []
17
46
 
18
47
  log(`Running generate-work-breakdown for ${featurePath}`)
48
+ const wbStartedAt = new Date().toISOString()
49
+ await appendLedgerEntry(featureDir, prefix, {
50
+ agent: 'generate-work-breakdown:phase2',
51
+ phase: 'phase2',
52
+ model: 'haiku',
53
+ status: 'running',
54
+ phase_delta_tokens: 0,
55
+ started_at: wbStartedAt,
56
+ completed_at: null,
57
+ })
19
58
  const beforeWB = budget.spent()
20
59
  await agent(featurePath, {
21
60
  agentType: 'generate-work-breakdown',
@@ -23,6 +62,11 @@ await agent(featurePath, {
23
62
  phase: 'Work Breakdown',
24
63
  })
25
64
  const wbTokens = budget.spent() - beforeWB
65
+ await updateLedgerEntry(featureDir, prefix, 'generate-work-breakdown:phase2', {
66
+ status: 'done',
67
+ completed_at: new Date().toISOString(),
68
+ phase_delta_tokens: wbTokens,
69
+ })
26
70
  tokenLedger.push({ agent: 'generate-work-breakdown', model: 'haiku', phase_delta_tokens: wbTokens })
27
71
  log(`generate-work-breakdown done — phase delta: ${wbTokens} tokens`)
28
72
 
@@ -48,7 +92,34 @@ const PARSE_SCHEMA = {
48
92
  required: ['prefix', 'user_stories', 'total_tasks', 'work_breakdown_path'],
49
93
  }
50
94
 
51
- const phase1Tokens = wbTokens // tokens consumed by this phase so far
95
+ // ── Load token pricing ────────────────────────────────────────────────────────
96
+ // Pricing config is read inline (haiku agent) so it survives path-agnostic installs.
97
+ // Cost formula: tokens * (0.8 * input + 0.2 * output) / 1_000_000 * usd_to_eur
98
+ // (80/20 input/output split — no per-agent breakdown available in the ledger)
99
+ const pricingRaw = await agent(
100
+ `Read the file docs/token-pricing.json and return its raw JSON contents as a string, nothing else.`,
101
+ { label: 'read-pricing', phase: 'Effort Estimate', model: 'haiku' }
102
+ )
103
+ let pricing = null
104
+ try { pricing = JSON.parse(typeof pricingRaw === 'string' ? pricingRaw.trim() : '{}') } catch (_) {}
105
+
106
+ function tokenCostEur(tokens, model) {
107
+ if (!pricing || !pricing.models) return null
108
+ const m = pricing.models[model] || pricing.models['sonnet']
109
+ const usdPer1M = 0.8 * m.input_per_1m_usd + 0.2 * m.output_per_1m_usd
110
+ return (tokens * usdPer1M / 1_000_000) * (pricing.usd_to_eur || 1)
111
+ }
112
+
113
+ function formatEur(val) {
114
+ if (val === null || val === undefined) return '—'
115
+ return '€' + val.toFixed(4)
116
+ }
117
+
118
+ // NOTE: pm-phase2 is a separate workflow and does NOT receive pm-phase1's token ledger,
119
+ // so the real Phase 1 actuals (generate-requirements/tech-spec/validate) are unknown here.
120
+ // They are written as "—" placeholders below and filled later by the orchestrator (the
121
+ // implement-feature skill Step 7, which holds all three phase ledgers). Do NOT reuse wbTokens
122
+ // as a stand-in for Phase 1 — that mislabels Phase 2's tokens as Phase 1's.
52
123
 
53
124
  const metrics = await agent(
54
125
  `You have four tasks:
@@ -113,46 +184,52 @@ Write {feature_dir}/{PREFIX}-Token-Estimate.md with this format:
113
184
 
114
185
  ## Phase 1 — Documentation (Actuals)
115
186
 
116
- | Agent | Task | Model | Tokens Est. | Tokens Actual |
117
- |-------|------|-------|------------|--------------|
118
- | generate-requirements | Generate requirements from feature.md | haiku | — | {phase1_req_tokens} |
119
- | generate-tech-spec | Generate tech spec from feature.md | haiku | — | {phase1_spec_tokens} |
120
- | validate-feature-docs | Validate requirements + tech spec | haiku | — | {phase1_val_tokens} |
121
- | **Phase 1 total** | | | **—** | **${phase1Tokens}** |
187
+ Phase 1 ran in a separate workflow (pm-phase1); its per-agent token actuals are filled in
188
+ by the orchestrator after implementation. Leave the Tokens Actual cells as "—" here.
189
+
190
+ | Agent | Task | Model | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € |
191
+ |-------|------|-------|------------|------------|--------------|--------------|
192
+ | generate-requirements | Generate requirements from feature.md | haiku | — | — | — | — |
193
+ | generate-tech-spec | Generate tech spec from feature.md | haiku | — | — | — | — |
194
+ | validate-feature-docs | Validate requirements + tech spec | haiku | — | — | — | — |
195
+ | **Phase 1 total** | | | **—** | **—** | **—** | **—** |
122
196
 
123
197
  ## Phase 2 — Work Breakdown (Actuals)
124
198
 
125
- | Agent | Task | Model | Tokens Est. | Tokens Actual |
126
- |-------|------|-------|------------|--------------|
127
- | generate-work-breakdown | Generate work breakdown from docs | haiku | — | {wbTokens from above} |
128
- | **Phase 2 total** | | | **—** | **{wbTokens from above}** |
199
+ | Agent | Task | Model | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € |
200
+ |-------|------|-------|------------|------------|--------------|--------------|
201
+ | generate-work-breakdown | Generate work breakdown from docs | haiku | — | — | {wbTokens from above} | ${formatEur(tokenCostEur(wbTokens, 'haiku'))} |
202
+ | **Phase 2 total** | | | **—** | **—** | **{wbTokens from above}** | **${formatEur(tokenCostEur(wbTokens, 'haiku'))}** |
129
203
 
130
204
  ## Phase 3 — Implementation (Estimates)
131
205
 
132
206
  Estimates based on {total_tasks} tasks ({domain_breakdown}), {user_stories} User Stories.
133
207
  Baseline: ~15,000 tokens/BE task, ~8,000/TEST task, ~5,000/INFRA task.
134
208
 
135
- | Agent | Task | Model | Tokens Est. | Tokens Actual |
136
- |-------|------|-------|------------|--------------|
137
- | developer-backend | Implement BE/INFRA tasks | sonnet | {estimated: be_tasks * 15000 + infra_tasks * 5000} | — |
138
- | developer-testing | Implement TEST tasks | sonnet | {estimated: test_tasks * 8000} | — |
139
- | review-solution (×{user_stories}) | Architect review per US | sonnet | {estimated: user_stories * 8000} | — |
140
- | remediation | Fix review issues | sonnet | ~10,000 | — |
141
- | pr-and-registry | Push branch, create PR | sonnet | ~5,000 | — |
142
- | write-actuals | Update Token/Effort Estimate | sonnet | ~3,000 | — |
143
- | **Phase 3 total** | | | **{sum of phase 3 estimates}** | **—** |
209
+ | Agent | Task | Model | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € |
210
+ |-------|------|-------|------------|------------|--------------|--------------|
211
+ | developer-backend | Implement BE/INFRA tasks | sonnet | {estimated: be_tasks * 15000 + infra_tasks * 5000} | {cost for that estimate, sonnet} | — | — |
212
+ | developer-testing | Implement TEST tasks | sonnet | {estimated: test_tasks * 8000} | {cost for that estimate, sonnet} | — | — |
213
+ | review-solution (×{user_stories}) | Architect review per US | sonnet | {estimated: user_stories * 8000} | {cost for that estimate, sonnet} | — | — |
214
+ | remediation | Fix review issues | sonnet | ~10,000 | {cost for 10000, sonnet} | — | — |
215
+ | pr-and-registry | Push branch, create PR | sonnet | ~5,000 | {cost for 5000, sonnet} | — | — |
216
+ | write-actuals | Update Token/Effort Estimate | sonnet | ~3,000 | {cost for 3000, sonnet} | — | — |
217
+ | **Phase 3 total** | | | **{sum of phase 3 token estimates}** | **{sum of phase 3 cost estimates}** | **—** | **—** |
218
+
219
+ For the "Est. cost €" column in Phase 3: use the formula tokens * (0.8 * 3.00 + 0.2 * 15.00) / 1_000_000 * ${pricing ? pricing.usd_to_eur : 0.92} for sonnet rows. Round to 4 decimal places, prefix with €.
144
220
 
145
221
  ## Grand Total
146
222
 
147
- | Phase | Tokens Est. | Tokens Actual |
148
- |-------|------------|--------------|
149
- | Phase 1 — Documentation | — | ${phase1Tokens} |
150
- | Phase 2 — Work Breakdown | — | {wbTokens from above} |
151
- | Phase 3 — Implementation | {sum of phase 3 estimates} | — |
152
- | **Total** | **{sum of phase 3 estimates}** | **{phase1Tokens + wbTokens} (partial)** |
223
+ | Phase | Tokens Est. | Est. cost € | Tokens Actual | Actual cost € |
224
+ |-------|------------|------------|--------------|--------------|
225
+ | Phase 1 — Documentation | — | — | — (filled by orchestrator) | — |
226
+ | Phase 2 — Work Breakdown | — | — | {wbTokens from above} | ${formatEur(tokenCostEur(wbTokens, 'haiku'))} |
227
+ | Phase 3 — Implementation | {sum of phase 3 token estimates} | {sum of phase 3 cost estimates} | — | — |
228
+ | **Total** | **{sum of phase 3 token estimates}** | **{sum of phase 3 cost estimates}** | **{wbTokens} (partial)** | **${formatEur(tokenCostEur(wbTokens, 'haiku'))} (partial)** |
153
229
 
154
230
  ---
155
231
  *Actuals will be appended by pm-phase3 after implementation completes.*
232
+ *Cost assumes 80% input / 20% output token split. Pricing: sonnet $3.00/$15.00 per 1M, haiku $0.80/$4.00 per 1M (USD). Rate: $1 = €${pricing ? pricing.usd_to_eur : '0.92'} (${pricing ? pricing.usd_to_eur_date : 'see docs/token-pricing.json'}).*
156
233
 
157
234
  TASK 4 — Set return values.
158
235
  Set effort_estimate_path to the full path of the written Effort-Estimate.md.