@dtlabs/ai-toolkit 0.1.7 → 0.2.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.
@@ -5,65 +5,247 @@ argument-hint: <path-to-feature.md> [--force]
5
5
 
6
6
  # Implement Feature
7
7
 
8
- Orchestrates the full feature delivery pipeline by spawning the `project-manager` agent
9
- and, once it completes, recording the real token consumption in a dedicated file.
8
+ Orchestrates the full feature delivery pipeline by invoking three sequential workflow phases
9
+ (`pm-phase1`, `pm-phase2`, `pm-phase3`), handling approval gates in the main loop between
10
+ each phase, and recording the real token consumption at the end.
11
+
12
+ Each workflow phase runs as a real subagent boundary, ensuring worker agents are dispatched
13
+ with their declared `model:` frontmatter honoured and producing accurate per-agent `usage` data.
14
+
15
+ ---
16
+
17
+ ## Step 1 — Invoke pm-phase1 (Documentation Phase)
18
+
19
+ Invoke the `pm-phase1` workflow with the feature path:
20
+
21
+ ```
22
+ subagent_type: pm-phase1
23
+ prompt: <path-to-feature.md>
24
+ ```
25
+
26
+ Wait for the workflow to complete. Do NOT proceed until it returns.
27
+
28
+ Extract from the result:
29
+ - `prefix` — feature prefix (e.g. `FTR-009`)
30
+ - `requirements.summary` — requirements file summary
31
+ - `tech_spec.summary` — tech-spec file summary
32
+ - `validation.summary` — validation result (gaps or clean)
33
+ - `token_ledger` — array of per-agent token data from phase 1
34
+ - `errors` — any agent failures during phase 1
35
+
36
+ If `errors` is non-empty, report them to the user but continue to Gate 1 if the
37
+ required documents were produced.
38
+
39
+ ---
40
+
41
+ ## Step 2 — Present Gate 1 (HARD STOP — present in main loop)
42
+
43
+ Present the following to the user:
44
+
45
+ ```
46
+ Documents generated for {PREFIX}:
47
+
48
+ {PREFIX}-Requirements.md — {requirements.summary}
49
+ {PREFIX}-Tech-Spec.md — {tech_spec.summary}
50
+ {PREFIX}-Validation-Report.md — {validation.summary}
51
+ ```
52
+
53
+ Then output this hard-stop message and **wait for a text reply from the user**:
54
+
55
+ ```
56
+ ⛔ GATE 1 — DOCS APPROVAL — HARD STOP
57
+
58
+ Please review the documents above and reply with one of:
59
+
60
+ "Approve — proceed to Work Breakdown"
61
+ "Request changes: <describe what to change>"
62
+ "Approve with notes: <your comments>"
63
+
64
+ The pipeline CANNOT continue until you reply directly.
65
+ ```
66
+
67
+ **If the user requests changes:** note them and stop. Do not invoke pm-phase2.
68
+
69
+ **If the user approves:** immediately write `{PREFIX}-Approvals.md`:
70
+
71
+ ```markdown
72
+ # Approval Record — {PREFIX}
73
+
74
+ ## Gate 1 — Document Approvals
75
+
76
+ | Document | Status | Date | Notes |
77
+ |----------|--------|------|-------|
78
+ | {PREFIX}-Requirements.md | ✅ Approved | {today} | {notes or —} |
79
+ | {PREFIX}-Tech-Spec.md | ✅ Approved | {today} | {notes or —} |
80
+ | {PREFIX}-Validation-Report.md | ✅ Approved | {today} | {notes or —} |
81
+
82
+ ## Approval History
83
+
84
+ | Cycle | Action | Date | Details |
85
+ |-------|--------|------|---------|
86
+ | 1 | Approved | {today} | {user reply text} |
87
+ ```
88
+
89
+ Read back `{PREFIX}-Approvals.md` and verify it exists with Gate 1 ✅ before continuing.
90
+ If the file is missing or incomplete, write it again before proceeding.
91
+
92
+ ---
93
+
94
+ ## Step 3 — Invoke pm-phase2 (Work Breakdown Phase)
95
+
96
+ Invoke the `pm-phase2` workflow:
97
+
98
+ ```
99
+ subagent_type: pm-phase2
100
+ prompt: <path-to-feature.md>
101
+ ```
102
+
103
+ Wait for the workflow to complete. Extract from the result:
104
+ - `user_stories` — number of User Stories
105
+ - `total_tasks` — total task count
106
+ - `domain_breakdown` — tasks per domain
107
+ - `implementation_phases` — number of phases
108
+ - `human_estimate` — human sequential estimate
109
+ - `agent_estimate` — agent parallel estimate
110
+ - `token_ledger` — per-agent token data from phase 2
10
111
 
11
112
  ---
12
113
 
13
- ## Step 1 — Spawn the Project Manager
114
+ ## Step 4 — Present Gate 2 (HARD STOP — present in main loop)
115
+
116
+ Pre-condition check: read `{PREFIX}-Approvals.md` and verify Gate 1 ✅ is present.
117
+ If missing, return to Step 2.
118
+
119
+ Present the following to the user:
120
+
121
+ ```
122
+ Work Breakdown for {PREFIX}:
123
+
124
+ User Stories: {user_stories} (US-01 ÷ US-{NN})
125
+ Total tasks: {total_tasks} ({domain_breakdown})
126
+ Implementation phases: {implementation_phases}
127
+ Human estimate: {human_estimate} | Agent estimate: {agent_estimate}
128
+ Reference: {PREFIX}-Effort-Estimate.md for full detail
129
+ ```
130
+
131
+ Then output this hard-stop message and **wait for a text reply from the user**:
132
+
133
+ ```
134
+ ⛔ GATE 2 — WORK BREAKDOWN APPROVAL — HARD STOP
135
+
136
+ Please review the Work Breakdown above and reply with one of:
137
+
138
+ "Approve — start implementation"
139
+ "Request changes: <describe what to change>"
140
+ "Approve with notes: <your comments>"
14
141
 
15
- Spawn the `project-manager` agent with the exact arguments the user provided:
142
+ Implementation CANNOT start until you reply directly.
143
+ ```
144
+
145
+ **If the user requests changes:** note them and stop. Do not invoke pm-phase3.
146
+
147
+ **If the user approves:** append Gate 2 to `{PREFIX}-Approvals.md`:
148
+
149
+ ```markdown
150
+ ## Gate 2 — Work Breakdown Approval
16
151
 
152
+ | Document | Status | Date | Notes |
153
+ |----------|--------|------|-------|
154
+ | {PREFIX}-Work-Breakdown.md | ✅ Approved | {today} | {total_tasks} tasks, {implementation_phases} phases |
17
155
  ```
18
- subagent_type: project-manager
19
- prompt: <path-to-feature.md> [--force]
156
+
157
+ Read back `{PREFIX}-Approvals.md` and verify Gate 2 ✅ is present before continuing.
158
+
159
+ ---
160
+
161
+ ## Step 5 — Create feature branch
162
+
163
+ Before invoking pm-phase3, create the feature branch:
164
+
165
+ ```bash
166
+ git checkout -b feature/{PREFIX}-{short-slug}
20
167
  ```
21
168
 
22
- Wait for the agent to complete. Capture its full result, including the `<usage>` block
23
- that appears at the end of the tool result (format: `subagent_tokens: N`).
169
+ If the branch already exists, switch to it without recreating:
170
+
171
+ ```bash
172
+ git checkout feature/{PREFIX}-{short-slug}
173
+ ```
24
174
 
25
175
  ---
26
176
 
27
- ## Step 2 — Extract orchestrator token usage
177
+ ## Step 6 — Invoke pm-phase3 (Implementation Phase)
178
+
179
+ Pre-condition check: read `{PREFIX}-Approvals.md` and verify BOTH Gate 1 ✅ and Gate 2 ✅.
180
+ If either is missing, return to the missing gate.
181
+
182
+ Invoke the `pm-phase3` workflow:
28
183
 
29
- From the `<usage>` block of the project-manager result, read:
30
- - `subagent_tokens` — total tokens consumed by the project-manager agent itself (input + output)
31
- - `duration_ms` — wall-clock duration of the project-manager run
184
+ ```
185
+ subagent_type: pm-phase3
186
+ prompt: <path-to-feature.md> --branch feature/{PREFIX}-{short-slug}
187
+ ```
188
+
189
+ Wait for the workflow to complete. Capture its full result, including the `<usage>` block
190
+ (format: `subagent_tokens: N`). Extract from the result:
191
+ - `pr_url` — the created pull request URL
192
+ - `token_ledger` — all per-agent token data from phase 3
193
+ - `issues_summary` — escalation and issues counts
194
+
195
+ If `issues_summary.escalations > 0`, report the escalation details to the user.
32
196
 
33
197
  ---
34
198
 
35
- ## Step 3 — Complete Token Estimate file
199
+ ## Step 7 — Complete Token Estimate file
200
+
201
+ From the `<usage>` block of the pm-phase3 result, read:
202
+ - `subagent_tokens` — total tokens consumed by the pm-phase3 workflow
203
+ - `duration_ms` — wall-clock duration of pm-phase3
36
204
 
37
- The project-manager will have already written `{PREFIX}-Token-Estimate.md` with the
38
- upfront estimate and the child agents' actuals. Read that file, then **append**
39
- the orchestrator row and the grand total:
205
+ Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
40
206
 
41
207
  ```markdown
42
- | project-manager (orchestrator) | — | sonnet | N (estimated) | N (actual) | ±N | Xh Ymin |
208
+ | project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 (estimated) | {subagent_tokens} (actual) | ±{delta} | {duration} |
43
209
 
44
210
  ---
45
211
 
46
- ### Grand Total
212
+ ## Actuals vs Estimate
213
+
214
+ | Agent | Task / Scope | Model | Est. tokens | Actual tokens | Delta | Est. cost ($) | Actual cost ($) | Duration |
215
+ |-------|-------------|-------|------------|---------------|-------|--------------|----------------|----------|
216
+ {one row per entry in token_ledger — from phase 1, 2, and 3}
217
+ | project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
218
+
219
+ ## Estimation accuracy by agent type
220
+
221
+ | Model | Count | Avg est. tokens | Avg actual tokens | Avg delta | Trend |
222
+ |-------|-------|----------------|------------------|-----------|-------|
223
+ {one row per model tier (haiku, sonnet) — exclude N/A rows from averages}
224
+
225
+ ## Grand Total
47
226
 
48
227
  | Metric | Estimated | Actual | Delta |
49
228
  |--------|-----------|--------|-------|
50
- | Total tokens (all agents) | N | N | ±N |
51
- | Total wall-clock | Xh Ymin | Xh Ymin | ±Zmin |
52
- | Average tokens per agent | N | N | — |
229
+ | Total tokens (all agents) | {sum_est} | {sum_actual} | ±{delta} |
230
+ | Total cost ($) | ${sum_est_cost} | ${sum_actual_cost} | ±${delta_cost} |
231
+ | Total wall-clock | — | {total_duration} | — |
53
232
  ```
54
233
 
55
- If the file does not exist yet (project-manager failed before writing it), create it
56
- from scratch using the data available.
234
+ > Per-agent values marked *(proportional)* are estimated distributions of the phase total.
235
+ > Phase totals and grand total are exact measurements from workflow subagent_tokens.
236
+
237
+ If the Token Estimate file does not exist (pm-phase3 failed before writing it), create it
238
+ from scratch using the data available in the token_ledger.
57
239
 
58
240
  ---
59
241
 
60
- ## Step 4 — Report to user
242
+ ## Step 8 — Report to user
61
243
 
62
244
  After writing the token file, report:
63
245
 
64
246
  ```
65
- ✅ Feature pipeline complete.
247
+ Feature pipeline complete.
66
248
  Token estimate + actuals → {PREFIX}-Token-Estimate.md
67
249
  Effort estimate + actuals → {PREFIX}-Effort-Estimate.md
68
- Pull Request → {PR URL}
250
+ Pull Request → {pr_url}
69
251
  ```
@@ -0,0 +1,304 @@
1
+ export const meta = {
2
+ name: 'am-phase1',
3
+ description: 'Assessment pipeline phase 1: discover assessment agents → scope filter → parallel assessment → intervention-documentation-standard → Token-Estimate → Effort-Estimate → findings_gate_payload.',
4
+ phases: [
5
+ { title: 'Discovery', detail: 'Scan .claude/agents/ for assessment agents, apply scope filter' },
6
+ { title: 'Assessment', detail: 'Dispatch all assessment agents in parallel' },
7
+ { title: 'Interventions', detail: 'Run intervention-documentation-standard on all findings' },
8
+ { title: 'Estimates', detail: 'Write Token-Estimate and Effort-Estimate files' },
9
+ ],
10
+ }
11
+
12
+ // args: "<target_path> [--scope=area1,area2] --prefix ASSESS-NNN [--force]"
13
+ const argStr = typeof args === 'string' ? args : '.'
14
+ const parts = argStr.trim().split(/\s+/)
15
+
16
+ let targetPath = '.'
17
+ let scope = null
18
+ let prefix = null
19
+ let force = false
20
+
21
+ for (const part of parts) {
22
+ if (part.startsWith('--scope=')) { scope = part.slice('--scope='.length).split(',').map(s => s.trim()) }
23
+ else if (part === '--force') { force = true }
24
+ else if (part === '--prefix') { /* next part is value */ }
25
+ else if (prefix === null && parts[parts.indexOf(part) - 1] === '--prefix') { prefix = part }
26
+ else if (!part.startsWith('--')) { targetPath = part }
27
+ }
28
+
29
+ // Re-parse prefix properly
30
+ const prefixMatch = argStr.match(/--prefix\s+(\S+)/)
31
+ if (prefixMatch) prefix = prefixMatch[1]
32
+
33
+ if (!prefix) prefix = 'ASSESS-001'
34
+
35
+ const outputDir = `docs/assessments/${prefix}`
36
+
37
+ // ── Discovery ─────────────────────────────────────────────────────────────────
38
+ phase('Discovery')
39
+
40
+ const DISCOVERY_SCHEMA = {
41
+ type: 'object',
42
+ properties: {
43
+ assessment_agents: {
44
+ type: 'array',
45
+ items: {
46
+ type: 'object',
47
+ properties: {
48
+ name: { type: 'string' },
49
+ description: { type: 'string' },
50
+ },
51
+ required: ['name'],
52
+ },
53
+ },
54
+ },
55
+ required: ['assessment_agents'],
56
+ }
57
+
58
+ const SCOPE_AGENT_MAP = {
59
+ architecture: ['layered-architecture-assessment'],
60
+ security: ['security-hardening'],
61
+ quality: ['generic-software-assessment'],
62
+ concurrency: ['concurrency-safety-assessment'],
63
+ devops: [],
64
+ 'domain-model': ['domain-model-refactoring'],
65
+ dependencies: ['dependency-supply-chain-security'],
66
+ }
67
+
68
+ const discovery = await agent(
69
+ `Scan .claude/agents/ and list all assessment agents.
70
+
71
+ An assessment agent is a .md file whose name or description contains one of: "assessment", "audit", "analysis".
72
+ Exclude orchestrators (files containing "manager" in their name).
73
+ Exclude remediation-only agents (files containing "refactoring", "hardening", "decomposition" in their name UNLESS they also contain "assessment" or "audit").
74
+
75
+ For each matching agent, read its frontmatter and extract:
76
+ - name (from the name: field in frontmatter)
77
+ - description (from the description: field)
78
+
79
+ Return { "assessment_agents": [ { "name": "...", "description": "..." }, ... ] }`,
80
+ {
81
+ label: 'discover-assessment-agents',
82
+ phase: 'Discovery',
83
+ schema: DISCOVERY_SCHEMA,
84
+ }
85
+ )
86
+
87
+ let assessmentAgents = discovery.assessment_agents || []
88
+
89
+ // Apply scope filter
90
+ if (scope && scope.length > 0) {
91
+ const allowed = new Set(scope.flatMap(s => SCOPE_AGENT_MAP[s] || []))
92
+ assessmentAgents = assessmentAgents.filter(a => allowed.has(a.name))
93
+ log(`Scope filter applied (${scope.join(', ')}): ${assessmentAgents.length} agents selected`)
94
+ } else {
95
+ log(`No scope filter: ${assessmentAgents.length} assessment agents discovered`)
96
+ }
97
+
98
+ if (assessmentAgents.length === 0) {
99
+ log('WARNING: No assessment agents to run — proceeding with empty assessment')
100
+ }
101
+
102
+ // ── Assessment (parallel) ─────────────────────────────────────────────────────
103
+ phase('Assessment')
104
+
105
+ log(`Dispatching ${assessmentAgents.length} assessment agents in parallel`)
106
+
107
+ const tokenLedger = []
108
+ const beforeAssess = budget.spent()
109
+
110
+ const assessmentResults = await parallel(
111
+ assessmentAgents.map(a => () =>
112
+ agent(
113
+ `${targetPath} --prefix ${prefix} --output-dir ${outputDir}`,
114
+ {
115
+ agentType: a.name,
116
+ label: a.name,
117
+ phase: 'Assessment',
118
+ }
119
+ ).then(result => ({ agent: a.name, success: true, result }))
120
+ .catch(err => ({ agent: a.name, success: false, error: String(err) }))
121
+ )
122
+ )
123
+
124
+ const assessTokens = budget.spent() - beforeAssess
125
+ log(`Assessment phase complete — ${assessmentResults.filter(r => r?.success).length}/${assessmentAgents.length} succeeded | total phase delta: ${assessTokens} tokens`)
126
+
127
+ // Proportional token distribution across agents
128
+ const perAgentTokens = assessmentAgents.length > 0
129
+ ? Math.round(assessTokens / assessmentAgents.length)
130
+ : 0
131
+
132
+ for (const r of assessmentResults.filter(Boolean)) {
133
+ tokenLedger.push({
134
+ agent: r.agent,
135
+ model: 'sonnet',
136
+ phase_delta_tokens: perAgentTokens,
137
+ note: 'proportional distribution of assessment phase total',
138
+ success: r.success,
139
+ error: r.error || null,
140
+ })
141
+ }
142
+
143
+ const failedAgents = assessmentResults.filter(r => r && !r.success)
144
+ const completedAgents = assessmentResults.filter(r => r && r.success)
145
+
146
+ // ── Interventions ─────────────────────────────────────────────────────────────
147
+ phase('Interventions')
148
+
149
+ log('Running intervention-documentation-standard')
150
+
151
+ const beforeInt = budget.spent()
152
+ await agent(
153
+ `--prefix ${prefix} --output-dir ${outputDir} --target ${targetPath}`,
154
+ {
155
+ agentType: 'intervention-documentation-standard',
156
+ label: 'intervention-documentation-standard',
157
+ phase: 'Interventions',
158
+ }
159
+ )
160
+ const intTokens = budget.spent() - beforeInt
161
+ tokenLedger.push({
162
+ agent: 'intervention-documentation-standard',
163
+ model: 'sonnet',
164
+ phase_delta_tokens: intTokens,
165
+ note: 'exact phase delta',
166
+ })
167
+ log(`intervention-documentation-standard done — phase delta: ${intTokens} tokens`)
168
+
169
+ // ── Estimates ─────────────────────────────────────────────────────────────────
170
+ phase('Estimates')
171
+
172
+ const ESTIMATES_SCHEMA = {
173
+ type: 'object',
174
+ properties: {
175
+ severity_counts: {
176
+ type: 'object',
177
+ properties: {
178
+ CRITICAL: { type: 'number' },
179
+ HIGH: { type: 'number' },
180
+ MEDIUM: { type: 'number' },
181
+ LOW: { type: 'number' },
182
+ },
183
+ },
184
+ total_interventions: { type: 'number' },
185
+ interventions_index_path: { type: 'string' },
186
+ token_estimate_path: { type: 'string' },
187
+ effort_estimate_path: { type: 'string' },
188
+ remediation_hours_est: { type: 'number' },
189
+ },
190
+ required: ['severity_counts', 'total_interventions'],
191
+ }
192
+
193
+ const totalPhaseTokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens || 0), 0)
194
+
195
+ const estimatesResult = await agent(
196
+ `You are the estimates recorder for the assessment pipeline.
197
+
198
+ Prefix: ${prefix}
199
+ Output directory: ${outputDir}
200
+ Assessment agents that ran: ${assessmentAgents.map(a => a.name).join(', ') || 'none'}
201
+ Failed agents: ${failedAgents.map(r => r.agent).join(', ') || 'none'}
202
+
203
+ Token ledger (phase-level measurements):
204
+ ${JSON.stringify(tokenLedger, null, 2)}
205
+
206
+ TASK 1 — Read ${prefix}-Interventions-Index.md from ${outputDir}.
207
+ Count rows by Criticality column: CRITICAL, HIGH, MEDIUM, LOW.
208
+ Compute remediation_hours_est = CRITICAL×8 + HIGH×4 + MEDIUM×2 + LOW×1.
209
+
210
+ TASK 2 — Write Token-Estimate file at ${outputDir}/${prefix}-Token-Estimate.md.
211
+
212
+ Use this template (fill in all values):
213
+
214
+ # Token Estimate — ${prefix} — Assessment Pipeline
215
+
216
+ > Phase totals are exact measurements from budget tracking.
217
+ > Per-agent values within the assessment phase are proportional distributions of the phase total.
218
+ > Pricing model: docs/pricing.md (80% input / 20% output split).
219
+ > ⚠️ Per-agent values marked (proportional) are estimated distributions, not exact measurements.
220
+
221
+ ## Estimation model
222
+
223
+ | Parameter | Value |
224
+ |-----------|-------|
225
+ | Avg chars per token | 4 |
226
+ | Sonnet system prompt | ~3,000 tokens |
227
+ | Base overhead per call | ~5,000 tokens |
228
+ | Input/output split | 80% / 20% |
229
+
230
+ ## Assessment agents (Phase 1)
231
+
232
+ | Agent | Model | Phase delta tokens | Note | Status |
233
+ |-------|-------|--------------------|------|--------|
234
+ {one row per agent from token ledger — use phase_delta_tokens and note fields}
235
+
236
+ ## Phase subtotals
237
+
238
+ | Phase | Tokens (exact) |
239
+ |-------|---------------|
240
+ | Assessment agents (parallel) | {sum of assessment agent phase_delta_tokens} |
241
+ | intervention-documentation-standard | {its phase_delta_tokens} |
242
+ | Total am-phase1 | ${totalPhaseTokens} |
243
+
244
+ ## Grand total
245
+
246
+ > Updated at pipeline end by assess-codebase skill.
247
+
248
+ | Metric | Value |
249
+ |--------|-------|
250
+ | Total tokens (am-phase1) | ${totalPhaseTokens} (exact) |
251
+
252
+ TASK 3 — Write Effort-Estimate file at ${outputDir}/${prefix}-Effort-Estimate.md.
253
+
254
+ Use this template:
255
+
256
+ # Effort Estimate — ${prefix} — Assessment Pipeline
257
+
258
+ > Wall-clock effort tracking for the assessment pipeline.
259
+ > Remediation effort: CRITICAL=8h, HIGH=4h, MEDIUM=2h, LOW=1h (human hours, sequential).
260
+
261
+ ## Assessment phase
262
+
263
+ | Agent | Status |
264
+ |-------|--------|
265
+ {one row per assessment agent}
266
+ | intervention-documentation-standard | complete |
267
+
268
+ ## Remediation effort estimate
269
+
270
+ | Severity | Count | Rate | Subtotal |
271
+ |----------|-------|------|---------|
272
+ | CRITICAL | {count} | 8h | {subtotal}h |
273
+ | HIGH | {count} | 4h | {subtotal}h |
274
+ | MEDIUM | {count} | 2h | {subtotal}h |
275
+ | LOW | {count} | 1h | {subtotal}h |
276
+ | **Total** | **{total}** | — | **{total_hours}h** |
277
+
278
+ Human sequential total: {total_hours}h
279
+
280
+ Return the structured metrics.`,
281
+ {
282
+ label: 'write-estimates',
283
+ phase: 'Estimates',
284
+ schema: ESTIMATES_SCHEMA,
285
+ }
286
+ )
287
+
288
+ log(`Estimates written — ${estimatesResult.total_interventions} interventions: CRITICAL=${estimatesResult.severity_counts?.CRITICAL} HIGH=${estimatesResult.severity_counts?.HIGH} MEDIUM=${estimatesResult.severity_counts?.MEDIUM} LOW=${estimatesResult.severity_counts?.LOW}`)
289
+ log(`Remediation estimate: ${estimatesResult.remediation_hours_est}h human sequential`)
290
+
291
+ return {
292
+ prefix,
293
+ output_dir: outputDir,
294
+ target_path: targetPath,
295
+ assessment_summaries: completedAgents.map(r => ({ agent: r.agent })),
296
+ interventions_index_path: estimatesResult.interventions_index_path || `${outputDir}/${prefix}-Interventions-Index.md`,
297
+ severity_counts: estimatesResult.severity_counts || { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 },
298
+ total_interventions: estimatesResult.total_interventions || 0,
299
+ remediation_hours_est: estimatesResult.remediation_hours_est || 0,
300
+ effort_estimate_path: estimatesResult.effort_estimate_path || `${outputDir}/${prefix}-Effort-Estimate.md`,
301
+ token_estimate_path: estimatesResult.token_estimate_path || `${outputDir}/${prefix}-Token-Estimate.md`,
302
+ token_ledger: tokenLedger,
303
+ errors: failedAgents.map(r => ({ agent: r.agent, error: r.error })),
304
+ }