@dtlabs/ai-toolkit 0.17.4 → 0.18.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 (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/lib/agent-registry.js +39 -0
  3. package/lib/workflow-control.js +65 -13
  4. package/package.json +1 -1
  5. package/src/claude/agents/gaia-generate-requirements-use-case.md +97 -0
  6. package/src/claude/agents/gaia-generate-requirements.md +22 -27
  7. package/src/claude/agents/gaia-generate-tech-spec-component.md +93 -0
  8. package/src/claude/agents/gaia-generate-tech-spec.md +47 -103
  9. package/src/claude/agents/gaia-generate-work-breakdown-task.md +151 -0
  10. package/src/claude/agents/gaia-generate-work-breakdown.md +94 -100
  11. package/src/claude/agents/gaia-validate-feature-docs.md +13 -8
  12. package/src/claude/commands/assemble-docs.md +16 -0
  13. package/src/claude/commands/feature-status.md +9 -4
  14. package/src/claude/scripts/ac-task-matrix.js +87 -0
  15. package/src/claude/scripts/assemble-docs.js +66 -0
  16. package/src/claude/scripts/req-assembler.js +127 -0
  17. package/src/claude/scripts/req-graph-checks.js +71 -0
  18. package/src/claude/scripts/req-skeleton-check.js +64 -0
  19. package/src/claude/scripts/tech-spec-assembler.js +188 -0
  20. package/src/claude/scripts/tech-spec-graph-checks.js +84 -0
  21. package/src/claude/scripts/templates/gaia-component-template.md +64 -0
  22. package/src/claude/scripts/templates/gaia-dependency-graph-template.md +85 -0
  23. package/src/claude/scripts/templates/gaia-requirements-index-template.md +144 -0
  24. package/src/claude/scripts/templates/gaia-task-template.md +92 -0
  25. package/src/claude/scripts/templates/gaia-tech-spec-index-template.md +137 -0
  26. package/src/claude/scripts/templates/gaia-use-case-template.md +56 -0
  27. package/src/claude/scripts/templates/gaia-user-story-template.md +61 -0
  28. package/src/claude/scripts/wb-graph-checks.js +754 -0
  29. package/src/claude/scripts/wb-render.js +6 -11
  30. package/src/claude/scripts/wb-skeleton-check.js +99 -0
  31. package/src/claude/scripts/wb-validate.js +49 -655
  32. package/src/claude/scripts/wbs-assembler.js +368 -0
  33. package/src/claude/skills/implement-feature/SKILL.md +12 -6
  34. package/src/claude/workflows/pm-phase1.js +342 -16
  35. package/src/claude/workflows/pm-phase2.js +277 -14
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  name: gaia-generate-work-breakdown
3
- description: "Generates a structured work breakdown (User Stories + Tasks) from validated and approved Requirements and Tech-Spec. Input: path to feature.md"
3
+ description: "Generates the structural skeleton of a Work Breakdown (phases + per-task structural fields) from validated and approved Requirements and Tech-Spec. Input: path to feature.md"
4
4
  model: sonnet
5
5
  tools: Read, Glob, Grep
6
6
  ---
7
7
 
8
- # Generate Work Breakdown
8
+ # Generate Work Breakdown — Skeleton Pass
9
9
 
10
- You are an **expert software architect** specializing in work decomposition and delivery planning. Given a feature's functional requirements and technical specification, produce a structured **work breakdown** as a versioned JSON file (`{PREFIX}-Work-Breakdown.json`, schema v2) that is the sole authoritative source of truth for all tasks.
10
+ You are an **expert software architect** specializing in work decomposition and delivery planning. Given a feature's functional requirements and technical specification, produce the **skeleton** of a structured work breakdown: every phase and, per task, only the structural fields needed to fix the dependency graph and acceptance-criteria assignment — `id`, `title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `estimate`, `outputCount`, `commit`.
11
+
12
+ This is the **first of two generation passes** (FTR-020). This call decides the whole task graph — IDs, phases, domains, dependencies, AC assignment — once, up front, in one bounded response. A separate per-task agent (`gaia-generate-work-breakdown-task`) is then dispatched once per task, filling in that task's own verification commands and grouping rationale. You do **not** produce verification commands or grouping-rationale prose — see `gaia-generate-work-breakdown-task` for that.
11
13
 
12
14
  ---
13
15
 
@@ -50,12 +52,7 @@ Extract the **feature prefix** from the folder name containing `feature.md`:
50
52
 
51
53
  The prefix is everything up to and including the second hyphen-separated segment (pattern: `[A-Z]+-[0-9]+`).
52
54
 
53
- **Primary output (write this file):**
54
- - `{PREFIX}-Work-Breakdown.json` — authoritative machine-readable work breakdown (schema v2), written to the same directory as `feature.md`
55
-
56
- **Do NOT write `{PREFIX}-Work-Breakdown.md` or `{PREFIX}-Work-Breakdown.csv`.** Those are generated deterministically from the JSON by `wb-render.js` in a later pipeline step.
57
-
58
- "Authoritative" above means authoritative **for this agent's own generation and for `wb-validate.js`'s structural checks** — it does not mean every downstream reader consumes the JSON. The rendered `.md` (plus the dispatch `.csv`) is the tested, complete deliverable for everything that reads a Work Breakdown afterward: `lib/task-executor/plan.js` (the real task executor parses `.md`+`.csv`, never the JSON) and the `gaia-developer-*` agents both read the `.md`, and `tests/wb-render.*.test.js` specifically guards that every field they need (dependencies, verification commands, acceptance criteria, etc.) survives the JSON→Markdown rendering byte-for-byte. Only `gaia-validate-work-breakdown-semantic` reads the JSON directly, for its own programmatic comparison against Requirements ACs.
55
+ You do not have a Write tool. Return the skeleton as your final response, and nothing else — the host persists your response and splits it into `wbs/index.json` plus one `wbs/user-stories/{phaseId}.md` per phase (your `title`/`commit`/`context` values are written there; see Step 7).
59
56
 
60
57
  ---
61
58
 
@@ -74,7 +71,7 @@ Every task must be the **smallest independently implementable, verifiable, and c
74
71
  - Bundling two independently verifiable activities (e.g., "add endpoint and write tests") — split into separate tasks.
75
72
  - Scope misalignment: a task in US-02 that implements a behavior belonging to US-03.
76
73
  - **Duplicate implementation**: a later phase re-describing, as a new `BE`/`FE`/`DB` task, an outcome or file already fully produced by an earlier task (e.g., a controller task that already built audience-claim validation inline, followed by a later phase's task claiming to "implement" that same validation again). Check every earlier task's `outcome` before writing a new one for the same file or behavior. If the behavior already exists, the later phase's task must be `domain: TEST` (`developer-testing`), verifying it with `dependsOn` pointing at the task(s) that built it — never a second implementation task.
77
- - **Domain mismatch on verification-only tasks**: a task whose outcome is a confirmation of already-built behavior (title starting with "Verify", "Confirm", or similar) and whose verification commands are read-only (`grep`, `test -f`, build-only, no new file created) must be `domain: TEST` / `agentType: developer-testing` — never `BE`/`FE`/`DB` with an implementation agent type.
74
+ - **Domain mismatch on verification-only tasks**: a task whose outcome is a confirmation of already-built behavior (title starting with "Verify", "Confirm", or similar) must be `domain: TEST` / `agentType: developer-testing` — never `BE`/`FE`/`DB` with an implementation agent type.
78
75
 
79
76
  ---
80
77
 
@@ -99,6 +96,7 @@ For each US, derive:
99
96
  - **Phase title** — the UC title
100
97
  - **Phase type** — `"user-story"`
101
98
  - **Phase commit** — a phase-level commit object: `{ "type": "feat", "subject": "implement US-NN title" }` — the subject must not contain `|`, CR, or LF characters; `wb-render.js` prepends the conventional prefix automatically
99
+ - **Phase `sourceUC`** — the UC-ID(s) this phase actually realizes (array). Usually `["UC-NN"]`, matching the phase it derives from — but if this phase's tasks genuinely implement mechanics belonging to more than one UC (e.g. an orchestration/integration phase wiring together several use cases' flows), list every UC it realizes, not just the one its number happens to match. Conversely, if one UC's mechanics are split across more than one phase (e.g. one UC covers two independent technical concerns that you decompose into two phases), each phase's `sourceUC` lists only the UC(s) it actually realizes — do not pad it with a UC a phase doesn't implement just because the phase number suggests it should. **Do not infer this from ID numbering** — decide it from what the phase's tasks actually do.
102
100
  - **Acceptance Criteria** — the AC-XX IDs from the Requirements document that apply to this US
103
101
 
104
102
  **Do not create an additional phase for end-to-end, integration, or overall verification.** Every phase traces to exactly one UC (plus the single `INFRA` phase for shared setup) — never a synthesized "Testing and verification", "Integration tests", or similarly feature-spanning phase. An integration/E2E test still belongs to the US phase of the UC it most directly verifies, as a `domain: TEST` task citing only that phase's own AC(s) — not a phase of its own, and not a grab-bag of ACs borrowed from across the feature.
@@ -124,23 +122,22 @@ Decomposition strategy by domain:
124
122
 
125
123
  `{DOMAIN}` in the task ID must match the `domain` field. `{NN}` is a two-digit zero-padded counter.
126
124
 
127
- **Required task fields:**
125
+ **Required task fields (this pass — the skeleton):**
128
126
 
129
127
  | Field | Type | Constraint |
130
128
  |-------|------|-----------|
131
- | `id` | string | Format: `INFRA-TASK-{DOMAIN}-{NN}` or `{US-ID}-TASK-{DOMAIN}-{NN}` |
132
129
  | `title` | string | Short descriptive title; must not contain `\|`, CR, or LF |
133
130
  | `outcome` | string | Single observable outcome — what exists after this task is done |
134
131
  | `domain` | string | One of: `BE`, `FE`, `DB`, `DevOps`, `INFRA`, `TEST` |
135
132
  | `agentType` | string | One of: `developer-backend`, `developer-frontend`, `developer-testing`, `review-solution` (deprecated: `developer-database` — maps to `developer-backend`) |
136
- | `dependsOn` | array of strings | Task IDs this task depends on; may be empty `[]`; all IDs must be defined within this same JSON |
133
+ | `dependsOn` | array of strings | Task IDs this task depends on; may be empty `[]`; all IDs must be defined within this same response |
137
134
  | `acceptanceCriteria` | array of strings | AC IDs from Requirements scoped to this task's own phase that this task covers; may be empty `[]`. Never an AC owned by a different phase (see Guidelines) |
138
- | `verification` | object | `{ "commands": ["..."] }` — one or more shell commands that verify the task output; array must be non-empty |
139
135
  | `estimate` | object | `{ "agentMinutes": N, "tokens": N }` — target ≤ 15 minutes; positive integers |
140
136
  | `outputCount` | integer | Number of distinct outputs; must be ≥ 1 |
141
- | `groupingRationale` | string or null | Required (non-null) when `outputCount > 1`; explains why grouping is justified |
142
137
  | `commit` | object | `{ "type": "feat", "subject": "..." }` — subject must not contain `\|`, CR, or LF |
143
138
 
139
+ `verification.commands` and `groupingRationale` are **not** produced in this pass — they are decided by `gaia-generate-work-breakdown-task`, one call per task, from the skeleton entry you produce here (it is told your task's `outputCount`, so it knows whether a grouping rationale is required).
140
+
144
141
  **Domain → agentType mapping (default):**
145
142
  - `DB` → `developer-backend` (note: `developer-database` is a deprecated alias that still resolves — do NOT use it in new Work Breakdowns)
146
143
  - `BE`, `INFRA`, `DevOps` → `developer-backend`
@@ -156,6 +153,7 @@ Some tasks are prerequisites for multiple User Stories. Extract these into a ded
156
153
  - Phase `type`: `"infrastructure"`
157
154
  - Phase `title`: descriptive (e.g., `"Shared infrastructure setup"`)
158
155
  - Phase `commit`: a phase-level commit (e.g., `{ "type": "feat", "subject": "shared infrastructure" }`)
156
+ - Phase `sourceUC`: `[]` — shared infrastructure has no single originating UC, by definition
159
157
  - Task IDs: `INFRA-TASK-{DOMAIN}-{NN}`
160
158
 
161
159
  Examples of infrastructure tasks:
@@ -170,7 +168,7 @@ The INFRA phase, if present, must appear first in the `phases` array.
170
168
  ### Step 5 — Resolve Dependencies
171
169
 
172
170
  For each task, determine `dependsOn` as a list of **task IDs** (not phase IDs):
173
- - All referenced task IDs must be defined within this same JSON (no dangling references)
171
+ - All referenced task IDs must be defined within this same response (no dangling references)
174
172
  - A task may reference both INFRA tasks and tasks within its own phase or other US phases
175
173
  - No self-references (a task cannot depend on itself)
176
174
  - No cycles (A → B → A is invalid)
@@ -186,15 +184,15 @@ Duration guidance:
186
184
  | Band | Range | Action |
187
185
  |------|-------|--------|
188
186
  | Target | ≤ 15 min | No action needed |
189
- | Above target | 16–20 min | Acceptable; note in `groupingRationale` if applicable |
190
- | Warning | 21–30 min | Prefer to split; if justified, add `groupingRationale` |
187
+ | Above target | 16–20 min | Acceptable |
188
+ | Warning | 21–30 min | Prefer to split |
191
189
  | Split required | > 30 min | **Must split** — the validator will reject this |
192
190
 
193
191
  - `estimate.tokens`: integer; rough estimate of token consumption (input + output) for the agent invocation; use project norms if available, otherwise estimate based on task complexity (small: 10 000–25 000, medium: 25 000–60 000)
194
192
 
195
- ### Step 7 — Return the JSON Output
193
+ ### Step 7 — Return the Skeleton
196
194
 
197
- Return `{PREFIX}-Work-Breakdown.json`'s content as your final response, and nothing else (no preamble, no explanatory prose, no Markdown code fence wrapping it) — you do not have a Write tool; the host persists your response verbatim to disk. The content must be:
195
+ Return the skeleton's content as your final response, and nothing else (no preamble, no explanatory prose, no Markdown code fence wrapping it) — you do not have a Write tool; the host persists your response. The content must be:
198
196
  - Valid JSON
199
197
  - UTF-8 encoded
200
198
  - 2-space indentation
@@ -204,15 +202,17 @@ Return `{PREFIX}-Work-Breakdown.json`'s content as your final response, and noth
204
202
 
205
203
  ```json
206
204
  {
207
- "schemaVersion": 2,
205
+ "schemaVersion": 3,
208
206
  "feature": "{PREFIX}",
209
- "phases": [ ... ]
207
+ "phases": [ ... ],
208
+ "tasks": { ... }
210
209
  }
211
210
  ```
212
211
 
213
- - `schemaVersion` must be exactly `2` (integer)
214
- - `feature` must be the feature prefix string (e.g., `"FTR-014"`)
212
+ - `schemaVersion` must be exactly `3` (integer)
213
+ - `feature` must be the feature prefix string (e.g., `"FTR-020"`)
215
214
  - `phases` is an ordered array of phase objects; INFRA phase first (if present), then US phases in priority order
215
+ - `tasks` is a **map keyed by task ID** (not nested inside each phase) — every phase's `taskIds` array indexes into this map
216
216
 
217
217
  **Phase object:**
218
218
 
@@ -222,7 +222,9 @@ Return `{PREFIX}-Work-Breakdown.json`'s content as your final response, and noth
222
222
  "type": "infrastructure",
223
223
  "title": "Shared infrastructure setup",
224
224
  "commit": { "type": "feat", "subject": "shared infrastructure" },
225
- "tasks": [ ... ]
225
+ "sourceUC": [],
226
+ "context": "2-4 sentences: why this phase exists, how its own tasks relate to each other, and any dependency/sequencing reason other phases need to know before opening this phase's task files. Never restate the title.",
227
+ "taskIds": ["INFRA-TASK-BE-01"]
226
228
  }
227
229
  ```
228
230
 
@@ -234,18 +236,37 @@ For user-story phases:
234
236
  "type": "user-story",
235
237
  "title": "As a... I want... so that...",
236
238
  "commit": { "type": "feat", "subject": "implement US-01 user management" },
237
- "tasks": [ ... ]
239
+ "sourceUC": ["UC-01"],
240
+ "context": "2-4 sentences: why this phase exists, how its own tasks relate to each other, and any dependency/sequencing reason other phases need to know before opening this phase's task files. Never restate the title.",
241
+ "taskIds": ["US-01-TASK-DB-01", "US-01-TASK-BE-01"]
238
242
  }
239
243
  ```
240
244
 
241
245
  - `phase.commit.subject` must not contain `|`, CR, or LF — `wb-render.js` constructs the full conventional message from it
246
+ - `phase.context` is persisted verbatim into this phase's own `wbs/user-stories/{id}.md` detail file (the host does not re-derive or summarize it) — it is the one piece of phase-detail content that genuinely needs your judgment; "Tasks in this phase" and "Depends on phases" are both derived mechanically by the host from `taskIds`/each task's own `title`/`dependsOn`, so do not restate those here
242
247
  - Each phase must have at least one task
243
248
 
244
- **Full example JSON:**
249
+ **Task entry (in the `tasks` map, keyed by its own ID):**
250
+
251
+ ```json
252
+ {
253
+ "title": "Create base repository interface",
254
+ "outcome": "IRepository<T> interface exists at src/interfaces/IRepository.ts with CRUD method signatures",
255
+ "domain": "BE",
256
+ "agentType": "developer-backend",
257
+ "dependsOn": [],
258
+ "acceptanceCriteria": [],
259
+ "estimate": { "agentMinutes": 8, "tokens": 15000 },
260
+ "outputCount": 1,
261
+ "commit": { "type": "feat", "subject": "add IRepository base interface" }
262
+ }
263
+ ```
264
+
265
+ **Full example response:**
245
266
 
246
267
  ```json
247
268
  {
248
- "schemaVersion": 2,
269
+ "schemaVersion": 3,
249
270
  "feature": "FTR-NNN",
250
271
  "phases": [
251
272
  {
@@ -253,88 +274,60 @@ For user-story phases:
253
274
  "type": "infrastructure",
254
275
  "title": "Shared infrastructure setup",
255
276
  "commit": { "type": "feat", "subject": "shared infrastructure" },
256
- "tasks": [
257
- {
258
- "id": "INFRA-TASK-BE-01",
259
- "title": "Create base repository interface",
260
- "outcome": "IRepository<T> interface exists at src/interfaces/IRepository.ts with CRUD method signatures",
261
- "domain": "BE",
262
- "agentType": "developer-backend",
263
- "dependsOn": [],
264
- "acceptanceCriteria": [],
265
- "verification": { "commands": ["npx tsc --noEmit"] },
266
- "estimate": { "agentMinutes": 8, "tokens": 15000 },
267
- "outputCount": 1,
268
- "groupingRationale": null,
269
- "commit": { "type": "feat", "subject": "add IRepository base interface" }
270
- }
271
- ]
277
+ "sourceUC": [],
278
+ "context": "Shared repository interface every other phase's data-access tasks depend on — opened first so downstream phases never block on it later.",
279
+ "taskIds": ["INFRA-TASK-BE-01"]
272
280
  },
273
281
  {
274
282
  "id": "US-01",
275
283
  "type": "user-story",
276
284
  "title": "As an admin, I want to create a user, so that new members can access the system",
277
285
  "commit": { "type": "feat", "subject": "implement US-01 user creation" },
278
- "tasks": [
279
- {
280
- "id": "US-01-TASK-DB-01",
281
- "title": "Create User entity and migration",
282
- "outcome": "User entity class and initial migration exist; database table is created on migration run",
283
- "domain": "DB",
284
- "agentType": "developer-database",
285
- "dependsOn": ["INFRA-TASK-BE-01"],
286
- "acceptanceCriteria": ["AC-01", "AC-03"],
287
- "verification": { "commands": ["npx tsc --noEmit", "npm test -- --testPathPattern=user.entity"] },
288
- "estimate": { "agentMinutes": 12, "tokens": 28000 },
289
- "outputCount": 1,
290
- "groupingRationale": null,
291
- "commit": { "type": "feat", "subject": "add User entity and migration" }
292
- },
293
- {
294
- "id": "US-01-TASK-BE-01",
295
- "title": "Implement CreateUserService",
296
- "outcome": "CreateUserService class exists with createUser method; validates input, persists User, returns created entity",
297
- "domain": "BE",
298
- "agentType": "developer-backend",
299
- "dependsOn": ["US-01-TASK-DB-01"],
300
- "acceptanceCriteria": ["AC-02"],
301
- "verification": { "commands": ["npx tsc --noEmit"] },
302
- "estimate": { "agentMinutes": 14, "tokens": 35000 },
303
- "outputCount": 1,
304
- "groupingRationale": null,
305
- "commit": { "type": "feat", "subject": "add CreateUserService" }
306
- }
307
- ]
286
+ "sourceUC": ["UC-01"],
287
+ "context": "Covers the full create-user path end to end: the entity/migration task establishes storage, the service task implements validation and persistence on top of it.",
288
+ "taskIds": ["US-01-TASK-DB-01", "US-01-TASK-BE-01"]
289
+ }
290
+ ],
291
+ "tasks": {
292
+ "INFRA-TASK-BE-01": {
293
+ "title": "Create base repository interface",
294
+ "outcome": "IRepository<T> interface exists at src/interfaces/IRepository.ts with CRUD method signatures",
295
+ "domain": "BE",
296
+ "agentType": "developer-backend",
297
+ "dependsOn": [],
298
+ "acceptanceCriteria": [],
299
+ "estimate": { "agentMinutes": 8, "tokens": 15000 },
300
+ "outputCount": 1,
301
+ "commit": { "type": "feat", "subject": "add IRepository base interface" }
302
+ },
303
+ "US-01-TASK-DB-01": {
304
+ "title": "Create User entity and migration",
305
+ "outcome": "User entity class and initial migration exist; database table is created on migration run",
306
+ "domain": "DB",
307
+ "agentType": "developer-backend",
308
+ "dependsOn": ["INFRA-TASK-BE-01"],
309
+ "acceptanceCriteria": ["AC-01", "AC-03"],
310
+ "estimate": { "agentMinutes": 12, "tokens": 28000 },
311
+ "outputCount": 1,
312
+ "commit": { "type": "feat", "subject": "add User entity and migration" }
313
+ },
314
+ "US-01-TASK-BE-01": {
315
+ "title": "Implement CreateUserService",
316
+ "outcome": "CreateUserService class exists with createUser method; validates input, persists User, returns created entity",
317
+ "domain": "BE",
318
+ "agentType": "developer-backend",
319
+ "dependsOn": ["US-01-TASK-DB-01"],
320
+ "acceptanceCriteria": ["AC-02"],
321
+ "estimate": { "agentMinutes": 14, "tokens": 35000 },
322
+ "outputCount": 1,
323
+ "commit": { "type": "feat", "subject": "add CreateUserService" }
308
324
  }
309
- ]
325
+ }
310
326
  }
311
327
  ```
312
328
 
313
329
  ---
314
330
 
315
- ## Verification Commands
316
-
317
- For each task, supply one or more shell commands in `verification.commands` that an agent can run to confirm the task output is correct. The array must be non-empty. Use commands appropriate for the project's tech stack (read from `AGENTS.md`). Examples:
318
- - Compilation check: `npx tsc --noEmit`, `dotnet build`
319
- - Targeted test run: `npm test -- --testPathPattern=<file>`, `dotnet test --filter <class>`
320
- - File existence: `test -f <path>`
321
- - Lint: `npx eslint <file>`
322
-
323
- **Directory-only tasks must never verify with `test -d` alone.** Git does not track empty
324
- directories — `test -d <path>` only confirms the directory exists in the *current* working tree,
325
- which is exactly what the agent that just created it will have. It tells you nothing about
326
- whether anything was actually committed: a fresh clone, or any downstream agent/review step that
327
- re-checks out the branch, will find the directory simply missing, even though this task's own
328
- verification reported success. If a task's sole outcome is creating one or more directories (no
329
- files):
330
- - Give it a `.gitkeep` (or equivalent) placeholder file in every leaf directory it creates, so
331
- there is something for git to actually track.
332
- - Verify with a command that checks the directory is *tracked*, not just present on disk — e.g.
333
- `git ls-files <path> | grep -q .` or `test -f <path>/.gitkeep` — instead of (or in addition to)
334
- `test -d <path>`.
335
-
336
- ---
337
-
338
331
  ## Clarification Protocol
339
332
 
340
333
  When decomposing User Stories into tasks, if the Requirements or Tech-Spec are ambiguous, **stop and ask the user** before proceeding. Use the `AskUserQuestion` tool to present:
@@ -354,11 +347,12 @@ Do NOT guess or invent task decompositions when the source is unclear. Ask first
354
347
  - **Atomicity is mandatory** — each task must have one outcome, one domain, one agent type, one commit subject
355
348
  - **Task granularity**: target ≤ 15 minutes of agent time; split any task that cannot be scoped to ≤ 15 minutes
356
349
  - **Domain assignment must be unambiguous** — use the domain table in Step 3
357
- - **dependsOn must reference only task IDs defined in this JSON** — no dangling references
350
+ - **dependsOn must reference only task IDs defined in this response** — no dangling references
358
351
  - **Cross-phase dependencies are expected** — US tasks may depend on INFRA tasks; cross-US task dependencies are also valid
359
352
  - **INFRA phase first** (if present), then US phases in priority order (Must before Should before Could)
360
353
  - **Within a phase, order tasks by layer dependency** (DB → BE → FE → TEST); independent tasks within the same layer may be listed in any order
361
354
  - **acceptanceCriteria** must list only AC IDs whose Requirements AC-table scope ("Related UC") matches this task's own phase, or that are marked cross-cutting ("All UCs"/multiple UCs including this phase's). **Never cite an AC owned by a different phase on this task, even when this task is prerequisite or contributing work for it** — an INFRA task that lays groundwork for an AC verified in a later US does not "cover" that AC; leave `acceptanceCriteria` empty `[]` on that task instead (e.g., pure infrastructure setup, or groundwork for an AC owned elsewhere). If an AC is genuinely cross-cutting, reflect that in the AC's own "Related UC" column, not by citing it from an unrelated task.
362
- - **schemaVersion must be exactly 2** — do not omit it, do not use any other value
355
+ - **`sourceUC` must reflect what a phase's tasks actually implement, not its number** — see Step 2.
356
+ - **schemaVersion must be exactly 3** — do not omit it, do not use any other value
363
357
  - **No pipes, CR, or LF** in `phase.title`, `task.title`, or `commit.subject` — these characters break CSV generation
364
358
  - **Traceability**: every UC must map to a US phase; every Must-priority AC must be covered by at least one task's `acceptanceCriteria` **within that AC's own owning US phase** (per its "Related UC" scope) — not by a task in an earlier or later phase, even one that contributes toward satisfying it eventually
@@ -7,18 +7,23 @@ tools: Read, Glob, Grep
7
7
 
8
8
  # Validate Feature Docs
9
9
 
10
- A QA agent that cross-references `feature.md` against `{PREFIX}-Requirements.md` and `{PREFIX}-Tech-Spec.md` and identifies coverage gaps in a single validation pass. The host workflow owns revisions and the three-cycle limit. Do not dispatch other agents or revise the input documents.
10
+ A QA agent that cross-references `feature.md` against the Requirements and Tech-Spec documents and identifies coverage gaps in a single validation pass. The host workflow owns revisions and the three-cycle limit. Do not dispatch other agents or revise the input documents.
11
11
 
12
12
  ---
13
13
 
14
14
  ## Phase 1 — Load Documents
15
15
 
16
16
  1. Extract the prefix from the folder name (`FTR-001-user-management` → `FTR-001`)
17
- 2. Read all three documents fully:
18
- - `feature.md` — source of truth
19
- - `{PREFIX}-Requirements.md`
20
- - `{PREFIX}-Tech-Spec.md`
21
- 3. If an input document is missing, record a finding identifying it, skip checks that require it, and complete Phases 6 and 7 with `valid: false`. Never claim full coverage when an input could not be read.
17
+ 2. Read `feature.md` fully — its path is given to you first; use your Read tool.
18
+ 3. The Requirements and Tech-Spec document text is included directly below, inlined into this
19
+ prompt under `Requirements:` and `Tech-Spec:` labels — do NOT attempt to locate or Read
20
+ `{PREFIX}-Requirements.md`/`{PREFIX}-Tech-Spec.md` on disk; they are not guaranteed to exist
21
+ (both documents are generated and stored in a split, per-item format by default — `req/`,
22
+ `spec/` — and only assembled into a single file when a user explicitly requests it). Use the
23
+ inlined text as the complete, authoritative content of each document for this validation pass.
24
+ 4. If either inlined document's text is empty or missing, record a finding identifying it, skip
25
+ checks that require it, and complete Phases 6 and 7 with `valid: false`. Never claim full
26
+ coverage when an input could not be read.
22
27
 
23
28
  ---
24
29
 
@@ -44,7 +49,7 @@ Label each claim with its origin section in `feature.md`.
44
49
 
45
50
  For each claim extracted in Phase 2, check whether it is addressed in the target document:
46
51
 
47
- ### Requirements coverage (against `{PREFIX}-Requirements.md`)
52
+ ### Requirements coverage (against the inlined Requirements text)
48
53
 
49
54
  | Check | Pass condition |
50
55
  |-------|---------------|
@@ -55,7 +60,7 @@ For each claim extracted in Phase 2, check whether it is addressed in the target
55
60
  | Every out-of-scope item → Out of Scope section | Listed |
56
61
  | Every functional behaviour → at least one Acceptance Criterion | AC in Given/When/Then format |
57
62
 
58
- ### Tech-Spec coverage (against `{PREFIX}-Tech-Spec.md`)
63
+ ### Tech-Spec coverage (against the inlined Tech-Spec text)
59
64
 
60
65
  | Check | Pass condition |
61
66
  |-------|---------------|
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: "Assemble Docs — renders the split Requirements/Tech-Spec/Work-Breakdown sources into single combined documents, on demand. Usage: /assemble-docs <feature-slug> [req|spec|wbs|all]"
3
+ argument-hint: <feature-slug> [req|spec|wbs|all]
4
+ allowed-tools: Bash, Glob, Read
5
+ ---
6
+
7
+ Produce an on-demand, single-file view of one or more of this feature's split documents.
8
+
9
+ Approach:
10
+ 1. Locate the feature folder under `docs/features/` (match by slug or FTR id) from the first argument.
11
+ 2. Extract the prefix from the folder name (`FTR-025-slug` → `FTR-025`).
12
+ 3. Determine `which` from the second argument (default `all`).
13
+ 4. Run: `node .claude/scripts/assemble-docs.js <feature-dir> <prefix> <which>`
14
+ 5. Report exactly what was written (the script's own JSON `written` array), or surface its error message verbatim if it failed — do not retry or guess an alternative path.
15
+
16
+ This command is a read-only rendering action: it never affects Gate 1/Gate 2, approvals, or any digest. Running it is always safe, at any point in the pipeline.
@@ -10,10 +10,15 @@ Steps:
10
10
  1. Locate the feature folder under `docs/features/`. Match by slug or FTR id (e.g. `FTR-001-user-login` matches both `001` and `user-login`).
11
11
  2. For each of the following artifacts, check presence and basic completeness:
12
12
  - `feature.md` — the source brief
13
- - `{PREFIX}-Requirements.md` — functional requirements
14
- - `{PREFIX}-Tech-Spec.md` — technical specification
15
- - `{PREFIX}-Work-Breakdown.md` — user stories + tasks
16
- - Implementation status: cross-check tasks in the work breakdown against the codebase. For each task, report `done` / `partial` / `not started` based on whether the referenced files/symbols exist.
13
+ - Requirements — ✅ if either `req/index.md` (split, default) or `{PREFIX}-Requirements.md`
14
+ (assembled, legacy or user-requested via `/assemble-docs`) exists; note which shape was found
15
+ - Tech-Spec — ✅ if either `spec/index.md` (split, default) or `{PREFIX}-Tech-Spec.md`
16
+ (assembled) exists; note which shape was found
17
+ - Work Breakdown — ✅ if either `wbs/index.json` (split, always the case since FTR-020) or
18
+ `{PREFIX}-Work-Breakdown.md` (rendered, only present if `/assemble-docs` was run) exists
19
+ - Implementation status: cross-check tasks in the work breakdown against the codebase (read
20
+ `wbs/` directly). For each task, report `done` / `partial` / `not started` based on whether
21
+ the referenced files/symbols exist.
17
22
  3. Output a single markdown table with columns: `Artifact | Status | Notes`. Status uses ✅ / ⚠️ / ❌. Keep notes under 80 chars.
18
23
  4. After the table, list the next 1–3 actionable items in priority order.
19
24
 
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // FTR-022: deterministic renderer producing {PREFIX}-AC-Task-Matrix.md — a table mapping every
5
+ // Requirements Acceptance Criterion to the Work Breakdown task(s)/phase(s) that cover it. No new
6
+ // data, no new validation: every fact here is already computed and enforced by wb-validate.js's
7
+ // checks 19-21 (checkAcCoverage, wb-graph-checks.js). This script never re-validates; it assumes
8
+ // its inputs already passed those checks and only renders what they already guarantee.
9
+
10
+ const fs = require('fs')
11
+ const path = require('path')
12
+ const { loadWorkBreakdown } = require('./wbs-assembler')
13
+ const { parseAcTable } = require('./wb-graph-checks')
14
+ const { parseFileInventoryEntries } = require('./tech-spec-graph-checks')
15
+
16
+ function computeCoverage(wb, acMap) {
17
+ // taskPhase mirrors checkAcCoverage's own seenTaskIds pattern — not read by this function's
18
+ // own return value, kept available for any future lookup that needs "which phase owns task X"
19
+ // directly instead of iterating coverage's per-AC sets.
20
+ const taskPhase = new Map()
21
+ for (const phase of wb.phases) for (const task of phase.tasks) taskPhase.set(task.id, phase.id)
22
+
23
+ const coverage = new Map([...acMap.keys()].map(acId => [acId, { taskIds: new Set(), phaseIds: new Set() }]))
24
+ for (const phase of wb.phases) {
25
+ for (const task of phase.tasks) {
26
+ for (const acId of (task.acceptanceCriteria || [])) {
27
+ if (!coverage.has(acId)) continue // AC_NOT_FOUND is wb-validate.js's job, not this script's
28
+ coverage.get(acId).taskIds.add(task.id)
29
+ coverage.get(acId).phaseIds.add(phase.id)
30
+ }
31
+ }
32
+ }
33
+ return coverage
34
+ }
35
+
36
+ // FTR-023: acId -> Set<filePath>, built from Tech-Spec §9 File Inventory "Addresses" cells.
37
+ function computeTechSpecCoverage(entries, acMap) {
38
+ const coverage = new Map([...acMap.keys()].map(acId => [acId, new Set()]))
39
+ for (const entry of entries) {
40
+ for (const acId of entry.acIds) {
41
+ if (coverage.has(acId)) coverage.get(acId).add(entry.filePath)
42
+ }
43
+ }
44
+ return coverage
45
+ }
46
+
47
+ function renderMatrix(prefix, acMap, coverage, techSpecCoverage = new Map()) {
48
+ const rows = [...acMap.entries()].map(([acId, ac]) => {
49
+ const relatedUc = ac.unscoped ? 'All UCs' : ac.relatedUCs.join(', ')
50
+ const cov = coverage.get(acId)
51
+ const taskIds = [...cov.taskIds].join(', ') || '—'
52
+ const phaseIds = [...cov.phaseIds].join(', ') || '—'
53
+ const techSpecFiles = [...(techSpecCoverage.get(acId) || [])].join(', ') || '—'
54
+ return `| ${acId} | ${ac.criterion} | ${ac.priority} | ${relatedUc} | ${techSpecFiles} | ${taskIds} | ${phaseIds} |`
55
+ })
56
+ return `# AC ↔ Task Traceability Matrix — ${prefix}\n\n` +
57
+ '| AC ID | Criterion | Priority | Related UC | Addressed by Tech-Spec File(s) | Covering Task(s) | Covering Phase(s) |\n' +
58
+ '|---|---|---|---|---|---|---|\n' +
59
+ rows.join('\n') + '\n\n' +
60
+ '> **Legend:** **AC ID** — unique identifier from the Requirements Acceptance Criteria ' +
61
+ 'table. **Criterion** — the testable Given/When/Then statement, copied verbatim. ' +
62
+ '**Priority** — MoSCoW; every `Must` row is guaranteed non-empty in **Covering Task(s)** by ' +
63
+ 'wb-validate.js check 21 before this file is ever generated. **Related UC** — which Use ' +
64
+ 'Case(s) this AC belongs to, or `All UCs`. **Addressed by Tech-Spec File(s)** — every ' +
65
+ 'Tech-Spec file whose `Addresses` column cites this AC, comma-separated, or `—` if none. ' +
66
+ '**Covering Task(s)** — every Work Breakdown task whose `acceptanceCriteria` cites this AC, ' +
67
+ 'comma-separated, or `—` if none. **Covering Phase(s)** — the distinct phase(s) those tasks ' +
68
+ 'belong to, comma-separated, or `—` if none.\n'
69
+ }
70
+
71
+ if (require.main === module) {
72
+ const [wbsPath, requirementsPath, techSpecPath, prefix] = process.argv.slice(2)
73
+ if (!wbsPath || !requirementsPath || !techSpecPath || !prefix) {
74
+ process.stderr.write('Usage: node ac-task-matrix.js <wbs-path> <requirements-path> <tech-spec-path> <prefix>\n')
75
+ process.exit(1)
76
+ }
77
+ const wb = loadWorkBreakdown(path.resolve(wbsPath))
78
+ const acMap = parseAcTable(path.resolve(requirementsPath))
79
+ const coverage = computeCoverage(wb, acMap)
80
+ const techSpecEntries = parseFileInventoryEntries(fs.readFileSync(path.resolve(techSpecPath), 'utf8'))
81
+ const techSpecCoverage = computeTechSpecCoverage(techSpecEntries, acMap)
82
+ const outPath = path.join(path.dirname(path.resolve(requirementsPath)), `${prefix}-AC-Task-Matrix.md`)
83
+ fs.writeFileSync(outPath, renderMatrix(prefix, acMap, coverage, techSpecCoverage))
84
+ console.log(JSON.stringify({ outputPath: outPath }))
85
+ }
86
+
87
+ module.exports = { computeCoverage, renderMatrix, computeTechSpecCoverage }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // FTR-025: on-demand assembly of the split Requirements/Tech-Spec/Work-Breakdown sources into
5
+ // single-file views. Runs as a plain Node CLI outside the sandboxed workflow runtime, so it can
6
+ // `require()` req-assembler.js/tech-spec-assembler.js directly (no subprocess round-trip needed)
7
+ // — it shells out only to wb-render.js, which is CLI-only (writes its own two output files and
8
+ // exits, no exported function to call in-process). This script never runs automatically as part
9
+ // of generation; it is the explicit, user-triggered alternative to that.
10
+
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+ const { execFileSync } = require('child_process')
14
+ const { loadRequirements, AssemblyError: ReqAssemblyError } = require('./req-assembler')
15
+ const { loadTechSpec, AssemblyError: SpecAssemblyError } = require('./tech-spec-assembler')
16
+
17
+ // loadRequirements()/loadTechSpec() already throw a structured AssemblyError naming the exact
18
+ // missing path when the directory doesn't exist (fs.statSync(inputPath, {throwIfNoEntry:false})
19
+ // returning null) — no separate existence pre-check needed here, it would only duplicate what
20
+ // the assembler already does correctly.
21
+ function assembleReq(featureDir, prefix) {
22
+ const outPath = path.join(featureDir, `${prefix}-Requirements.md`)
23
+ fs.writeFileSync(outPath, loadRequirements(path.join(featureDir, 'req')))
24
+ return outPath
25
+ }
26
+
27
+ function assembleSpec(featureDir, prefix) {
28
+ const outPath = path.join(featureDir, `${prefix}-Tech-Spec.md`)
29
+ fs.writeFileSync(outPath, loadTechSpec(path.join(featureDir, 'spec')))
30
+ return outPath
31
+ }
32
+
33
+ function assembleWbs(featureDir, prefix) {
34
+ const wbsDir = path.join(featureDir, 'wbs')
35
+ if (!fs.existsSync(wbsDir)) throw new ReqAssemblyError(`wbs/ not found at ${wbsDir}`)
36
+ // wb-render.js is CLI-only (writes {prefix}-Work-Breakdown.md/.csv itself and exits) — shell
37
+ // out, same subprocess convention pm-phase2.js used for its own (now-removed) automatic call.
38
+ // No destination-dir argument needed: wb-render.js defaults it to the parent of its own
39
+ // <wbs-dir> argument, which is already featureDir.
40
+ execFileSync(process.execPath, [path.join(__dirname, 'wb-render.js'), wbsDir, prefix])
41
+ return [path.join(featureDir, `${prefix}-Work-Breakdown.md`), path.join(featureDir, `${prefix}-Work-Breakdown.csv`)]
42
+ }
43
+
44
+ const HANDLERS = { req: assembleReq, spec: assembleSpec, wbs: assembleWbs }
45
+
46
+ if (require.main === module) {
47
+ const [featureDir, prefix, which = 'all'] = process.argv.slice(2)
48
+ if (!featureDir || !prefix) {
49
+ process.stderr.write('Usage: node assemble-docs.js <feature-dir> <prefix> [req|spec|wbs|all]\n')
50
+ process.exit(1)
51
+ }
52
+ const targets = which === 'all' ? Object.keys(HANDLERS) : [which]
53
+ if (!targets.every(t => HANDLERS[t])) {
54
+ process.stderr.write(`Unknown target "${which}" — expected req, spec, wbs, or all\n`)
55
+ process.exit(1)
56
+ }
57
+ try {
58
+ const written = targets.map(t => HANDLERS[t](path.resolve(featureDir), prefix)).flat()
59
+ process.stdout.write(JSON.stringify({ written }) + '\n')
60
+ } catch (err) {
61
+ process.stderr.write(`Error: ${err.message}\n`)
62
+ process.exit((err instanceof ReqAssemblyError || err instanceof SpecAssemblyError) ? 2 : 1)
63
+ }
64
+ }
65
+
66
+ module.exports = { assembleReq, assembleSpec, assembleWbs }