@dtlabs/ai-toolkit 0.5.0 → 0.6.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.
@@ -6,7 +6,75 @@ model: sonnet
6
6
 
7
7
  # Define Feature
8
8
 
9
- Helps the user define a new feature by asking relentless, focused questions — then synthesizes the answers into a `feature.md` file ready for the `/implement-feature` pipeline.
9
+ Helps the user define a new feature by first **reading everything the repository already knows**, then asking only the questions whose answers are genuinely missing — and finally synthesizing the result into a `feature.md` file ready for the `/implement-feature` pipeline.
10
+
11
+ **Core principle: never ask what the repo can already answer.** Every question you ask must be one you could not resolve by reading `AGENTS.md`, an existing backlog/ticket, the existing feature docs, or the code itself. Redundant questions are a defect.
12
+
13
+ ---
14
+
15
+ ## Input
16
+
17
+ The user MAY provide a **source reference** as an argument — treat it as authoritative input, not as a starting point to re-derive from scratch:
18
+
19
+ - A path to a backlog entry or section (e.g. `docs/token-optimization-backlog.md → OPT-09`)
20
+ - A path to a ticket, spec, or design note
21
+ - A free-text description of the feature
22
+ - Nothing at all (fully interactive definition)
23
+
24
+ If a source reference is given, your job is to **ingest it, pre-fill the feature draft, and grill only on the real gaps** — not to ask the user to restate what the source already says.
25
+
26
+ ---
27
+
28
+ ## Phase 0 — Context Ingestion (MANDATORY — before any question)
29
+
30
+ Do NOT ask the user anything until you have read the available context. Silently gather, then build a pre-filled draft.
31
+
32
+ ### 0a. Read project conventions
33
+
34
+ - Read `AGENTS.md` from the project root if it exists (tech stack, directory structure, patterns, constraints). This answers most stack/structure/permission questions without asking.
35
+ - If `AGENTS.md` is absent, note it — you will infer conventions from the code and may confirm the stack in one consolidated question.
36
+
37
+ ### 0b. Ingest the source reference (if provided)
38
+
39
+ - Read the referenced file/section fully. Extract every field it already provides: objective, scope, out-of-scope, affected files, acceptance criteria, dependencies, effort/risk.
40
+ - Follow obvious pointers (e.g. a backlog entry that says "deriva da `docs/token-optimization.md`" → read that too).
41
+
42
+ ### 0c. Study existing features and code
43
+
44
+ - List `docs/features/` and `internal_docs/features/` (whichever exists) to learn the house style of prior `feature.md` files and to detect overlap/dependencies with existing work.
45
+ - Read the code the feature will touch (files named in the source, or found via Grep/Glob) enough to understand the current behavior being changed.
46
+
47
+ ### 0d. Classify the feature type — this drives which questions are asked
48
+
49
+ Decide the primary type from the evidence gathered:
50
+
51
+ | Type | Signals | Rounds that DON'T apply |
52
+ |------|---------|-------------------------|
53
+ | **business/UI** | end-user pages, forms, actors, CRUD on domain entities | (all rounds apply) |
54
+ | **internal/technical** | refactor, tooling, script, workflow, agent, build/infra, optimization | Actors, Core-flow-as-page, Data model, Roles/permissions |
55
+ | **library/API** | public functions/endpoints, no UI, consumed by other code | Core-flow-as-page, Roles/permissions (unless auth is the point) |
56
+
57
+ Record the type. In Phase 2 you will **skip inapplicable rounds entirely** and mark their template sections `N/A — <type> feature`.
58
+
59
+ ### 0e. Build and present the pre-filled draft
60
+
61
+ Assemble a draft of the feature fields from 0a–0c and show it to the user **before grilling**:
62
+
63
+ ```
64
+ 📋 Pre-filled from: <source reference or "no source — interactive">
65
+ Feature type: <business/UI | internal/technical | library/API>
66
+ ──────────────────────────────────────────────────
67
+ Objective : <from source, or "❓ unknown">
68
+ Scope : <from source, or "❓ unknown">
69
+ Out of scope : <from source, or "❓ unknown">
70
+ Files affected : <from source/code, or "❓ unknown">
71
+ Acceptance : <from source, or "❓ unknown">
72
+ Dependencies : <from source, or "❓ unknown">
73
+ ──────────────────────────────────────────────────
74
+ Gaps I still need to resolve: <list only the ❓ items + genuine ambiguities>
75
+ ```
76
+
77
+ Ask the user to **confirm or correct the draft**. Then grill ONLY on the `❓` gaps and ambiguities — skip every field already answered by the source.
10
78
 
11
79
  ---
12
80
 
@@ -30,11 +98,16 @@ Example: "Supplier Onboarding" → `FTR-003-supplier-onboarding`
30
98
 
31
99
  ---
32
100
 
33
- ## Phase 2 — Grilling
101
+ ## Phase 2 — Grilling (only the gaps)
102
+
103
+ **Objective**: resolve the `❓` gaps and ambiguities identified in Phase 0 — nothing already answered.
34
104
 
35
- **Objective**: surface every assumption, gap, and edge case before writing a single line of spec.
105
+ **Hard rules before asking any question:**
106
+ 1. **Never ask what Phase 0 already answered.** If the source, `AGENTS.md`, existing features, or the code already provides a field, do NOT ask about it — at most ask a single confirmation if genuinely ambiguous.
107
+ 2. **Skip rounds that don't apply to the feature type** (from Phase 0d). For an internal/technical feature, skip Actors, page-based Core Flow, Data model, and Roles/permissions entirely and mark those template sections `N/A — technical feature`.
108
+ 3. **One dimension at a time** — use `AskUserQuestion` per round; adapt follow-ups to prior answers.
36
109
 
37
- Ask questions in rounds. Each round focuses on a dimension. Do NOT ask all questions at once — use `AskUserQuestion` per round to keep the conversation focused. Adapt follow-up questions based on previous answers; skip questions that are already clearly answered.
110
+ Below are the possible rounds. Run a round **only if** it applies to the feature type AND it contains at least one unresolved gap. If a round has no open gap, skip it silently.
38
111
 
39
112
  ### Round 1 — The problem
40
113
 
@@ -49,15 +122,21 @@ Follow-up if vague:
49
122
 
50
123
  ### Round 2 — The core flow
51
124
 
52
- > "Walk me through the main scenario step by step — from the moment the user opens the page to the moment they're done."
125
+ **Applies to:** all types — but phrase it to the type.
126
+ - business/UI: *"Walk me through the main scenario step by step — from the moment the user opens the page to the moment they're done."*
127
+ - internal/technical or library/API: *"Walk me through the main execution path — what triggers it, what it does step by step, and what the end state is."* (There is no page; do not ask page questions.)
53
128
 
54
- Goal: extract the happy path in concrete user actions.
129
+ Goal: extract the happy path in concrete steps.
55
130
 
56
- Follow-up:
131
+ Follow-up (business/UI):
57
132
  - "What does the user see first?"
58
133
  - "What data do they need to input or select?"
59
134
  - "What happens after they submit/save?"
60
- - "How does the system respond?"
135
+
136
+ Follow-up (technical/API):
137
+ - "What is the entry point (function, script, command, event)?"
138
+ - "What are the inputs and where do they come from?"
139
+ - "What is the observable output or side effect when it completes?"
61
140
 
62
141
  ### Round 3 — Boundaries
63
142
 
@@ -81,6 +160,8 @@ Follow-up:
81
160
 
82
161
  ### Round 5 — Data and entities
83
162
 
163
+ **Applies to:** business/UI and library/API features that manage domain data. **Skip for internal/technical/refactor/tooling features that create no domain entities** — mark the Data Model section `N/A — technical feature`.
164
+
84
165
  > "What data does this feature create, read, update, or delete?"
85
166
 
86
167
  Follow-up:
@@ -90,6 +171,8 @@ Follow-up:
90
171
 
91
172
  ### Round 6 — Roles and permissions
92
173
 
174
+ **Applies to:** business/UI features (and API features where authorization is in scope). **Skip for internal/technical features with no end-user roles** — mark the Roles and Permissions section `N/A — technical feature`.
175
+
93
176
  > "Who can do what? Are there different permission levels within this feature?"
94
177
 
95
178
  Follow-up:
@@ -226,9 +309,10 @@ For each entity: name, key fields (name, type, constraints), relationships.
226
309
  ### Writing rules
227
310
 
228
311
  - Write in **English** (regardless of the language used during grilling)
229
- - Derive content **strictly from the user's answers** — do not invent requirements
312
+ - Derive content from **the source reference (Phase 0) plus the user's answers** — do not invent requirements
313
+ - For sections that don't apply to the feature type, write `N/A — <type> feature` instead of leaving them blank or inventing content (e.g. Actors / Data Model / Roles for an internal/technical feature)
230
314
  - Mark open questions explicitly rather than guessing
231
- - Acceptance criteria must be **testable** (Given/When/Then)
315
+ - Acceptance criteria must be **testable** (Given/When/Then) — for technical features, express them as observable/verifiable checks (e.g. "grep returns nothing", "build passes", "output file exists")
232
316
  - Be concrete and specific — avoid vague statements like "the system should be fast"
233
317
 
234
318
  ---
@@ -258,9 +342,12 @@ Next step: run /implement-feature docs/features/{PREFIX}-{slug}/feature.md
258
342
 
259
343
  ## Guidelines
260
344
 
261
- - **Grill relentlessly** — vague answers get follow-up questions, not acceptance
262
- - **One dimension at a time** — do not overwhelm the user with all questions at once
263
- - **Challenge contradictions** — if answers conflict, surface it before writing
264
- - **Do not invent** — if something is unclear and the user can't answer, record it as an open question
265
- - **Create the folder** if it doesn't exist — do not ask the user to do it manually
266
- - **Always write in English** — feature.md is input to English-language downstream agents
345
+ - **Read before you ask** — Phase 0 is mandatory; never ask what `AGENTS.md`, the source reference, existing feature docs, or the code already answers. A redundant question is a defect.
346
+ - **Match questions to the feature type** — skip Actors / Data model / Roles / page-flow rounds for internal/technical features; do not force business-feature questions onto a refactor or tooling task.
347
+ - **Confirm the draft, then grill the gaps** — present the pre-filled draft first; only unresolved `❓` items and genuine ambiguities become questions.
348
+ - **Grill relentlessly on real gaps** — vague answers to open questions get follow-ups, not acceptance.
349
+ - **One dimension at a time** — do not overwhelm the user with all questions at once.
350
+ - **Challenge contradictions** — if answers conflict with each other or with the source, surface it before writing.
351
+ - **Do not invent** — if something is unclear and the user can't answer, record it as an open question.
352
+ - **Create the folder** if it doesn't exist — do not ask the user to do it manually.
353
+ - **Always write in English** — feature.md is input to English-language downstream agents.
@@ -121,9 +121,11 @@ For each document with gaps, invoke the corresponding agent with a **targeted re
121
121
 
122
122
  ---
123
123
 
124
- ## Phase 6 — Write Validation Report
124
+ ## Phase 6 — Write Validation Report (MANDATORY — your final action)
125
125
 
126
- **Always execute this phase**, regardless of whether gaps were found. The report is required even when validation is clean.
126
+ **This is not optional and not the same as the Phase 4 gap report.** The coverage report you produced in Phase 4 is on-screen text; it is NOT the deliverable. Your job is not complete until the file `{PREFIX}-Validation-Report.md` exists **on disk**. You MUST call the `Write` tool to create it — even when validation is clean and zero gaps were found. Returning a summary without having called `Write` is a failure.
127
+
128
+ Execute this phase **last, always**, regardless of outcome. Downstream agents read this file as a hard precondition; if it is missing, the entire pipeline aborts.
127
129
 
128
130
  Write `{PREFIX}-Validation-Report.md` in the same directory:
129
131
 
@@ -166,6 +168,7 @@ Do NOT guess how to fill ambiguous gaps. Ask first, then revise only with a clea
166
168
 
167
169
  ## Guidelines
168
170
 
171
+ - **Always call `Write` for the Validation Report before finishing** (Phase 6) — the on-screen gap report is not the deliverable; the file on disk is. This is your last action, clean or not.
169
172
  - **Do not regenerate documents from scratch** — targeted revisions only
170
173
  - **Be specific about gaps** — point to the exact section, claim, and what is missing
171
174
  - **Stop after 3 revision cycles** — if coverage is still incomplete, report remaining gaps and exit
@@ -1,13 +1,15 @@
1
1
  ---
2
- description: "Define Feature — grills the user to define a new feature, then writes feature.md in docs/features/FTR-XXX-slug/. Usage: /define-feature"
2
+ description: "Define Feature — reads existing repo context, then grills only on the real gaps and writes feature.md in docs/features/FTR-XXX-slug/. Usage: /define-feature [source reference]"
3
3
  disable-model-invocation: true
4
4
  ---
5
5
 
6
- Spawn the `define-feature` agent with no additional arguments:
6
+ Spawn the `define-feature` agent, passing through whatever the user provided as a source reference (a backlog entry, ticket path, spec path, or free-text description). If the user gave nothing, pass an empty source.
7
7
 
8
8
  ```
9
9
  subagent_type: define-feature
10
- prompt: Start the feature definition session.
10
+ prompt: |
11
+ Start the feature definition session.
12
+ Source reference (may be empty): {{user arguments verbatim}}
11
13
  ```
12
14
 
13
- Do not add anything else. The agent handles discovery, grilling, and file creation autonomously.
15
+ The agent runs Phase 0 (context ingestion) first — it reads AGENTS.md, the source reference, existing feature docs, and the relevant code, then pre-fills a draft and grills ONLY on the genuine gaps. Do not add instructions telling it to skip discovery.
@@ -103,6 +103,7 @@ if (discoveryResult.needs_tech_spec) {
103
103
  phase('Validation')
104
104
 
105
105
  let validationSummary = 'skipped'
106
+ let lastValText = ''
106
107
  const MAX_CYCLES = 3
107
108
 
108
109
  for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
@@ -115,6 +116,7 @@ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
115
116
  // valResult is the agent's text output — check for gap indicators
116
117
  // Use only 'MISSING:' as the gap signal: 'gaps found' also matches 'ZERO GAPS FOUND'
117
118
  const resultText = typeof valResult === 'string' ? valResult : JSON.stringify(valResult)
119
+ lastValText = resultText
118
120
  const hasGaps = resultText.includes('MISSING:')
119
121
 
120
122
  if (!hasGaps) {
@@ -139,6 +141,56 @@ for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
139
141
  }
140
142
  }
141
143
 
144
+ // ── Guarantee the Validation Report exists on disk ───────────────────────────
145
+ // The validate-feature-docs agent (haiku) is instructed to write the report in its
146
+ // Phase 6, but this is LLM-dependent and unreliable when validation is clean. Downstream
147
+ // pm-phase2 (generate-work-breakdown) treats the report as a hard precondition, so its
148
+ // absence aborts the whole pipeline. This step deterministically ensures the file exists.
149
+ if (validationSummary !== 'skipped') {
150
+ const validationReportPath = `${feature_dir}/${prefix}-Validation-Report.md`
151
+ await agent(
152
+ `Ensure the Validation Report file exists on disk for this feature delivery run.
153
+
154
+ File path: ${validationReportPath}
155
+
156
+ Steps:
157
+ 1. Check whether the file already exists (use Read or Glob).
158
+ 2. If it ALREADY EXISTS: do nothing, return "exists".
159
+ 3. If it DOES NOT EXIST: write it now using the Write tool, based on the validation outcome below.
160
+ Get the current date via Bash: run \`date -u +"%Y-%m-%d"\`.
161
+
162
+ Validation outcome summary: ${validationSummary}
163
+
164
+ Validation agent output (source of truth for gaps found/resolved):
165
+ ──────────────────────────────────────────────────
166
+ ${lastValText.slice(0, 4000)}
167
+ ──────────────────────────────────────────────────
168
+
169
+ File format to write (fill from the outcome above; if the run was clean, mark both documents Clean with 0 gaps):
170
+
171
+ # Validation Report — ${prefix}
172
+
173
+ ## Summary
174
+ | Document | Gaps found | Gaps resolved | Status |
175
+ |----------|-----------|--------------|--------|
176
+ | ${prefix}-Requirements.md | N | N | Clean / Gaps remain |
177
+ | ${prefix}-Tech-Spec.md | N | N | Clean / Gaps remain |
178
+
179
+ ## Gaps found and resolved
180
+ (derive from the validation output above; write "(none)" if clean)
181
+
182
+ ## Remaining gaps (if any)
183
+ (list any unresolved gaps, or "(none)")
184
+
185
+ ## Validation date
186
+ {date from Bash}
187
+
188
+ Return "written" if you created the file, or "exists" if it was already present.`,
189
+ { label: 'ensure-validation-report', phase: 'Validation' }
190
+ )
191
+ log(`Validation report ensured at ${validationReportPath}`)
192
+ }
193
+
142
194
  // ── Write process-log (phase 1 snapshot) ─────────────────────────────────────
143
195
  const reqEntry = tokenLedger.find(e => e.agent === 'generate-requirements')
144
196
  const specEntry = tokenLedger.find(e => e.agent === 'generate-tech-spec')
@@ -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 (commit per phase) → single architect review → PR → actuals.',
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.',
4
4
  phases: [
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' },
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' },
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,10 +17,12 @@ const branchMatch = argStr.match(/--branch\s+(\S+)/)
17
17
  const branch = branchMatch ? branchMatch[1] : null
18
18
 
19
19
  const tokenLedger = []
20
+ const escalations = []
20
21
 
21
22
  // ── Parse Work Breakdown CSV (deterministic, no AI) ───────────────────────────
22
23
  phase('Parse')
23
24
 
25
+ // Derive feature_dir and prefix from featurePath (e.g. "docs/features/FTR-004-.../feature.md")
24
26
  const featureDir = featurePath.replace(/\/[^/]+$/, '')
25
27
  const prefixMatch = featureDir.match(/([A-Z]+-\d+)/)
26
28
  const prefix = prefixMatch ? prefixMatch[1] : 'FTR-000'
@@ -37,7 +39,7 @@ const csvContent = await agent(
37
39
  const rows = csvContent
38
40
  .split('\n')
39
41
  .map(l => l.trim())
40
- .filter(l => l && !l.startsWith('phase_id'))
42
+ .filter(l => l && !l.startsWith('phase_id')) // skip header and empty lines
41
43
  .map(l => {
42
44
  const [phase_id, phase_title, commit_message, depends_on, task_id, task_title, domain, agent_type] = l.split('|')
43
45
  return { phase_id, phase_title, commit_message, depends_on: depends_on || '', task_id, task_title, domain, agent_type }
@@ -52,7 +54,7 @@ for (const row of rows) {
52
54
  phase_id: row.phase_id,
53
55
  title: row.phase_title,
54
56
  commit_message: row.commit_message,
55
- depends_on: row.depends_on.split(' ').filter(Boolean),
57
+ depends_on: row.depends_on.split(' ').filter(Boolean), // [] for empty
56
58
  impl_tasks: [],
57
59
  test_tasks: [],
58
60
  })
@@ -85,9 +87,11 @@ const allPhases = Array.from(phaseMap.values()).map(p => ({
85
87
  }))
86
88
 
87
89
  const wb = { prefix, feature_dir: featureDir, phases: allPhases }
90
+
88
91
  log(`CSV parsed: ${rows.length} tasks → ${wb.phases.length} phases for ${wb.prefix}`)
89
92
 
90
- // Build execution waves: phases whose dependencies are all satisfied run in parallel
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.
91
95
  const buildWaves = (phases) => {
92
96
  const done = new Set()
93
97
  const waves = []
@@ -95,6 +99,7 @@ const buildWaves = (phases) => {
95
99
  while (remaining.length > 0) {
96
100
  const ready = remaining.filter(p => p.depends_on.every(d => done.has(d)))
97
101
  if (ready.length === 0) {
102
+ // Circular or unresolvable deps — run the rest sequentially to avoid deadlock
98
103
  waves.push(remaining)
99
104
  break
100
105
  }
@@ -108,71 +113,130 @@ const buildWaves = (phases) => {
108
113
  const waves = buildWaves(wb.phases)
109
114
  log(`Execution plan: ${waves.length} wave(s) — ${waves.map((w, i) => `wave${i+1}:[${w.map(p => p.phase_id).join(',')}]`).join(' ')}`)
110
115
 
111
- let phasesDone = 0
112
- const usPassed = []
116
+ let phasesDone = 0
117
+ const usPassed = []
118
+ const usEscalated = []
119
+ let issuesPath = ''
120
+ let issuesOpen = 0
113
121
 
114
122
  // ── Implementation ────────────────────────────────────────────────────────────
115
123
  phase('Implementation')
116
124
 
117
- // executePhase: impl_groups (parallel) → test_groups (parallel) → commit
118
- // No inline review — review runs once after all phases complete.
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
119
136
  const executePhase = async (implPhase) => {
120
137
  log(`Starting phase ${implPhase.phase_id}: ${implPhase.title}`)
121
138
 
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
136
- tokenLedger.push({
137
- agent: `${implPhase.impl_groups.map(g => g.agent_type).join('+')}:${implPhase.phase_id}`,
138
- model: 'sonnet',
139
- phase_delta_tokens: implTokens,
140
- })
141
- log(`Phase ${implPhase.phase_id} impl done — ${implTokens} tokens`)
142
- }
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
+ }
143
191
 
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
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
158
204
  tokenLedger.push({
159
- agent: `developer-testing:${implPhase.phase_id}`,
160
- model: 'sonnet',
161
- phase_delta_tokens: testTokens,
205
+ agent: `review-solution:${implPhase.phase_id}`,
206
+ model: 'sonnet',
207
+ phase_delta_tokens: reviewTokens,
162
208
  })
163
- log(`Phase ${implPhase.phase_id} tests done — ${testTokens} tokens`)
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
+ }
164
223
  }
165
224
 
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
- )
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`)
237
+ }
172
238
 
173
- if (implPhase.phase_id !== 'INFRA') usPassed.push(implPhase.phase_id)
174
239
  phasesDone++
175
- log(`Phase ${implPhase.phase_id} committed`)
176
240
  }
177
241
 
178
242
  // Execute waves: phases in the same wave run in parallel, waves are sequential
@@ -185,57 +249,69 @@ for (const wave of waves) {
185
249
  }
186
250
  }
187
251
 
188
- log(`Implementation complete — ${phasesDone} phases, ${usPassed.length} US implemented`)
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`)
189
269
 
190
- // ── Review — single pass over the full feature diff ───────────────────────────
191
- phase('Review')
270
+ // ── Remediation ───────────────────────────────────────────────────────────────
271
+ phase('Remediation')
192
272
 
193
- const REVIEW_SCHEMA = {
273
+ const REM_SCHEMA = {
194
274
  type: 'object',
195
275
  properties: {
196
- has_critical: { type: 'boolean' },
197
- findings: { type: 'string' },
198
- issues_md: { type: 'string' },
276
+ issues_fixed: { type: 'number' },
277
+ issues_deferred: { type: 'number' },
199
278
  },
200
- required: ['has_critical'],
279
+ required: ['issues_fixed', 'issues_deferred'],
201
280
  }
202
281
 
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 })
282
+ let issuesFixed = 0
283
+ let issuesDeferred = 0
215
284
 
216
- let issuesOpen = 0
217
- let issuesPath = `${wb.feature_dir}/${prefix}-Issues.md`
218
- let hasCritical = false
285
+ if (issuesOpen > 0 && issuesPath) {
286
+ log(`Remediation: ${issuesOpen} OPEN issues`)
287
+ const beforeRem = budget.spent()
219
288
 
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
- }
233
- } else {
234
- log(`Review agent returned null — skipping issues register`)
235
- }
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.
236
297
 
237
- if (hasCritical) {
238
- log(`WARNING: critical issues found — PR will be created but marked as blocked. Inspect ${prefix}-Issues.md before merging.`)
298
+ After processing all issues, return: { "issues_fixed": N, "issues_deferred": N }
299
+
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
+ )
307
+
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`)
313
+ } else {
314
+ log('Remediation: no OPEN issues — skipped')
239
315
  }
240
316
 
241
317
  // ── PR ────────────────────────────────────────────────────────────────────────
@@ -258,37 +334,39 @@ const prResult = await agent(
258
334
  Feature path: ${featurePath}
259
335
  Branch: ${branch || 'current branch'}
260
336
  Prefix: ${prefix}
261
- Critical issues found: ${hasCritical}
262
- Open issues: ${issuesOpen}
337
+ Issues fixed: ${issuesFixed}, deferred: ${issuesDeferred}
263
338
 
264
339
  Your tasks:
265
340
 
266
- 1. Push the branch:
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:
267
346
  git push -u origin ${branch || 'HEAD'}
268
347
 
269
- 2. Create a PR targeting develop:
348
+ 3. Create a PR targeting develop:
270
349
  gh pr create \\
271
350
  --title "feat(${prefix}): <Feature Title from feature.md>" \\
272
351
  --base develop \\
273
352
  --body "## Summary
274
- - Implements <Feature Title from feature.md> (${usPassed.length} User Stories, ${phasesDone} phases)
353
+ - Implements <Feature Title> (<N> User Stories, <N> tasks)
275
354
  - Source docs: ${prefix}-Requirements.md, ${prefix}-Tech-Spec.md
276
355
 
277
356
  ## Implementation
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
357
+ - Architect review: all US passed
358
+ - Issues Register: ${issuesFixed} fixed, ${issuesDeferred} deferred → see ${prefix}-Issues.md
281
359
 
282
360
  ## Test plan
283
361
  - [ ] Build passes
284
362
  - [ ] Tests pass
285
363
  - [ ] Manual smoke test of key flows"
286
364
 
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'}".
365
+ 4. Update the Feature Registry (internal_docs/features/REGISTRY.md):
366
+ Find or append the entry for ${prefix}. Set Status to "completed".
289
367
 
290
368
  Return { "pr_url": "<url>", "registry_updated": true/false }.
291
- If the PR already exists, return its URL. If creation fails, return { "pr_url": "(PR creation failed)", "registry_updated": false }.`,
369
+ If the PR creation fails, return { "pr_url": "(PR creation failed)", "registry_updated": false }.`,
292
370
  {
293
371
  label: 'pr-and-registry',
294
372
  phase: 'PR',
@@ -305,6 +383,15 @@ phase('Actuals')
305
383
 
306
384
  const totalPhase3Tokens = tokenLedger.reduce((s, e) => s + (e.phase_delta_tokens || 0), 0)
307
385
 
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
+
308
395
  const beforeActuals = budget.spent()
309
396
 
310
397
  await agent(
@@ -348,17 +435,14 @@ ${tokenLedger.map(e => `| ${e.agent} | — | ${e.model} | — | ${e.phase_delta_
348
435
  | Metric | Value |
349
436
  |--------|-------|
350
437
  | Implementation phases done | ${phasesDone} |
351
- | US implemented | ${usPassed.join(', ') || 'N/A'} |
352
- | Critical issues | ${hasCritical} |
353
- | Open issues | ${issuesOpen} |
438
+ | US passed | ${usPassed.join(', ') || 'N/A'} |
439
+ | US escalated | ${usEscalated.join(', ') || 'none'} |
354
440
 
355
441
  TASK 2 — Update actuals in ${prefix}-Effort-Estimate.md:
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:
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:
362
446
 
363
447
  ---
364
448
 
@@ -367,14 +451,14 @@ Also append this section at the end of the file:
367
451
  | Metric | Estimated | Actual |
368
452
  |--------|-----------|--------|
369
453
  | Implementation phases | ${phasesDone} | ${phasesDone} |
370
- | Critical review issues | — | ${hasCritical ? 'yes' : 'none'} |
371
- | Open issues | — | ${issuesOpen} |
454
+ | Issues fixed | — | ${issuesFixed} |
455
+ | Issues deferred | — | ${issuesDeferred} |
372
456
 
373
457
  Return { "token_estimate_path": "<path>", "effort_estimate_path": "<path>" }.`,
374
458
  {
375
459
  label: 'write-actuals',
376
460
  phase: 'Actuals',
377
- schema: { type: 'object', properties: { token_estimate_path: { type: 'string' }, effort_estimate_path: { type: 'string' } }, required: [] },
461
+ schema: ACTUALS_SCHEMA,
378
462
  }
379
463
  )
380
464
 
@@ -383,22 +467,28 @@ tokenLedger.push({ agent: 'write-actuals', model: 'sonnet', phase_delta_tokens:
383
467
  log(`Actuals written — ${actualsTokens} tokens`)
384
468
 
385
469
  // ── Append to process-log ─────────────────────────────────────────────────────
470
+ const usPassed3 = usPassed.join(', ') || 'none'
471
+ const usEscalated3 = usEscalated.join(', ') || 'none'
386
472
  const phase3Events = [
387
473
  `APPROVAL GRANTED by user — Gate 2`,
388
474
  `pm-phase3 START — implementation phase — branch: ${branch || '(current)'}`,
389
475
  `WB parsed: ${wb.phases.length} phases`,
390
- `Implementation DONE — ${phasesDone} phases implemented: ${usPassed.join(', ') || 'none'}`,
391
- `Review DONE — critical: ${hasCritical}, open issues: ${issuesOpen}`,
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)`,
392
480
  `PR created: ${prResult.pr_url}`,
393
481
  `Actuals written: Token-Estimate.md + Effort-Estimate.md`,
394
482
  `RUN COMPLETE`,
395
483
  `════════════════════════════════════════════════════════`,
396
484
  ].join('\n')
397
485
 
486
+ const featureDir3 = featurePath.replace(/\/[^/]+$/, '')
487
+
398
488
  await agent(
399
489
  `Append phase 3 events to the process-log for this feature delivery run.
400
490
 
401
- Process-log path: ${featureDir}/${prefix}-process-log.txt
491
+ Process-log path: ${featureDir3}/${prefix}-process-log.txt
402
492
 
403
493
  Steps:
404
494
  1. Get current UTC datetime via Bash: run \`date -u +"%Y-%m-%dT%H:%M:%S"\`
@@ -408,14 +498,16 @@ Steps:
408
498
 
409
499
  Events to append:
410
500
  ${phase3Events}`,
411
- { label: 'finalize-process-log', phase: 'Actuals', model: 'haiku' }
501
+ { label: 'finalize-process-log', phase: 'Actuals' }
412
502
  )
413
503
 
414
504
  return {
415
505
  pr_url: prResult.pr_url,
416
506
  token_ledger: tokenLedger,
417
507
  issues_summary: {
418
- has_critical: hasCritical,
419
- open_issues: issuesOpen,
508
+ escalations: escalations.length,
509
+ escalation_details: escalations,
510
+ issues_fixed: issuesFixed,
511
+ issues_deferred: issuesDeferred,
420
512
  },
421
513
  }
package/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.6.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.5.0...v0.6.0) (2026-07-29)
3
+
4
+
5
+ ### Features
6
+
7
+ * **define-feature:** context ingestion + adaptive grilling; add AGENTS.md ([581b783](https://github.com/dtlab-tech/ai-toolkit/commit/581b783a18626796ae764ce69f658556b83ab490))
8
+
9
+
10
+ ### Bug Fixes
11
+
12
+ * **pipeline:** persist Validation Report + smarter define-feature + AGENTS.md ([a5bae6a](https://github.com/dtlab-tech/ai-toolkit/commit/a5bae6aab65ca78aa5daa08a4a731b7c16051754))
13
+ * **pm-phase1:** guarantee Validation Report is persisted to disk ([53e10c0](https://github.com/dtlab-tech/ai-toolkit/commit/53e10c0e8f20021da9408d6b9e0c077142a449ae))
14
+
2
15
  ## [0.5.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.4.0...v0.5.0) (2026-07-29)
3
16
 
4
17
 
package/README.md CHANGED
@@ -104,7 +104,7 @@ npm run test:coverage # run all tests and generate a coverage report
104
104
  The test suite covers:
105
105
 
106
106
  - **`tests/cli/`** — unit tests for pure functions in `bin/cli.js`
107
- (`fileHash`, `walkDir`, `expandMappings`, `categorize`, `readInstalledVersion`, `isMattPocockInstalled`)
107
+ (`fileHash`, `walkDir`, `expandMappings`, `categorize`, `readInstalledVersion`)
108
108
  - **`tests/frontmatter/`** — structural validation of all `.claude/agents/*.md`
109
109
  and `.claude/skills/**/SKILL.md` frontmatter fields
110
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",