5-phase-workflow 3.0.0 → 3.1.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.
Files changed (56) hide show
  1. package/README.md +31 -101
  2. package/bin/install.js +168 -176
  3. package/bin/sync-agents.js +59 -54
  4. package/docs/workflow-guide.md +35 -79
  5. package/package.json +2 -2
  6. package/src/agents/step-executor-agent.md +5 -4
  7. package/src/agents/step-orchestrator-agent.md +12 -12
  8. package/src/agents/verification-agent.md +7 -8
  9. package/src/codex-commands/plan.md +65 -0
  10. package/src/commands/5/address-review-findings.md +43 -52
  11. package/src/commands/5/analyze-feature.md +1 -1
  12. package/src/commands/5/configure.md +75 -179
  13. package/src/commands/5/eject.md +0 -1
  14. package/src/commands/5/implement.md +78 -68
  15. package/src/commands/5/lean-check.md +75 -0
  16. package/src/commands/5/reconfigure.md +48 -129
  17. package/src/commands/5/review.md +42 -14
  18. package/src/commands/5/synchronize-agents.md +1 -1
  19. package/src/commands/5/update.md +2 -2
  20. package/src/hooks/dependency-guard.js +150 -0
  21. package/src/hooks/statusline.js +5 -3
  22. package/src/references/configure-tables.md +0 -33
  23. package/src/settings.json +3 -4
  24. package/src/skills/baseline-conventions/SKILL.md +37 -0
  25. package/src/skills/cognitive-load/SKILL.md +52 -0
  26. package/src/skills/configure-agents-md/SKILL.md +72 -0
  27. package/src/skills/configure-rules/SKILL.md +146 -0
  28. package/src/skills/documentation-style/SKILL.md +32 -0
  29. package/src/skills/fowler-code-smells/SKILL.md +25 -0
  30. package/src/skills/review-style/SKILL.md +142 -0
  31. package/src/templates/AGENTS.md +31 -31
  32. package/src/templates/workflow/REVIEW-FINDINGS.md +2 -9
  33. package/src/workflows/5-implement.js +67 -92
  34. package/docs/findings.md +0 -361
  35. package/docs/progress.md +0 -102
  36. package/src/commands/5/apply-review-findings.md +0 -66
  37. package/src/commands/5/commit.md +0 -43
  38. package/src/commands/5/plan.md +0 -189
  39. package/src/commands/5/reply-pr-comments.md +0 -48
  40. package/src/commands/5/split.md +0 -86
  41. package/src/commands/5/triage-pr-comments.md +0 -70
  42. package/src/hooks/config-guard.js +0 -30
  43. package/src/skills/configure-docs-index/SKILL.md +0 -243
  44. package/src/skills/configure-skills/SKILL.md +0 -311
  45. package/src/skills/generate-readme/EXAMPLES.md +0 -168
  46. package/src/skills/generate-readme/SKILL.md +0 -122
  47. package/src/skills/generate-readme/TEMPLATE.md +0 -141
  48. package/src/skills/use-index/SKILL.md +0 -49
  49. package/src/templates/ARCHITECTURE.md +0 -33
  50. package/src/templates/CONCERNS.md +0 -33
  51. package/src/templates/CONVENTIONS.md +0 -74
  52. package/src/templates/TESTING.md +0 -24
  53. package/src/templates/workflow/FIX-PLAN.md +0 -55
  54. package/src/templates/workflow/PLAN-COMPACT.md +0 -42
  55. package/src/templates/workflow/PLAN.md +0 -76
  56. package/src/templates/workflow/REVIEW-SUMMARY.md +0 -53
@@ -1,9 +1,9 @@
1
1
  export const meta = {
2
2
  name: '5-implement',
3
- description: 'Execute a unified plan: derive steps, run executors in parallel waves, verify. Claude Code only; Codex uses the prose loop.',
4
- whenToUse: 'Invoked by the /5:implement command when the Workflow tool is available. Reads args from the command, returns the run result for the command to persist to the slim state.json.',
3
+ description: 'Execute a full plan: derive steps, run executors in parallel waves, verify. Claude Code only; Codex uses the prose loop.',
4
+ whenToUse: 'Invoked by the /5:implement command for full plans when the Workflow tool is available. Compact plans never reach it; the command implements them inline.',
5
5
  phases: [
6
- { title: 'Orchestrate', detail: 'derive steps + components (agent for non-trivial plans, inline for compact)' },
6
+ { title: 'Orchestrate', detail: 'one orchestrator agent derives steps + components' },
7
7
  { title: 'Execute', detail: 'one executor agent per component; parallel components fire together per step' },
8
8
  { title: 'Verify', detail: 'inline when all passed + mechanical, else one verification agent' }
9
9
  ]
@@ -15,24 +15,24 @@ export const meta = {
15
15
  // the canonical human-readable contract; keep these prompts in sync with them.
16
16
  //
17
17
  // This script has NO filesystem access. It orchestrates agents and RETURNS the
18
- // final result. The /5:implement command (which has Write) persists the slim
19
- // state.json and runs auto-commit after this returns.
18
+ // final result. The /5:implement command runs the fast-path build/test check and
19
+ // auto-commit after this returns. Nothing is persisted: a rerun starts over.
20
20
  //
21
- // The execution graph (steps/components) is derived fresh on every run and is
22
- // NOT persisted. Resume works because component names are copied verbatim from
23
- // the plan's Component Checklist, so re-derivation is stable by name.
21
+ // Models: haiku for mechanical work, sonnet for ordinary logic, opus at effort
22
+ // medium for complex work and for every retry.
24
23
  // ─────────────────────────────────────────────────────────────────────────────
25
24
 
26
25
  const RESULT_SCHEMA = {
27
26
  type: 'object',
28
27
  additionalProperties: false,
29
- required: ['status', 'verify'],
28
+ required: ['status', 'verify', 'skipped'],
30
29
  properties: {
31
30
  status: { enum: ['success', 'failed'] },
32
31
  filesCreated: { type: 'array', items: { type: 'string' } },
33
32
  filesModified: { type: 'array', items: { type: 'string' } },
34
33
  verify: { enum: ['passed', 'failed', 'skipped'] },
35
34
  deviations: { type: 'string', description: 'none or a brief list' },
35
+ skipped: { type: 'string', description: 'none, or what was deliberately not built and when to add it' },
36
36
  error: { type: 'string', description: 'none or an error description' }
37
37
  }
38
38
  }
@@ -40,13 +40,13 @@ const RESULT_SCHEMA = {
40
40
  const VERIFICATION_SCHEMA = {
41
41
  type: 'object',
42
42
  additionalProperties: false,
43
- required: ['status', 'completeness', 'infrastructure', 'quality'],
43
+ required: ['status', 'completeness', 'infrastructure', 'scope'],
44
44
  properties: {
45
45
  status: { enum: ['passed', 'partial', 'failed'] },
46
46
  completeness: { enum: ['passed', 'partial', 'failed'] },
47
47
  infrastructure: { enum: ['passed', 'failed'] },
48
48
  acceptanceCriteria: { type: 'string', description: 'satisfied/total, e.g. "3/3"' },
49
- quality: { enum: ['passed', 'partial', 'failed'] },
49
+ scope: { enum: ['passed', 'drift'] },
50
50
  commands: {
51
51
  type: 'array',
52
52
  items: {
@@ -79,7 +79,7 @@ const STATE_SCHEMA = {
79
79
  number: { type: 'integer' },
80
80
  name: { type: 'string' },
81
81
  mode: { enum: ['parallel', 'sequential'] },
82
- model: { enum: ['haiku', 'sonnet'] },
82
+ model: { enum: ['haiku', 'sonnet', 'opus'] },
83
83
  components: { type: 'array', items: { type: 'string' } }
84
84
  }
85
85
  }
@@ -95,7 +95,7 @@ const STATE_SCHEMA = {
95
95
  action: { enum: ['create', 'modify', 'delete', 'rename'] },
96
96
  step: { type: 'integer' },
97
97
  mode: { enum: ['parallel', 'sequential'] },
98
- model: { enum: ['haiku', 'sonnet'] },
98
+ model: { enum: ['haiku', 'sonnet', 'opus'] },
99
99
  file: { type: 'string' },
100
100
  sourceFile: { type: ['string', 'null'] },
101
101
  description: { type: 'string' },
@@ -128,17 +128,15 @@ function orchestratorPrompt(a) {
128
128
 
129
129
  Feature: ${a.feature}
130
130
  Plan: ${a.paths.plan}
131
- Codebase scan: ${a.paths.scan} (read only if present)
132
131
  Config: ${a.paths.config} (read only if present)
133
132
 
134
- Turn the lean component checklist in the plan into steps + components:
135
- - COPY EVERY COMPONENT NAME VERBATIM from the plan's Component Checklist. Resume matches components by name across runs, so a renamed or reformatted name makes completed work run twice.
133
+ The plan has no fixed shape. Derive the components from whatever it describes: one component per target file (or tightly coupled file group), named in short kebab-case, then turn them into steps + components:
136
134
  - Group independent components into one step with mode "parallel"; use "sequential" only when components touch the same file, one imports another, or there is an explicit dependency.
137
135
  - Prefer fewer steps. Tests run after the components they validate.
138
- - model "haiku" by default (mechanical, UI, tests, docs, config, single-file). model "sonnet" only for complex logic, cross-module, security/auth, migrations, or public API.
139
- - 1-2 high-signal patternRefs per component with line ranges or symbols so executors avoid whole-file reads.
136
+ - model "haiku" for mechanical work (UI tweaks, tests, docs, config, simple single-file edits); "sonnet" for ordinary logic; "opus" only for complex logic, cross-module behavior, security/auth, data migrations, or public API contracts.
137
+ - 1-2 high-signal patternRefs per component with line ranges or symbols so executors avoid whole-file reads. Take them from the plan where it names patterns, else find them with Glob/Grep.
140
138
  - verifyCommands: narrowest relevant check first, then project build/test.
141
- - Exclude [DEFERRED] work. Represent every other component once. dependsOn must reference existing component names.
139
+ - Exclude work the plan defers or rules out. Represent every other component once. dependsOn must reference existing component names.
142
140
 
143
141
  Return an object matching the schema (steps[] and pendingComponents[]).`
144
142
  }
@@ -146,6 +144,9 @@ Return an object matching the schema (steps[] and pendingComponents[]).`
146
144
  function executorPrompt(c, a, opts) {
147
145
  const refs = (c.patternRefs || []).map(r => ` - ${r.file}${r.read ? ` (${r.read})` : ''}${r.reason ? ` — ${r.reason}` : ''}`).join('\n') || ' - none'
148
146
  const verify = (c.verifyCommands || []).join(' && ') || 'infer the narrowest relevant test/build command'
147
+ const standards = a.standards || {}
148
+ const repoStandards = (standards.repo || []).map(f => ` - ${f}`).join('\n') || ' - none found'
149
+ const styles = (standards.styles || []).map(f => ` - ${f}`).join('\n') || ' - none found'
149
150
  const retry = opts && opts.retry
150
151
  ? '\nThis is a RETRY of a failed attempt — read the error context carefully and correct the root cause.'
151
152
  : ''
@@ -156,23 +157,27 @@ Action: ${c.action}
156
157
  Target file: ${c.file}${c.sourceFile ? `\nSource file (rename from): ${c.sourceFile}` : ''}
157
158
  Intent: ${c.description || ''}
158
159
 
159
- Read only these pattern references (ranges/symbols), plus the target file for modify/rename:
160
+ Read only these pattern references (ranges/symbols), plus the target file for modify/rename — and for create when it already exists:
160
161
  ${refs}
161
162
 
162
- Rules: smallest coherent change; mirror existing naming/exports/layout/tests; follow any local skill or rule; add no new dependency; no abstraction or error handling beyond what the intent requires. Fix mechanical issues you cause (imports, types, lint). STOP and report failed for missing dependencies, unplanned auth/schema/API/contract changes, or unclear product decisions. If verify fails only from pre-existing unrelated issues, report that as a deviation with the exact evidence — do not mark the component failed. Do not make more than three attempts on the same failing issue.
163
+ Standards — read the parts that apply to your target and follow them. Repository standards win over the style skills.
164
+ Repository standards:
165
+ ${repoStandards}
166
+ Style skills:
167
+ ${styles}
168
+
169
+ Rules: Nothing is persisted between runs, so your work may already be done: for \`create\`, read the target first when it exists — if it already fulfils the intent, report success with no change; otherwise complete it, never overwrite it blindly. Smallest coherent change; reuse what already exists in this codebase before writing new code, then prefer stdlib, native platform features, and installed dependencies; touch only the target file and what it strictly needs (its test, an import site); mirror existing naming/exports/layout/tests; follow any local skill or rule; add no new dependency; no abstraction or error handling beyond what the intent requires. Fix mechanical issues you cause (imports, types, lint). STOP and report failed for missing dependencies, unplanned auth/schema/API/contract changes, or unclear product decisions. If verify fails only from pre-existing unrelated issues, report that as a deviation with the exact evidence — do not mark the component failed. Do not make more than three attempts on the same failing issue.
163
170
 
164
171
  Then run: ${verify}
165
172
 
166
- Return structured output: status, filesCreated, filesModified, verify, deviations (none or brief), error (none or description). Keep it concise — no logs or diffs unless failed.`
173
+ Return structured output: status, filesCreated, filesModified, verify, deviations (none or brief), skipped (none, or what you deliberately did not build — add when ...), error (none or description). Keep it concise — no logs or diffs unless failed.`
167
174
  }
168
175
 
169
- function verifyPrompt(a, results, resumeDone) {
176
+ function verifyPrompt(a, results) {
170
177
  const ran = results.flatMap(s => s.items.map(i =>
171
- ` step ${s.step} ${i.component.name}: ${i.result ? i.result.status : 'no result'} (verify ${i.result ? i.result.verify : '?'})${i.escalated ? ' [escalated to sonnet]' : ''}`))
172
- const prior = [...(resumeDone || [])]
173
- .filter(n => !results.some(s => s.items.some(i => i.component.name === n)))
174
- .map(n => ` ${n}: completed in a prior session`)
175
- const summary = [...ran, ...prior].join('\n') || ' (no components ran this invocation)'
178
+ ` step ${s.step} ${i.component.name}: ${i.result ? i.result.status : 'no result'} (verify ${i.result ? i.result.verify : '?'})${i.escalated ? ' [escalated to opus]' : ''}`))
179
+ const summary = ran.join('\n') || ' (no components ran)'
180
+ const repoStandards = ((a.standards && a.standards.repo) || []).join(', ') || 'none found'
176
181
  const baseline = (a.baseline && a.baseline.length)
177
182
  ? a.baseline.map(b => ` ${b.command || b.details && b.details.command || '?'}: ${b.status}${b.summary ? ` — ${b.summary}` : ''}`).join('\n')
178
183
  : ' (none recorded)'
@@ -181,6 +186,7 @@ function verifyPrompt(a, results, resumeDone) {
181
186
  Feature: ${a.feature}
182
187
  Plan: ${a.paths.plan}
183
188
  Config: ${a.paths.config} (read only if present)
189
+ Repository standards: ${repoStandards}
184
190
 
185
191
  Baseline (pre-change) command results — treat any failure listed here as PRE-EXISTING, not caused by this change:
186
192
  ${baseline}
@@ -188,40 +194,16 @@ ${baseline}
188
194
  Component results:
189
195
  ${summary}
190
196
 
191
- Checks: completeness (every planned component done, acceptance criteria addressed); files exist for create/modify, rename moved correctly, delete removed; build + test pass (reuse the baseline and component results above when they already prove status — rerun only the commands whose inputs changed, not identical passing ones); correctness (inspect changed files, not just existence); quality (logic-bearing changes have tests when a test framework exists). Prefer changed files over broad scanning.
197
+ Checks — only whether the plan was built and works; code quality and correctness belong to /5:review, which runs next: completeness (every planned component done, and any acceptance criteria the plan states addressed); files exist for create/modify, rename moved correctly, delete removed; build + test pass (reuse the baseline and component results above when they already prove status — rerun only the commands whose inputs changed, not identical passing ones); scope (run \`git status --short\` and \`git diff HEAD --stat\`, and read untracked files directly since \`git diff HEAD\` omits them: every changed file traces to a planned component — its target, its test, an import site, or a file a repository standard requires (such as a changelog entry); flag files outside the plan, work the plan rules out or defers, new dependencies no component requires, and unrequested abstractions — report drift only in scope; it does not change status). Prefer changed files over broad scanning.
192
198
 
193
- Return structured output: status, completeness, infrastructure, acceptanceCriteria ("satisfied/total"), quality, commands[], failures[].`
199
+ Return structured output: status, completeness, infrastructure, acceptanceCriteria ("satisfied/total"), scope, commands[], failures[] (one "drift: {path} — {why}" entry per drift finding).`
194
200
  }
195
201
 
196
202
  // ── Helpers ──────────────────────────────────────────────────────────────────
197
203
 
198
- // Build a single trivial step in JS for compact plans (no orchestrator agent).
199
- function compactSteps(components) {
200
- const norm = components.map((c, i) => ({
201
- name: c.name,
202
- action: c.action || 'modify',
203
- step: 1,
204
- mode: 'parallel',
205
- model: c.model || 'haiku',
206
- file: c.file,
207
- sourceFile: c.sourceFile || null,
208
- description: c.description || '',
209
- dependsOn: c.dependsOn || [],
210
- patternRefs: c.patternRefs || [],
211
- verifyCommands: c.verifyCommands || [],
212
- notes: []
213
- }))
214
- // If two components write the same file or one depends on another, fall back to sequential.
215
- const files = norm.map(c => c.file)
216
- const fileConflict = new Set(files).size !== files.length
217
- const hasDep = norm.some(c => c.dependsOn && c.dependsOn.length)
218
- const mode = (fileConflict || hasDep) ? 'sequential' : 'parallel'
219
- norm.forEach(c => { c.mode = mode })
220
- const stepModel = norm.some(c => c.model === 'sonnet') ? 'sonnet' : 'haiku'
221
- return {
222
- steps: [{ number: 1, name: 'implement', mode, model: stepModel, components: norm.map(c => c.name) }],
223
- pendingComponents: norm
224
- }
204
+ // Model options per tier. Opus runs at medium effort: deep enough for complex work, cheaper than high.
205
+ function modelOpts(model) {
206
+ return model === 'opus' ? { model: 'opus', effort: 'medium' } : { model: model || 'haiku' }
225
207
  }
226
208
 
227
209
  async function runStep(step, components, a) {
@@ -232,28 +214,28 @@ async function runStep(step, components, a) {
232
214
  if (step.mode === 'parallel' && stepComps.length > 1) {
233
215
  // Fire every parallel component together — true concurrency, one wave.
234
216
  waveResults = await parallel(stepComps.map(c => () =>
235
- agent(executorPrompt(c, a), { label: `exec:${c.name}`, phase: 'Execute', model: c.model || 'haiku', schema: RESULT_SCHEMA })
217
+ agent(executorPrompt(c, a), { label: `exec:${c.name}`, phase: 'Execute', ...modelOpts(c.model), schema: RESULT_SCHEMA })
236
218
  .then(result => ({ component: c, result }))
237
219
  .catch(() => ({ component: c, result: null }))))
238
220
  } else {
239
221
  waveResults = []
240
222
  for (const c of stepComps) {
241
- const result = await agent(executorPrompt(c, a), { label: `exec:${c.name}`, phase: 'Execute', model: c.model || 'haiku', schema: RESULT_SCHEMA })
223
+ const result = await agent(executorPrompt(c, a), { label: `exec:${c.name}`, phase: 'Execute', ...modelOpts(c.model), schema: RESULT_SCHEMA })
242
224
  .catch(() => null)
243
225
  waveResults.push({ component: c, result })
244
226
  }
245
227
  }
246
228
 
247
- // Retry failed OR crashed (null) components up to twice, escalating to sonnet.
229
+ // Retry failed OR crashed (null) components up to twice, escalating to opus.
248
230
  // A crashed agent (null result) is retried too — an infra error should not be
249
231
  // treated more harshly than a clean structured failure.
250
232
  for (const wr of waveResults) {
251
233
  let attempt = 1
252
234
  while ((wr.result == null || wr.result.status === 'failed') && attempt <= 2) {
253
- log(`retry ${wr.component.name} (attempt ${attempt + 1}) on sonnet`)
235
+ log(`retry ${wr.component.name} (attempt ${attempt + 1}) on opus`)
254
236
  wr.escalated = true
255
237
  const retried = await agent(executorPrompt(wr.component, a, { retry: true }),
256
- { label: `retry:${wr.component.name}`, phase: 'Execute', model: 'sonnet', schema: RESULT_SCHEMA }).catch(() => null)
238
+ { label: `retry:${wr.component.name}`, phase: 'Execute', ...modelOpts('opus'), schema: RESULT_SCHEMA }).catch(() => null)
257
239
  if (retried) wr.result = retried
258
240
  attempt++
259
241
  }
@@ -265,58 +247,50 @@ async function runStep(step, components, a) {
265
247
  // ── Main ─────────────────────────────────────────────────────────────────────
266
248
 
267
249
  const a = args || {}
268
- const resumeDone = new Set(a.completedComponents || [])
269
250
 
270
251
  phase('Orchestrate')
271
- if (resumeDone.size) log(`resume: ${resumeDone.size} component(s) already complete, matched by name`)
272
- let plan
273
- if (a.isCompact && Array.isArray(a.components) && a.components.length) {
274
- log(`compact plan: ${a.components.length} component(s), no orchestrator agent`)
275
- plan = compactSteps(a.components)
276
- } else {
277
- plan = await agent(orchestratorPrompt(a), { label: 'orchestrate', phase: 'Orchestrate', schema: STATE_SCHEMA })
278
- }
252
+ const plan = await agent(orchestratorPrompt(a), { label: 'orchestrate', phase: 'Orchestrate', schema: STATE_SCHEMA })
279
253
  const steps = plan.steps
280
254
  const components = plan.pendingComponents
281
255
 
282
256
  phase('Execute')
283
257
  const results = []
284
258
  for (const step of steps) {
285
- const remaining = components.filter(c => step.components.includes(c.name) && !resumeDone.has(c.name))
286
- if (!remaining.length) { log(`step ${step.number} already complete (resume), skipping`); continue }
287
- const stepResult = await runStep({ ...step, components: remaining.map(c => c.name) }, components, a)
288
- results.push(stepResult)
259
+ results.push(await runStep(step, components, a))
289
260
  }
290
261
 
291
262
  phase('Verify')
292
263
  const allItems = results.flatMap(s => s.items)
293
264
  const allPassed = allItems.length > 0 && allItems.every(i => i.result && i.result.status === 'success' && i.result.verify !== 'failed')
294
- // Non-mechanical when a component was planned as sonnet OR needed a sonnet retry to pass —
295
- // either signals reasoning-level work that warrants the full verification agent.
296
- const usedSonnet = components.some(c => (c.model || 'haiku') === 'sonnet') || allItems.some(i => i.escalated)
265
+ // Mechanical when every component was planned as haiku and none needed a retry; anything
266
+ // else signals reasoning-level work that warrants the full verification agent.
267
+ const usedOpus = components.some(c => c.model === 'opus') || allItems.some(i => i.escalated)
268
+ const mechanical = !usedOpus && components.every(c => (c.model || 'haiku') === 'haiku')
269
+ // Deterministic scope signal: files an executor touched beyond its planned target(s).
270
+ // Tests and import sites are legitimate, so this is reported for the user, never a failure.
271
+ const extraFiles = allItems.flatMap(i => {
272
+ const stripDot = f => f.replace(/^\.\//, '')
273
+ const planned = new Set([i.component.file, i.component.sourceFile].filter(Boolean).map(stripDot))
274
+ const touched = i.result ? [...(i.result.filesCreated || []), ...(i.result.filesModified || [])].map(stripDot) : []
275
+ return touched.filter(f => !planned.has(f)).map(f => ({ component: i.component.name, file: f }))
276
+ })
277
+ if (extraFiles.length) log(`scope: ${extraFiles.length} file(s) touched outside planned targets`)
297
278
  let verification
298
- if (allPassed && !usedSonnet) {
299
- log('all components passed + mechanical (no sonnet, no escalation) — inline verify, no verification agent')
300
- verification = { status: 'passed', completeness: 'passed', infrastructure: 'passed', acceptanceCriteria: 'n/a', quality: 'passed', commands: [], failures: [], inline: true }
279
+ if (allPassed && mechanical) {
280
+ log('all components passed + mechanical — no verification agent; /5:implement runs build/test and the scope check')
281
+ // No shell access here: /5:implement runs build/test and the scope check itself when it sees inline: true.
282
+ verification = { status: 'passed', completeness: 'passed', infrastructure: 'passed', acceptanceCriteria: 'n/a', commands: [], failures: [], inline: true }
301
283
  } else {
302
- if (!allItems.length) log('no components ran this invocation (resume) — verifying the already-completed feature against the plan')
303
- verification = await agent(verifyPrompt(a, results, resumeDone), {
304
- label: 'verify', phase: 'Verify', model: usedSonnet ? 'sonnet' : 'haiku', schema: VERIFICATION_SCHEMA
284
+ verification = await agent(verifyPrompt(a, results), {
285
+ label: 'verify', phase: 'Verify', ...modelOpts(usedOpus ? 'opus' : 'sonnet'), schema: VERIFICATION_SCHEMA
305
286
  })
306
287
  }
307
288
 
308
- // Union prior + newly-succeeded so the command can MERGE (not replace) completed history on resume.
309
- const newlyCompleted = allItems.filter(i => i.result && i.result.status === 'success').map(i => i.component.name)
310
- const completedComponents = [...new Set([...resumeDone, ...newlyCompleted])]
311
-
312
- // Returned to the /5:implement command, which persists the slim state.json and auto-commits.
313
- // steps + components are returned for reporting and per-step staging only — they are NOT persisted;
314
- // the next run re-derives them, and resume matches on completedComponents by name.
289
+ // Returned to the /5:implement command for the fast-path check, per-step staging, and the report.
315
290
  return {
316
291
  feature: a.feature,
317
292
  steps,
318
293
  components,
319
- completedComponents,
320
294
  results: results.map(s => ({
321
295
  step: s.step,
322
296
  name: s.name,
@@ -324,5 +298,6 @@ return {
324
298
  items: s.items.map(i => ({ component: i.component.name, file: i.component.file, sourceFile: i.component.sourceFile, escalated: !!i.escalated, result: i.result }))
325
299
  })),
326
300
  verification,
301
+ extraFiles,
327
302
  status: verification.status === 'passed' ? 'completed' : 'failed'
328
303
  }