@dtlabs/ai-toolkit 0.24.0 → 0.24.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,13 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.24.1](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.0...v0.24.1) (2026-10-24)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **gaia-generate-work-breakdown-task:** drop stale estimate-as-input contradiction from FTR-032 ([c157277](https://github.com/dtlab-tech/ai-toolkit/commit/c157277847aedb33b69022d76d2b87ed5eb0ff49))
8
+ * **pm-phase1:** tolerate a leading conversational preamble before per-item headings ([afe638f](https://github.com/dtlab-tech/ai-toolkit/commit/afe638f6bb138335b34b97b1eda776c77ab0778e))
9
+ * tolerate per-item generation preamble + drop stale estimate contradiction in work-breakdown-task agent ([d3438d4](https://github.com/dtlab-tech/ai-toolkit/commit/d3438d4bde7f29b12b12ba589903f951a6c2bf10))
10
+
2
11
  ## [0.24.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.23.2...v0.24.0) (2026-10-22)
3
12
 
4
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -49,7 +49,10 @@ do not reword, re-derive, or second-guess them. Your only new content is **Preco
49
49
 
50
50
  Return exactly this block as your final response, and nothing else (no preamble, no "here is the
51
51
  use case", no Markdown code fence wrapping the whole thing) — you do not have a Write tool; the
52
- host persists your response verbatim to `req/use-cases/{UC-ID}.md`:
52
+ host persists your response verbatim to `req/use-cases/{UC-ID}.md`. This means: do NOT write a
53
+ lead-in sentence like "Now I'll generate the complete use case details for {UC-ID}..." before the
54
+ `### {UC-ID}: {Title}` heading — the heading is the first thing in your response, with nothing
55
+ before it, not even a single line of narration.
53
56
 
54
57
  ```markdown
55
58
  ### {UC-ID}: {Title}
@@ -48,7 +48,10 @@ description, its actual design, and its `Tests` section.
48
48
 
49
49
  Return exactly this block as your final response, and nothing else (no preamble, no "here is the
50
50
  component", no Markdown code fence wrapping the whole thing) — you do not have a Write tool; the
51
- host persists your response verbatim to `spec/components/{fileSlug}.md`:
51
+ host persists your response verbatim to `spec/components/{fileSlug}.md`. This means: do NOT write
52
+ a lead-in sentence like "Now I have the information. Let me generate the design for {Path}..."
53
+ before the `# {Path} — {New | Modified}` heading — the heading is the first thing in your
54
+ response, with nothing before it, not even a single line of narration.
52
55
 
53
56
  ```markdown
54
57
  # {Path} — {New | Modified}
@@ -7,7 +7,7 @@ tools: Read, Glob, Grep
7
7
 
8
8
  # Generate Work Breakdown Task Detail
9
9
 
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.
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, output count, and commit subject are already fixed — and you fill in the fields that are still missing: **implementation notes**, **verification commands**, **estimate** (`agentMinutes`/`tokens` — the skeleton pass deliberately does not produce this; see the dedicated Estimate section below), and, if applicable, **grouping rationale**. You do not re-decide anything the skeleton already fixed.
11
11
 
12
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.
13
13
 
@@ -17,7 +17,7 @@ This is the **second of two generation passes** (FTR-020). The skeleton pass (`g
17
17
 
18
18
  The caller supplies, in the prompt (you do not read `{PREFIX}-Work-Breakdown.json`/`wbs/index.json` yourself — the host extracts and hands you exactly what you need):
19
19
 
20
- 1. **This task's own skeleton entry**, verbatim: `id`, `title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `estimate`, `outputCount`, `commit`.
20
+ 1. **This task's own skeleton entry**, verbatim: `id`, `title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `outputCount`, `commit`. It deliberately has no `estimate` field — that is your own new content (see the Estimate section below), not something already decided for you to restate.
21
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.
22
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
23
  4. **The canonical output template**, inlined under a `Template (tasks/{TASK_ID}.md):` label — the
@@ -42,8 +42,9 @@ Before writing verification commands, you MUST:
42
42
 
43
43
  ## Mandatory Instruction — Restate, Do Not Re-Derive
44
44
 
45
- Restate every given field (`title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `estimate`, `outputCount`, `commit`) **exactly as given** in your output block — do not reword, re-estimate, or re-derive them, even if you would have phrased them differently. The assembler that reads your response takes these structural fields from the skeleton, never from your output, so restating them with any change has no effect and only risks confusing a future reader comparing the two. Your only genuinely new content is:
45
+ Restate every given field (`title`, `outcome`, `domain`, `agentType`, `dependsOn`, `acceptanceCriteria`, `outputCount`, `commit`) **exactly as given** in your output block — do not reword or re-derive them, even if you would have phrased them differently. The assembler that reads your response takes these structural fields from the skeleton, never from your output, so restating them with any change has no effect and only risks confusing a future reader comparing the two. Your only genuinely new content is:
46
46
  - **Implementation notes** — always required, non-empty (see the dedicated section below)
47
+ - **Estimate** (`agentMinutes`/`tokens`) — always required; you decide this, the skeleton never supplies it (see the dedicated Estimate section below)
47
48
  - `verification.commands` — always required, non-empty
48
49
  - `groupingRationale` — required (non-null) only when the skeleton's `outputCount` is greater than 1; otherwise `—`
49
50
 
@@ -111,6 +112,26 @@ If the skeleton's `outputCount` is greater than `1`: write a short, specific exp
111
112
 
112
113
  ---
113
114
 
115
+ ## Estimate
116
+
117
+ The skeleton deliberately does not supply `estimate.agentMinutes`/`estimate.tokens` — you decide both,
118
+ now, with the fuller picture you have (the task's own Tech-Spec excerpt and the Implementation
119
+ Notes you are about to write), which the skeleton pass never had. Do not treat this as a field to
120
+ look for in your input; it does not exist there.
121
+
122
+ Base `agentMinutes` on the same bands the Work Breakdown's own atomicity rule already uses: target
123
+ ≤15 (the common case for a single, atomic outcome); 16–20 is acceptable without flagging anything;
124
+ 21–30 is a non-blocking warning and requires a real, non-`—` `Grouping rationale` justifying why
125
+ this task's scope could not be split further; if your own honest estimate would exceed 30, say so
126
+ plainly in Implementation Notes instead of understating the number — a task this large should have
127
+ been split at the skeleton stage, and silently reporting a lower number here only hides that. Base
128
+ `tokens` on a rough order of magnitude for the agent invocation that will actually do this work
129
+ (reading the relevant files, writing the change, running the verification commands) — thousands to
130
+ tens of thousands is typical for a single atomic task; there is no fixed formula, use judgment
131
+ informed by the task's own real scope.
132
+
133
+ ---
134
+
114
135
  ## Output
115
136
 
116
137
  Return exactly this block as your final response, and nothing else (no preamble, no explanatory prose, no Markdown code fence wrapping it) — you do not have a Write tool; the host persists your response verbatim to the task's detail file:
@@ -104,6 +104,25 @@ return c.run(meta.name, featureDir, prefix, async () => {
104
104
  return new RegExp(`^###\\s+${escaped}:\\s`).test(content.trimStart())
105
105
  }
106
106
 
107
+ // A per-item generation agent occasionally prefixes its real deliverable with a short
108
+ // conversational lead-in ("Now I'll generate the complete use case details for...") before the
109
+ // heading it was explicitly instructed to start with — a real, recurring model habit confirmed
110
+ // across multiple production runs and multiple agents (gaia-generate-requirements-use-case,
111
+ // gaia-generate-tech-spec-component), not prevented by the agents' own existing "no preamble"
112
+ // instruction alone. Tolerated here, bounded and conservative: only lines STRICTLY BEFORE the
113
+ // first line satisfying headingTest, and only within the first few lines, are ever dropped —
114
+ // content whose heading never appears at all within that short window (genuinely wrong/missing,
115
+ // not just narrated) is returned completely unchanged and still fails the caller's own validity
116
+ // check exactly as before this fix.
117
+ const MAX_PREAMBLE_LINES = 5
118
+ function stripLeadingNarration(content, headingTest) {
119
+ const lines = content.split('\n')
120
+ for (let i = 1; i < Math.min(lines.length, MAX_PREAMBLE_LINES); i++) {
121
+ if (headingTest(lines[i])) return lines.slice(i).join('\n')
122
+ }
123
+ return content
124
+ }
125
+
107
126
  // Business Rules' "Applies to" / Acceptance Criteria's "Related UC" columns are the same
108
127
  // cross-cutting-reference shape: a comma-separated UC-ID list, or the literal "All UCs". Both
109
128
  // are 100% mechanically derivable from req/index.md's own tables — no LLM judgment needed,
@@ -202,10 +221,13 @@ return c.run(meta.name, featureDir, prefix, async () => {
202
221
  if (!regenSkeleton && c.exists(ucPath) && isValidUseCaseFile(c.read(ucPath), row.id)) continue // resume: skip, no dispatch
203
222
  // FTR-029/UC-04: validate immediately, before writing to disk and before moving to the next
204
223
  // use case — same reasoning as the per-component loop above.
224
+ const ucEscaped = row.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
225
+ const ucHeadingRe = new RegExp(`^###\\s+${ucEscaped}:\\s`)
205
226
  let detail, valid = false
206
227
  for (let attempt = 1; attempt <= USE_CASE_RETRY_ATTEMPTS && !valid; attempt++) {
207
- detail = await c.worker(reqUcId, buildUcPrompt(row, ucTable, featurePath, ucTemplate),
228
+ const raw = await c.worker(reqUcId, buildUcPrompt(row, ucTable, featurePath, ucTemplate),
208
229
  { label: `requirements-use-case:${row.id}:${attempt}` })
230
+ detail = stripLeadingNarration(raw, l => ucHeadingRe.test(l))
209
231
  valid = isValidUseCaseFile(detail, row.id)
210
232
  }
211
233
  if (!valid) throw new Error(`Requirements use case "${row.id}" failed structural validation after ${USE_CASE_RETRY_ATTEMPTS} attempts — missing the required "### ${row.id}: " heading`)
@@ -494,10 +516,14 @@ return c.run(meta.name, featureDir, prefix, async () => {
494
516
  // FTR-029/UC-04: validate immediately, before writing to disk and before moving to the next
495
517
  // component — a bad result otherwise silently continued through every remaining component,
496
518
  // only discovered (expensively) at final assembly or a later invocation's resume check.
519
+ // isValidComponentDetail itself tolerates a leading preamble (it searches for ## Change/##
520
+ // Tests anywhere, not anchored to the start), so it never hard-fails on one — but the file
521
+ // actually WRITTEN to disk must not carry that narration forward silently; strip it first.
497
522
  let detail, valid = false
498
523
  for (let attempt = 1; attempt <= COMPONENT_RETRY_ATTEMPTS && !valid; attempt++) {
499
- detail = await c.worker(specComponentId, buildComponentPrompt(comp, components, featurePath, componentTemplate),
524
+ const raw = await c.worker(specComponentId, buildComponentPrompt(comp, components, featurePath, componentTemplate),
500
525
  { label: `tech-spec-component:${comp.fileSlug}:${attempt}` })
526
+ detail = stripLeadingNarration(raw, l => /^#\s+\S/.test(l))
501
527
  valid = isValidComponentDetail(detail)
502
528
  }
503
529
  if (!valid) throw new Error(`Tech-Spec component "${comp.filePath}" (${comp.fileSlug}) failed structural validation after ${COMPONENT_RETRY_ATTEMPTS} attempts — missing a real "## Change"/"## Tests" heading`)