@dtlabs/ai-toolkit 0.23.0 → 0.23.2

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,19 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.23.2](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.1...v0.23.2) (2026-10-22)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **pm-phase2:** bound the WBS skeleton call's own Tech-Spec exploration ([6bca6b5](https://github.com/dtlab-tech/ai-toolkit/commit/6bca6b51f4d495b5912434f4ff83bdcefed2d979))
8
+ * **pm-phase2:** bound the WBS skeleton call's own Tech-Spec exploration (needsComponentDetail) ([804d2cc](https://github.com/dtlab-tech/ai-toolkit/commit/804d2ccf529e0e441b81f9e814a168a9a005a783))
9
+
10
+ ## [0.23.1](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.0...v0.23.1) (2026-10-22)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **task-executor:** disable MSYS argv path-conversion for verification commands ([00d66ee](https://github.com/dtlab-tech/ai-toolkit/commit/00d66eecb94f7b5464c58cbfec33eb1793e82664))
16
+
2
17
  ## [0.23.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.22.1...v0.23.0) (2026-10-22)
3
18
 
4
19
 
@@ -1056,6 +1056,21 @@ function _runVerificationCommand(command, cwd, timeoutMs) {
1056
1056
  shell: false, // never true: shell:true on Windows silently runs cmd.exe, not Bash
1057
1057
  stdio: ['ignore', 'pipe', 'pipe'],
1058
1058
  windowsHide: true,
1059
+ // MSYS2_ARG_CONV_EXCL: this `bash` is MSYS2/git-bash on Windows, which by default
1060
+ // auto-converts any argument shaped like a POSIX path before handing it to a native
1061
+ // (non-MSYS-aware) child process. A verification command invoking a native tool with
1062
+ // leading-slash switches — e.g. MSBuild's `/p:Configuration=Release`, this project's own
1063
+ // documented build syntax for a .NET consumer — gets silently mangled: MSYS treats
1064
+ // `/p:Configuration=Release` as a POSIX-path-shaped argument, finds no matching drive for
1065
+ // `p:`, and strips the leading slash entirely, leaving MSBuild unable to recognize it as
1066
+ // a property switch (MSB1008: "Only one project can be specified"). Confirmed by direct
1067
+ // reproduction (`bash -c 'node dump-argv.js /p:Configuration=Release'` prints
1068
+ // `p:Configuration=Release`, leading slash gone, UNLESS this variable is set). '*'
1069
+ // excludes every argument from conversion — verification commands never rely on MSYS's
1070
+ // automatic POSIX-to-Windows path conversion for an argument (a real path either
1071
+ // resolves natively as a relative repo path, or is already given in Windows-native form
1072
+ // for a native tool), so excluding all of them is safe, not just the switch-shaped ones.
1073
+ env: Object.assign({}, process.env, { MSYS2_ARG_CONV_EXCL: '*' }),
1059
1074
  });
1060
1075
  } catch (err) {
1061
1076
  finish({ spawnError: 'failed to spawn bash: ' + err.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -7,32 +7,40 @@ tools: Read, Glob, Grep
7
7
 
8
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 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`.
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`, `outputCount`, `commit`.
11
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.
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, grouping rationale, AND effort estimate (`estimate.agentMinutes`/`estimate.tokens` — moved there, not produced in this pass; see that agent's own instructions). You do **not** produce verification commands, grouping-rationale prose, or an effort estimate — see `gaia-generate-work-breakdown-task` for those.
13
13
 
14
14
  ---
15
15
 
16
16
  ## Input
17
17
 
18
- The user provides the path to a `feature.md` file. From the same directory, read:
19
-
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
28
-
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:
18
+ Your prompt directly inlines the content you need — you do **not** read Requirements or Tech-Spec
19
+ files yourself:
20
+
21
+ 1. **Requirements — full text, every Use Case's own flow detail included.** Inlined under a
22
+ `Requirements:` label.
23
+ 2. **Tech-Spec — SKELETON ONLY.** Inlined under a `Tech-Spec:` label: Section 9's own component
24
+ index table (Purpose, Addresses, Domain, Spec-link columns, one row per component) plus every
25
+ feature-wide section (Overview, Architecture, Security, Database, Configuration, Implementation
26
+ Order, Risks). It deliberately does **not** include any component's own Change/Algorithm/
27
+ Schema/Tests design prose. Do NOT attempt to locate or Read `req/use-cases/*.md`,
28
+ `spec/components/*`, `{PREFIX}-Requirements.md`, or `{PREFIX}-Tech-Spec.md` on disk — use the
29
+ inlined text as the complete input for this pass. If a task-domain/dependency/AC-assignment
30
+ decision genuinely needs a specific component's own design body beyond its Purpose/Addresses/
31
+ Domain summary, do not guess and do not explore the filesystem for it — request it via
32
+ `needsComponentDetail` instead (see "Requesting Component Detail" below). The host will supply
33
+ it and re-ask you in a second, final call.
34
+ 3. `{PREFIX}-Validation-Report.md` and `{PREFIX}-Approvals.md` — the host has already confirmed
35
+ both exist and are satisfied before dispatching you; you do not need to re-check them.
36
+
37
+ The path to `feature.md` is given to you first; Read it yourself for the feature's own narrative
38
+ context (Problem Statement, Core Flow, Edge Cases) — it is small and not inlined.
39
+
40
+ **This agent runs ONLY after validation and human approval.** If the inlined Requirements or
41
+ Tech-Spec text is empty or missing, abort and report:
34
42
  ```
35
- Cannot generate work breakdown: `{file}` not found.
43
+ Cannot generate work breakdown: Requirements/Tech-Spec text was not provided.
36
44
  The work breakdown requires validated and approved Requirements + Tech-Spec.
37
45
  Run /implement-feature to orchestrate the full pipeline (including validation and approval gates).
38
46
  ```
@@ -60,7 +68,7 @@ Extract the **feature prefix** from the folder name containing `feature.md`:
60
68
 
61
69
  The prefix is everything up to and including the second hyphen-separated segment (pattern: `[A-Z]+-[0-9]+`).
62
70
 
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).
71
+ 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 6).
64
72
 
65
73
  ---
66
74
 
@@ -85,27 +93,36 @@ Every task must be the **smallest independently implementable, verifiable, and c
85
93
 
86
94
  ## Process
87
95
 
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.
96
+ ### Step 1 — Cross-Reference the Inlined Documents
97
+
98
+ Use the Requirements text (full detail, every UC's own flow included) and the Tech-Spec text
99
+ (skeleton: Section 9's component index table plus feature-wide sections) exactly as inlined in
100
+ your prompt. Cross-reference them to derive every phase and task below — do not Read, Glob, or
101
+ Grep for either document or any of their per-item files; see "Requesting Component Detail" below if
102
+ you hit a genuine information gap only a specific component's own design body can fill.
103
+
104
+ #### Requesting Component Detail
105
+
106
+ The Tech-Spec's Section 9 table gives you each component's file path, change type, one-line
107
+ Purpose, `Addresses` (AC IDs), and `Domain` — enough for the large majority of domain/dependency/
108
+ AC-assignment decisions. If a SPECIFIC task decision genuinely needs detail beyond that one-line
109
+ summary (e.g. you cannot tell whether a component's own design implies a second, dependent task,
110
+ or which exact AC(s) within a multi-AC row a particular task slice actually covers), do not guess
111
+ and do not attempt to locate the file yourself. Instead, in your structured output, populate
112
+ `needsComponentDetail` with one entry per component whose own body you need, naming its exact
113
+ Spec-link `fileSlug` and a one-line `reason`:
114
+
115
+ ```json
116
+ "needsComponentDetail": [
117
+ { "fileSlug": "AuthMiddleware.cs.md", "reason": "need to know whether token refresh is a separate task from validation" }
118
+ ]
119
+ ```
120
+
121
+ The host will read exactly those files (nothing else) and dispatch you a SECOND, final call with
122
+ their content appended — answer with your complete, final skeleton on that call; there is no third
123
+ round. Leave `needsComponentDetail` as an empty array `[]` when you have everything you need from
124
+ the skeleton-level summary — this is the common case and costs nothing extra. Never name a
125
+ `fileSlug` that is not a real row in Section 9's own table.
109
126
 
110
127
  ### Step 2 — Derive User Stories from Use Cases
111
128
 
@@ -157,11 +174,10 @@ Decomposition strategy by domain:
157
174
  | `agentType` | string | One of: `developer-backend`, `developer-frontend`, `developer-testing`, `review-solution` (deprecated: `developer-database` — maps to `developer-backend`) |
158
175
  | `dependsOn` | array of strings | Task IDs this task depends on; may be empty `[]`; all IDs must be defined within this same response |
159
176
  | `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
177
  | `outputCount` | integer | Number of distinct outputs; must be ≥ 1 |
162
178
  | `commit` | object | `{ "type": "feat", "subject": "..." }` — subject must not contain `\|`, CR, or LF |
163
179
 
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).
180
+ `verification.commands`, `groupingRationale`, and `estimate` (`agentMinutes`/`tokens`) 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). You still judge task SIZE here — see the Atomicity Contract's own ≤15-minute target, which governs how finely you split tasks — you just don't emit a numeric estimate for it.
165
181
 
166
182
  **Domain → agentType mapping (default):**
167
183
  - `DB` → `developer-backend` (note: `developer-database` is a deprecated alias that still resolves — do NOT use it in new Work Breakdowns)
@@ -200,22 +216,7 @@ For each task, determine `dependsOn` as a list of **task IDs** (not phase IDs):
200
216
 
201
217
  **Tip:** List only direct dependencies. Transitive dependencies are implied.
202
218
 
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
+ ### Step 6 — Return the Skeleton
219
220
 
220
221
  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
222
  - Valid JSON
@@ -230,7 +231,8 @@ Return the skeleton's content as your final response, and nothing else (no pream
230
231
  "schemaVersion": 3,
231
232
  "feature": "{PREFIX}",
232
233
  "phases": [ ... ],
233
- "tasks": { ... }
234
+ "tasks": { ... },
235
+ "needsComponentDetail": [ ... ]
234
236
  }
235
237
  ```
236
238
 
@@ -238,6 +240,7 @@ Return the skeleton's content as your final response, and nothing else (no pream
238
240
  - `feature` must be the feature prefix string (e.g., `"FTR-020"`)
239
241
  - `phases` is an ordered array of phase objects; INFRA phase first (if present), then US phases in priority order
240
242
  - `tasks` is a **map keyed by task ID** (not nested inside each phase) — every phase's `taskIds` array indexes into this map
243
+ - `needsComponentDetail` is optional — omit it or leave it `[]` when you have everything you need (the common case); see "Requesting Component Detail" in Step 1. When non-empty, `phases`/`tasks` may be your best DRAFT given what you currently know — the host discards them and re-asks you with the requested detail appended; only your response on that second call is final.
241
244
 
242
245
  **Phase object:**
243
246
 
@@ -281,7 +284,6 @@ For user-story phases:
281
284
  "agentType": "developer-backend",
282
285
  "dependsOn": [],
283
286
  "acceptanceCriteria": [],
284
- "estimate": { "agentMinutes": 8, "tokens": 15000 },
285
287
  "outputCount": 1,
286
288
  "commit": { "type": "feat", "subject": "add IRepository base interface" }
287
289
  }
@@ -321,7 +323,6 @@ For user-story phases:
321
323
  "agentType": "developer-backend",
322
324
  "dependsOn": [],
323
325
  "acceptanceCriteria": [],
324
- "estimate": { "agentMinutes": 8, "tokens": 15000 },
325
326
  "outputCount": 1,
326
327
  "commit": { "type": "feat", "subject": "add IRepository base interface" }
327
328
  },
@@ -332,7 +333,6 @@ For user-story phases:
332
333
  "agentType": "developer-backend",
333
334
  "dependsOn": ["INFRA-TASK-BE-01"],
334
335
  "acceptanceCriteria": ["AC-01", "AC-03"],
335
- "estimate": { "agentMinutes": 12, "tokens": 28000 },
336
336
  "outputCount": 1,
337
337
  "commit": { "type": "feat", "subject": "add User entity and migration" }
338
338
  },
@@ -343,7 +343,6 @@ For user-story phases:
343
343
  "agentType": "developer-backend",
344
344
  "dependsOn": ["US-01-TASK-DB-01"],
345
345
  "acceptanceCriteria": ["AC-02"],
346
- "estimate": { "agentMinutes": 14, "tokens": 35000 },
347
346
  "outputCount": 1,
348
347
  "commit": { "type": "feat", "subject": "add CreateUserService" }
349
348
  }
@@ -369,6 +368,7 @@ Do NOT guess or invent task decompositions when the source is unclear. Ask first
369
368
 
370
369
  - Write in **English**
371
370
  - **Derive all content from the Requirements and Tech-Spec** — do not invent tasks not grounded in those documents
371
+ - **Never self-explore the filesystem for Requirements/Tech-Spec content** — both are inlined in your prompt (Requirements full, Tech-Spec skeleton-only); use `needsComponentDetail` for a genuine component-body gap instead of Reading/Globbing/Grepping for it
372
372
  - **Atomicity is mandatory** — each task must have one outcome, one domain, one agent type, one commit subject
373
373
  - **Task granularity**: target ≤ 15 minutes of agent time; split any task that cannot be scoped to ≤ 15 minutes
374
374
  - **Domain assignment must be unambiguous** — use the domain table in Step 3
@@ -56,9 +56,63 @@ const SKELETON_SCHEMA = {
56
56
  properties: { type: { type: 'string' }, subject: { type: 'string' } } },
57
57
  },
58
58
  } },
59
+ // Bounded drill-down (mirrors pm-phase1.js's own needsComponentDetail precedent for the
60
+ // feature-doc validator, FTR-028) — declared in `properties` only, never `required`: the
61
+ // common case is an empty array, costing nothing extra. See gaia-generate-work-breakdown.md's
62
+ // own "Requesting Component Detail" section for when the agent should populate this instead
63
+ // of self-exploring spec/components/* (the real, evidenced dominant cost driver on a large
64
+ // feature — confirmed against a real run: 1.18M of 1.38M total tokens on one failing attempt
65
+ // were cache_read_input_tokens, not the declared output schema size).
66
+ needsComponentDetail: { type: 'array', items: { type: 'object',
67
+ properties: { fileSlug: { type: 'string' }, reason: { type: 'string' } }, required: ['fileSlug'] } },
59
68
  },
60
69
  }
61
70
 
71
+ // Fixed, not externally configurable (same "no real evidence yet justifies external tuning"
72
+ // precedent as every other bounded retry/batch constant in this codebase) — a safety cap, not a
73
+ // typical case; the skeleton agent's own instructions already frame needsComponentDetail as the
74
+ // exception, reserved for a genuine per-task decision gap, not a substitute for broad exploration.
75
+ const SKELETON_DRILL_DOWN_MAX_COMPONENTS = 20
76
+
77
+ // Builds the skeleton-generation prompt: Requirements FULL text + Tech-Spec SKELETON ONLY
78
+ // (never per-component bodies) inlined directly, mirroring pm-phase1.js's own
79
+ // gaia-validate-feature-docs prompt shape exactly — the agent no longer self-reads either
80
+ // document or any per-item file via its own Read/Glob/Grep tool access.
81
+ function buildSkeletonPrompt(featurePath, requirementsText, techSpecSkeletonText, drillDownText) {
82
+ const parts = [
83
+ featurePath,
84
+ '',
85
+ 'Requirements:',
86
+ requirementsText,
87
+ '',
88
+ 'Tech-Spec:',
89
+ techSpecSkeletonText,
90
+ ]
91
+ if (drillDownText) {
92
+ parts.push('', 'Component detail you requested:', drillDownText)
93
+ }
94
+ return parts.join('\n')
95
+ }
96
+
97
+ // Validates each needsComponentDetail entry's fileSlug against the real component table (a
98
+ // hallucinated/mistyped reference is dropped, never dispatched — mirrors pm-phase1.js's own
99
+ // prepareDrillDownBatches), deduplicates by fileSlug, and caps the total at
100
+ // SKELETON_DRILL_DOWN_MAX_COMPONENTS. Returns the inlinable text block (or null if nothing valid
101
+ // remains) — this reads files directly (deterministic, free), never dispatches another agent.
102
+ function buildComponentDrillDownText(needsComponentDetail, specIndexContent, specDirArg) {
103
+ const components = parseComponentsTableLocal(specIndexContent)
104
+ const bySlug = new Map(components.map(c => [c.fileSlug, c]))
105
+ const uniqueSlugs = [...new Set((needsComponentDetail || [])
106
+ .map(e => e.fileSlug)
107
+ .filter(slug => bySlug.has(slug)))]
108
+ .slice(0, SKELETON_DRILL_DOWN_MAX_COMPONENTS)
109
+ if (uniqueSlugs.length === 0) return null
110
+ return uniqueSlugs.map(slug => {
111
+ const comp = bySlug.get(slug)
112
+ return `### ${comp.filePath} (${slug})\n\n${c.read(`${specDirArg}/components/${slug}`)}`
113
+ }).join('\n\n---\n\n')
114
+ }
115
+
62
116
  // Splits the skeleton response into (a) index.json's structural, map-keyed shape and
63
117
  // (b) nothing else — phase title/commit/context are NOT copied into index.json (OQ-04); they
64
118
  // are rendered straight to each phase's detail file by the caller, from the same skeleton
@@ -334,7 +388,33 @@ return c.run(meta.name, featureDir, prefix, async () => {
334
388
  // 1. Skeleton (no declared worker() output — host writes every file itself)
335
389
  let index
336
390
  if (!c.exists(indexPath)) {
337
- const skeleton = await c.worker('gaia.agent.planner.work-breakdown', featurePath, { schema: SKELETON_SCHEMA })
391
+ // Requirements FULL text + Tech-Spec SKELETON ONLY, inlined directly — same shape as
392
+ // pm-phase1.js's own validator prompt (FTR-028). Computed unconditionally here (not reused
393
+ // from the Gate-1 digest check above, which only runs when Approvals.md carries a digest
394
+ // line) so this prompt is always built the same way regardless of that check's own outcome.
395
+ const requirementsForSkeleton = hasReq
396
+ ? c.command(['run-asset', 'scripts/req-assembler.js', '--project', c.root, '--', reqDir]).stdout
397
+ : c.read(file('Requirements.md'))
398
+ const techSpecSkeletonForSkeleton = hasSpec ? c.read(specIndexPath) : c.read(file('Tech-Spec.md'))
399
+
400
+ let skeleton = await c.worker('gaia.agent.planner.work-breakdown',
401
+ buildSkeletonPrompt(featurePath, requirementsForSkeleton, techSpecSkeletonForSkeleton),
402
+ { schema: SKELETON_SCHEMA })
403
+
404
+ // Bounded, single-round drill-down (FTR-028 precedent) — see buildComponentDrillDownText's
405
+ // own comment. No further rounds: the second call's own response is used as-is, even if it
406
+ // asks for more (fail-fast rather than open-ended retries, matching this codebase's other
407
+ // bounded-retry conventions).
408
+ const needsComponentDetail = skeleton.needsComponentDetail || []
409
+ if (hasSpec && needsComponentDetail.length > 0) {
410
+ const drillDownText = buildComponentDrillDownText(needsComponentDetail, c.read(specIndexPath), specDir)
411
+ if (drillDownText) {
412
+ skeleton = await c.worker('gaia.agent.planner.work-breakdown',
413
+ buildSkeletonPrompt(featurePath, requirementsForSkeleton, techSpecSkeletonForSkeleton, drillDownText),
414
+ { schema: SKELETON_SCHEMA, label: 'work-breakdown-skeleton:with-drilldown' })
415
+ }
416
+ }
417
+
338
418
  index = materializeIndex(skeleton)
339
419
  const taskIdToPhase = new Map(skeleton.phases.flatMap(p => p.taskIds.map(taskId => [taskId, p.id])))
340
420
  for (const phase of index.phases) {