@dtlabs/ai-toolkit 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.24.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.2...v0.24.0) (2026-10-22)
3
+
4
+
5
+ ### Features
6
+
7
+ * **pm-phase2:** split WBS skeleton generation into phase enumeration + per-phase task generation (FTR-033) ([762684c](https://github.com/dtlab-tech/ai-toolkit/commit/762684c64aac4b3c9c9138cb6620d3c7d26146c9))
8
+ * **pm-phase2:** split WBS skeleton generation into phase enumeration + per-phase task generation (FTR-033) ([a817707](https://github.com/dtlab-tech/ai-toolkit/commit/a817707e7a6aaf7f9b09d51235659ef68435cd49))
9
+
10
+ ## [0.23.2](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.1...v0.23.2) (2026-10-22)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **pm-phase2:** bound the WBS skeleton call's own Tech-Spec exploration ([6bca6b5](https://github.com/dtlab-tech/ai-toolkit/commit/6bca6b51f4d495b5912434f4ff83bdcefed2d979))
16
+ * **pm-phase2:** bound the WBS skeleton call's own Tech-Spec exploration (needsComponentDetail) ([804d2cc](https://github.com/dtlab-tech/ai-toolkit/commit/804d2ccf529e0e441b81f9e814a168a9a005a783))
17
+
2
18
  ## [0.23.1](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.0...v0.23.1) (2026-10-22)
3
19
 
4
20
 
@@ -220,6 +220,19 @@ const CATALOG = [
220
220
  deprecationTarget: null,
221
221
  platforms: ['claude'],
222
222
  },
223
+ {
224
+ agentId: 'gaia.agent.planner.work-breakdown-phase',
225
+ nativeNames: { claude: 'gaia-generate-work-breakdown-phase' },
226
+ relativeInstallPath: '.claude/agents/gaia-generate-work-breakdown-phase.md',
227
+ role: 'planner',
228
+ type: 'work-breakdown',
229
+ authorisedPhases: ['planning'],
230
+ allowedPipelines: ['implement-feature'],
231
+ minimumToolkitVersion: '0.13.0',
232
+ deprecated: false,
233
+ deprecationTarget: null,
234
+ platforms: ['claude'],
235
+ },
223
236
  {
224
237
  agentId: 'gaia.agent.planner.work-breakdown-task',
225
238
  nativeNames: { claude: 'gaia-generate-work-breakdown-task' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: gaia-generate-work-breakdown-phase
3
+ description: "Generates one phase's own tasks (structural fields) and narrative context for a Work Breakdown, given that phase's scoped Requirements/Tech-Spec context and every prior phase's already-decided tasks. Input: the phase to generate, its scoped Requirements excerpt, the Tech-Spec skeleton, and prior-phase task projections"
4
+ model: sonnet
5
+ tools: Read, Glob, Grep
6
+ ---
7
+
8
+ # Generate Work Breakdown — Per-Phase Task Pass
9
+
10
+ You are an **expert software architect**. Given ONE phase's own scoped Requirements/Tech-Spec
11
+ context, decide that phase's own tasks and narrative context. A separate, lighter agent
12
+ (`gaia-generate-work-breakdown`) has already decided the full ORDERED list of phases (including
13
+ this one's own `id`/`type`/`title`/`sourceUC`/`commit`) before you were ever dispatched — you do
14
+ not decide that; you decompose THIS phase's own work into tasks.
15
+
16
+ ## Input
17
+
18
+ Your prompt directly inlines:
19
+ 1. **This phase's own skeleton entry** — `id`/`type`/`title`/`commit`/`sourceUC`, already decided,
20
+ FINAL (do not revise `sourceUC` — it already scopes the Requirements excerpt you were given).
21
+ 2. **Scoped Requirements** — `req/index.md`'s own feature-wide content (Business Rules, Acceptance
22
+ Criteria table, NFRs — never per-UC-scoped) PLUS only the UC(s) THIS phase's own `sourceUC`
23
+ realizes (their own full flow detail). You do not need, and will not receive, any OTHER UC's
24
+ own flow detail.
25
+ 3. **Tech-Spec — SKELETON ONLY**, same as every other tier in this pipeline (Section 9's
26
+ component index, feature-wide sections; never a component's own design body). If a task
27
+ decision genuinely needs a specific component's own body, see "Requesting Component Detail"
28
+ below — do NOT self-explore `spec/components/*` for it.
29
+ 4. **Every PRIOR phase's own already-decided tasks** — as `id`/`title`/`outcome`/`domain` only
30
+ (not full task objects): use these to reference a real prerequisite in `dependsOn`, and to
31
+ recognize/avoid re-implementing something a prior phase already built. You will NEVER be given
32
+ a LATER phase's tasks — they do not exist yet; never invent a dependency on one.
33
+
34
+ ## Step 0 — Read Project Conventions (MANDATORY)
35
+
36
+ Read `AGENTS.md` from the current working directory — tech stack/frameworks (domain
37
+ classification), directory structure (task file paths), build/verification commands. Ensures your
38
+ tasks reference the correct technologies, paths, and commands.
39
+
40
+ ## Atomicity Contract
41
+
42
+ Every task must be the **smallest independently implementable, verifiable, and committable unit of
43
+ work**:
44
+
45
+ - **One observable outcome** — the task produces exactly one concrete artifact or behavioral
46
+ change. State it in `outcome` as a factual sentence describing what exists after the task is
47
+ done.
48
+ - **One domain** — exactly one of `BE`, `FE`, `DB`, `DevOps`, `INFRA`, `TEST`.
49
+ - **One agent type** — exactly one of `developer-backend`, `developer-frontend`,
50
+ `developer-testing`, `review-solution` (`developer-database` is deprecated — use
51
+ `developer-backend` for DB domain tasks).
52
+ - **One commit** — a single `commit.subject` describing the change.
53
+ - **Estimated ≤ 15 minutes of agent time** — the target. Up to 20 minutes is acceptable but above
54
+ target. Over 30 minutes is invalid — split the task instead.
55
+
56
+ **Anti-patterns to avoid (caught by semantic validation):**
57
+ - Titles or outcomes containing "N types", "all adapters", "complete CRUD", "implement and test" —
58
+ hidden multiplicity.
59
+ - Bundling two independently verifiable activities — split into separate tasks.
60
+ - Scope misalignment: a task that implements a behavior belonging to a different phase.
61
+ - **Duplicate implementation**: re-describing, as a new `BE`/`FE`/`DB` task, an outcome or file
62
+ already fully produced by a PRIOR phase's task (given to you in Input #4). If the behavior
63
+ already exists, this phase's task must be `domain: TEST` (`developer-testing`), verifying it via
64
+ `dependsOn` pointing at the task(s) that built it — never a second implementation task.
65
+ - **Domain mismatch on verification-only tasks**: a task whose outcome is a confirmation of
66
+ already-built behavior (title starting with "Verify", "Confirm", or similar) must be
67
+ `domain: TEST` / `agentType: developer-testing` — never an implementation agent type.
68
+
69
+ ## Decomposition
70
+
71
+ Analyze this phase's own scoped Requirements excerpt and the Tech-Spec to identify all concrete
72
+ implementation tasks, following the Atomicity Contract.
73
+
74
+ | Domain | What to extract |
75
+ |--------|----------------|
76
+ | **DB** | Entity models, schema changes, migrations, seed data, indexes |
77
+ | **BE** | DTOs/models, validators, services, API endpoints, DI registration, mapping config |
78
+ | **FE** | Pages, components, route configuration, i18n keys, type definitions, API service calls |
79
+ | **INFRA** | Configuration, packages, manifest updates, auth/policy setup, agent definitions, scripts |
80
+ | **TEST** | Unit tests, integration tests, E2E tests, build verification |
81
+ | **DevOps** | CI/CD pipeline config, deployment scripts, infrastructure-as-code |
82
+
83
+ **Task ID format (mandatory):**
84
+ - If this is the INFRA phase: `INFRA-TASK-{DOMAIN}-{NN}`
85
+ - Otherwise: `{PHASE-ID}-TASK-{DOMAIN}-{NN}` (counter resets per phase)
86
+
87
+ `{DOMAIN}` in the task ID must match the `domain` field. `{NN}` is a two-digit zero-padded counter.
88
+
89
+ **Required task fields** — identical shape to the skeleton task entry this pipeline used before
90
+ FTR-033, MINUS `estimate` (produced later by `gaia-generate-work-breakdown-task`, unchanged since
91
+ FTR-032): `title`, `outcome`, `domain`, `agentType`, `dependsOn` (task IDs from THIS phase or any
92
+ PRIOR phase only — never a later one, and never a dangling reference), `acceptanceCriteria` (AC IDs
93
+ scoped to this phase's own `sourceUC`, never an AC owned by a different phase), `outputCount` (≥1),
94
+ `commit` (`{ "type": "...", "subject": "..." }`, subject with no `|`, CR, or LF).
95
+
96
+ **Domain → agentType mapping (default):** `DB`/`BE`/`INFRA`/`DevOps` → `developer-backend`;
97
+ `FE` → `developer-frontend`; `TEST` → `developer-testing`. Override when the Tech-Spec specifies a
98
+ different agent type for a task.
99
+
100
+ ### Requesting Component Detail
101
+
102
+ Identical mechanism to the phase-enumeration agent's own sibling concern (and to
103
+ `gaia-validate-feature-docs`'s own established FTR-028 precedent): if a task decision genuinely
104
+ needs a specific component's own design body beyond Section 9's one-line summary, populate
105
+ `needsComponentDetail` with `{fileSlug, reason}` entries instead of guessing or exploring. The host
106
+ answers with a single, bounded second call; there is no third round. Leave it `[]` when you have
107
+ everything you need (the common case).
108
+
109
+ ## Output
110
+
111
+ Return, as your final structured response, and nothing else:
112
+
113
+ ```json
114
+ {
115
+ "context": "2-4 sentences: why this phase exists, how its own tasks relate, any sequencing note. Never restate the title.",
116
+ "tasks": {
117
+ "US-01-TASK-BE-01": {
118
+ "title": "...", "outcome": "...", "domain": "BE", "agentType": "developer-backend",
119
+ "dependsOn": [], "acceptanceCriteria": ["AC-01"], "outputCount": 1,
120
+ "commit": { "type": "feat", "subject": "..." }
121
+ }
122
+ },
123
+ "needsComponentDetail": []
124
+ }
125
+ ```
126
+
127
+ Every phase must have at least one task. `needsComponentDetail` is optional — omit or leave `[]`
128
+ in the common case.
129
+
130
+ ## Guidelines
131
+
132
+ - Write in English. Derive content only from the inlined Requirements/Tech-Spec — never invent a
133
+ task not grounded in them.
134
+ - Never self-explore the filesystem for Requirements/Tech-Spec content — both are inlined.
135
+ - Atomicity is mandatory (see Atomicity Contract above).
136
+ - `dependsOn` may reference a task from this phase or any prior phase — never a later one, never a
137
+ dangling reference.
138
+ - Order tasks by layer dependency within the phase (DB → BE → FE → TEST); independent tasks in the
139
+ same layer may be listed in any order.
140
+ - `acceptanceCriteria` scoped to this phase's own UC(s) — never cite an AC owned by a different
141
+ phase, even for groundwork/prerequisite work; leave it empty `[]` instead.
142
+ - No pipes, CR, or LF in `task.title` or `commit.subject`.
@@ -1,383 +1,73 @@
1
1
  ---
2
2
  name: gaia-generate-work-breakdown
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"
3
+ description: "Enumerates the ordered phase list (no tasks) for a Work Breakdown, from validated and approved Requirements and Tech-Spec. Input: path to feature.md"
4
4
  model: sonnet
5
- tools: Read, Glob, Grep
5
+ tools: Read
6
6
  ---
7
7
 
8
- # Generate Work Breakdown — Skeleton Pass
8
+ # Generate Work Breakdown — Phase Enumeration 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 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.
13
-
14
- ---
10
+ You are an **expert software architect**. Given a feature's functional requirements and technical
11
+ specification, decide ONLY the ordered list of phases a Work Breakdown will have — never any task.
12
+ A separate, per-phase agent (`gaia-generate-work-breakdown-phase`) is dispatched once per phase,
13
+ AFTER this call returns, to decide that phase's own tasks. You do **not** produce tasks, task
14
+ counts, or a phase's own narrative `context` — see that agent's own instructions for those.
15
15
 
16
16
  ## Input
17
17
 
18
- The user provides the path to a `feature.md` file. From the same directory, read:
18
+ Your prompt directly inlines the content you need:
19
+ 1. Requirements — full text, every UC's own flow detail included
20
+ 2. Tech-Spec — SKELETON ONLY (Section 9's component index, feature-wide sections; never a
21
+ component's own design body)
19
22
 
20
- 1. Requirements — `req/index.md` (the split format, FTR-021; source of User Stories derived from
21
- Use Cases, acceptance criteria, business rules) if it exists, otherwise the legacy
22
- `{PREFIX}-Requirements.md`
23
- 2. Tech-Spec — `spec/index.md` (the split format, FTR-024; source of technical tasks,
24
- implementation order, file inventory, architecture decisions) if it exists, otherwise the
25
- legacy `{PREFIX}-Tech-Spec.md`
26
- 3. `{PREFIX}-Validation-Report.md` — confirms both documents passed coverage validation
27
- 4. `{PREFIX}-Approvals.md` — confirms user has approved the documents
23
+ Do NOT self-explore the filesystem for either document. The path to `feature.md` is given first;
24
+ Read it yourself for narrative context (Problem Statement, Core Flow) — it is small, not inlined.
28
25
 
29
- See Step 1 below for exactly how to read whichever shape (1)/(2) actually are, including per-item
30
- drilldown into `req/use-cases/*.md`/`spec/components/*.md`.
31
-
32
- **This agent runs ONLY after validation and human approval.** If Requirements/Tech-Spec exist in
33
- neither shape, or (3)/(4) are missing, abort and report:
26
+ **This agent runs ONLY after validation and human approval.** If the inlined Requirements or
27
+ Tech-Spec text is empty or missing, abort and report:
34
28
  ```
35
- Cannot generate work breakdown: `{file}` not found.
29
+ Cannot generate work breakdown: Requirements/Tech-Spec text was not provided.
36
30
  The work breakdown requires validated and approved Requirements + Tech-Spec.
37
31
  Run /implement-feature to orchestrate the full pipeline (including validation and approval gates).
38
32
  ```
39
33
 
40
- ---
41
-
42
- ## Step 0 — Read Project Conventions (MANDATORY)
43
-
44
- Before generating the work breakdown, you MUST:
45
-
46
- 1. Read `AGENTS.md` from the current working directory — this defines:
47
- - Tech stack and frameworks (determines domain classification)
48
- - Directory structure (determines file paths in tasks)
49
- - Build and verification commands
50
-
51
- This ensures task descriptions reference the correct technologies, file paths, and verification commands.
52
-
53
- ---
54
-
55
34
  ## Output
56
35
 
57
- Extract the **feature prefix** from the folder name containing `feature.md`:
58
- - Folder: `FTR-001-user-management` → prefix: `FTR-001`
59
- - Folder: `FTR-042-Search-Engine` → prefix: `FTR-042`
60
-
61
- The prefix is everything up to and including the second hyphen-separated segment (pattern: `[A-Z]+-[0-9]+`).
62
-
63
- 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).
64
-
65
- ---
66
-
67
- ## Atomicity Contract
68
-
69
- Every task must be the **smallest independently implementable, verifiable, and committable unit of work**. This means:
70
-
71
- - **One observable outcome** — the task produces exactly one concrete artifact or behavioral change (one file, one endpoint, one migration, one test suite for one check). State it in the `outcome` field as a factual sentence describing what exists after the task is done.
72
- - **One domain** — choose exactly one: `BE`, `FE`, `DB`, `DevOps`, `INFRA`, or `TEST`.
73
- - **One agent type** — choose exactly one: `developer-backend`, `developer-frontend`, `developer-testing`, or `review-solution`. (`developer-database` is deprecated — use `developer-backend` for DB domain tasks.)
74
- - **One commit** — supply a single `commit.subject` describing the change.
75
- - **Estimated ≤ 15 minutes of agent time** — this is the target. If a task cannot be scoped to ≤ 15 minutes, split it further. Tasks up to 20 minutes are acceptable but above target. Tasks over 30 minutes are invalid and will be rejected by the validator.
76
-
77
- **Anti-patterns to avoid (these will be caught by semantic validation):**
78
- - Titles or outcomes containing "N types", "all adapters", "complete CRUD", "implement and test" — these indicate hidden multiplicity.
79
- - Bundling two independently verifiable activities (e.g., "add endpoint and write tests") — split into separate tasks.
80
- - Scope misalignment: a task in US-02 that implements a behavior belonging to US-03.
81
- - **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.
82
- - **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.
83
-
84
- ---
85
-
86
- ## Process
87
-
88
- ### Step 1 — Load and Cross-Reference
89
-
90
- 1. Check whether `req/index.md` exists (the split Requirements format, FTR-021). If it does:
91
- - Read `req/index.md` fully — it already contains the full Use Cases index table, all Business
92
- Rules, all Acceptance Criteria, and all NFRs (FTR-021 kept these feature-wide, not per-UC)
93
- - For any UC whose own full flow description you need beyond the index's short Title/Actor
94
- summary, Read `req/use-cases/{UC-ID}.md` directly
95
- If `req/index.md` does not exist (a legacy feature), Read `{PREFIX}-Requirements.md` fully
96
- instead — extract the same information from the monolithic file.
97
- 2. Check whether `spec/index.md` exists (the split Tech-Spec format, FTR-024). If it does:
98
- - Read `spec/index.md` fully — it already contains Architecture, Section 9's complete File
99
- Inventory/Components table (with Domain/Addresses routing), Implementation Order, and Risks
100
- - For any component whose own Backend/Frontend/design detail you need beyond the Components
101
- table's one-line Purpose/Addresses summary, Read `spec/components/{file-slug}.md` directly —
102
- the `Spec` column in Section 9's table gives you the exact path
103
- - If, after reading the index, you cannot determine from its own summary tables which per-item
104
- files are relevant to a given phase/task decision, fall back to reading every file under
105
- `spec/components/` rather than proceeding with incomplete context
106
- If `spec/index.md` does not exist (a legacy feature), Read `{PREFIX}-Tech-Spec.md` fully
107
- instead — extract architecture, file inventory, implementation order, data model, API
108
- endpoints, frontend components from the monolithic file.
109
-
110
- ### Step 2 — Derive User Stories from Use Cases
111
-
112
- Map each Use Case to a User Story using this transformation:
113
-
114
- | Requirements (UC) | Work Breakdown (US) |
115
- |---|---|
116
- | UC-01: [Title] | US-01: [Title] |
117
- | UC-02: [Title] | US-02: [Title] |
118
-
119
- For each US, derive:
120
- - **Phase id** — `US-NN` matching the UC (e.g., `US-01`)
121
- - **Phase title** — the UC title
122
- - **Phase type** — `"user-story"`
123
- - **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
124
- - **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.
125
- - **Acceptance Criteria** — the AC-XX IDs from the Requirements document that apply to this US
126
-
127
- **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.
128
-
129
- ### Step 3 — Decompose into Tasks
130
-
131
- For each User Story (and for shared infrastructure), analyze the Tech-Spec to identify all concrete implementation tasks following the Atomicity Contract above.
132
-
133
- Decomposition strategy by domain:
134
-
135
- | Domain | What to extract |
136
- |--------|----------------|
137
- | **DB** | Entity models, schema changes, migrations, seed data, indexes |
138
- | **BE** | DTOs/models, validators, services (interface + implementation), API endpoints, DI registration, mapping config |
139
- | **FE** | Pages, components, route configuration, i18n keys, type definitions, API service calls |
140
- | **INFRA** | Configuration (env vars, settings), packages, manifest updates, auth/policy setup, agent definitions, scripts |
141
- | **TEST** | Unit tests, integration tests, E2E tests, build verification |
142
- | **DevOps** | CI/CD pipeline config, deployment scripts, infrastructure-as-code |
143
-
144
- **Task ID format (mandatory — wrong format is a validator error):**
145
- - Infrastructure tasks: `INFRA-TASK-{DOMAIN}-{NN}` — globally unique at feature level (e.g., `INFRA-TASK-BE-01`, `INFRA-TASK-INFRA-02`)
146
- - User Story tasks: `{US-ID}-TASK-{DOMAIN}-{NN}` — counter resets per US; US prefix disambiguates at feature level (e.g., `US-01-TASK-BE-01`, `US-02-TASK-DB-01`)
147
-
148
- `{DOMAIN}` in the task ID must match the `domain` field. `{NN}` is a two-digit zero-padded counter.
149
-
150
- **Required task fields (this pass — the skeleton):**
151
-
152
- | Field | Type | Constraint |
153
- |-------|------|-----------|
154
- | `title` | string | Short descriptive title; must not contain `\|`, CR, or LF |
155
- | `outcome` | string | Single observable outcome — what exists after this task is done |
156
- | `domain` | string | One of: `BE`, `FE`, `DB`, `DevOps`, `INFRA`, `TEST` |
157
- | `agentType` | string | One of: `developer-backend`, `developer-frontend`, `developer-testing`, `review-solution` (deprecated: `developer-database` — maps to `developer-backend`) |
158
- | `dependsOn` | array of strings | Task IDs this task depends on; may be empty `[]`; all IDs must be defined within this same response |
159
- | `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) |
160
- | `estimate` | object | `{ "agentMinutes": N, "tokens": N }` — target ≤ 15 minutes; positive integers |
161
- | `outputCount` | integer | Number of distinct outputs; must be ≥ 1 |
162
- | `commit` | object | `{ "type": "feat", "subject": "..." }` — subject must not contain `\|`, CR, or LF |
163
-
164
- `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).
165
-
166
- **Domain → agentType mapping (default):**
167
- - `DB` → `developer-backend` (note: `developer-database` is a deprecated alias that still resolves — do NOT use it in new Work Breakdowns)
168
- - `BE`, `INFRA`, `DevOps` → `developer-backend`
169
- - `FE` → `developer-frontend`
170
- - `TEST` → `developer-testing`
171
-
172
- The mapping above is a default; override when the Tech-Spec specifies a different agent type for a task.
173
-
174
- ### Step 4 — Identify Shared Infrastructure Tasks
175
-
176
- Some tasks are prerequisites for multiple User Stories. Extract these into a dedicated INFRA phase with:
177
- - Phase `id`: `"INFRA"`
178
- - Phase `type`: `"infrastructure"`
179
- - Phase `title`: descriptive (e.g., `"Shared infrastructure setup"`)
180
- - Phase `commit`: a phase-level commit (e.g., `{ "type": "feat", "subject": "shared infrastructure" }`)
181
- - Phase `sourceUC`: `[]` — shared infrastructure has no single originating UC, by definition
182
- - Task IDs: `INFRA-TASK-{DOMAIN}-{NN}`
183
-
184
- Examples of infrastructure tasks:
185
- - Database context/schema registration (needed by all DB tasks)
186
- - Base model/validator infrastructure (needed by all BE tasks)
187
- - Auth policy setup (needed by all endpoints)
188
- - Route configuration (needed by all FE pages)
189
- - Shared scripts, constants, or fixtures used across multiple USs
190
-
191
- The INFRA phase, if present, must appear first in the `phases` array.
192
-
193
- ### Step 5 — Resolve Dependencies
194
-
195
- For each task, determine `dependsOn` as a list of **task IDs** (not phase IDs):
196
- - All referenced task IDs must be defined within this same response (no dangling references)
197
- - A task may reference both INFRA tasks and tasks within its own phase or other US phases
198
- - No self-references (a task cannot depend on itself)
199
- - No cycles (A → B → A is invalid)
200
-
201
- **Tip:** List only direct dependencies. Transitive dependencies are implied.
202
-
203
- ### Step 6 — Estimate
204
-
205
- For each task, provide:
206
- - `estimate.agentMinutes`: integer; **target ≤ 15**; above 30 is rejected by the validator
207
-
208
- Duration guidance:
209
- | Band | Range | Action |
210
- |------|-------|--------|
211
- | Target | ≤ 15 min | No action needed |
212
- | Above target | 16–20 min | Acceptable |
213
- | Warning | 21–30 min | Prefer to split |
214
- | Split required | > 30 min | **Must split** — the validator will reject this |
215
-
216
- - `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)
217
-
218
- ### Step 7 — Return the Skeleton
219
-
220
- 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:
221
- - Valid JSON
222
- - UTF-8 encoded
223
- - 2-space indentation
224
- - Conform to the schema below
225
-
226
- **Top-level structure:**
36
+ Extract the feature prefix from the folder name containing `feature.md` (pattern `[A-Z]+-[0-9]+`).
37
+ Return, as your final structured response, and nothing else:
227
38
 
228
39
  ```json
229
40
  {
230
41
  "schemaVersion": 3,
231
42
  "feature": "{PREFIX}",
232
- "phases": [ ... ],
233
- "tasks": { ... }
234
- }
235
- ```
236
-
237
- - `schemaVersion` must be exactly `3` (integer)
238
- - `feature` must be the feature prefix string (e.g., `"FTR-020"`)
239
- - `phases` is an ordered array of phase objects; INFRA phase first (if present), then US phases in priority order
240
- - `tasks` is a **map keyed by task ID** (not nested inside each phase) — every phase's `taskIds` array indexes into this map
241
-
242
- **Phase object:**
243
-
244
- ```json
245
- {
246
- "id": "INFRA",
247
- "type": "infrastructure",
248
- "title": "Shared infrastructure setup",
249
- "commit": { "type": "feat", "subject": "shared infrastructure" },
250
- "sourceUC": [],
251
- "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.",
252
- "taskIds": ["INFRA-TASK-BE-01"]
253
- }
254
- ```
255
-
256
- For user-story phases:
257
-
258
- ```json
259
- {
260
- "id": "US-01",
261
- "type": "user-story",
262
- "title": "As a... I want... so that...",
263
- "commit": { "type": "feat", "subject": "implement US-01 user management" },
264
- "sourceUC": ["UC-01"],
265
- "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.",
266
- "taskIds": ["US-01-TASK-DB-01", "US-01-TASK-BE-01"]
267
- }
268
- ```
269
-
270
- - `phase.commit.subject` must not contain `|`, CR, or LF — `wb-render.js` constructs the full conventional message from it
271
- - `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
272
- - Each phase must have at least one task
273
-
274
- **Task entry (in the `tasks` map, keyed by its own ID):**
275
-
276
- ```json
277
- {
278
- "title": "Create base repository interface",
279
- "outcome": "IRepository<T> interface exists at src/interfaces/IRepository.ts with CRUD method signatures",
280
- "domain": "BE",
281
- "agentType": "developer-backend",
282
- "dependsOn": [],
283
- "acceptanceCriteria": [],
284
- "estimate": { "agentMinutes": 8, "tokens": 15000 },
285
- "outputCount": 1,
286
- "commit": { "type": "feat", "subject": "add IRepository base interface" }
287
- }
288
- ```
289
-
290
- **Full example response:**
291
-
292
- ```json
293
- {
294
- "schemaVersion": 3,
295
- "feature": "FTR-NNN",
296
43
  "phases": [
297
- {
298
- "id": "INFRA",
299
- "type": "infrastructure",
300
- "title": "Shared infrastructure setup",
301
- "commit": { "type": "feat", "subject": "shared infrastructure" },
302
- "sourceUC": [],
303
- "context": "Shared repository interface every other phase's data-access tasks depend on — opened first so downstream phases never block on it later.",
304
- "taskIds": ["INFRA-TASK-BE-01"]
305
- },
306
- {
307
- "id": "US-01",
308
- "type": "user-story",
309
- "title": "As an admin, I want to create a user, so that new members can access the system",
310
- "commit": { "type": "feat", "subject": "implement US-01 user creation" },
311
- "sourceUC": ["UC-01"],
312
- "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.",
313
- "taskIds": ["US-01-TASK-DB-01", "US-01-TASK-BE-01"]
314
- }
315
- ],
316
- "tasks": {
317
- "INFRA-TASK-BE-01": {
318
- "title": "Create base repository interface",
319
- "outcome": "IRepository<T> interface exists at src/interfaces/IRepository.ts with CRUD method signatures",
320
- "domain": "BE",
321
- "agentType": "developer-backend",
322
- "dependsOn": [],
323
- "acceptanceCriteria": [],
324
- "estimate": { "agentMinutes": 8, "tokens": 15000 },
325
- "outputCount": 1,
326
- "commit": { "type": "feat", "subject": "add IRepository base interface" }
327
- },
328
- "US-01-TASK-DB-01": {
329
- "title": "Create User entity and migration",
330
- "outcome": "User entity class and initial migration exist; database table is created on migration run",
331
- "domain": "DB",
332
- "agentType": "developer-backend",
333
- "dependsOn": ["INFRA-TASK-BE-01"],
334
- "acceptanceCriteria": ["AC-01", "AC-03"],
335
- "estimate": { "agentMinutes": 12, "tokens": 28000 },
336
- "outputCount": 1,
337
- "commit": { "type": "feat", "subject": "add User entity and migration" }
338
- },
339
- "US-01-TASK-BE-01": {
340
- "title": "Implement CreateUserService",
341
- "outcome": "CreateUserService class exists with createUser method; validates input, persists User, returns created entity",
342
- "domain": "BE",
343
- "agentType": "developer-backend",
344
- "dependsOn": ["US-01-TASK-DB-01"],
345
- "acceptanceCriteria": ["AC-02"],
346
- "estimate": { "agentMinutes": 14, "tokens": 35000 },
347
- "outputCount": 1,
348
- "commit": { "type": "feat", "subject": "add CreateUserService" }
349
- }
350
- }
44
+ { "id": "INFRA", "type": "infrastructure", "title": "Shared infrastructure setup",
45
+ "commit": { "type": "feat", "subject": "shared infrastructure" }, "sourceUC": [] },
46
+ { "id": "US-01", "type": "user-story", "title": "As an admin, I want...",
47
+ "commit": { "type": "feat", "subject": "implement US-01 ..." }, "sourceUC": ["UC-01"] }
48
+ ]
351
49
  }
352
50
  ```
353
51
 
354
- ---
355
-
356
- ## Clarification Protocol
357
-
358
- 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:
359
-
360
- 1. A clear description of what is unclear
361
- 2. Concrete options (2–4) representing reasonable decomposition choices
362
- 3. **Always include** an option: "Leave as open point to discuss later"
363
-
364
- Do NOT guess or invent task decompositions when the source is unclear. Ask first, then continue.
365
-
366
- ---
52
+ - Map each Use Case to a User Story phase (`US-NN` matching the UC by default; see Guidelines
53
+ below for when `sourceUC` legitimately diverges from the number).
54
+ - INFRA phase (if shared prerequisites exist across multiple User Stories) appears first, with
55
+ `sourceUC: []`.
56
+ - **Do not create an additional phase for end-to-end, integration, or overall verification** —
57
+ every phase traces to exactly one UC's worth of work (plus the single INFRA phase).
58
+ - `commit.subject` must not contain `|`, CR, or LF.
367
59
 
368
60
  ## Guidelines
369
61
 
370
- - Write in **English**
371
- - **Derive all content from the Requirements and Tech-Spec** — do not invent tasks not grounded in those documents
372
- - **Atomicity is mandatory** — each task must have one outcome, one domain, one agent type, one commit subject
373
- - **Task granularity**: target ≤ 15 minutes of agent time; split any task that cannot be scoped to ≤ 15 minutes
374
- - **Domain assignment must be unambiguous** — use the domain table in Step 3
375
- - **dependsOn must reference only task IDs defined in this response** — no dangling references
376
- - **Cross-phase dependencies are expected** — US tasks may depend on INFRA tasks; cross-US task dependencies are also valid
377
- - **INFRA phase first** (if present), then US phases in priority order (Must before Should before Could)
378
- - **Within a phase, order tasks by layer dependency** (DB → BE → FE → TEST); independent tasks within the same layer may be listed in any order
379
- - **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.
380
- - **`sourceUC` must reflect what a phase's tasks actually implement, not its number** — see Step 2.
381
- - **schemaVersion must be exactly 3** — do not omit it, do not use any other value
382
- - **No pipes, CR, or LF** in `phase.title`, `task.title`, or `commit.subject` — these characters break CSV generation
383
- - **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
62
+ - Derive every phase from the Requirements and Tech-Spec — do not invent a phase not grounded in
63
+ them.
64
+ - `sourceUC` must reflect what a phase's own (eventual) tasks will actually implement, not its
65
+ number — if a phase's work spans more than one UC, or a UC's own work is split across more than
66
+ one phase, decide `sourceUC` from the REAL decomposition, not ID numbering. This decision is
67
+ FINAL: `sourceUC` is handed to the per-phase agent as a fixed input (it scopes that phase's own
68
+ Requirements excerpt) and cannot be revised downstream — get it right here.
69
+ - INFRA phase first (if present), then US phases in priority order (Must before Should before
70
+ Could).
71
+ - `schemaVersion` must be exactly `3`.
72
+ - Write in English. No pipes, CR, or LF in `title` or `commit.subject` — these break CSV
73
+ generation downstream.
@@ -5,33 +5,40 @@ export const meta = {
5
5
  phases: [{ title: 'Work Breakdown' }, { title: 'Effort Estimate' }],
6
6
  }
7
7
 
8
- // ── Skeleton JSON schema (FTR-020) ───────────────────────────────────────────
9
- // Passed as opts.schema to the skeleton-generation worker call. See
10
- // src/claude/agents/gaia-generate-work-breakdown.md Step 7 for the authoritative
11
- // description of this shape.
12
- const SKELETON_SCHEMA = {
8
+ // ── Phase-enumeration + per-phase JSON schemas (FTR-033) ─────────────────────
9
+ // FTR-033 splits the single monolithic skeleton call (deciding the ENTIRE feature's task graph in
10
+ // one turn — confirmed to fail reliably at real feature scale, independent of prompt size or
11
+ // dispatch mechanism) into two bounded tiers: enumeration (this schema) decides only the ordered
12
+ // phase list; PHASE_TASKS_SCHEMA below decides one phase's own tasks per call.
13
+ const PHASE_ENUMERATION_SCHEMA = {
13
14
  type: 'object',
14
- required: ['schemaVersion', 'feature', 'phases', 'tasks'],
15
+ required: ['schemaVersion', 'feature', 'phases'],
15
16
  properties: {
16
17
  schemaVersion: { const: 3 },
17
18
  feature: { type: 'string' },
18
19
  phases: { type: 'array', items: {
19
20
  type: 'object',
20
- required: ['id', 'type', 'title', 'commit', 'sourceUC', 'context', 'taskIds'],
21
+ required: ['id', 'type', 'title', 'commit', 'sourceUC'],
21
22
  properties: {
22
23
  id: { type: 'string' }, type: { enum: ['infrastructure', 'user-story'] },
23
24
  title: { type: 'string' },
24
25
  commit: { type: 'object', required: ['type', 'subject'],
25
26
  properties: { type: { type: 'string' }, subject: { type: 'string' } } },
26
27
  sourceUC: { type: 'array', items: { type: 'string' } },
27
- // 2-4 sentences: why this phase exists, how its tasks relate, any sequencing note other
28
- // phases/tasks need before opening this phase's own task files — the one part of the
29
- // FTR-020 phase-detail template that genuinely needs narrative judgment, not something
30
- // the host can derive mechanically the way it does Source UC/Tasks-in-phase/Depends-on.
31
- context: { type: 'string' },
32
- taskIds: { type: 'array', items: { type: 'string' } },
33
28
  },
34
29
  } },
30
+ },
31
+ }
32
+
33
+ const PHASE_TASKS_SCHEMA = {
34
+ type: 'object',
35
+ required: ['context', 'tasks'],
36
+ properties: {
37
+ // 2-4 sentences: why this phase exists, how its tasks relate, any sequencing note other
38
+ // phases/tasks need before opening this phase's own task files — the one part of the
39
+ // FTR-020 phase-detail template that genuinely needs narrative judgment, not something
40
+ // the host can derive mechanically the way it does Source UC/Tasks-in-phase/Depends-on.
41
+ context: { type: 'string' },
35
42
  tasks: { type: 'object', additionalProperties: {
36
43
  type: 'object',
37
44
  required: ['title', 'outcome', 'domain', 'agentType', 'dependsOn',
@@ -56,22 +63,78 @@ const SKELETON_SCHEMA = {
56
63
  properties: { type: { type: 'string' }, subject: { type: 'string' } } },
57
64
  },
58
65
  } },
66
+ // Bounded drill-down (mirrors pm-phase1.js's own needsComponentDetail precedent for the
67
+ // feature-doc validator, FTR-028) — declared in `properties` only, never `required`: the
68
+ // common case is an empty array, costing nothing extra. See
69
+ // gaia-generate-work-breakdown-phase.md's own "Requesting Component Detail" section for when
70
+ // the agent should populate this instead of self-exploring spec/components/* (the real,
71
+ // evidenced dominant cost driver on a large feature — confirmed against a real run: 1.18M of
72
+ // 1.38M total tokens on one failing attempt were cache_read_input_tokens, not the declared
73
+ // output schema size). FTR-033: available per-phase, not at the enumeration tier — phase-level
74
+ // decisions (id/type/title/sourceUC/commit) never need a component's own design body.
75
+ needsComponentDetail: { type: 'array', items: { type: 'object',
76
+ properties: { fileSlug: { type: 'string' }, reason: { type: 'string' } }, required: ['fileSlug'] } },
59
77
  },
60
78
  }
61
79
 
62
- // Splits the skeleton response into (a) index.json's structural, map-keyed shape and
63
- // (b) nothing else — phase title/commit/context are NOT copied into index.json (OQ-04); they
64
- // are rendered straight to each phase's detail file by the caller, from the same skeleton
65
- // response, since phase content is fully known the moment this one call returns.
66
- function materializeIndex(skeleton) {
67
- const tasks = {}
68
- for (const [taskId, t] of Object.entries(skeleton.tasks)) {
69
- tasks[taskId] = { ...t, detailFile: `tasks/${taskId}.md`, status: 'pending' }
70
- }
71
- const phases = skeleton.phases.map(p => ({
72
- id: p.id, type: p.type, sourceUC: p.sourceUC, detailFile: `user-stories/${p.id}.md`, taskIds: p.taskIds,
80
+ // Fixed, not externally configurable (same "no real evidence yet justifies external tuning"
81
+ // precedent as every other bounded retry/batch constant in this codebase) — a safety cap, not a
82
+ // typical case; the skeleton agent's own instructions already frame needsComponentDetail as the
83
+ // exception, reserved for a genuine per-task decision gap, not a substitute for broad exploration.
84
+ const SKELETON_DRILL_DOWN_MAX_COMPONENTS = 20
85
+
86
+ // Builds the phase-enumeration prompt: Requirements FULL text + Tech-Spec SKELETON ONLY
87
+ // (never per-component bodies) inlined directly, mirroring pm-phase1.js's own
88
+ // gaia-validate-feature-docs prompt shape exactly — the agent no longer self-reads either
89
+ // document or any per-item file via its own Read/Glob/Grep tool access. No drillDownText
90
+ // parameter — the enumeration call never declares needsComponentDetail (FTR-033: phase-level
91
+ // decisions never need a component's own design body; that mechanism now lives only at the
92
+ // per-phase tier, see buildPhaseTaskPrompt below).
93
+ function buildPhaseEnumerationPrompt(featurePath, requirementsText, techSpecSkeletonText) {
94
+ return [
95
+ featurePath,
96
+ '',
97
+ 'Requirements:',
98
+ requirementsText,
99
+ '',
100
+ 'Tech-Spec:',
101
+ techSpecSkeletonText,
102
+ ].join('\n')
103
+ }
104
+
105
+ // Validates each needsComponentDetail entry's fileSlug against the real component table (a
106
+ // hallucinated/mistyped reference is dropped, never dispatched — mirrors pm-phase1.js's own
107
+ // prepareDrillDownBatches), deduplicates by fileSlug, and caps the total at
108
+ // SKELETON_DRILL_DOWN_MAX_COMPONENTS. Returns the inlinable text block (or null if nothing valid
109
+ // remains) — this reads files directly (deterministic, free), never dispatches another agent.
110
+ function buildComponentDrillDownText(needsComponentDetail, specIndexContent, specDirArg) {
111
+ const components = parseComponentsTableLocal(specIndexContent)
112
+ const bySlug = new Map(components.map(c => [c.fileSlug, c]))
113
+ const uniqueSlugs = [...new Set((needsComponentDetail || [])
114
+ .map(e => e.fileSlug)
115
+ .filter(slug => bySlug.has(slug)))]
116
+ .slice(0, SKELETON_DRILL_DOWN_MAX_COMPONENTS)
117
+ if (uniqueSlugs.length === 0) return null
118
+ return uniqueSlugs.map(slug => {
119
+ const comp = bySlug.get(slug)
120
+ return `### ${comp.filePath} (${slug})\n\n${c.read(`${specDirArg}/components/${slug}`)}`
121
+ }).join('\n\n---\n\n')
122
+ }
123
+
124
+ // Materializes the enumeration response into index.json's structural, map-keyed shape.
125
+ // FTR-033 (BR-09): title/commit ARE additively persisted here (unlike the old materializeIndex,
126
+ // which deliberately left them out, OQ-04) — a resumed run between phase-enumeration and the last
127
+ // phase's completion must be able to re-render any EARLIER-but-already-completed phase's own
128
+ // detail file (which needs title/commit) without re-calling the phase-enumeration agent.
129
+ // `context` is still NOT persisted here — it is only known once that phase's own per-phase call
130
+ // returns, and is rendered straight to the phase's detail file at that point (see the per-phase
131
+ // loop below), same "render immediately, don't round-trip through index.json" principle as before.
132
+ function materializeEnumeratedPhases(phaseList) {
133
+ const phases = phaseList.phases.map(p => ({
134
+ id: p.id, type: p.type, title: p.title, commit: p.commit, sourceUC: p.sourceUC,
135
+ detailFile: `user-stories/${p.id}.md`, taskIds: [],
73
136
  }))
74
- return { schemaVersion: 3, feature: skeleton.feature, phases, tasks }
137
+ return { schemaVersion: 3, feature: phaseList.feature, phases, tasks: {} }
75
138
  }
76
139
 
77
140
  // Duplicated from src/claude/scripts/wbs-assembler.js's renderPhaseDetailBlock/template-engine
@@ -224,6 +287,50 @@ function parseComponentsTableLocal(specIndexContent) {
224
287
  return entries
225
288
  }
226
289
 
290
+ // FTR-033: local re-implementation of req-assembler.js's own parseUseCaseIndexTable, reusing the
291
+ // ALREADY-local _findSectionRange/_parseTableRows this file established for Tech-Spec's own
292
+ // Section 9 table (FTR-026) — same "sandboxed runtime has no require" reason.
293
+ function parseUseCaseIndexTableLocal(reqIndexContent) {
294
+ const { start, end, lines } = _findSectionRange(reqIndexContent, 2)
295
+ return _parseTableRows(lines, start, end).map(cells => {
296
+ const [id, title, actor, priority, fileCol] = cells
297
+ const m = fileCol && fileCol.match(/\(([^)]+)\)/)
298
+ if (!m) throw new Error(`malformed File column for UC "${id}" in req/index.md`)
299
+ return { id, file: m[1] }
300
+ })
301
+ }
302
+
303
+ // Scopes Requirements to one phase's own sourceUC: req/index.md's feature-wide content (Business
304
+ // Rules/AC table/NFRs, never per-UC) is ALWAYS included in full; only the per-UC flow detail is
305
+ // narrowed to this phase's own UC(s). Falls back to the raw text unchanged for a legacy
306
+ // (non-split) feature — hasReq is false, so sourceUC-based file lookups are meaningless anyway.
307
+ function buildPhaseRequirementsExcerpt(reqIndexContent, sourceUC, reqDirArg) {
308
+ const ucTable = parseUseCaseIndexTableLocal(reqIndexContent)
309
+ const matched = ucTable.filter(row => (sourceUC || []).includes(row.id))
310
+ if (matched.length === 0) return reqIndexContent
311
+ const ucBodies = matched.map(row => c.read(`${reqDirArg}/${row.file}`)).join('\n\n')
312
+ return `${reqIndexContent}\n\n${ucBodies}`
313
+ }
314
+
315
+ // Compact prompt for the per-phase task-generation worker: this phase's own skeleton entry, its
316
+ // scoped Requirements excerpt, the Tech-Spec skeleton, and every prior phase's own tasks as a
317
+ // lightweight id/title/outcome/domain projection (never a later phase's tasks, which do not exist
318
+ // yet) — mirrors buildTaskPrompt's own "siblings" list further below, same duplicate-
319
+ // implementation-awareness purpose, one tier up.
320
+ function buildPhaseTaskPrompt(phase, requirementsExcerpt, techSpecSkeletonText, priorTasks, drillDownText) {
321
+ const priorTasksList = priorTasks.length
322
+ ? priorTasks.map(t => `- ${t.id}: ${t.title} — ${t.outcome} (domain: ${t.domain})`).join('\n')
323
+ : '(none — this is the first phase generated)'
324
+ const parts = [
325
+ `Phase:\n${JSON.stringify(phase, null, 2)}`,
326
+ `Requirements (scoped to this phase's own sourceUC, plus feature-wide sections):\n${requirementsExcerpt}`,
327
+ `Tech-Spec:\n${techSpecSkeletonText}`,
328
+ `Tasks already decided by prior phases:\n${priorTasksList}`,
329
+ ]
330
+ if (drillDownText) parts.push(`Component detail you requested:\n${drillDownText}`)
331
+ return parts.join('\n\n')
332
+ }
333
+
227
334
  // Matches a task's own acceptanceCriteria against each component's Addresses column (the same
228
335
  // relation spec/index.md's own Section 9 table already expresses) to give the per-task worker
229
336
  // only the Tech-Spec content relevant to its own task, instead of the entire assembled spec —
@@ -331,21 +438,81 @@ return c.run(meta.name, featureDir, prefix, async () => {
331
438
  const wbsDir = `${featureDir}/wbs`
332
439
  const indexPath = `${wbsDir}/index.json`
333
440
 
334
- // 1. Skeleton (no declared worker() output — host writes every file itself)
441
+ // 1. Phase enumeration (no declared worker() output — host writes every file itself). FTR-033:
442
+ // split from the old single monolithic skeleton call (which decided every phase AND every task
443
+ // in one turn — confirmed to fail reliably at real feature scale, independent of prompt size or
444
+ // dispatch mechanism) into this lightweight enumeration tier plus the bounded per-phase loop
445
+ // below.
335
446
  let index
336
447
  if (!c.exists(indexPath)) {
337
- const skeleton = await c.worker('gaia.agent.planner.work-breakdown', featurePath, { schema: SKELETON_SCHEMA })
338
- index = materializeIndex(skeleton)
339
- const taskIdToPhase = new Map(skeleton.phases.flatMap(p => p.taskIds.map(taskId => [taskId, p.id])))
340
- for (const phase of index.phases) {
341
- const skeletonPhase = skeleton.phases.find(p => p.id === phase.id)
342
- const taskTitles = skeletonPhase.taskIds.map(taskId => ({ id: taskId, title: skeleton.tasks[taskId].title }))
343
- const dependsOnPhases = computeDependsOnPhases(skeleton, phase.id, taskIdToPhase)
344
- c.writeOutput(`${wbsDir}/${phase.detailFile}`, renderPhaseDetailBlock(skeletonPhase, taskTitles, dependsOnPhases))
345
- }
448
+ // Requirements FULL text + Tech-Spec SKELETON ONLY, inlined directly — same shape as
449
+ // pm-phase1.js's own validator prompt (FTR-028). Computed unconditionally here (not reused
450
+ // from the Gate-1 digest check above, which only runs when Approvals.md carries a digest
451
+ // line) so this prompt is always built the same way regardless of that check's own outcome.
452
+ const requirementsForSkeleton = hasReq
453
+ ? c.command(['run-asset', 'scripts/req-assembler.js', '--project', c.root, '--', reqDir]).stdout
454
+ : c.read(file('Requirements.md'))
455
+ const techSpecSkeletonForSkeleton = hasSpec ? c.read(specIndexPath) : c.read(file('Tech-Spec.md'))
456
+
457
+ // No needsComponentDetail round here — the enumeration agent never declares or asks for it
458
+ // (PHASE_ENUMERATION_SCHEMA has no such property; gaia-generate-work-breakdown.md's own Output
459
+ // section doesn't mention it either). Phase-level decisions (id/type/title/sourceUC/commit)
460
+ // never need a component's own design body; the drill-down mechanism now lives ONLY at the
461
+ // per-phase tier below, where task-level decisions genuinely can need it.
462
+ const phaseList = await c.worker('gaia.agent.planner.work-breakdown',
463
+ buildPhaseEnumerationPrompt(featurePath, requirementsForSkeleton, techSpecSkeletonForSkeleton),
464
+ { schema: PHASE_ENUMERATION_SCHEMA })
465
+
466
+ index = materializeEnumeratedPhases(phaseList)
346
467
  c.writeOutput(indexPath, JSON.stringify(index, null, 2))
347
468
  } else {
348
- index = c.json(indexPath) // resume: reuse existing skeleton as-is
469
+ index = c.json(indexPath) // resume: reuse already-enumerated phases / already-generated tasks as-is
470
+ }
471
+
472
+ // 1b. Per-phase task generation (resume-by-phase: a phase whose taskIds is already non-empty is
473
+ // skipped entirely, never redispatched — BR-04/BR-05). Dispatched strictly in enumerated order:
474
+ // INFRA first (if present), then US phases in priority order (same order gaia-generate-
475
+ // work-breakdown.md's own Guidelines already produce for the phases array).
476
+ const reqIndexContentForPhases = c.read(reqIndexPath)
477
+ const techSpecSkeletonForPhases = hasSpec ? c.read(specIndexPath) : c.read(file('Tech-Spec.md'))
478
+ const taskIdToPhase = new Map()
479
+ for (const phase of index.phases) {
480
+ if (phase.taskIds.length > 0) {
481
+ for (const taskId of phase.taskIds) taskIdToPhase.set(taskId, phase.id)
482
+ continue
483
+ }
484
+ const reqExcerpt = hasReq
485
+ ? buildPhaseRequirementsExcerpt(reqIndexContentForPhases, phase.sourceUC, reqDir)
486
+ : reqIndexContentForPhases
487
+ const priorTasks = Object.entries(index.tasks).map(([id, t]) => ({ id, title: t.title, outcome: t.outcome, domain: t.domain }))
488
+
489
+ let phaseResult = await c.worker('gaia.agent.planner.work-breakdown-phase',
490
+ buildPhaseTaskPrompt(phase, reqExcerpt, techSpecSkeletonForPhases, priorTasks),
491
+ { schema: PHASE_TASKS_SCHEMA, label: `work-breakdown-phase:${phase.id}` })
492
+
493
+ // Bounded, single-round drill-down (FTR-028 precedent, reused unmodified from FTR-032's own
494
+ // whole-feature-skeleton mechanism) — scoped to this one phase's call now.
495
+ const phaseNeedsComponentDetail = phaseResult.needsComponentDetail || []
496
+ if (hasSpec && phaseNeedsComponentDetail.length > 0) {
497
+ const drillDownText = buildComponentDrillDownText(phaseNeedsComponentDetail, c.read(specIndexPath), specDir)
498
+ if (drillDownText) {
499
+ phaseResult = await c.worker('gaia.agent.planner.work-breakdown-phase',
500
+ buildPhaseTaskPrompt(phase, reqExcerpt, techSpecSkeletonForPhases, priorTasks, drillDownText),
501
+ { schema: PHASE_TASKS_SCHEMA, label: `work-breakdown-phase:${phase.id}:with-drilldown` })
502
+ }
503
+ }
504
+
505
+ for (const [taskId, t] of Object.entries(phaseResult.tasks)) {
506
+ index.tasks[taskId] = { ...t, detailFile: `tasks/${taskId}.md`, status: 'pending' }
507
+ taskIdToPhase.set(taskId, phase.id)
508
+ }
509
+ phase.taskIds = Object.keys(phaseResult.tasks)
510
+
511
+ const taskTitles = phase.taskIds.map(taskId => ({ id: taskId, title: index.tasks[taskId].title }))
512
+ const dependsOnPhases = computeDependsOnPhases(index, phase.id, taskIdToPhase)
513
+ c.writeOutput(`${wbsDir}/${phase.detailFile}`,
514
+ renderPhaseDetailBlock({ ...phase, context: phaseResult.context }, taskTitles, dependsOnPhases))
515
+ c.writeOutput(indexPath, JSON.stringify(index, null, 2)) // persist BEFORE the next phase's call (BR-04)
349
516
  }
350
517
 
351
518
  // 2. Structural pre-checks (wb-skeleton-check.js, same wb-graph-checks.js functions