@dtlabs/ai-toolkit 0.24.4 → 0.24.6

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,21 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.24.6](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.5...v0.24.6) (2026-10-29)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **pm-phase1:** feed validation findings back to the generators and d… ([6fcb46d](https://github.com/dtlab-tech/ai-toolkit/commit/6fcb46d4e0d6d1d9ad8e2bf97313ce23691ce665))
8
+ * **pm-phase1:** feed validation findings back to the generators and drop duplicate Related blocks ([86d01df](https://github.com/dtlab-tech/ai-toolkit/commit/86d01dffbaccd9f5b99e3a356cd13e5d902d699b))
9
+ * **pm-phase1:** feed validation findings back to the generators and drop duplicate Related blocks ([9411ea5](https://github.com/dtlab-tech/ai-toolkit/commit/9411ea529d8eccdc54abcd4f8f296542b73e14b9))
10
+
11
+ ## [0.24.5](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.4...v0.24.5) (2026-10-27)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **pm-phase2:** run the semantic WB validator per phase ([3f279e6](https://github.com/dtlab-tech/ai-toolkit/commit/3f279e6e386eee6c86cea90caec7e5d64b6aa756))
17
+ * **pm-phase2:** run the semantic WB validator per phase, not once over the whole breakdown ([62389cb](https://github.com/dtlab-tech/ai-toolkit/commit/62389cbb8fcf70f4019ee83ed9f8a5a99a96dd23))
18
+
2
19
  ## [0.24.4](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.3...v0.24.4) (2026-10-27)
3
20
 
4
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.24.4",
3
+ "version": "0.24.6",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: gaia-validate-work-breakdown-semantic
3
- description: "Semantic validator for Work Breakdown JSON — analyzes task coherence, scope alignment, and estimate realism. Runs after wb-validate.js exits 0. Input: path to {PREFIX}-Work-Breakdown.json"
3
+ description: "Semantic validator for one phase of a Work Breakdown — analyzes task coherence, scope alignment, and estimate realism. Runs once per phase after wb-validate.js exits 0. Input: the phase's inlined task details, a scoped Requirements excerpt, and an index of other tasks"
4
4
  model: sonnet
5
- tools: Read, Glob, Grep
5
+ tools: Read
6
6
  ---
7
7
 
8
8
  # Validate Work Breakdown — Semantic
@@ -15,17 +15,16 @@ You do NOT repeat structural checks (schema conformance, required fields, ID for
15
15
 
16
16
  ## Input
17
17
 
18
- The prompt gives you two paths, one per line, in this order:
18
+ Your prompt directly inlines everything you need — you review ONE phase per call:
19
19
 
20
- 1. The Work Breakdown directory (the split format, FTR-020) — contains `index.json` (the
21
- validated skeleton, schema v2) plus `user-stories/*.md`/`tasks/*.md` detail files. Use Glob/Read
22
- to explore it as needed; do not expect a single `{PREFIX}-Work-Breakdown.json` file.
23
- 2. The Requirements source — `req/index.md` for a feature using the split Requirements format
24
- (FTR-021; User Story descriptions and Acceptance Criteria text live in its own tables there,
25
- unaffected by the per-UC split), or a legacy `{PREFIX}-Requirements.md` file otherwise. Read
26
- whichever path is actually given — do not assume either shape.
20
+ 1. **Requirements source path** and a **scoped Requirements excerpt** — feature-wide sections in full, plus only the use case(s) this phase realizes.
21
+ 2. **The phase under review** — its id, type, title, and `sourceUC`.
22
+ 3. **Task details for THIS phase only** — the full detail file of every task in the phase. These are what you review.
23
+ 4. **Other tasks in the feature** — a one-line index (id — title — outcome) of every task outside this phase. Use it only to recognize a duplicate of work another phase already owns; do not review those tasks.
27
24
 
28
- Derive the prefix from the Work Breakdown directory's own `index.json`'s `feature` field.
25
+ Do **not** read any file yourself. Do **not** explore the filesystem. Everything you need is in your prompt, and another call reviews every other phase.
26
+
27
+ Because each call sees only one phase, cross-phase concerns are out of scope for you. Judge the tasks in front of you.
29
28
 
30
29
  ---
31
30
 
@@ -98,4 +97,4 @@ Or when issues are found:
98
97
 
99
98
  ## Tool use
100
99
 
101
- Use `Read` to load both files. Do not write any files. Do not produce any output other than the single JSON object described above.
100
+ None needed — everything is inlined. Do not write any files. Do not produce any output other than the single JSON object described above.
@@ -55,6 +55,28 @@ return c.run(meta.name, featureDir, prefix, async () => {
55
55
  return `${prompt}\n\nTemplate (${label}):\n${readTemplate(templateFile)}`
56
56
  }
57
57
 
58
+ // Gaps the validator reported on the previous cycle, set only by the revision branch below
59
+ // before it regenerates. Without this a "revision" was a blind full regeneration from
60
+ // feature.md alone: the generators never saw what was wrong, so three cycles were three
61
+ // independent attempts at the same input and convergence depended on luck.
62
+ let revisionFindings = []
63
+ function revisionSection(findings) {
64
+ if (!findings.length) return ''
65
+ return '\n\nThe previous version of these documents was validated and these gaps were reported. ' +
66
+ 'Resolve every one that concerns the document you are generating, and do not raise a new open ' +
67
+ 'question for anything feature.md already decides:\n' + findings.map(f => `- ${f}`).join('\n')
68
+ }
69
+
70
+ // The use-case agent is told not to write the "Related Business Rules"/"Related Acceptance
71
+ // Criteria" lines (the host computes them from req/index.md and appends them), but it
72
+ // sometimes writes its own anyway — with guessed IDs and "—" for the ACs it cannot see. Appending
73
+ // the host's block after it left two conflicting blocks in the file. Drop anything the model
74
+ // wrote from its first such line onward, so exactly one host-computed block remains.
75
+ function stripModelCrossReferences(detail) {
76
+ const idx = detail.search(/^\*\*Related (?:Business Rules|Acceptance Criteria):\*\*/m)
77
+ return idx === -1 ? detail : detail.slice(0, idx)
78
+ }
79
+
58
80
  // Deliberately NOT file('req') — same reasoning as FTR-020's wbsDir: this is a directory
59
81
  // nested inside the feature's own already-prefixed folder, so a second prefix would be
60
82
  // redundant.
@@ -162,7 +184,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
162
184
  const base = `${featurePathArg}\n\nGenerate the full detail for this use case:\n` +
163
185
  `UC ID: ${row.id}\nTitle: ${row.title}\nActor: ${row.actor}\nPriority: ${row.priority}\n\n` +
164
186
  `Full use-case index (for cross-UC awareness, do not restate):\n${compactIndex}`
165
- return `${base}\n\nTemplate (req/use-cases/{UC-ID}.md):\n${ucTemplate}`
187
+ return `${base}\n\nTemplate (req/use-cases/{UC-ID}.md):\n${ucTemplate}${revisionSection(revisionFindings)}`
166
188
  }
167
189
 
168
190
  // forceAll: bypasses resume-by-existence entirely, re-dispatching the skeleton and every
@@ -193,7 +215,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
193
215
  // 1. Skeleton (no declared worker() output — host writes it itself, mirroring FTR-020's
194
216
  // skeleton step)
195
217
  if (regenSkeleton) {
196
- const skeleton = await c.worker(reqId, withTemplate(featurePath, 'req/index.md', 'gaia-requirements-index-template.md'))
218
+ const skeleton = await c.worker(reqId, withTemplate(featurePath, 'req/index.md', 'gaia-requirements-index-template.md') + revisionSection(revisionFindings))
197
219
  c.writeOutput(reqIndexPath, skeleton)
198
220
  }
199
221
 
@@ -231,7 +253,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
231
253
  valid = isValidUseCaseFile(detail, row.id)
232
254
  }
233
255
  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`)
234
- c.writeOutput(ucPath, detail.trimEnd() + ucCrossReferences(c.read(reqIndexPath), row.id))
256
+ c.writeOutput(ucPath, stripModelCrossReferences(detail).trimEnd() + ucCrossReferences(c.read(reqIndexPath), row.id))
235
257
  }
236
258
  // No assembly/write step here — generation only ever produces req/index.md + its per-UC
237
259
  // files. Assembly happens in-memory, on demand, at the top level below (never persisted by
@@ -484,7 +506,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
484
506
  const base = `${featurePathArg}\n\nGenerate the full technical design for this component:\n` +
485
507
  `Path: ${row.filePath}\nChange: ${row.changeType}\nDomain: ${row.domain}\nAddresses: ${row.addresses}\n\n` +
486
508
  `Full component index (for cross-component awareness, do not restate):\n${compactIndex}`
487
- return `${base}\n\nTemplate (spec/components/{fileSlug}.md):\n${componentTemplate}`
509
+ return `${base}\n\nTemplate (spec/components/{fileSlug}.md):\n${componentTemplate}${revisionSection(revisionFindings)}`
488
510
  }
489
511
 
490
512
  // A row's own content fingerprint — the same four fields FTR-030's row-diffing has always
@@ -543,7 +565,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
543
565
 
544
566
  // 1. Skeleton (no declared worker() output — host writes it itself)
545
567
  if (regenSkeleton) {
546
- const skeleton = await c.worker(specId, withTemplate(featurePath, 'spec/index.md', 'gaia-tech-spec-index-template.md'))
568
+ const skeleton = await c.worker(specId, withTemplate(featurePath, 'spec/index.md', 'gaia-tech-spec-index-template.md') + revisionSection(revisionFindings))
547
569
  c.writeOutput(specIndexPath, applyComponentSlugs(skeleton, manifest))
548
570
  }
549
571
 
@@ -740,6 +762,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
740
762
  }
741
763
  if (cycle === 3) throw new Error('Validation gaps remain after 3 cycles')
742
764
  if (!mergedNeedsRequirementsFix && !mergedNeedsTechSpecFix) throw new Error(`Validation agent reported gaps on cycle ${cycle} but flagged neither document for revision`)
765
+ revisionFindings = mergedFindings
743
766
  if (mergedNeedsRequirementsFix) { await generateRequirements(true); assembledRequirements = assembleRequirements() }
744
767
  if (mergedNeedsTechSpecFix) { await generateTechSpec(true); assembledTechSpec = assembleTechSpec() }
745
768
  }
@@ -375,6 +375,21 @@ function buildAgentsConventions(root) {
375
375
  return kept.length ? kept.join('\n').trim() : null
376
376
  }
377
377
 
378
+ // Per-phase semantic validation prompt. The whole-WB validator used to receive a directory path and
379
+ // explore it itself with Read/Glob/Grep — on a 123-task feature that single unbounded turn hit the
380
+ // 30-minute worker ceiling, the same failure mode FTR-033 removed from skeleton generation. Each
381
+ // call now sees only its own phase's task details, a one-line index of every OTHER task (for
382
+ // duplicate-across-phases awareness), and that phase's scoped Requirements excerpt.
383
+ function buildSemanticPhasePrompt(phase, taskDetailsText, otherTasksText, requirementsExcerpt, reqIndexPath) {
384
+ return [
385
+ `Requirements source: ${reqIndexPath}`,
386
+ `Phase under review:\n${JSON.stringify({ id: phase.id, type: phase.type, title: phase.title, sourceUC: phase.sourceUC }, null, 2)}`,
387
+ `Requirements (scoped to this phase's own sourceUC, plus feature-wide sections):\n${requirementsExcerpt}`,
388
+ `Task details for THIS phase (review these):\n${taskDetailsText}`,
389
+ `Other tasks in the feature (id — title — outcome; for cross-phase duplicate awareness only, do not review them):\n${otherTasksText || '(none)'}`,
390
+ ].join('\n\n')
391
+ }
392
+
378
393
  // Compact prompt for the per-phase task-generation worker: this phase's own skeleton entry, its
379
394
  // scoped Requirements excerpt, the Tech-Spec skeleton, and every prior phase's own tasks as a
380
395
  // lightweight id/title/outcome/domain projection (never a later phase's tasks, which do not exist
@@ -678,11 +693,35 @@ return c.run(meta.name, featureDir, prefix, async () => {
678
693
  if (!parsed.valid || parsed.errors.length) throw new Error('Work breakdown structural validation failed: ' + res.stdout)
679
694
  return parsed
680
695
  })
681
- const semantic = await c.worker('gaia.agent.planner.validate-work-breakdown',
682
- `${wbsDir}\n${reqIndexPath}`, { schema: {
683
- type: 'object', properties: { valid: { type: 'boolean' }, findings: { type: 'array' } }, required: ['valid', 'findings'],
684
- } })
685
- if (!semantic.valid || semantic.findings.some(f => f.blocking)) throw new Error('Work breakdown semantic validation failed')
696
+ // Every phase is reviewed even after one fails, so a single run surfaces every blocking finding
697
+ // at once instead of making the operator fix them one resume at a time.
698
+ const semanticSchema = {
699
+ type: 'object', properties: { valid: { type: 'boolean' }, findings: { type: 'array' } }, required: ['valid', 'findings'],
700
+ }
701
+ const reqIndexForSemantic = c.read(reqIndexPath)
702
+ const allFindings = []
703
+ let semanticValid = true
704
+ for (const phase of index.phases) {
705
+ const phaseTaskIds = phase.taskIds
706
+ const taskDetailsText = phaseTaskIds
707
+ .map(taskId => `### ${taskId}\n\n${c.read(`${wbsDir}/${index.tasks[taskId].detailFile}`)}`).join('\n\n---\n\n')
708
+ const otherTasksText = Object.entries(index.tasks)
709
+ .filter(([id]) => !phaseTaskIds.includes(id))
710
+ .map(([id, t]) => `- ${id} — ${t.title} — ${t.outcome}`).join('\n')
711
+ const requirementsExcerpt = hasReq
712
+ ? buildPhaseRequirementsExcerpt(reqIndexForSemantic, phase.sourceUC, reqDir)
713
+ : reqIndexForSemantic
714
+ const verdict = await c.worker('gaia.agent.planner.validate-work-breakdown',
715
+ buildSemanticPhasePrompt(phase, taskDetailsText, otherTasksText, requirementsExcerpt, reqIndexPath),
716
+ { schema: semanticSchema, label: `validate-work-breakdown:${phase.id}` })
717
+ if (!verdict.valid) semanticValid = false
718
+ for (const finding of verdict.findings) allFindings.push({ ...finding, phaseId: phase.id })
719
+ }
720
+ const semantic = { valid: semanticValid, findings: allFindings }
721
+ if (!semantic.valid || semantic.findings.some(f => f.blocking)) {
722
+ const blocking = semantic.findings.filter(f => f.blocking).map(f => `[${f.phaseId}] ${f.taskId}: ${f.description}`)
723
+ throw new Error('Work breakdown semantic validation failed' + (blocking.length ? ':\n' + blocking.join('\n') : ''))
724
+ }
686
725
  // FTR-025: wb-render.js is no longer called automatically here — nothing downstream ever read
687
726
  // {PREFIX}-Work-Breakdown.md/.csv (every real consumer already reads wbs/ directly via
688
727
  // wbs-assembler.js's loadWorkBreakdown()), so this view is now produced only on demand, via