@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.
- package/.claude/workflows/pm-phase3.js +128 -220
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
|
@@ -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 (
|
|
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
|
|
6
|
-
{ title: 'Implementation', detail: 'Dispatch developer agents per
|
|
7
|
-
{ title: '
|
|
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'))
|
|
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),
|
|
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
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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:
|
|
206
|
-
model:
|
|
207
|
-
phase_delta_tokens:
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
// ──
|
|
271
|
-
phase('
|
|
190
|
+
// ── Review — single pass over the full feature diff ───────────────────────────
|
|
191
|
+
phase('Review')
|
|
272
192
|
|
|
273
|
-
const
|
|
193
|
+
const REVIEW_SCHEMA = {
|
|
274
194
|
type: 'object',
|
|
275
195
|
properties: {
|
|
276
|
-
|
|
277
|
-
|
|
196
|
+
has_critical: { type: 'boolean' },
|
|
197
|
+
findings: { type: 'string' },
|
|
198
|
+
issues_md: { type: 'string' },
|
|
278
199
|
},
|
|
279
|
-
required: ['
|
|
200
|
+
required: ['has_critical'],
|
|
280
201
|
}
|
|
281
202
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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(
|
|
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
|
-
|
|
261
|
+
Critical issues found: ${hasCritical}
|
|
262
|
+
Open issues: ${issuesOpen}
|
|
338
263
|
|
|
339
264
|
Your tasks:
|
|
340
265
|
|
|
341
|
-
1.
|
|
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
|
-
|
|
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> (
|
|
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
|
-
-
|
|
358
|
-
-
|
|
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
|
-
|
|
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
|
|
439
|
-
|
|
|
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
|
|
443
|
-
|
|
444
|
-
- Phases
|
|
445
|
-
-
|
|
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
|
-
|
|
|
455
|
-
|
|
|
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:
|
|
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
|
|
477
|
-
|
|
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: ${
|
|
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
|
-
|
|
509
|
-
|
|
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
|
|