@dtlabs/ai-toolkit 0.24.5 → 0.24.7

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,22 @@
1
1
  ## 0.1.4 (2026-07-21)
2
+ ## [0.24.7](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.6...v0.24.7) (2026-10-30)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **pm-phase1:** route validator findings to a document by their prefix when the flags are omitted ([7bd9a38](https://github.com/dtlab-tech/ai-toolkit/commit/7bd9a38de571c07d233a5212dfba9213a1b7d562))
8
+ * **pm-phase1:** route validator findings to a document by their prefix when the flags are omitted ([f957d72](https://github.com/dtlab-tech/ai-toolkit/commit/f957d726294d2b563e67ba1c3f4e59f9c8d57be4))
9
+ * **pm-phase1:** route validator findings to a document by their prefix when the flags are omitted ([e33faef](https://github.com/dtlab-tech/ai-toolkit/commit/e33faefc4488f256e42ec459cefa547f7b5efbf2))
10
+
11
+ ## [0.24.6](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.5...v0.24.6) (2026-10-29)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **pm-phase1:** feed validation findings back to the generators and d… ([6fcb46d](https://github.com/dtlab-tech/ai-toolkit/commit/6fcb46d4e0d6d1d9ad8e2bf97313ce23691ce665))
17
+ * **pm-phase1:** feed validation findings back to the generators and drop duplicate Related blocks ([86d01df](https://github.com/dtlab-tech/ai-toolkit/commit/86d01dffbaccd9f5b99e3a356cd13e5d902d699b))
18
+ * **pm-phase1:** feed validation findings back to the generators and drop duplicate Related blocks ([9411ea5](https://github.com/dtlab-tech/ai-toolkit/commit/9411ea529d8eccdc54abcd4f8f296542b73e14b9))
19
+
2
20
  ## [0.24.5](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.4...v0.24.5) (2026-10-27)
3
21
 
4
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.24.5",
3
+ "version": "0.24.7",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -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
 
@@ -722,8 +744,14 @@ return c.run(meta.name, featureDir, prefix, async () => {
722
744
  // now-stale self-report. The only authoritative signal for the final verdict is whether any
723
745
  // finding remains, from any source, after every check this cycle has run.
724
746
  const mergedValid = mergedFindings.length === 0
725
- const mergedNeedsTechSpecFix = result.needsTechSpecFix || newFindings.some(f => f.startsWith('Tech-Spec:'))
726
- const mergedNeedsRequirementsFix = result.needsRequirementsFix || newFindings.some(f => f.startsWith('Requirements:'))
747
+ // Routing reads the prefix of EVERY finding, the validator's own included: the validator is
748
+ // required to prefix each finding with the document it concerns (gaia-validate-feature-docs,
749
+ // output contract), and its own example verdict omits needsRequirementsFix/needsTechSpecFix
750
+ // altogether. Reading the prefix only on host-computed findings made a verdict with prefixed
751
+ // findings and no flags fail with "flagged neither document" (seen on FTR-034). A finding
752
+ // with no prefix and no flag still fails closed below.
753
+ const mergedNeedsTechSpecFix = result.needsTechSpecFix || mergedFindings.some(f => f.startsWith('Tech-Spec:'))
754
+ const mergedNeedsRequirementsFix = result.needsRequirementsFix || mergedFindings.some(f => f.startsWith('Requirements:'))
727
755
 
728
756
  // c.worker() already wrote result.report verbatim via its own outputs[0] contract; only
729
757
  // overwrite it when this cycle actually added findings the LLM's own report text doesn't
@@ -740,6 +768,7 @@ return c.run(meta.name, featureDir, prefix, async () => {
740
768
  }
741
769
  if (cycle === 3) throw new Error('Validation gaps remain after 3 cycles')
742
770
  if (!mergedNeedsRequirementsFix && !mergedNeedsTechSpecFix) throw new Error(`Validation agent reported gaps on cycle ${cycle} but flagged neither document for revision`)
771
+ revisionFindings = mergedFindings
743
772
  if (mergedNeedsRequirementsFix) { await generateRequirements(true); assembledRequirements = assembleRequirements() }
744
773
  if (mergedNeedsTechSpecFix) { await generateTechSpec(true); assembledTechSpec = assembleTechSpec() }
745
774
  }