@dtlabs/ai-toolkit 0.4.0 → 0.5.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.
@@ -1,10 +1,10 @@
1
1
  export const meta = {
2
2
  name: 'pm-phase3',
3
- description: 'Feature delivery phase 3: parse WB → dispatch specialist agents per US (developer-backend/frontend/testing + review-solution per US, max 2 rework cycles, git commit per US) → remediation → PR → Token-Estimate actuals → Effort-Estimate actuals.',
3
+ description: 'Feature delivery phase 3: parse WB → dispatch specialist agents per US (commit per phase) → single architect review → PR → actuals.',
4
4
  phases: [
5
- { title: 'Parse', detail: 'Parse Work Breakdown into structured implementation phases' },
6
- { title: 'Implementation', detail: 'Dispatch developer agents per US, review, commit' },
7
- { title: 'Remediation', detail: 'Fix OPEN issues from Issues Register' },
5
+ { title: 'Parse', detail: 'Parse Work Breakdown CSV into structured phases' },
6
+ { title: 'Implementation', detail: 'Dispatch developer agents per phase, commit each one' },
7
+ { title: 'Review', detail: 'Single architect review on full feature diff' },
8
8
  { title: 'PR', detail: 'Push branch, create PR, update registry' },
9
9
  { title: 'Actuals', detail: 'Write Token-Estimate and Effort-Estimate actuals' },
10
10
  ],
@@ -17,12 +17,10 @@ const branchMatch = argStr.match(/--branch\s+(\S+)/)
17
17
  const branch = branchMatch ? branchMatch[1] : null
18
18
 
19
19
  const tokenLedger = []
20
- const escalations = []
21
20
 
22
21
  // ── Parse Work Breakdown CSV (deterministic, no AI) ───────────────────────────
23
22
  phase('Parse')
24
23
 
25
- // Derive feature_dir and prefix from featurePath (e.g. "docs/features/FTR-004-.../feature.md")
26
24
  const featureDir = featurePath.replace(/\/[^/]+$/, '')
27
25
  const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
28
26
  const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
@@ -39,7 +37,7 @@ const csvContent = await agent(
39
37
  const rows = csvContent
40
38
  .split('\n')
41
39
  .map(l => l.trim())
42
- .filter(l => l && !l.startsWith('phase_id')) // skip header and empty lines
40
+ .filter(l => l && !l.startsWith('phase_id'))
43
41
  .map(l => {
44
42
  const [phase_id, phase_title, commit_message, depends_on, task_id, task_title, domain, agent_type] = l.split('|')
45
43
  return { phase_id, phase_title, commit_message, depends_on: depends_on || '', task_id, task_title, domain, agent_type }
@@ -54,7 +52,7 @@ for (const row of rows) {
54
52
  phase_id: row.phase_id,
55
53
  title: row.phase_title,
56
54
  commit_message: row.commit_message,
57
- depends_on: row.depends_on.split(' ').filter(Boolean), // [] for empty
55
+ depends_on: row.depends_on.split(' ').filter(Boolean),
58
56
  impl_tasks: [],
59
57
  test_tasks: [],
60
58
  })
@@ -87,11 +85,9 @@ const allPhases = Array.from(phaseMap.values()).map(p => ({
87
85
  }))
88
86
 
89
87
  const wb = { prefix, feature_dir: featureDir, phases: allPhases }
90
-
91
88
  log(`CSV parsed: ${rows.length} tasks → ${wb.phases.length} phases for ${wb.prefix}`)
92
89
 
93
- // Build execution waves: phases whose dependencies are all satisfied can run in parallel.
94
- // Wave 0 = no deps, Wave 1 = depends only on Wave 0 phases, etc.
90
+ // Build execution waves: phases whose dependencies are all satisfied run in parallel
95
91
  const buildWaves = (phases) => {
96
92
  const done = new Set()
97
93
  const waves = []
@@ -99,7 +95,6 @@ const buildWaves = (phases) => {
99
95
  while (remaining.length > 0) {
100
96
  const ready = remaining.filter(p => p.depends_on.every(d => done.has(d)))
101
97
  if (ready.length === 0) {
102
- // Circular or unresolvable deps — run the rest sequentially to avoid deadlock
103
98
  waves.push(remaining)
104
99
  break
105
100
  }
@@ -113,130 +108,71 @@ const buildWaves = (phases) => {
113
108
  const waves = buildWaves(wb.phases)
114
109
  log(`Execution plan: ${waves.length} wave(s) — ${waves.map((w, i) => `wave${i+1}:[${w.map(p => p.phase_id).join(',')}]`).join(' ')}`)
115
110
 
116
- let phasesDone = 0
117
- const usPassed = []
118
- const usEscalated = []
119
- let issuesPath = ''
120
- let issuesOpen = 0
111
+ let phasesDone = 0
112
+ const usPassed = []
121
113
 
122
114
  // ── Implementation ────────────────────────────────────────────────────────────
123
115
  phase('Implementation')
124
116
 
125
- const REVIEW_SCHEMA = {
126
- type: 'object',
127
- properties: {
128
- has_critical: { type: 'boolean' },
129
- findings: { type: 'string' },
130
- issues_md: { type: 'string' },
131
- },
132
- required: ['has_critical'],
133
- }
134
-
135
- // executePhase: runs impl_groups → test_groups → review → rework → commit for one phase
117
+ // executePhase: impl_groups (parallel) → test_groups (parallel) → commit
118
+ // No inline review — review runs once after all phases complete.
136
119
  const executePhase = async (implPhase) => {
137
120
  log(`Starting phase ${implPhase.phase_id}: ${implPhase.title}`)
138
121
 
139
- let reworkCycle = 0
140
- let passed = false
141
- let lastFindings = ''
142
-
143
- while (reworkCycle <= 2 && !passed) {
144
- const reworkSuffix = reworkCycle > 0
145
- ? ` -- REWORK CYCLE ${reworkCycle}: address these critical findings:\n${lastFindings}`
146
- : ''
147
-
148
- // Step 1 — impl groups in parallel (INFRA / BE / FE)
149
- if (implPhase.impl_groups.length > 0) {
150
- const beforeImpl = budget.spent()
151
- await parallel(implPhase.impl_groups.map(group => () =>
152
- agent(
153
- `${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
154
- {
155
- agentType: group.agent_type,
156
- label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
157
- phase: 'Implementation',
158
- }
159
- )
160
- ))
161
- const implTokens = budget.spent() - beforeImpl
162
- tokenLedger.push({
163
- agent: `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
164
- model: 'sonnet',
165
- phase_delta_tokens: implTokens,
166
- })
167
- log(`Phase ${implPhase.phase_id} impl groups done — ${implTokens} tokens`)
168
- }
169
-
170
- // Step 2 — test groups in parallel (TEST) — after impl groups
171
- if (implPhase.test_groups.length > 0) {
172
- const beforeTest = budget.spent()
173
- await parallel(implPhase.test_groups.map(group => () =>
174
- agent(
175
- `${featurePath} ${group.task_ids.join(',')}${reworkSuffix}`,
176
- {
177
- agentType: group.agent_type,
178
- label: `${group.agent_type}:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
179
- phase: 'Implementation',
180
- }
181
- )
182
- ))
183
- const testTokens = budget.spent() - beforeTest
184
- tokenLedger.push({
185
- agent: `developer-testing:${implPhase.phase_id}${reworkCycle > 0 ? ':rework' + reworkCycle : ''}`,
186
- model: 'sonnet',
187
- phase_delta_tokens: testTokens,
188
- })
189
- log(`Phase ${implPhase.phase_id} test groups done — ${testTokens} tokens`)
190
- }
191
-
192
- // Step 3 — review-solution
193
- const beforeReview = budget.spent()
194
- const review = await agent(
195
- `${featurePath} --scope ${implPhase.phase_id}`,
196
- {
197
- agentType: 'review-solution',
198
- label: `review-solution:${implPhase.phase_id}`,
199
- phase: 'Implementation',
200
- schema: REVIEW_SCHEMA,
201
- }
202
- )
203
- const reviewTokens = budget.spent() - beforeReview
122
+ // Step 1 — impl groups in parallel (INFRA / BE / FE)
123
+ if (implPhase.impl_groups.length > 0) {
124
+ const beforeImpl = budget.spent()
125
+ await parallel(implPhase.impl_groups.map(group => () =>
126
+ agent(
127
+ `${featurePath} ${group.task_ids.join(',')}`,
128
+ {
129
+ agentType: group.agent_type,
130
+ label: `${group.agent_type}:${implPhase.phase_id}`,
131
+ phase: 'Implementation',
132
+ }
133
+ )
134
+ ))
135
+ const implTokens = budget.spent() - beforeImpl
204
136
  tokenLedger.push({
205
- agent: `review-solution:${implPhase.phase_id}`,
206
- model: 'sonnet',
207
- phase_delta_tokens: reviewTokens,
137
+ agent: `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}`,
138
+ model: 'sonnet',
139
+ phase_delta_tokens: implTokens,
208
140
  })
209
-
210
- if (review && !review.has_critical) {
211
- passed = true
212
- if (review.issues_md) {
213
- await agent(
214
- `Append the following WARNING/INFO findings to ${wb.feature_dir}/${prefix}-Issues.md (create the file if it does not exist, with a header "# Issues Register — ${prefix}"):\n\n${review.issues_md}`,
215
- { label: `log-issues:${implPhase.phase_id}`, phase: 'Implementation' }
216
- )
217
- }
218
- } else {
219
- lastFindings = review?.findings || 'unspecified critical issues'
220
- reworkCycle++
221
- log(`Phase ${implPhase.phase_id}: critical findings — rework cycle ${reworkCycle}`)
222
- }
141
+ log(`Phase ${implPhase.phase_id} impl done — ${implTokens} tokens`)
223
142
  }
224
143
 
225
- if (!passed) {
226
- log(`Phase ${implPhase.phase_id} ESCALATED after 2 rework cycles`)
227
- escalations.push({ phaseId: implPhase.phase_id, reason: 'CRITICAL review findings unresolved after 2 rework cycles' })
228
- usEscalated.push(implPhase.phase_id)
229
- } else {
230
- const commitMsg = implPhase.commit_message || `feat(${prefix}): implement ${implPhase.phase_id}`
231
- await agent(
232
- `Run these git commands in the repository root:\ngit add -A\ngit commit -m "${commitMsg}"\nIf there is nothing to commit, that is fine — just report success.`,
233
- { label: `commit:${implPhase.phase_id}`, phase: 'Implementation' }
234
- )
235
- if (implPhase.phase_id !== 'INFRA') usPassed.push(implPhase.phase_id)
236
- log(`Phase ${implPhase.phase_id} passed review and committed`)
144
+ // Step 2 — test groups in parallel (TEST)
145
+ if (implPhase.test_groups.length > 0) {
146
+ const beforeTest = budget.spent()
147
+ await parallel(implPhase.test_groups.map(group => () =>
148
+ agent(
149
+ `${featurePath} ${group.task_ids.join(',')}`,
150
+ {
151
+ agentType: group.agent_type,
152
+ label: `developer-testing:${implPhase.phase_id}`,
153
+ phase: 'Implementation',
154
+ }
155
+ )
156
+ ))
157
+ const testTokens = budget.spent() - beforeTest
158
+ tokenLedger.push({
159
+ agent: `developer-testing:${implPhase.phase_id}`,
160
+ model: 'sonnet',
161
+ phase_delta_tokens: testTokens,
162
+ })
163
+ log(`Phase ${implPhase.phase_id} tests done — ${testTokens} tokens`)
237
164
  }
238
165
 
166
+ // Step 3 — commit this phase
167
+ const commitMsg = implPhase.commit_message || `feat(${prefix}): implement ${implPhase.phase_id}`
168
+ await agent(
169
+ `Run these git commands in the repository root:\ngit add -A\ngit commit -m "${commitMsg}"\nIf there is nothing to commit, that is fine — just report success.`,
170
+ { label: `commit:${implPhase.phase_id}`, phase: 'Implementation', model: 'haiku' }
171
+ )
172
+
173
+ if (implPhase.phase_id !== 'INFRA') usPassed.push(implPhase.phase_id)
239
174
  phasesDone++
175
+ log(`Phase ${implPhase.phase_id} committed`)
240
176
  }
241
177
 
242
178
  // Execute waves: phases in the same wave run in parallel, waves are sequential
@@ -249,69 +185,57 @@ for (const wave of waves) {
249
185
  }
250
186
  }
251
187
 
252
- // Count open issues
253
- const ISSUES_SCHEMA = {
254
- type: 'object',
255
- properties: {
256
- exists: { type: 'boolean' },
257
- open_count: { type: 'number' },
258
- path: { type: 'string' },
259
- },
260
- required: ['exists', 'open_count'],
261
- }
262
- const issuesResult = await agent(
263
- `Check if ${wb.feature_dir}/${prefix}-Issues.md exists. If it does, count rows with status OPEN. Return exists, open_count, and the full path.`,
264
- { label: 'count-open-issues', phase: 'Implementation', schema: ISSUES_SCHEMA }
265
- )
266
- issuesPath = issuesResult?.path || ''
267
- issuesOpen = issuesResult?.open_count || 0
268
- log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US passed, ${usEscalated.length} escalated, ${issuesOpen} open issues`)
188
+ log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US implemented`)
269
189
 
270
- // ── Remediation ───────────────────────────────────────────────────────────────
271
- phase('Remediation')
190
+ // ── Review — single pass over the full feature diff ───────────────────────────
191
+ phase('Review')
272
192
 
273
- const REM_SCHEMA = {
193
+ const REVIEW_SCHEMA = {
274
194
  type: 'object',
275
195
  properties: {
276
- issues_fixed: { type: 'number' },
277
- issues_deferred: { type: 'number' },
196
+ has_critical: { type: 'boolean' },
197
+ findings: { type: 'string' },
198
+ issues_md: { type: 'string' },
278
199
  },
279
- required: ['issues_fixed', 'issues_deferred'],
200
+ required: ['has_critical'],
280
201
  }
281
202
 
282
- let issuesFixed = 0
283
- let issuesDeferred = 0
284
-
285
- if (issuesOpen > 0 && issuesPath) {
286
- log(`Remediation: ${issuesOpen} OPEN issues`)
287
- const beforeRem = budget.spent()
288
-
289
- const remResult = await agent(
290
- `You are a remediation agent for the feature delivery pipeline.
291
-
292
- Read the Issues Register at: ${issuesPath}
293
-
294
- For each OPEN issue:
295
- - If severity is INFO: mark it DEFERRED immediately (update Status column to DEFERRED). Do not dispatch a developer agent.
296
- - If severity is WARNING: dispatch the appropriate developer agent (developer-backend for most cases) with the issue description. Then spawn review-solution to verify the fix. Max 1 retry per issue. If unresolved after 1 retry, mark DEFERRED.
297
-
298
- After processing all issues, return: { "issues_fixed": N, "issues_deferred": N }
203
+ const beforeReview = budget.spent()
204
+ const review = await agent(
205
+ `${featurePath} --scope all`,
206
+ {
207
+ agentType: 'review-solution',
208
+ label: 'review-solution:full-feature',
209
+ phase: 'Review',
210
+ schema: REVIEW_SCHEMA,
211
+ }
212
+ )
213
+ const reviewTokens = budget.spent() - beforeReview
214
+ tokenLedger.push({ agent: 'review-solution:full-feature', model: 'sonnet', phase_delta_tokens: reviewTokens })
299
215
 
300
- Update the Issues Register Status column in place for each resolved/deferred issue.`,
301
- {
302
- label: 'remediation',
303
- phase: 'Remediation',
304
- schema: REM_SCHEMA,
305
- }
306
- )
216
+ let issuesOpen = 0
217
+ let issuesPath = `${wb.feature_dir}/${prefix}-Issues.md`
218
+ let hasCritical = false
307
219
 
308
- const remTokens = budget.spent() - beforeRem
309
- tokenLedger.push({ agent: 'remediation', model: 'sonnet', phase_delta_tokens: remTokens })
310
- issuesFixed = remResult.issues_fixed || 0
311
- issuesDeferred = remResult.issues_deferred || 0
312
- log(`Remediation done — fixed: ${issuesFixed}, deferred: ${issuesDeferred} — ${remTokens} tokens`)
220
+ if (review) {
221
+ hasCritical = review.has_critical || false
222
+ if (review.issues_md) {
223
+ await agent(
224
+ `Write the following content to ${issuesPath} (create or overwrite the file):\n\n# Issues Register — ${prefix}\n\n${review.issues_md}`,
225
+ { label: 'write-issues', phase: 'Review', model: 'haiku' }
226
+ )
227
+ // Count OPEN issues from issues_md (rough count of "OPEN" occurrences)
228
+ issuesOpen = (review.issues_md.match(/\bOPEN\b/g) || []).length
229
+ log(`Review complete — has_critical: ${hasCritical}, open issues: ${issuesOpen}`)
230
+ } else {
231
+ log(`Review complete — no issues found`)
232
+ }
313
233
  } else {
314
- log('Remediation: no OPEN issues — skipped')
234
+ log(`Review agent returned null — skipping issues register`)
235
+ }
236
+
237
+ if (hasCritical) {
238
+ log(`WARNING: critical issues found — PR will be created but marked as blocked. Inspect ${prefix}-Issues.md before merging.`)
315
239
  }
316
240
 
317
241
  // ── PR ────────────────────────────────────────────────────────────────────────
@@ -334,39 +258,37 @@ const prResult = await agent(
334
258
  Feature path: ${featurePath}
335
259
  Branch: ${branch || 'current branch'}
336
260
  Prefix: ${prefix}
337
- Issues fixed: ${issuesFixed}, deferred: ${issuesDeferred}
261
+ Critical issues found: ${hasCritical}
262
+ Open issues: ${issuesOpen}
338
263
 
339
264
  Your tasks:
340
265
 
341
- 1. If there are remediation fixes, commit them:
342
- git add -A
343
- git commit -m "fix(${prefix}): remediate review issues"
344
-
345
- 2. Push the branch:
266
+ 1. Push the branch:
346
267
  git push -u origin ${branch || 'HEAD'}
347
268
 
348
- 3. Create a PR targeting develop:
269
+ 2. Create a PR targeting develop:
349
270
  gh pr create \\
350
271
  --title "feat(${prefix}): <Feature Title from feature.md>" \\
351
272
  --base develop \\
352
273
  --body "## Summary
353
- - Implements <Feature Title> (<N> User Stories, <N> tasks)
274
+ - Implements <Feature Title from feature.md> (${usPassed.length} User Stories, ${phasesDone} phases)
354
275
  - Source docs: ${prefix}-Requirements.md, ${prefix}-Tech-Spec.md
355
276
 
356
277
  ## Implementation
357
- - Architect review: all US passed
358
- - Issues Register: ${issuesFixed} fixed, ${issuesDeferred} deferred → see ${prefix}-Issues.md
278
+ - Phases implemented: ${usPassed.join(', ') || 'N/A'}
279
+ - Architect review: ${hasCritical ? '⚠️ CRITICAL issues found — see ' + prefix + '-Issues.md before merging' : '✅ No critical issues'}
280
+ - Open issues: ${issuesOpen} → see ${prefix}-Issues.md
359
281
 
360
282
  ## Test plan
361
283
  - [ ] Build passes
362
284
  - [ ] Tests pass
363
285
  - [ ] Manual smoke test of key flows"
364
286
 
365
- 4. Update the Feature Registry (internal_docs/features/REGISTRY.md):
366
- Find or append the entry for ${prefix}. Set Status to "completed".
287
+ 3. Update the Feature Registry (internal_docs/features/REGISTRY.md):
288
+ Find or append the entry for ${prefix}. Set Status to "${hasCritical ? 'review-required' : 'completed'}".
367
289
 
368
290
  Return { "pr_url": "<url>", "registry_updated": true/false }.
369
- If the PR creation fails, return { "pr_url": "(PR creation failed)", "registry_updated": false }.`,
291
+ If the PR already exists, return its URL. If creation fails, return { "pr_url": "(PR creation failed)", "registry_updated": false }.`,
370
292
  {
371
293
  label: 'pr-and-registry',
372
294
  phase: 'PR',
@@ -383,15 +305,6 @@ phase('Actuals')
383
305
 
384
306
  const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens || 0), 0)
385
307
 
386
- const ACTUALS_SCHEMA = {
387
- type: 'object',
388
- properties: {
389
- token_estimate_path: { type: 'string' },
390
- effort_estimate_path: { type: 'string' },
391
- },
392
- required: [],
393
- }
394
-
395
308
  const beforeActuals = budget.spent()
396
309
 
397
310
  await agent(
@@ -435,14 +348,17 @@ ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_
435
348
  | Metric | Value |
436
349
  |--------|-------|
437
350
  | Implementation phases done | ${phasesDone} |
438
- | US passed | ${usPassed.join(', ') || 'N/A'} |
439
- | US escalated | ${usEscalated.join(', ') || 'none'} |
351
+ | US implemented | ${usPassed.join(', ') || 'N/A'} |
352
+ | Critical issues | ${hasCritical} |
353
+ | Open issues | ${issuesOpen} |
440
354
 
441
355
  TASK 2 — Update actuals in ${prefix}-Effort-Estimate.md:
442
- 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
- - Phases in usPassed: ${usPassed.join(', ') || 'none'} — mark Actual Agent as completed (write the wall-clock duration if available, otherwise write "done (agent)")
444
- - Phases escalated: ${usEscalated.join(', ') || 'none'} — mark Actual Agent as "escalated"
445
- - Also append this summary section at the end of the file:
356
+ Find the file in the feature directory. Read the existing Per-Phase Breakdown table and update
357
+ the "Actual Agent" column for each phase that was implemented:
358
+ - Phases implemented (${usPassed.join(', ') || 'none'} + INFRA): write "done (agent)"
359
+ - Any phase not in the list: leave "—"
360
+
361
+ Also append this section at the end of the file:
446
362
 
447
363
  ---
448
364
 
@@ -451,14 +367,14 @@ Find the file in the feature directory. Read the existing Per-Phase Breakdown ta
451
367
  | Metric | Estimated | Actual |
452
368
  |--------|-----------|--------|
453
369
  | Implementation phases | ${phasesDone} | ${phasesDone} |
454
- | Issues fixed | — | ${issuesFixed} |
455
- | Issues deferred | — | ${issuesDeferred} |
370
+ | Critical review issues | — | ${hasCritical ? 'yes' : 'none'} |
371
+ | Open issues | — | ${issuesOpen} |
456
372
 
457
373
  Return { "token_estimate_path": "<path>", "effort_estimate_path": "<path>" }.`,
458
374
  {
459
375
  label: 'write-actuals',
460
376
  phase: 'Actuals',
461
- schema: ACTUALS_SCHEMA,
377
+ schema: { type: 'object', properties: { token_estimate_path: { type: 'string' }, effort_estimate_path: { type: 'string' } }, required: [] },
462
378
  }
463
379
  )
464
380
 
@@ -467,28 +383,22 @@ tokenLedger.push({ agent: 'write-actuals', model: 'sonnet', phase_delta_tokens:
467
383
  log(`Actuals written — ${actualsTokens} tokens`)
468
384
 
469
385
  // ── Append to process-log ─────────────────────────────────────────────────────
470
- const usPassed3 = usPassed.join(', ') || 'none'
471
- const usEscalated3 = usEscalated.join(', ') || 'none'
472
386
  const phase3Events = [
473
387
  `APPROVAL GRANTED by user — Gate 2`,
474
388
  `pm-phase3 START — implementation phase — branch: ${branch || '(current)'}`,
475
389
  `WB parsed: ${wb.phases.length} phases`,
476
- `Implementation DONE — ${phasesDone} phases | US passed: ${usPassed3} | US escalated: ${usEscalated3}`,
477
- issuesFixed + issuesDeferred > 0
478
- ? `Remediation: ${issuesFixed} fixed, ${issuesDeferred} deferred`
479
- : `Remediation: skipped (no open issues)`,
390
+ `Implementation DONE — ${phasesDone} phases implemented: ${usPassed.join(', ') || 'none'}`,
391
+ `Review DONE — critical: ${hasCritical}, open issues: ${issuesOpen}`,
480
392
  `PR created: ${prResult.pr_url}`,
481
393
  `Actuals written: Token-Estimate.md + Effort-Estimate.md`,
482
394
  `RUN COMPLETE`,
483
395
  `════════════════════════════════════════════════════════`,
484
396
  ].join('\n')
485
397
 
486
- const featureDir3 = featurePath.replace(/\/[^/]+$/, '')
487
-
488
398
  await agent(
489
399
  `Append phase 3 events to the process-log for this feature delivery run.
490
400
 
491
- Process-log path: ${featureDir3}/${prefix}-process-log.txt
401
+ Process-log path: ${featureDir}/${prefix}-process-log.txt
492
402
 
493
403
  Steps:
494
404
  1. Get current UTC datetime via Bash: run \`date -u +"%Y-%m-%dT%H:%M:%S"\`
@@ -498,16 +408,14 @@ Steps:
498
408
 
499
409
  Events to append:
500
410
  ${phase3Events}`,
501
- { label: 'finalize-process-log', phase: 'Actuals' }
411
+ { label: 'finalize-process-log', phase: 'Actuals', model: 'haiku' }
502
412
  )
503
413
 
504
414
  return {
505
415
  pr_url: prResult.pr_url,
506
416
  token_ledger: tokenLedger,
507
417
  issues_summary: {
508
- escalations: escalations.length,
509
- escalation_details: escalations,
510
- issues_fixed: issuesFixed,
511
- issues_deferred: issuesDeferred,
418
+ has_critical: hasCritical,
419
+ open_issues: issuesOpen,
512
420
  },
513
421
  }
package/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.5.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.4.0...v0.5.0) (2026-07-29)
3
+
4
+
5
+ ### Features
6
+
7
+ * **pm-phase3:** Option C review architecture + estimate format improvements ([b385b3e](https://github.com/dtlab-tech/ai-toolkit/commit/b385b3e35ef9121b8d9008223c77c88291c4173a))
8
+
2
9
  ## [0.4.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.3.1...v0.4.0) (2026-07-29)
3
10
 
4
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",