@dtlabs/ai-toolkit 0.19.0 → 0.19.1

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,11 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.19.1](https://github.com/dtlab-tech/ai-toolkit/compare/v0.19.0...v0.19.1) (2026-10-19)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * stop headless workers from Reading their own toolkit templates ([0f2c707](https://github.com/dtlab-tech/ai-toolkit/commit/0f2c7079799cdfb95ab99dcf7333f615a8f49a8e))
8
+
2
9
  ## [0.19.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.18.0...v0.19.0) (2026-10-19)
3
10
 
4
11
 
package/bin/cli.js CHANGED
@@ -2056,6 +2056,51 @@ async function main() {
2056
2056
  process.stderr.write(err.message + '\n');
2057
2057
  process.exit(1);
2058
2058
  }
2059
+ } else if (argv[0] === 'print-asset') {
2060
+ // print-asset <relativePath> [--project <dir>] [--home <dir>]
2061
+ // Security constraints: scripts/templates/ subpath only; .md extension only — the
2062
+ // read-only counterpart to run-asset's "scripts category, .js only, execute" contract.
2063
+ //
2064
+ // Exists so a sandboxed workflow script (pm-phase1.js/pm-phase2.js — no `require()`, no
2065
+ // filesystem access outside its own project root) can fetch a toolkit template's raw
2066
+ // text and inline it directly into a worker's prompt, instead of telling the headless
2067
+ // agent to Read the template file itself. That file lives outside the project root for
2068
+ // a globally-installed toolkit, which is exactly the boundary a non-interactive `--print`
2069
+ // session (no TTY to answer a permission prompt) cannot grant — confirmed by a real
2070
+ // worker run failing with "Worker permission denied: ...; tools: Read" against exactly
2071
+ // this path. Inlining via the host (a plain Node process, never subject to the Claude
2072
+ // Code tool-permission system) sidesteps the problem entirely, the same pattern FTR-025
2073
+ // already established for inlining Requirements/Tech-Spec text into gaia-validate-
2074
+ // feature-docs' prompt instead of telling it to Read those files itself.
2075
+ const remaining = argv.slice(1);
2076
+ let relativePath = null;
2077
+ let projectDir = process.cwd();
2078
+ let home;
2079
+ for (let i = 0; i < remaining.length; i++) {
2080
+ if (remaining[i] === '--project' && remaining[i + 1]) { projectDir = remaining[++i]; }
2081
+ else if (remaining[i] === '--home' && remaining[i + 1]) { home = remaining[++i]; }
2082
+ else if (!remaining[i].startsWith('-')) { relativePath = remaining[i]; }
2083
+ }
2084
+ if (!relativePath) {
2085
+ process.stderr.write('Error: print-asset requires a relative path argument\n');
2086
+ process.exit(1);
2087
+ }
2088
+ if (!relativePath.startsWith('scripts/templates/')) {
2089
+ process.stderr.write(`Error: print-asset is restricted to scripts/templates/ (got: ${relativePath})\n`);
2090
+ process.exit(1);
2091
+ }
2092
+ if (!relativePath.endsWith('.md')) {
2093
+ process.stderr.write(`Error: print-asset only reads .md files (got: ${relativePath})\n`);
2094
+ process.exit(1);
2095
+ }
2096
+ try {
2097
+ const assetPath = resolveClaudeRuntimeAsset(relativePath, { projectDir, home });
2098
+ process.stdout.write(fs.readFileSync(assetPath, 'utf8'));
2099
+ process.exit(0);
2100
+ } catch (err) {
2101
+ process.stderr.write(err.message + '\n');
2102
+ process.exit(1);
2103
+ }
2059
2104
  } else if (argv[0] === 'list-assets') {
2060
2105
  // list-assets --project <dir> --category <name> [--format json|plain] [--home <dir>]
2061
2106
  // Both --project and --category are MANDATORY (Tech Spec contract).
@@ -106,7 +106,7 @@ function createControl(options) {
106
106
  }
107
107
  function command(argv, accepted = [0]) {
108
108
  if (!Array.isArray(argv) || argv.some(a => typeof a !== 'string')) throw new Error('CLI argv array required');
109
- if (options.home && argv[0] === 'run-asset') {
109
+ if (options.home && (argv[0] === 'run-asset' || argv[0] === 'print-asset')) {
110
110
  argv = argv.slice();
111
111
  const split = argv.indexOf('--');
112
112
  argv.splice(split < 0 ? argv.length : split, 0, '--home', options.home);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -23,18 +23,21 @@ The host supplies, in the dispatch prompt:
23
23
  3. The path to `feature.md` — read it in full before proceeding, to ground this use case's flow
24
24
  in the feature's actual described behavior rather than inventing detail the row alone would
25
25
  not carry.
26
+ 4. The canonical field-table/flow structure, inlined under a `Template (req/use-cases/{UC-ID}.md):`
27
+ label. Use this text — do NOT attempt to Read, Glob, or otherwise search the filesystem for
28
+ `gaia-use-case-template.md` yourself; it is not guaranteed to be reachable from where this agent
29
+ runs, and searching for it is not a safe fallback either.
26
30
 
27
31
  You do not read `req/index.md` yourself and you never decide which use cases exist — that is
28
32
  already fixed by the skeleton call that produced the row you were given.
29
33
 
30
- Read `.claude/scripts/templates/gaia-use-case-template.md` before writing your response for the
31
- canonical field-table/flow structure. Note: that template also shows "Related Business Rules"/
32
- "Related Acceptance Criteria" cross-reference lines at the end of the file — do NOT produce them
33
- yourself. The host computes both, mechanically, from `req/index.md`'s own Business Rules/
34
- Acceptance Criteria tables, and appends them to your response after you return it — you are not
35
- given that table data (and must not re-read `req/index.md` yourself to get it), so any version you
36
- wrote would risk being wrong or duplicated. The Output section below is your actual, complete
37
- output contract; stop after Postconditions.
34
+ Note: the inlined template also shows "Related Business Rules"/"Related Acceptance Criteria"
35
+ cross-reference lines at the end of the file — do NOT produce them yourself. The host computes
36
+ both, mechanically, from `req/index.md`'s own Business Rules/Acceptance Criteria tables, and
37
+ appends them to your response after you return it — you are not given that table data (and must
38
+ not re-read `req/index.md` yourself to get it), so any version you wrote would risk being wrong or
39
+ duplicated. The Output section below is your actual, complete output contract; stop after
40
+ Postconditions.
38
41
 
39
42
  ## Mandatory instruction
40
43
 
@@ -19,11 +19,15 @@ together with those per-use-case files afterward (FTR-021).
19
19
 
20
20
  The user provides the path to a `feature.md` file. Read it in full before proceeding.
21
21
 
22
- Also read `.claude/scripts/templates/gaia-requirements-index-template.md` before writing your
23
- response — it is the canonical, authoritative structure for `req/index.md` (section order,
24
- exact headings, table column contracts, and the parsing contract `req-skeleton-check.js`/
25
- `req-assembler.js` rely on). The Output Structure section below summarizes it; the template
26
- itself is the source of truth if the two ever disagree.
22
+ The prompt also includes, inlined under a `Template (req/index.md):` label, the canonical,
23
+ authoritative structure for `req/index.md` (section order, exact headings, table column
24
+ contracts, and the parsing contract `req-skeleton-check.js`/`req-assembler.js` rely on). Use the
25
+ inlined text — do NOT attempt to Read, Glob, or otherwise search the filesystem for
26
+ `gaia-requirements-index-template.md` yourself; it is not guaranteed to be reachable from where
27
+ this agent runs, and searching for it is not a safe fallback either (your Glob/Grep tools remain
28
+ available for exploring the feature's own project, just not for locating this template). The
29
+ Output Structure section below summarizes it; the inlined template text is the source of truth if
30
+ the two ever disagree.
27
31
 
28
32
  ## Process
29
33
 
@@ -28,10 +28,15 @@ The host supplies, in the dispatch prompt:
28
28
  You do not read `spec/index.md` yourself and you never decide which components exist — that is
29
29
  already fixed by the skeleton call that produced the row you were given.
30
30
 
31
- Read `.claude/scripts/templates/gaia-component-template.md` before writing your response — it is
32
- the canonical, authoritative structure for your output (Change/design-body/Tests, and the parsing
33
- contract `tech-spec-assembler.js`'s `parseComponentDetail` relies on). The Output section below is
34
- a summary; the template itself is the source of truth if the two ever disagree.
31
+ The prompt also includes, inlined under a `Template (spec/components/{fileSlug}.md):` label, the
32
+ canonical, authoritative structure for your output (Change/design-body/Tests, and the parsing
33
+ contract `tech-spec-assembler.js`'s `parseComponentDetail` relies on). Use the inlined text — do
34
+ NOT attempt to Read, Glob, or otherwise search the filesystem for `gaia-component-template.md`
35
+ yourself under any circumstances, including as a fallback if you are unsure of its exact path; it
36
+ is not guaranteed to be reachable from where this agent runs, and guessing at or searching for its
37
+ absolute path is not a safe substitute for the text already given to you below. The Output
38
+ section below is a summary; the inlined template text is the source of truth if
39
+ the two ever disagree.
35
40
 
36
41
  ## Mandatory instruction
37
42
 
@@ -19,11 +19,15 @@ together with those per-component files afterward (FTR-024).
19
19
 
20
20
  The user provides the path to a `feature.md` file. Read it in full before proceeding. Also read the relevant existing codebase files to ensure the spec aligns with current architecture.
21
21
 
22
- Also read `.claude/scripts/templates/gaia-tech-spec-index-template.md` before writing your
23
- response — it is the canonical, authoritative structure for `spec/index.md` (section order, exact
24
- headings, the Section 9 column-order contract `tech-spec-graph-checks.js` relies on, and the
25
- parsing contract `tech-spec-assembler.js` relies on). The Output Structure section below
26
- summarizes it; the template itself is the source of truth if the two ever disagree.
22
+ The prompt also includes, inlined under a `Template (spec/index.md):` label, the canonical,
23
+ authoritative structure for `spec/index.md` (section order, exact headings, the Section 9
24
+ column-order contract `tech-spec-graph-checks.js` relies on, and the parsing contract
25
+ `tech-spec-assembler.js` relies on). Use the inlined text — do NOT attempt to Read, Glob, or
26
+ otherwise search the filesystem for `gaia-tech-spec-index-template.md` yourself; it is not
27
+ guaranteed to be reachable from where this agent runs, and searching for it is not a safe fallback
28
+ either (your Glob/Grep tools remain available for exploring the project's existing codebase, just
29
+ not for locating this template). The Output Structure section below summarizes it; the inlined
30
+ template text is the source of truth if the two ever disagree.
27
31
 
28
32
  ## Step 0 — Read Project Conventions (MANDATORY)
29
33
 
@@ -9,11 +9,6 @@ tools: Read, Glob, Grep
9
9
 
10
10
  You are a **senior implementation planner**. You are given exactly one task's already-decided skeleton entry — its ID, title, outcome, domain, agent type, dependencies, acceptance criteria, estimate, output count, and commit subject are already fixed — and you fill in the fields that are still missing: **implementation notes**, **verification commands**, and, if applicable, **grouping rationale**. You do not re-decide anything the skeleton already fixed.
11
11
 
12
- Read `.claude/scripts/templates/gaia-task-template.md` before writing your response — it is the
13
- canonical, authoritative structure for your output (field order, the two free-content sections,
14
- and the exact parsing contract downstream tooling relies on). This section and the Output section
15
- below summarize it; the template itself is the source of truth if the two ever disagree.
16
-
17
12
  This is the **second of two generation passes** (FTR-020). The skeleton pass (`gaia-generate-work-breakdown`) already produced the whole task graph in one bounded call; this agent is dispatched once per task, so a failure or truncation on one task never requires regenerating any other task.
18
13
 
19
14
  ---
@@ -25,8 +20,15 @@ The caller supplies, in the prompt (you do not read `{PREFIX}-Work-Breakdown.jso
25
20
  1. **This task's own skeleton entry**, verbatim: `id`, `title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `estimate`, `outputCount`, `commit`.
26
21
  2. **The relevant Tech-Spec excerpt** for this task's own Acceptance Criteria (matched by the host against each component's `Addresses` column in `spec/index.md` — not the whole Tech-Spec, which is the whole point of the per-task split). If no component's `Addresses` column cites any of this task's Acceptance Criteria, you instead receive the full Components table from `spec/index.md` as a fallback — in that case, use your own judgment about which listed component(s) are actually relevant, the same way you would if asked to work from the full Tech-Spec directly.
27
22
  3. **The compact full-skeleton task list**: every task's `id` + `title` + `outcome` only (no detail), so you can tell whether another task already built the behavior this one would otherwise duplicate.
23
+ 4. **The canonical output template**, inlined under a `Template (tasks/{TASK_ID}.md):` label — the
24
+ authoritative structure for your output (field order, the two free-content sections, and the
25
+ exact parsing contract downstream tooling relies on). Use the inlined text — do NOT attempt to
26
+ Read, Glob, or otherwise search the filesystem for `gaia-task-template.md` yourself; it is not
27
+ guaranteed to be reachable from where this agent runs, and searching for it is not a safe
28
+ fallback either. This section and the Output section below summarize it; the inlined template
29
+ text is the source of truth if the two ever disagree.
28
30
 
29
- If the prompt is missing any of these three, stop and report exactly what is missing — do not guess at a skeleton entry you were not given.
31
+ If the prompt is missing any of these four, stop and report exactly what is missing — do not guess at a skeleton entry you were not given.
30
32
 
31
33
  ---
32
34
 
@@ -142,9 +144,9 @@ Return exactly this block as your final response, and nothing else (no preamble,
142
144
  ```
143
145
 
144
146
  This is the same format already rendered today inside a Work Breakdown's "Task Details"
145
- section — canonical reference: `.claude/scripts/templates/gaia-task-template.md` (you have a Read
146
- tool; read it directly rather than relying only on this summary). That template's parsing-contract
147
- note applies to your own output too: a naive parser reads only the field lines immediately
147
+ section — canonical reference: the `Template (tasks/{TASK_ID}.md):`-labeled text inlined in your
148
+ own prompt (see Input §4 above; rely on that, not just this summary). That template's
149
+ parsing-contract note applies to your own output too: a naive parser reads only the field lines immediately
148
150
  following the heading, and the *last* `**Implementation notes:**`/`**Verification commands:**`
149
151
  occurrence — so if you need to illustrate this format inside a longer explanation (e.g. showing
150
152
  another agent's expected output shape), make sure your own real sections remain the last
@@ -36,6 +36,21 @@ return c.run(meta.name, featureDir, prefix, async () => {
36
36
  const valId = 'gaia.agent.planner.validate-feature-docs'
37
37
  const output = (suffix, inputs = [featurePath]) => ({ path: file(suffix), inputs })
38
38
 
39
+ // Templates live under the toolkit's own installed scripts/templates/ — outside this project
40
+ // for a globally-installed toolkit, which a non-interactive `--print` worker invocation has no
41
+ // way to get permission for (no TTY to answer a prompt; confirmed by a real run failing with
42
+ // "Worker permission denied: ...; tools: Read" against exactly this kind of path). The host
43
+ // reads the template itself (a plain Node process, never subject to the Claude Code
44
+ // tool-permission system) via the dedicated print-asset CLI command, and inlines its text
45
+ // directly into the prompt — the agent is never asked to Read it. Same pattern FTR-025 already
46
+ // established for inlining Requirements/Tech-Spec text into gaia-validate-feature-docs' prompt.
47
+ function readTemplate(templateFile) {
48
+ return c.command(['print-asset', `scripts/templates/${templateFile}`, '--project', c.root]).stdout
49
+ }
50
+ function withTemplate(prompt, label, templateFile) {
51
+ return `${prompt}\n\nTemplate (${label}):\n${readTemplate(templateFile)}`
52
+ }
53
+
39
54
  // Deliberately NOT file('req') — same reasoning as FTR-020's wbsDir: this is a directory
40
55
  // nested inside the feature's own already-prefixed folder, so a second prefix would be
41
56
  // redundant.
@@ -109,11 +124,12 @@ return c.run(meta.name, featureDir, prefix, async () => {
109
124
  (relatedACs.length ? relatedACs.join(', ') : '—') + ' (see [index.md](../index.md#7-acceptance-criteria))\n'
110
125
  }
111
126
 
112
- function buildUcPrompt(row, ucTable, featurePathArg) {
127
+ function buildUcPrompt(row, ucTable, featurePathArg, ucTemplate) {
113
128
  const compactIndex = ucTable.map(r => `${r.id}: ${r.title} (${r.actor})`).join('\n')
114
- return `${featurePathArg}\n\nGenerate the full detail for this use case:\n` +
129
+ const base = `${featurePathArg}\n\nGenerate the full detail for this use case:\n` +
115
130
  `UC ID: ${row.id}\nTitle: ${row.title}\nActor: ${row.actor}\nPriority: ${row.priority}\n\n` +
116
131
  `Full use-case index (for cross-UC awareness, do not restate):\n${compactIndex}`
132
+ return `${base}\n\nTemplate (req/use-cases/{UC-ID}.md):\n${ucTemplate}`
117
133
  }
118
134
 
119
135
  // forceAll: bypasses resume-by-existence entirely, re-dispatching the skeleton and every
@@ -144,7 +160,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
144
160
  // 1. Skeleton (no declared worker() output — host writes it itself, mirroring FTR-020's
145
161
  // skeleton step)
146
162
  if (regenSkeleton) {
147
- const skeleton = await c.worker(reqId, featurePath)
163
+ const skeleton = await c.worker(reqId, withTemplate(featurePath, 'req/index.md', 'gaia-requirements-index-template.md'))
148
164
  c.writeOutput(reqIndexPath, skeleton)
149
165
  }
150
166
 
@@ -162,13 +178,15 @@ return c.run(meta.name, featureDir, prefix, async () => {
162
178
 
163
179
  // 3. Per-use-case generation, resume-by-existence, sequential
164
180
  const ucTable = parseUseCaseIndexTable(c.read(reqIndexPath))
181
+ // Read once, outside the loop — the template is identical for every use case in this run.
182
+ const ucTemplate = ucTable.length ? readTemplate('gaia-use-case-template.md') : null
165
183
  for (const row of ucTable) {
166
184
  const ucPath = `${reqDir}/${row.file}`
167
185
  // If the skeleton itself was just (re)generated, every use case must regenerate too —
168
186
  // an existing use-case file would have been written against the OLD skeleton's row
169
187
  // (title/actor/priority may have changed) and cannot be trusted as-is.
170
188
  if (!regenSkeleton && c.exists(ucPath)) continue // resume: skip, no dispatch
171
- const detail = await c.worker(reqUcId, buildUcPrompt(row, ucTable, featurePath),
189
+ const detail = await c.worker(reqUcId, buildUcPrompt(row, ucTable, featurePath, ucTemplate),
172
190
  { label: `requirements-use-case:${row.id}` })
173
191
  c.writeOutput(ucPath, detail.trimEnd() + ucCrossReferences(c.read(reqIndexPath), row.id))
174
192
  }
@@ -223,11 +241,12 @@ return c.run(meta.name, featureDir, prefix, async () => {
223
241
  return entries
224
242
  }
225
243
 
226
- function buildComponentPrompt(row, components, featurePathArg) {
244
+ function buildComponentPrompt(row, components, featurePathArg, componentTemplate) {
227
245
  const compactIndex = components.map(c => `${c.filePath} (${c.domain})`).join('\n')
228
- return `${featurePathArg}\n\nGenerate the full technical design for this component:\n` +
246
+ const base = `${featurePathArg}\n\nGenerate the full technical design for this component:\n` +
229
247
  `Path: ${row.filePath}\nChange: ${row.changeType}\nDomain: ${row.domain}\nAddresses: ${row.addresses}\n\n` +
230
248
  `Full component index (for cross-component awareness, do not restate):\n${compactIndex}`
249
+ return `${base}\n\nTemplate (spec/components/{fileSlug}.md):\n${componentTemplate}`
231
250
  }
232
251
 
233
252
  // Deterministic, host-computed — never asks the model to pick unique names (same principle
@@ -279,7 +298,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
279
298
 
280
299
  // 1. Skeleton (no declared worker() output — host writes it itself)
281
300
  if (regenSkeleton) {
282
- const skeleton = await c.worker(specId, featurePath)
301
+ const skeleton = await c.worker(specId, withTemplate(featurePath, 'spec/index.md', 'gaia-tech-spec-index-template.md'))
283
302
  c.writeOutput(specIndexPath, disambiguateFileSlugs(skeleton))
284
303
  }
285
304
 
@@ -298,10 +317,12 @@ return c.run(meta.name, featureDir, prefix, async () => {
298
317
 
299
318
  // 3. Per-component generation, resume-by-existence, sequential
300
319
  const components = parseComponentsTable(c.read(specIndexPath))
320
+ // Read once, outside the loop — the template is identical for every component in this run.
321
+ const componentTemplate = components.length ? readTemplate('gaia-component-template.md') : null
301
322
  for (const comp of components) {
302
323
  const compPath = `${specDir}/components/${comp.fileSlug}`
303
324
  if (!regenSkeleton && c.exists(compPath)) continue // resume: skip, no dispatch
304
- const detail = await c.worker(specComponentId, buildComponentPrompt(comp, components, featurePath),
325
+ const detail = await c.worker(specComponentId, buildComponentPrompt(comp, components, featurePath, componentTemplate),
305
326
  { label: `tech-spec-component:${comp.fileSlug}` })
306
327
  c.writeOutput(compPath, detail)
307
328
  }
@@ -239,7 +239,7 @@ function buildTaskTechSpecExcerpt(components, task, specDir, specIndexContent) {
239
239
  // implementation awareness), and the Tech-Spec excerpt (FTR-026: already pre-sliced to this
240
240
  // task's own relevant component(s) by buildTaskTechSpecExcerpt, or the full legacy Tech-Spec
241
241
  // text for a feature with no spec/ directory).
242
- function buildTaskPrompt(index, taskId, techSpecText) {
242
+ function buildTaskPrompt(index, taskId, techSpecText, taskTemplate) {
243
243
  const task = index.tasks[taskId]
244
244
  const siblingList = Object.entries(index.tasks)
245
245
  .map(([id, t]) => `- ${id}: ${t.title} — ${t.outcome}`).join('\n')
@@ -247,6 +247,7 @@ function buildTaskPrompt(index, taskId, techSpecText) {
247
247
  `Task skeleton entry (restate these fields verbatim; do not re-derive them):\n${JSON.stringify({ id: taskId, ...task }, null, 2)}`,
248
248
  `Tech-Spec:\n${techSpecText}`,
249
249
  `All tasks in this feature (for duplicate-implementation awareness):\n${siblingList}`,
250
+ `Template (tasks/{TASK_ID}.md):\n${taskTemplate}`,
250
251
  ].join('\n\n')
251
252
  }
252
253
 
@@ -339,6 +340,13 @@ return c.run(meta.name, featureDir, prefix, async () => {
339
340
  // own Acceptance Criteria, not the entire assembled spec — computed once, before the loop, by
340
341
  // matching each task's acceptanceCriteria against spec/index.md's own Addresses column.
341
342
  const components = hasSpec ? parseComponentsTableLocal(c.read(specIndexPath)) : []
343
+ // The template lives under the toolkit's own installed scripts/templates/ — outside the
344
+ // project for a globally-installed toolkit, which a non-interactive worker invocation has no
345
+ // way to get Read permission for. Read once via print-asset, inline into every task's prompt,
346
+ // instead of telling the agent to Read it itself (same reasoning as pm-phase1.js's
347
+ // readTemplate/withTemplate — this runtime duplicates the primitive, not requires it, for the
348
+ // same "no `require`" reason as every other duplicated helper in this file).
349
+ const taskTemplate = c.command(['print-asset', 'scripts/templates/gaia-task-template.md', '--project', c.root]).stdout
342
350
  for (const phase of index.phases) {
343
351
  for (const taskId of phase.taskIds) {
344
352
  const task = index.tasks[taskId]
@@ -348,7 +356,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
348
356
  ? buildTaskTechSpecExcerpt(components, task, specDir, c.read(specIndexPath))
349
357
  : c.read(file('Tech-Spec.md')) // legacy fallback: unchanged, full text
350
358
  const detail = await c.worker('gaia.agent.planner.work-breakdown-task',
351
- buildTaskPrompt(index, taskId, techSpecExcerpt), { label: `work-breakdown-task:${taskId}` })
359
+ buildTaskPrompt(index, taskId, techSpecExcerpt, taskTemplate), { label: `work-breakdown-task:${taskId}` })
352
360
  c.writeOutput(detailPath, detail)
353
361
  task.status = 'generated'
354
362
  c.writeOutput(indexPath, JSON.stringify(index, null, 2)) // persist status after each task