@dtlabs/ai-toolkit 0.17.5 → 0.19.0
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 +29 -0
- package/lib/agent-registry.js +39 -0
- package/package.json +1 -1
- package/src/claude/agents/gaia-generate-requirements-use-case.md +97 -0
- package/src/claude/agents/gaia-generate-requirements.md +22 -27
- package/src/claude/agents/gaia-generate-tech-spec-component.md +93 -0
- package/src/claude/agents/gaia-generate-tech-spec.md +47 -103
- package/src/claude/agents/gaia-generate-work-breakdown-task.md +151 -0
- package/src/claude/agents/gaia-generate-work-breakdown.md +124 -105
- package/src/claude/agents/gaia-validate-feature-docs.md +13 -8
- package/src/claude/agents/gaia-validate-work-breakdown-semantic.md +9 -4
- package/src/claude/commands/assemble-docs.md +16 -0
- package/src/claude/commands/feature-status.md +9 -4
- package/src/claude/scripts/ac-task-matrix.js +121 -0
- package/src/claude/scripts/assemble-docs.js +66 -0
- package/src/claude/scripts/req-assembler.js +127 -0
- package/src/claude/scripts/req-graph-checks.js +71 -0
- package/src/claude/scripts/req-skeleton-check.js +64 -0
- package/src/claude/scripts/tech-spec-assembler.js +188 -0
- package/src/claude/scripts/tech-spec-graph-checks.js +84 -0
- package/src/claude/scripts/templates/gaia-component-template.md +64 -0
- package/src/claude/scripts/templates/gaia-dependency-graph-template.md +85 -0
- package/src/claude/scripts/templates/gaia-requirements-index-template.md +144 -0
- package/src/claude/scripts/templates/gaia-task-template.md +92 -0
- package/src/claude/scripts/templates/gaia-tech-spec-index-template.md +137 -0
- package/src/claude/scripts/templates/gaia-use-case-template.md +56 -0
- package/src/claude/scripts/templates/gaia-user-story-template.md +61 -0
- package/src/claude/scripts/wb-graph-checks.js +754 -0
- package/src/claude/scripts/wb-render.js +6 -11
- package/src/claude/scripts/wb-skeleton-check.js +99 -0
- package/src/claude/scripts/wb-validate.js +49 -655
- package/src/claude/scripts/wbs-assembler.js +368 -0
- package/src/claude/skills/implement-feature/SKILL.md +12 -6
- package/src/claude/workflows/pm-phase1.js +324 -18
- package/src/claude/workflows/pm-phase2.js +358 -15
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// FTR-024: loader/renderer for the split Tech-Spec format (spec/index.md +
|
|
5
|
+
// spec/components/*.md). Reconstructs the exact 12-section monolithic shape
|
|
6
|
+
// gaia-generate-tech-spec already produced in one turn before this feature: Section 9 rebuilt
|
|
7
|
+
// from the skeleton's own Components table (authoritative, restated from each component, never
|
|
8
|
+
// re-derived); Sections 3 (Backend)/4 (Frontend)/5 (External Integrations) populated per
|
|
9
|
+
// component Domain routing; Section 10 (Testing Strategy) built from every component's own
|
|
10
|
+
// Tests subsection, unconditionally. Legacy input (a single monolithic file) is returned
|
|
11
|
+
// unchanged.
|
|
12
|
+
|
|
13
|
+
const fs = require('fs')
|
|
14
|
+
const path = require('path')
|
|
15
|
+
const { findSectionRange, parseTableRows } = require('./req-assembler')
|
|
16
|
+
|
|
17
|
+
const SECTION_BY_DOMAIN = { BE: 3, FE: 4 } // anything else (DB/DevOps/INFRA/TEST) routes to 5
|
|
18
|
+
|
|
19
|
+
class AssemblyError extends Error {}
|
|
20
|
+
|
|
21
|
+
// Same helper tech-spec-graph-checks.js already has (FTR-023) — duplicated here deliberately,
|
|
22
|
+
// not imported: this script reads 5-column rows for a structurally different purpose (section
|
|
23
|
+
// routing, not Addresses validation), and importing a function named for a different module's
|
|
24
|
+
// job would read as a false shared contract. Same precedent as wbs-assembler.js independently
|
|
25
|
+
// reimplementing plan.js's field-parsing convention (FTR-020).
|
|
26
|
+
function findSubsectionRange(lines, start, end, heading) {
|
|
27
|
+
let subStart = -1
|
|
28
|
+
for (let i = start; i < end; i++) {
|
|
29
|
+
if (lines[i].trim() === `### ${heading}`) { subStart = i; break }
|
|
30
|
+
}
|
|
31
|
+
if (subStart === -1) return null
|
|
32
|
+
let subEnd = end
|
|
33
|
+
for (let i = subStart + 1; i < end; i++) {
|
|
34
|
+
if (/^#{2,3}\s/.test(lines[i])) { subEnd = i; break }
|
|
35
|
+
}
|
|
36
|
+
return { start: subStart, end: subEnd }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseComponentsTable(indexContent) {
|
|
40
|
+
const { start, end, lines } = findSectionRange(indexContent, 9)
|
|
41
|
+
const entries = []
|
|
42
|
+
// changeType ('New'/'Modified') comes from WHICH table a row was found in, not from any
|
|
43
|
+
// column's text — the "Purpose-or-Change" column is free-text (e.g. "does backend stuff"),
|
|
44
|
+
// never literally the word "New"/"Modified". A real bug, found only by actually assembling a
|
|
45
|
+
// hand-built fixture and inspecting Section 9's rebuilt New/Modified split: every row landed
|
|
46
|
+
// under "Modified" because `change.toLowerCase() === 'new'` almost never matched free text.
|
|
47
|
+
for (const [heading, changeType] of [['New files', 'New'], ['Modified files', 'Modified']]) {
|
|
48
|
+
const sub = findSubsectionRange(lines, start, end, heading)
|
|
49
|
+
if (!sub) continue
|
|
50
|
+
for (const cells of parseTableRows(lines, sub.start, sub.end)) {
|
|
51
|
+
// Column order: Addresses MUST stay 3rd (index 2) — the exact position
|
|
52
|
+
// tech-spec-graph-checks.js's unmodified, positional parser already reads from a
|
|
53
|
+
// 3-column legacy table (FTR-023). Domain/Spec are new trailing columns.
|
|
54
|
+
const [filePath, purposeOrChange, addresses, domain, specLink] = cells
|
|
55
|
+
const m = specLink && specLink.match(/\(components\/([^)]+)\)/)
|
|
56
|
+
if (!m) throw new AssemblyError(`malformed Spec column for "${filePath}" in spec/index.md`)
|
|
57
|
+
entries.push({ filePath, purposeOrChange, changeType, domain, addresses, fileSlug: m[1] })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return entries
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Parses one component file into { change, body, tests }: `body` is everything between
|
|
64
|
+
// "## Change" (exclusive of its own heading) and "## Tests" — the component's own
|
|
65
|
+
// Algorithm/Schema content, whatever heading it actually used (the component template's own
|
|
66
|
+
// section-naming is a free-text "menu, not a checklist" — this parser does not care what that
|
|
67
|
+
// heading is literally called, only that it comes after Change and before Tests).
|
|
68
|
+
//
|
|
69
|
+
// Fence-aware: a component's own Change/body/Tests content may legitimately quote another
|
|
70
|
+
// file's literal "## Heading" line inside a fenced code example (e.g. a before/after excerpt of
|
|
71
|
+
// a markdown prompt file being modified, exactly the shape FTR-025's own spec/components/
|
|
72
|
+
// gaia-validate-feature-docs.md.md needed) — a naive line scan mistook that quoted line for a
|
|
73
|
+
// real section boundary, corrupting the assembled Section 3/4/5/10 content. Lines inside a
|
|
74
|
+
// fence are never treated as a heading, regardless of whether the fence appears before or after
|
|
75
|
+
// the real heading in the file.
|
|
76
|
+
//
|
|
77
|
+
// Length-aware, not a plain open/close boolean toggle: real CommonMark fences close only on a
|
|
78
|
+
// backtick run at least as long as the one that opened them, which is exactly what lets a
|
|
79
|
+
// longer outer fence (e.g. ````markdown) legitimately contain a shorter inner one (```) as a
|
|
80
|
+
// nested quoted example — a simple boolean toggle miscounts any such nesting (every backtick
|
|
81
|
+
// run flips it, regardless of length), landing in the wrong state for the rest of the file the
|
|
82
|
+
// moment the total run count is odd. Found exactly this way: FTR-025's own spec/components/
|
|
83
|
+
// SKILL.md.md quotes a before/after Markdown example that itself contains a fenced block.
|
|
84
|
+
function _nonFencedHeadingIndexes(lines, predicate) {
|
|
85
|
+
let fenceLen = 0 // 0 = not in a fence; otherwise the backtick-run length that opened it
|
|
86
|
+
const indexes = []
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
const m = lines[i].match(/^(`{3,})/)
|
|
89
|
+
if (m) {
|
|
90
|
+
const runLen = m[1].length
|
|
91
|
+
if (fenceLen === 0) fenceLen = runLen // opens a new fence
|
|
92
|
+
else if (runLen >= fenceLen) fenceLen = 0 // a run at least as long as the opener closes it
|
|
93
|
+
// a shorter run while already fenced is just literal backtick content inside the fence
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (fenceLen !== 0) continue
|
|
97
|
+
if (predicate(lines[i])) indexes.push(i)
|
|
98
|
+
}
|
|
99
|
+
return indexes
|
|
100
|
+
}
|
|
101
|
+
function parseComponentDetail(content) {
|
|
102
|
+
const lines = content.split('\n')
|
|
103
|
+
const changeIdx = _nonFencedHeadingIndexes(lines, l => l.trim() === '## Change')[0] ?? -1
|
|
104
|
+
const testsIdx = _nonFencedHeadingIndexes(lines, l => l.trim() === '## Tests')[0] ?? -1
|
|
105
|
+
if (changeIdx === -1) throw new AssemblyError('component file is missing its "## Change" section')
|
|
106
|
+
if (testsIdx === -1) throw new AssemblyError('component file is missing its "## Tests" section')
|
|
107
|
+
const nextHeadingIdx = _nonFencedHeadingIndexes(lines, l => /^##\s/.test(l)).find(i => i > changeIdx)
|
|
108
|
+
const changeEnd = nextHeadingIdx === undefined ? lines.length : nextHeadingIdx
|
|
109
|
+
return {
|
|
110
|
+
change: lines.slice(changeIdx + 1, changeEnd).join('\n').trim(),
|
|
111
|
+
body: lines.slice(changeEnd, testsIdx).join('\n').trim(),
|
|
112
|
+
tests: lines.slice(testsIdx + 1).join('\n').trim(),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Replaces everything between "## {n}. " and the next "## " heading with `body`, keeping the
|
|
117
|
+
// heading line itself — same splice primitive as req-assembler.js's spliceSection2, generalized
|
|
118
|
+
// to any section number instead of hardcoding 2.
|
|
119
|
+
function spliceSectionBody(doc, n, body) {
|
|
120
|
+
const { start, end, lines } = findSectionRange(doc, n)
|
|
121
|
+
const heading = lines[start]
|
|
122
|
+
const before = lines.slice(0, start).concat([heading]).join('\n')
|
|
123
|
+
const after = lines.slice(end).join('\n')
|
|
124
|
+
return `${before}\n\n${body}\n\n${after}`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function rewriteSection9(doc, components) {
|
|
128
|
+
const newFiles = components.filter(c => c.changeType === 'New')
|
|
129
|
+
const modifiedFiles = components.filter(c => c.changeType === 'Modified')
|
|
130
|
+
const table = rows => '| Path | Purpose-or-Change | Addresses |\n|---|---|---|\n' +
|
|
131
|
+
rows.map(c => `| ${c.filePath} | ${c.purposeOrChange} | ${c.addresses} |`).join('\n')
|
|
132
|
+
const body = `### New files\n${table(newFiles)}\n\n### Modified files\n${table(modifiedFiles)}`
|
|
133
|
+
return spliceSectionBody(doc, 9, body)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function loadTechSpec(inputPath) {
|
|
137
|
+
const stat = fs.statSync(inputPath, { throwIfNoEntry: false })
|
|
138
|
+
if (!stat) throw new AssemblyError(`path does not exist: ${inputPath}`)
|
|
139
|
+
if (stat.isFile()) return fs.readFileSync(inputPath, 'utf8') // legacy path
|
|
140
|
+
|
|
141
|
+
const indexPath = path.join(inputPath, 'index.md')
|
|
142
|
+
if (!fs.existsSync(indexPath)) throw new AssemblyError(`missing spec/index.md at ${indexPath}`)
|
|
143
|
+
const indexContent = fs.readFileSync(indexPath, 'utf8')
|
|
144
|
+
const components = parseComponentsTable(indexContent)
|
|
145
|
+
if (components.length === 0) throw new AssemblyError('spec/index.md has no rows in its Components table')
|
|
146
|
+
|
|
147
|
+
const detailByPath = {}
|
|
148
|
+
for (const c of components) {
|
|
149
|
+
const compPath = path.join(inputPath, 'components', c.fileSlug)
|
|
150
|
+
if (!fs.existsSync(compPath)) throw new AssemblyError(`component "${c.filePath}" has no detail file at ${compPath}`)
|
|
151
|
+
detailByPath[c.filePath] = parseComponentDetail(fs.readFileSync(compPath, 'utf8'))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let doc = rewriteSection9(indexContent, components)
|
|
155
|
+
for (const sectionNum of [3, 4, 5]) {
|
|
156
|
+
const matching = components.filter(c => (SECTION_BY_DOMAIN[c.domain] || 5) === sectionNum)
|
|
157
|
+
const body = matching.length
|
|
158
|
+
? matching.map(c => `### ${c.filePath}\n\n${detailByPath[c.filePath].body}`).join('\n\n')
|
|
159
|
+
: '_No components in this category._'
|
|
160
|
+
doc = spliceSectionBody(doc, sectionNum, body)
|
|
161
|
+
}
|
|
162
|
+
const testsBody = components.map(c => `### ${c.filePath}\n\n${detailByPath[c.filePath].tests}`).join('\n\n')
|
|
163
|
+
doc = spliceSectionBody(doc, 10, testsBody)
|
|
164
|
+
return doc
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (require.main === module) {
|
|
168
|
+
const inputPath = process.argv[2]
|
|
169
|
+
if (!inputPath) {
|
|
170
|
+
process.stderr.write('Usage: node tech-spec-assembler.js <path-to-spec-dir-or-legacy-file>\n')
|
|
171
|
+
process.exit(1)
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
process.stdout.write(loadTechSpec(path.resolve(inputPath)))
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`Error: ${err.message}\n`)
|
|
177
|
+
process.exit(err instanceof AssemblyError ? 1 : 2)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
loadTechSpec,
|
|
183
|
+
parseComponentsTable,
|
|
184
|
+
parseComponentDetail,
|
|
185
|
+
spliceSectionBody,
|
|
186
|
+
findSubsectionRange,
|
|
187
|
+
AssemblyError,
|
|
188
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// FTR-023: deterministic structural check that every Tech-Spec File Inventory "Addresses"
|
|
5
|
+
// reference resolves to a real Requirements AC ID. Combined parse/check/CLI in one file — unlike
|
|
6
|
+
// wb-graph-checks.js/req-graph-checks.js (several checks, multiple consumers), this feature has
|
|
7
|
+
// exactly one check used in exactly one context, so a separate checks-module + CLI-wrapper split
|
|
8
|
+
// isn't justified.
|
|
9
|
+
|
|
10
|
+
const fs = require('fs')
|
|
11
|
+
const path = require('path')
|
|
12
|
+
const { findSectionRange, parseTableRows } = require('./req-assembler')
|
|
13
|
+
const { parseAcTable } = require('./wb-graph-checks')
|
|
14
|
+
|
|
15
|
+
// Locates a "### {heading}" line within [start, end) and returns its own sub-range, up to the
|
|
16
|
+
// next "###" or "##" heading (or `end`). Tech-Spec-specific: req-assembler.js's
|
|
17
|
+
// findSectionRange only knows about numbered "## N." headings, one level up from this.
|
|
18
|
+
function findSubsectionRange(lines, start, end, heading) {
|
|
19
|
+
let subStart = -1
|
|
20
|
+
for (let i = start; i < end; i++) {
|
|
21
|
+
if (lines[i].trim() === `### ${heading}`) { subStart = i; break }
|
|
22
|
+
}
|
|
23
|
+
if (subStart === -1) return null
|
|
24
|
+
let subEnd = end
|
|
25
|
+
for (let i = subStart + 1; i < end; i++) {
|
|
26
|
+
if (/^#{2,3}\s/.test(lines[i])) { subEnd = i; break }
|
|
27
|
+
}
|
|
28
|
+
return { start: subStart, end: subEnd }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseFileInventoryEntries(techSpecContent) {
|
|
32
|
+
const { start, end, lines } = findSectionRange(techSpecContent, 9)
|
|
33
|
+
const entries = []
|
|
34
|
+
for (const heading of ['New files', 'Modified files']) {
|
|
35
|
+
const sub = findSubsectionRange(lines, start, end, heading)
|
|
36
|
+
if (!sub) continue // a Tech-Spec with only one of the two tables is valid (e.g. a pure-addition feature)
|
|
37
|
+
for (const cells of parseTableRows(lines, sub.start, sub.end)) {
|
|
38
|
+
const filePath = cells[0]
|
|
39
|
+
const addressesCell = cells[2] // Path | Purpose-or-Change-description | Addresses
|
|
40
|
+
const acIds = (!addressesCell || addressesCell.trim() === '—') ? []
|
|
41
|
+
: addressesCell.split(',').map(s => s.trim()).filter(Boolean)
|
|
42
|
+
entries.push({ filePath, acIds })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return entries
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkAddressesResolve(entries, validAcIds) {
|
|
49
|
+
const idSet = new Set(validAcIds)
|
|
50
|
+
const errors = []
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
for (const acId of entry.acIds) {
|
|
53
|
+
if (!idSet.has(acId)) {
|
|
54
|
+
errors.push({
|
|
55
|
+
category: 'DANGLING_TECH_SPEC_AC_REFERENCE', severity: 'error',
|
|
56
|
+
filePath: entry.filePath, acId,
|
|
57
|
+
message: `File "${entry.filePath}"'s "Addresses" column references unknown AC "${acId}"`,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return errors
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (require.main === module) {
|
|
66
|
+
const [techSpecPath, requirementsPath] = process.argv.slice(2)
|
|
67
|
+
if (!techSpecPath || !requirementsPath) {
|
|
68
|
+
process.stderr.write('Usage: node tech-spec-graph-checks.js <tech-spec-path> <requirements-path>\n')
|
|
69
|
+
process.exit(2)
|
|
70
|
+
}
|
|
71
|
+
let entries, acMap
|
|
72
|
+
try {
|
|
73
|
+
entries = parseFileInventoryEntries(fs.readFileSync(path.resolve(techSpecPath), 'utf8'))
|
|
74
|
+
acMap = parseAcTable(path.resolve(requirementsPath))
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.log(JSON.stringify({ valid: false, errors: [{ message: `parse failure: ${e.message}` }] }))
|
|
77
|
+
process.exit(2)
|
|
78
|
+
}
|
|
79
|
+
const errors = checkAddressesResolve(entries, [...acMap.keys()])
|
|
80
|
+
console.log(JSON.stringify({ valid: errors.length === 0, errors }, null, 2))
|
|
81
|
+
process.exit(errors.length === 0 ? 0 : 1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { parseFileInventoryEntries, checkAddressesResolve, findSubsectionRange }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
COMPONENT TEMPLATE — spec/components/{fileSlug}.md
|
|
3
|
+
|
|
4
|
+
Canonical structure for one component's technical design in the split Tech-Spec format (FTR-024).
|
|
5
|
+
A "component" is one file this feature creates or modifies — the granularity is the same as one
|
|
6
|
+
row in spec/index.md's own Section 9 "File Inventory" / Components table, not an arbitrary
|
|
7
|
+
subdivision.
|
|
8
|
+
|
|
9
|
+
This is the REAL, currently-implemented contract — authoritative source:
|
|
10
|
+
`src/claude/agents/gaia-generate-tech-spec-component.md`'s own "Output" section, and
|
|
11
|
+
`src/claude/scripts/tech-spec-assembler.js`'s `parseComponentDetail`, which requires exactly
|
|
12
|
+
`## Change` and `## Tests` to exist, in that relative order, to parse a component file correctly.
|
|
13
|
+
An earlier FTR-020 draft of this template (component-template.md) additionally specified
|
|
14
|
+
`Related Open Questions`/`Related components`/`Addresses` header lines and a `Decision:` section —
|
|
15
|
+
none of those exist in the real, shipped format; this file reflects what is actually generated and
|
|
16
|
+
parsed today, not that earlier draft.
|
|
17
|
+
|
|
18
|
+
Fill in every placeholder (angle brackets) and delete every HTML comment block (including this
|
|
19
|
+
one) before treating the file as finished.
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
# {Path} — {New | Modified}
|
|
23
|
+
|
|
24
|
+
## Change
|
|
25
|
+
|
|
26
|
+
<!--
|
|
27
|
+
One paragraph: what changes about this file and why — name the specific functions/sections
|
|
28
|
+
affected, not just "this file is updated to support the new feature". If New: what it exports and
|
|
29
|
+
the shape of its inputs/outputs. If Modified: what specifically is added, replaced, or removed,
|
|
30
|
+
and what is explicitly left unchanged.
|
|
31
|
+
-->
|
|
32
|
+
|
|
33
|
+
<prose>
|
|
34
|
+
|
|
35
|
+
## {Algorithm | Data Model | Service Interface | Component Props | whichever name fits this
|
|
36
|
+
component's own real nature — pick one, do not invent multiple competing headings}
|
|
37
|
+
|
|
38
|
+
<!--
|
|
39
|
+
Real pseudocode or real code, not a prose description of "what it does". If this component
|
|
40
|
+
relocates logic from an existing file, name the exact source file and line ranges if known.
|
|
41
|
+
|
|
42
|
+
If this component genuinely needs no design content beyond Change (e.g. a trivial configuration
|
|
43
|
+
file), omit this entire middle heading rather than inventing content — Change and Tests are always
|
|
44
|
+
required, this one is not.
|
|
45
|
+
-->
|
|
46
|
+
|
|
47
|
+
<pseudocode/code + prose explaining non-obvious choices>
|
|
48
|
+
|
|
49
|
+
## Tests
|
|
50
|
+
|
|
51
|
+
<!--
|
|
52
|
+
The specific test file(s) this component's change requires, and the key cases that matter — not a
|
|
53
|
+
generic "add tests for X".
|
|
54
|
+
-->
|
|
55
|
+
|
|
56
|
+
<prose / bullet list of test file(s) and key cases>
|
|
57
|
+
|
|
58
|
+
<!--
|
|
59
|
+
PARSING CONTRACT — read by tech-spec-assembler.js's parseComponentDetail: `## Change` and
|
|
60
|
+
`## Tests` must both exist, in that relative order; everything between them (including the middle
|
|
61
|
+
heading above, whatever it's actually called) is this component's own design body, concatenated
|
|
62
|
+
verbatim into the assembled Tech-Spec's Section 3 (Backend) / 4 (Frontend) / 5 (External
|
|
63
|
+
Integrations), routed by this component's own Domain from spec/index.md's Components table.
|
|
64
|
+
-->
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
DEPENDENCY GRAPH TEMPLATE — developer-plan/dependency-graph.md
|
|
3
|
+
|
|
4
|
+
STATUS (as of FTR-025): reference/design-only. No generator currently produces this file — grep
|
|
5
|
+
for "dependency-graph"/"dependencyGraph" across lib/ and src/claude/ turns up nothing that writes
|
|
6
|
+
it. Kept here, moved and renamed alongside the other six templates, for whenever the
|
|
7
|
+
developer-plan/dependency-graph.md concept is actually implemented; do not assume it is wired into
|
|
8
|
+
any real pipeline today.
|
|
9
|
+
|
|
10
|
+
Human-readable companion to developer-plan/plan.json, produced BY THE SAME GENERATOR that writes
|
|
11
|
+
plan.json — never hand-authored per feature, and never a place to restate plan.json's own arrays.
|
|
12
|
+
This file is pure output: nothing reads it back programmatically (unlike the wbs/ task and phase
|
|
13
|
+
templates, there is no parsing contract here — delete this whole comment block in a generated
|
|
14
|
+
instance, there is nothing downstream to keep honest).
|
|
15
|
+
|
|
16
|
+
Structure, in this fixed order: (1) a one-line pointer note, (2) the Mermaid dependency graph —
|
|
17
|
+
the ONLY data visualization this file keeps, because a diagram is a genuinely different
|
|
18
|
+
representation of plan.json's `readyQueue[].dependsOn`, not the same facts reformatted as another
|
|
19
|
+
table, (3) a fixed "Sequence and waves" section that explicitly refuses to reprint
|
|
20
|
+
`readyQueue`/`phaseWaves` as Markdown tables, (4) a fixed "Integrity" section. An earlier draft of
|
|
21
|
+
this file DID reprint the ready-queue and phase-waves as tables and was reverted after review —
|
|
22
|
+
same facts in two places drifts the moment plan.json regenerates and this file doesn't. Do not
|
|
23
|
+
reintroduce those tables.
|
|
24
|
+
|
|
25
|
+
Placeholders:
|
|
26
|
+
{FEATURE} feature ID, e.g. FTR-020
|
|
27
|
+
{PHASE_ID} a phase ID from plan.json's phases[].id
|
|
28
|
+
{TASK_ID} a task ID from plan.json's readyQueue[].id
|
|
29
|
+
{NODE_ID} {TASK_ID} with every hyphen removed/underscored so it's a legal Mermaid node
|
|
30
|
+
name (Mermaid node IDs can't contain hyphens) — drop the phase prefix once the
|
|
31
|
+
node already lives inside that phase's subgraph, drop the literal "TASK", keep
|
|
32
|
+
it short and unique within the subgraph (e.g. US-02-TASK-BE-01 -> US02_BE01)
|
|
33
|
+
{sourceDigest} plan.json's own sourceDigest value, copied verbatim, including the "sha256:"
|
|
34
|
+
prefix
|
|
35
|
+
-->
|
|
36
|
+
|
|
37
|
+
# {FEATURE} — Dependency Graph & Implementation Sequence
|
|
38
|
+
|
|
39
|
+
> Human-readable companion to [`plan.json`](plan.json) — the machine-readable file is
|
|
40
|
+
> authoritative; this is a rendered view of the same data, regenerated alongside it, never
|
|
41
|
+
> hand-edited independently (same convention as `{PREFIX}-Work-Breakdown.md` being generated
|
|
42
|
+
> from the JSON it describes — see `wb-render.js`).
|
|
43
|
+
|
|
44
|
+
## Dependency graph
|
|
45
|
+
|
|
46
|
+
<!--
|
|
47
|
+
One `subgraph {PHASE_ID}` block per phase, in plan.json's phases[] order, listing that phase's
|
|
48
|
+
own taskIds as Mermaid nodes ({NODE_ID}[{TASK_ID}]). Below every subgraph block, one
|
|
49
|
+
`{FROM_NODE_ID} --> {TO_NODE_ID}` edge per (task, dependency) pair found by walking every
|
|
50
|
+
readyQueue[].dependsOn entry — one edge per dependency, not one edge per task (a task with 3
|
|
51
|
+
entries in dependsOn gets 3 edges). Edge order: by readyQueue[].sequence, then by the order the
|
|
52
|
+
dependency appears inside that task's own dependsOn array. A task with an empty dependsOn
|
|
53
|
+
contributes no edges, only its node.
|
|
54
|
+
-->
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
graph TD
|
|
58
|
+
subgraph {PHASE_ID}
|
|
59
|
+
{NODE_ID}[{TASK_ID}]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{FROM_NODE_ID} --> {TO_NODE_ID}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Sequence and waves
|
|
66
|
+
|
|
67
|
+
The dispatch order (`readyQueue`), the per-task dependencies (`dependsOn`), and the phase
|
|
68
|
+
waves (`phaseWaves`) are **not repeated here** — they already exist as structured data in
|
|
69
|
+
[`plan.json`](plan.json) and reprinting them as a Markdown table would just be the same facts
|
|
70
|
+
in a second place, free to drift the moment `plan.json` is regenerated and this file isn't.
|
|
71
|
+
Read them straight from there; the Mermaid diagram above is the only rendering kept in this
|
|
72
|
+
file, because it's a genuinely different representation (visual graph) rather than the same
|
|
73
|
+
rows reformatted.
|
|
74
|
+
|
|
75
|
+
## Integrity
|
|
76
|
+
|
|
77
|
+
- **Source digest:** `{sourceDigest}` — computed over every file under `wbs/` (sorted by
|
|
78
|
+
relative path, each length-prefixed before hashing, same technique as
|
|
79
|
+
`lib/task-executor/plan.js`'s own `_computePlanDigest`, extended from "two byte blobs" to
|
|
80
|
+
"every file in a directory"). A re-run of the same computation against the current `wbs/`
|
|
81
|
+
must reproduce this exact value, or this plan is stale and must be regenerated before
|
|
82
|
+
implementation proceeds — mirrors `plan.js`'s existing resume-safety contract, made an
|
|
83
|
+
explicit, reviewable file instead of an internal runtime-only check.
|
|
84
|
+
- Per-task and per-phase checksums in `plan.json` let a drift check point at the *specific*
|
|
85
|
+
file that changed, not just "something changed somewhere."
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
REQUIREMENTS INDEX TEMPLATE — req/index.md
|
|
3
|
+
|
|
4
|
+
Canonical structure for the Requirements index in the split format (FTR-021). Holds everything
|
|
5
|
+
that is NOT naturally owned by a single Use Case — i.e. everything except each Use Case's own flow
|
|
6
|
+
body, which gets its own file under use-cases/{UC-ID}.md (see gaia-use-case-template.md).
|
|
7
|
+
|
|
8
|
+
This is the REAL, currently-implemented 9-section contract — authoritative source:
|
|
9
|
+
`src/claude/agents/gaia-generate-requirements.md`'s own "Output Structure" section, and
|
|
10
|
+
`src/claude/scripts/req-skeleton-check.js`/`req-assembler.js`, which require exactly these 9
|
|
11
|
+
numbered top-level `## N. ` headings, in order, to exist. An earlier FTR-020 exploratory draft of
|
|
12
|
+
this template used a different 7-section numbering (no "UI Requirements" or "Open Questions" as
|
|
13
|
+
their own sections) that was never what Requirements generation actually shipped — this file
|
|
14
|
+
reflects the real, current format, not that earlier draft.
|
|
15
|
+
|
|
16
|
+
SPLITTING PRINCIPLE (apply this test to any new section before deciding where it goes): does this
|
|
17
|
+
content belong to exactly one UC, or does it reference/apply to several? A Business Rule that
|
|
18
|
+
applies to 3 UCs, a Data Requirement describing a schema used by the whole feature, an NFR that
|
|
19
|
+
constrains the feature as a whole — none of these are owned by one UC any more than a shared setup
|
|
20
|
+
module is owned by one Work Breakdown task. Only split out content that is genuinely per-item;
|
|
21
|
+
everything cross-cutting stays here, in one place.
|
|
22
|
+
|
|
23
|
+
EVERY table in this document (and in every use-case file) MUST be followed immediately by a
|
|
24
|
+
one-line legend (blockquote) explaining each column.
|
|
25
|
+
|
|
26
|
+
Fill in every placeholder (angle brackets) and delete every HTML comment block (including this
|
|
27
|
+
one) before treating the file as finished.
|
|
28
|
+
-->
|
|
29
|
+
|
|
30
|
+
# {PREFIX} — Functional Requirements (Index)
|
|
31
|
+
|
|
32
|
+
## 1. Introduction
|
|
33
|
+
|
|
34
|
+
### 1.1 Purpose
|
|
35
|
+
|
|
36
|
+
<prose: brief description of what this requirements document covers>
|
|
37
|
+
|
|
38
|
+
### 1.2 Scope
|
|
39
|
+
|
|
40
|
+
<prose: what is in scope and out of scope>
|
|
41
|
+
|
|
42
|
+
### 1.3 Actors
|
|
43
|
+
|
|
44
|
+
| Actor | Description |
|
|
45
|
+
|-------|-------------|
|
|
46
|
+
| <actor> | <description> |
|
|
47
|
+
|
|
48
|
+
## 2. Use Cases
|
|
49
|
+
|
|
50
|
+
<!-- One row per UC. This table is the single place that lists every UC that exists — the
|
|
51
|
+
use-cases/ directory listing must match it exactly. Title here is a short label, NOT the
|
|
52
|
+
full flow description — that's in the UC's own file (FTR-021: a separate call per UC fills
|
|
53
|
+
in each row's full detail after this skeleton is accepted). -->
|
|
54
|
+
|
|
55
|
+
| UC ID | Title | Actor | Priority | File |
|
|
56
|
+
|-------|-------|-------|----------|------|
|
|
57
|
+
| UC-{NN} | <short title> | <actor(s)> | <Must \| Should \| Could> | [use-cases/UC-{NN}.md](use-cases/UC-{NN}.md) |
|
|
58
|
+
|
|
59
|
+
> **Legend:** **UC ID** — unique identifier, referenced from Business Rules' `Applies to` and
|
|
60
|
+
> Acceptance Criteria's `Related UC` columns. **Title** — short label; full flow is in the linked
|
|
61
|
+
> file. **Actor** — who/what initiates or participates. **Priority** — MoSCoW; drives downstream
|
|
62
|
+
> Must-coverage checks. **File** — this UC's own detail file.
|
|
63
|
+
|
|
64
|
+
## 3. Business Rules
|
|
65
|
+
|
|
66
|
+
| ID | Rule | Applies to |
|
|
67
|
+
|----|------|-----------|
|
|
68
|
+
| BR-{NN} | <rule statement> | <UC-ID, UC-ID, ... \| All UCs> |
|
|
69
|
+
|
|
70
|
+
> **Legend:** **ID** — unique identifier. **Rule** — the constraint itself. **Applies to** —
|
|
71
|
+
> which UC(s) this rule constrains, or "All UCs" if universal.
|
|
72
|
+
|
|
73
|
+
## 4. Data Requirements
|
|
74
|
+
|
|
75
|
+
### 4.1 Entities
|
|
76
|
+
|
|
77
|
+
<!-- For each entity: fields, types, constraints, relationships. -->
|
|
78
|
+
|
|
79
|
+
<prose>
|
|
80
|
+
|
|
81
|
+
### 4.2 Validation Rules
|
|
82
|
+
|
|
83
|
+
| Field | Rule |
|
|
84
|
+
|-------|------|
|
|
85
|
+
| <field> | <rule> |
|
|
86
|
+
|
|
87
|
+
## 5. Non-Functional Requirements
|
|
88
|
+
|
|
89
|
+
| ID | Category | Requirement |
|
|
90
|
+
|----|----------|-------------|
|
|
91
|
+
| NFR-{NN} | <Security \| Performance \| Usability \| ...> | <requirement statement> |
|
|
92
|
+
|
|
93
|
+
## 6. UI Requirements
|
|
94
|
+
|
|
95
|
+
<!-- N/A — internal/technical feature; no UI surface is introduced or changed, if that's true for
|
|
96
|
+
this feature — otherwise fill in both subsections below. -->
|
|
97
|
+
|
|
98
|
+
### 6.1 Pages / Screens
|
|
99
|
+
|
|
100
|
+
<!-- For each page: purpose, layout description, components, interactions. -->
|
|
101
|
+
|
|
102
|
+
<prose>
|
|
103
|
+
|
|
104
|
+
### 6.2 Navigation Flow
|
|
105
|
+
|
|
106
|
+
<prose: how pages connect and how the user moves between them>
|
|
107
|
+
|
|
108
|
+
## 7. Acceptance Criteria
|
|
109
|
+
|
|
110
|
+
<!--
|
|
111
|
+
Column contract: first three columns MUST be exactly "ID", "Criterion", "Related UC" in that
|
|
112
|
+
order — parsed as-is by wb-graph-checks.js's parseAcTable (do not reinvent the column format).
|
|
113
|
+
"Related UC" must be a single UC-ID, a comma-separated list, or the literal "All UCs".
|
|
114
|
+
-->
|
|
115
|
+
|
|
116
|
+
| ID | Criterion | Related UC | Priority |
|
|
117
|
+
|----|-----------|-------------|----------|
|
|
118
|
+
| AC-{NN} | <Given/When/Then or plain testable statement> | <UC-ID \| UC-ID, UC-ID \| All UCs> | <Must \| Should \| Could> |
|
|
119
|
+
|
|
120
|
+
> **Legend:** **ID** — unique identifier; the anchor used across the whole
|
|
121
|
+
> feature.md↔req↔spec↔Work-Breakdown traceability chain. **Criterion** — a testable Given/When/Then
|
|
122
|
+
> statement. **Related UC** — which UC(s) this AC belongs to, or "All UCs"; machine-parsed by
|
|
123
|
+
> `wb-graph-checks.js`'s `parseAcTable`, not just descriptive. **Priority** — MoSCoW; every Must AC
|
|
124
|
+
> must be covered by ≥1 WB task.
|
|
125
|
+
|
|
126
|
+
## 8. Dependencies and Assumptions
|
|
127
|
+
|
|
128
|
+
<!-- External prerequisites, prior features this one builds on, fixed constraints, and anything
|
|
129
|
+
that must be resolved before Gate 1. -->
|
|
130
|
+
|
|
131
|
+
<prose / bullet list>
|
|
132
|
+
|
|
133
|
+
## 9. Open Questions
|
|
134
|
+
|
|
135
|
+
| # | Question | Impact | Suggested resolution |
|
|
136
|
+
|---|----------|--------|---------------------|
|
|
137
|
+
| <N> | <question> | <impact if unresolved> | <suggested resolution, or "open — deferred"> |
|
|
138
|
+
|
|
139
|
+
<!--
|
|
140
|
+
PARSING CONTRACT — req-skeleton-check.js/req-assembler.js require exactly these 9 numbered
|
|
141
|
+
`## N. ` headings, in order, to exist (assertCompleteSections's truncation guard in pm-phase1.js
|
|
142
|
+
checks the same 9 headings). Section 2's table is the index into use-cases/{UC-ID}.md — never
|
|
143
|
+
write a UC's flow body inline here.
|
|
144
|
+
-->
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
TASK DETAIL TEMPLATE — wbs/tasks/{TASK_ID}.md
|
|
3
|
+
|
|
4
|
+
Canonical structure for a task file in the FTR-020 split Work Breakdown format. Every task file
|
|
5
|
+
has exactly three parts, in this order: (1) the field block, (2) Implementation notes,
|
|
6
|
+
(3) Verification commands. Fill in every placeholder below (angle brackets) and delete every
|
|
7
|
+
HTML comment block (including this one) before treating the file as finished. Do not add, remove,
|
|
8
|
+
or reorder the 14 fields — they are parsed positionally as the contiguous run immediately after
|
|
9
|
+
the heading (see "Parsing contract" at the bottom of this file).
|
|
10
|
+
-->
|
|
11
|
+
<a id="task-{TASK_ID}"></a>
|
|
12
|
+
### {TASK_ID}
|
|
13
|
+
|
|
14
|
+
- **Task ID:** {TASK_ID}
|
|
15
|
+
- **Title:** <Short, specific, action-oriented. Not a restatement of the outcome.>
|
|
16
|
+
- **Outcome:** <The ONE observable thing that exists/changed after this task is done — one
|
|
17
|
+
factual, verifiable sentence. If you need "and" to describe it, the task is probably not
|
|
18
|
+
atomic — split it (see Atomicity Contract in gaia-generate-work-breakdown.md).>
|
|
19
|
+
- **Domain:** <one of: BE | FE | DB | DevOps | INFRA | TEST>
|
|
20
|
+
- **Agent type:** <one of: developer-backend | developer-frontend | developer-testing | review-solution>
|
|
21
|
+
- **Dependencies:** <comma-separated task IDs this task's work requires to exist first, or `—` if none>
|
|
22
|
+
- **Acceptance criteria:** <comma-separated AC IDs from Requirements that this task covers, scoped
|
|
23
|
+
to this task's own phase — or `—` if none>
|
|
24
|
+
- **Estimate — agent minutes:** <integer. Target ≤15 (no flag); 16–20 acceptable (no flag);
|
|
25
|
+
21–30 is a non-blocking warning — justify in Grouping rationale below; >30 is rejected, split
|
|
26
|
+
the task instead.>
|
|
27
|
+
- **Estimate — tokens:** <integer, rough order of magnitude for the agent invocation>
|
|
28
|
+
- **Output count:** <integer ≥1. Almost always 1 — see Atomicity Contract. >1 requires a non-null
|
|
29
|
+
Grouping rationale below.>
|
|
30
|
+
- **Grouping rationale:** <required (non-`—`) only when Output count >1 or the estimate is in the
|
|
31
|
+
21–30 min warning band; otherwise `—`>
|
|
32
|
+
- **Commit type:** <conventional-commit type: feat | fix | refactor | test | chore | docs | ...>
|
|
33
|
+
- **Commit scope:** <optional conventional-commit scope, or `—`>
|
|
34
|
+
- **Commit subject:** <imperative, concise, no trailing period — the renderer prepends
|
|
35
|
+
`type(scope): ` automatically, do not include it here>
|
|
36
|
+
|
|
37
|
+
**Implementation notes:**
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
This is the part that makes a task file self-sufficient: someone (human or agent) reading ONLY
|
|
41
|
+
this file — not the whole Tech-Spec — should know exactly what to do and how.
|
|
42
|
+
|
|
43
|
+
Write:
|
|
44
|
+
- If this task RELOCATES or MODIFIES existing code: name the exact file(s) and, if you know
|
|
45
|
+
them, the exact current line ranges to copy from or change. Show a before/after snippet, not
|
|
46
|
+
just a description of the change.
|
|
47
|
+
- If this task CREATES something new: give its real signature(s) (function names, parameters,
|
|
48
|
+
return shape) and the actual algorithm as pseudocode or real code — not just a description of
|
|
49
|
+
what it should do.
|
|
50
|
+
- If this task is a TEST: show at least one concrete `test()`/`describe()` block with real
|
|
51
|
+
assertions against the real function/CLI being tested, not a prose list of "things to check".
|
|
52
|
+
- Length target: not 10 lines (too thin to act on without re-deriving the design yourself), not
|
|
53
|
+
10 pages (defeats the point of a small, bounded, independently generated task). A few concrete
|
|
54
|
+
snippets plus the reasoning that connects them is usually right.
|
|
55
|
+
- It is fine — often good — to include a literal example of this exact task-file format inside a
|
|
56
|
+
fenced code block (e.g. to show another agent's expected output shape). If you do, see the
|
|
57
|
+
parsing-contract warning at the bottom of this file: it is specifically about this situation.
|
|
58
|
+
-->
|
|
59
|
+
|
|
60
|
+
<prose + pseudocode/code here>
|
|
61
|
+
|
|
62
|
+
**Verification commands:**
|
|
63
|
+
|
|
64
|
+
<!--
|
|
65
|
+
One or more real shell commands (use AGENTS.md's actual build/test commands for this project)
|
|
66
|
+
that verify this task's output. Must be non-empty. Each command gets its own fenced block,
|
|
67
|
+
preserved byte-for-byte verbatim — never edited, escaped, or "cleaned up" by any tool that
|
|
68
|
+
touches this file; this is what lets a command contain `|`, backticks, or other shell syntax
|
|
69
|
+
safely.
|
|
70
|
+
-->
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
<command>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
<!--
|
|
77
|
+
PARSING CONTRACT (do not delete this section from the real template file; delete only from a
|
|
78
|
+
filled-in task instance) — read by wbs-assembler.js's parseTaskDetailBlock:
|
|
79
|
+
1. Field values are read from the CONTIGUOUS RUN of `- **Label:** value` lines starting
|
|
80
|
+
immediately after the `### {TASK_ID}` heading. Scanning STOPS at the first line that is not
|
|
81
|
+
in that shape — so an example block further down in Implementation notes that happens to
|
|
82
|
+
contain `- **Task ID:** ...`-shaped lines is never mistaken for the real fields. (A real bug,
|
|
83
|
+
found and fixed during this feature's own definition — see US-03-TASK-INFRA-01.md and
|
|
84
|
+
Tech-Spec §4.)
|
|
85
|
+
2. The verification commands are the fenced block(s) after the LAST occurrence of
|
|
86
|
+
"**Verification commands:**" in the file — not the first — for the same reason: an
|
|
87
|
+
illustrative example earlier in the file must never be mistaken for the real section.
|
|
88
|
+
3. Conclusion for anyone writing Implementation notes: you may safely show an example of this
|
|
89
|
+
format inside your own notes. You may NOT rely on your example being the last occurrence of
|
|
90
|
+
"**Verification commands:**" in the file — the real one (at the bottom) always is, by
|
|
91
|
+
construction, as long as you don't add a second real verification section.
|
|
92
|
+
-->
|