@dtlabs/ai-toolkit 0.9.0 → 0.10.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/.claude/agents/generate-work-breakdown.md +212 -192
- package/.claude/agents/install-toolkit.md +6 -1
- package/.claude/agents/validate-work-breakdown-semantic.md +96 -0
- package/.claude/scripts/.gitkeep +0 -0
- package/.claude/scripts/tests/fixtures/ac-table-format.md +48 -0
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-Work-Breakdown.json +234 -0
- package/.claude/scripts/tests/fixtures/test-feature/TEST-001-semantic-output.json +4 -0
- package/.claude/scripts/tests/fixtures/test-feature/feature.md +45 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-bad-domain.json +39 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-cycle.json +61 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-duplicate-id.json +72 -0
- package/.claude/scripts/tests/fixtures/wb-invalid-missing-field.json +38 -0
- package/.claude/scripts/tests/fixtures/wb-valid.json +189 -0
- package/.claude/scripts/tests/helpers/buildWaves.js +21 -0
- package/.claude/scripts/tests/helpers/pm-phase2-flow.js +29 -0
- package/.claude/scripts/tests/helpers/pm-phase2-gate2.js +55 -0
- package/.claude/scripts/tests/helpers/pm-phase2-ledger.js +108 -0
- package/.claude/scripts/tests/installer.scripts-distribution.test.js +131 -0
- package/.claude/scripts/tests/pm-phase2.gate2-payload.test.js +353 -0
- package/.claude/scripts/tests/pm-phase2.ledger-transitions.test.js +346 -0
- package/.claude/scripts/tests/pm-phase2.orchestration-flow.test.js +276 -0
- package/.claude/scripts/tests/verify-us-01-t03.js +288 -0
- package/.claude/scripts/tests/verify-us-03-t04.js +262 -0
- package/.claude/scripts/tests/wb-render.checks-md-csv-deps.test.js +337 -0
- package/.claude/scripts/tests/wb-render.checks-sanitize-commit-paths.test.js +352 -0
- package/.claude/scripts/tests/wb-render.regression-pm-phase3-csv.test.js +219 -0
- package/.claude/scripts/tests/wb-validate.checks-1-7.test.js +330 -0
- package/.claude/scripts/tests/wb-validate.checks-11.test.js +166 -0
- package/.claude/scripts/tests/wb-validate.checks-12-14.test.js +222 -0
- package/.claude/scripts/tests/wb-validate.checks-15-18.test.js +359 -0
- package/.claude/scripts/tests/wb-validate.checks-19-21.test.js +362 -0
- package/.claude/scripts/tests/wb-validate.checks-22-23-report.test.js +297 -0
- package/.claude/scripts/tests/wb-validate.checks-8-10.test.js +243 -0
- package/.claude/scripts/wb-render.js +309 -0
- package/.claude/scripts/wb-validate.js +1007 -0
- package/.claude/workflows/pm-phase2.js +289 -28
- package/CHANGELOG.md +67 -0
- package/bin/cli.js +1 -0
- package/package.json +1 -1
|
@@ -13,13 +13,15 @@ export const meta = {
|
|
|
13
13
|
|
|
14
14
|
async function appendLedgerEntry(featureDir, prefix, entry) {
|
|
15
15
|
const ledgerPath = `${featureDir}/${prefix}-token-ledger.json`
|
|
16
|
-
//
|
|
17
|
-
|
|
16
|
+
// Defaults come first so the caller can override started_at/completed_at.
|
|
17
|
+
// Skipped entries pass completed_at: '__TS__' to get a real timestamp; running entries
|
|
18
|
+
// pass completed_at: null to keep it null. Both are replaced/kept by the agent below.
|
|
19
|
+
const entryWithoutTs = JSON.stringify({ started_at: '__TS__', completed_at: null, ...entry })
|
|
18
20
|
await agent(
|
|
19
21
|
`Append a JSON object to the ledger array at: ${ledgerPath}\n\n` +
|
|
20
22
|
`1. Run: date -u +"%Y-%m-%dT%H:%M:%SZ" and capture the output as NOW.\n` +
|
|
21
23
|
`2. Read the file. If it does not exist or cannot be parsed as a JSON array, start with [].\n` +
|
|
22
|
-
`3. Push this object onto the array, replacing "__TS__" in
|
|
24
|
+
`3. Push this object onto the array, replacing every "__TS__" string value in the object with NOW: ${entryWithoutTs}\n` +
|
|
23
25
|
`4. Write the full array back (JSON, 2-space indent). Return no output.`,
|
|
24
26
|
{ label: 'append-ledger', phase: 'Work Breakdown', model: 'haiku' }
|
|
25
27
|
)
|
|
@@ -41,6 +43,48 @@ async function updateLedgerEntry(featureDir, prefix, agentKey, updates) {
|
|
|
41
43
|
)
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
// Output schemas for wb-validate and wb-render agent invocations (used by US-05)
|
|
47
|
+
const WB_WRAPPER_SCHEMA = {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
exitCode: { type: 'number' },
|
|
51
|
+
stdout: { type: 'string' },
|
|
52
|
+
stderr: { type: 'string' },
|
|
53
|
+
},
|
|
54
|
+
required: ['exitCode', 'stdout', 'stderr'],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const WB_RENDER_SCHEMA = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
exitCode: { type: 'number' },
|
|
61
|
+
stdout: { type: 'string' },
|
|
62
|
+
stderr: { type: 'string' },
|
|
63
|
+
markdownPath: { type: 'string' },
|
|
64
|
+
csvPath: { type: 'string' },
|
|
65
|
+
markdownExists: { type: 'boolean' },
|
|
66
|
+
csvExists: { type: 'boolean' },
|
|
67
|
+
},
|
|
68
|
+
required: ['exitCode', 'stdout', 'stderr', 'markdownPath', 'csvPath', 'markdownExists', 'csvExists'],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const WB_SEMANTIC_SCHEMA = {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
valid: { type: 'boolean' },
|
|
75
|
+
findings: { type: 'array' },
|
|
76
|
+
},
|
|
77
|
+
required: ['valid', 'findings'],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeError(err) {
|
|
81
|
+
if (!err) return '<unknown error>'
|
|
82
|
+
if (typeof err === 'string') return err
|
|
83
|
+
if (err.message) return err.message
|
|
84
|
+
if (err.stderr) return err.stderr
|
|
85
|
+
try { return JSON.stringify(err) } catch (_) { return '<unknown error>' }
|
|
86
|
+
}
|
|
87
|
+
|
|
44
88
|
// ── Parse args ────────────────────────────────────────────────────────────────
|
|
45
89
|
|
|
46
90
|
// args: "<path-to-feature.md>"
|
|
@@ -81,6 +125,225 @@ await updateLedgerEntry(featureDir, prefix, 'generate-work-breakdown:phase2', {
|
|
|
81
125
|
tokenLedger.push({ agent: 'generate-work-breakdown', model: 'haiku', phase_delta_tokens: wbTokens })
|
|
82
126
|
log(`generate-work-breakdown done — phase delta: ${wbTokens} tokens`)
|
|
83
127
|
|
|
128
|
+
// ── Step 2: wb-validate.js (deterministic structural validator) ───────────────
|
|
129
|
+
let wbValidatorReport = null
|
|
130
|
+
let validateFailed = false
|
|
131
|
+
let validateExitCode = null
|
|
132
|
+
await appendLedgerEntry(featureDir, prefix, {
|
|
133
|
+
agent: 'wb-validate:phase2',
|
|
134
|
+
phase: 'phase2', model: 'haiku', status: 'running',
|
|
135
|
+
phase_delta_tokens: 0, started_at: '__TS__', completed_at: null,
|
|
136
|
+
})
|
|
137
|
+
const beforeValidate = budget.spent()
|
|
138
|
+
try {
|
|
139
|
+
const validateWrapper = await agent(
|
|
140
|
+
`Run this command and return the result as structured output:\n` +
|
|
141
|
+
`node .claude/scripts/wb-validate.js "${featureDir}/${prefix}-Work-Breakdown.json" "${featureDir}/${prefix}-Requirements.md"\n\n` +
|
|
142
|
+
`Capture exitCode (integer), stdout (string), stderr (string). Return all three.`,
|
|
143
|
+
{
|
|
144
|
+
label: 'wb-validate',
|
|
145
|
+
phase: 'Work Breakdown',
|
|
146
|
+
model: 'haiku',
|
|
147
|
+
schema: WB_WRAPPER_SCHEMA,
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
validateExitCode = validateWrapper.exitCode
|
|
151
|
+
if (validateWrapper.stderr?.trim()) {
|
|
152
|
+
log(`wb-validate stderr: ${validateWrapper.stderr.trim()}`)
|
|
153
|
+
}
|
|
154
|
+
if (validateWrapper.exitCode === 2) {
|
|
155
|
+
throw Object.assign(
|
|
156
|
+
new Error(validateWrapper.stderr?.trim() || 'wb-validate runtime error (exit 2)'),
|
|
157
|
+
{ _exitCode: validateWrapper.exitCode }
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
if (![0, 1].includes(validateWrapper.exitCode)) {
|
|
161
|
+
throw Object.assign(
|
|
162
|
+
new Error(`wb-validate unexpected exit code: ${validateWrapper.exitCode}`),
|
|
163
|
+
{ _exitCode: validateWrapper.exitCode }
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
if (!validateWrapper.stdout?.trim()) {
|
|
167
|
+
throw new Error('wb-validate returned empty stdout')
|
|
168
|
+
}
|
|
169
|
+
wbValidatorReport = JSON.parse(validateWrapper.stdout)
|
|
170
|
+
if (validateWrapper.exitCode === 0 && !wbValidatorReport.valid) {
|
|
171
|
+
throw new Error('Inconsistent wb-validate result: exit 0 with valid=false')
|
|
172
|
+
}
|
|
173
|
+
if (validateWrapper.exitCode === 1 && wbValidatorReport.valid) {
|
|
174
|
+
throw new Error('Inconsistent wb-validate result: exit 1 with valid=true')
|
|
175
|
+
}
|
|
176
|
+
await updateLedgerEntry(featureDir, prefix, 'wb-validate:phase2', {
|
|
177
|
+
status: 'done', completed_at: '__TS__',
|
|
178
|
+
phase_delta_tokens: budget.spent() - beforeValidate,
|
|
179
|
+
})
|
|
180
|
+
} catch (err) {
|
|
181
|
+
validateFailed = true
|
|
182
|
+
await updateLedgerEntry(featureDir, prefix, 'wb-validate:phase2', {
|
|
183
|
+
status: 'failed', completed_at: '__TS__',
|
|
184
|
+
phase_delta_tokens: budget.spent() - beforeValidate,
|
|
185
|
+
error_summary: normalizeError(err),
|
|
186
|
+
exit_code: err._exitCode ?? validateExitCode ?? null,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
const validateTokens = budget.spent() - beforeValidate
|
|
190
|
+
const wbValidatorPassed = !validateFailed && wbValidatorReport !== null
|
|
191
|
+
&& wbValidatorReport.valid === true && wbValidatorReport.errors.length === 0
|
|
192
|
+
log(`wb-validate: ${wbValidatorPassed ? 'passed' : 'failed/errors'} — ${validateTokens} tokens`)
|
|
193
|
+
|
|
194
|
+
// ── Step 3: Semantic validator (only if wb-validate passed) ───────────────────
|
|
195
|
+
let semanticResult = null
|
|
196
|
+
let semanticFailed = false
|
|
197
|
+
let semanticTokens = 0
|
|
198
|
+
if (!wbValidatorPassed) {
|
|
199
|
+
await appendLedgerEntry(featureDir, prefix, {
|
|
200
|
+
agent: 'validate-work-breakdown-semantic:phase2',
|
|
201
|
+
phase: 'phase2', model: 'sonnet', status: 'skipped',
|
|
202
|
+
phase_delta_tokens: 0, started_at: '__TS__', completed_at: '__TS__',
|
|
203
|
+
})
|
|
204
|
+
} else {
|
|
205
|
+
await appendLedgerEntry(featureDir, prefix, {
|
|
206
|
+
agent: 'validate-work-breakdown-semantic:phase2',
|
|
207
|
+
phase: 'phase2', model: 'sonnet', status: 'running',
|
|
208
|
+
phase_delta_tokens: 0, started_at: '__TS__', completed_at: null,
|
|
209
|
+
})
|
|
210
|
+
const beforeSemantic = budget.spent()
|
|
211
|
+
try {
|
|
212
|
+
semanticResult = await agent(
|
|
213
|
+
`${featureDir}/${prefix}-Work-Breakdown.json\n${featureDir}/${prefix}-Requirements.md`,
|
|
214
|
+
{
|
|
215
|
+
label: 'validate-work-breakdown-semantic',
|
|
216
|
+
phase: 'Work Breakdown',
|
|
217
|
+
agentType: 'validate-work-breakdown-semantic',
|
|
218
|
+
schema: WB_SEMANTIC_SCHEMA,
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
semanticTokens = budget.spent() - beforeSemantic
|
|
222
|
+
await updateLedgerEntry(featureDir, prefix, 'validate-work-breakdown-semantic:phase2', {
|
|
223
|
+
status: 'done', completed_at: '__TS__', phase_delta_tokens: semanticTokens,
|
|
224
|
+
})
|
|
225
|
+
} catch (err) {
|
|
226
|
+
semanticFailed = true
|
|
227
|
+
semanticTokens = budget.spent() - beforeSemantic
|
|
228
|
+
await updateLedgerEntry(featureDir, prefix, 'validate-work-breakdown-semantic:phase2', {
|
|
229
|
+
status: 'failed', completed_at: '__TS__', phase_delta_tokens: semanticTokens,
|
|
230
|
+
error_summary: normalizeError(err), exit_code: null,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
log(`semantic validator: ${semanticFailed ? 'failed' : semanticResult ? 'done' : 'skipped'}`)
|
|
235
|
+
|
|
236
|
+
// ── Step 4: wb-render.js (only if wb-validate passed and semantic did not fail) ─
|
|
237
|
+
const canRender = wbValidatorPassed && !semanticFailed
|
|
238
|
+
let renderFailed = false
|
|
239
|
+
let renderResult = null
|
|
240
|
+
if (!canRender) {
|
|
241
|
+
await appendLedgerEntry(featureDir, prefix, {
|
|
242
|
+
agent: 'wb-render:phase2',
|
|
243
|
+
phase: 'phase2', model: 'haiku', status: 'skipped',
|
|
244
|
+
phase_delta_tokens: 0, started_at: '__TS__', completed_at: '__TS__',
|
|
245
|
+
})
|
|
246
|
+
} else {
|
|
247
|
+
await appendLedgerEntry(featureDir, prefix, {
|
|
248
|
+
agent: 'wb-render:phase2',
|
|
249
|
+
phase: 'phase2', model: 'haiku', status: 'running',
|
|
250
|
+
phase_delta_tokens: 0, started_at: '__TS__', completed_at: null,
|
|
251
|
+
})
|
|
252
|
+
const beforeRender = budget.spent()
|
|
253
|
+
try {
|
|
254
|
+
renderResult = await agent(
|
|
255
|
+
`Run this command and return the result as structured output:\n` +
|
|
256
|
+
`node .claude/scripts/wb-render.js "${featureDir}/${prefix}-Work-Breakdown.json" "${prefix}"\n\n` +
|
|
257
|
+
`Capture exitCode (integer), stdout (string), stderr (string).\n` +
|
|
258
|
+
`Resolve the expected output paths:\n` +
|
|
259
|
+
` markdownPath = "${featureDir}/${prefix}-Work-Breakdown.md"\n` +
|
|
260
|
+
` csvPath = "${featureDir}/${prefix}-Work-Breakdown.csv"\n` +
|
|
261
|
+
`Check whether each file exists on disk and return:\n` +
|
|
262
|
+
` markdownExists (boolean), csvExists (boolean).\n` +
|
|
263
|
+
`Return all seven fields.`,
|
|
264
|
+
{
|
|
265
|
+
label: 'wb-render',
|
|
266
|
+
phase: 'Work Breakdown',
|
|
267
|
+
model: 'haiku',
|
|
268
|
+
schema: WB_RENDER_SCHEMA,
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
if (renderResult.stderr?.trim()) {
|
|
272
|
+
log(`wb-render stderr: ${renderResult.stderr.trim()}`)
|
|
273
|
+
}
|
|
274
|
+
if (renderResult.exitCode !== 0) {
|
|
275
|
+
throw Object.assign(
|
|
276
|
+
new Error(`wb-render exited ${renderResult.exitCode}: ${renderResult.stderr?.trim() || renderResult.stdout?.trim()}`),
|
|
277
|
+
{ _exitCode: renderResult.exitCode }
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
if (!renderResult.markdownPath?.trim()) throw new Error('wb-render did not return markdownPath')
|
|
281
|
+
if (!renderResult.csvPath?.trim()) throw new Error('wb-render did not return csvPath')
|
|
282
|
+
if (!renderResult.markdownExists) {
|
|
283
|
+
throw Object.assign(
|
|
284
|
+
new Error(`wb-render output file not found: ${renderResult.markdownPath}`),
|
|
285
|
+
{ _exitCode: renderResult.exitCode }
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
if (!renderResult.csvExists) {
|
|
289
|
+
throw Object.assign(
|
|
290
|
+
new Error(`wb-render output file not found: ${renderResult.csvPath}`),
|
|
291
|
+
{ _exitCode: renderResult.exitCode }
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
await updateLedgerEntry(featureDir, prefix, 'wb-render:phase2', {
|
|
295
|
+
status: 'done', completed_at: '__TS__',
|
|
296
|
+
phase_delta_tokens: budget.spent() - beforeRender,
|
|
297
|
+
})
|
|
298
|
+
} catch (err) {
|
|
299
|
+
renderFailed = true
|
|
300
|
+
await updateLedgerEntry(featureDir, prefix, 'wb-render:phase2', {
|
|
301
|
+
status: 'failed', completed_at: '__TS__',
|
|
302
|
+
phase_delta_tokens: budget.spent() - beforeRender,
|
|
303
|
+
error_summary: normalizeError(err),
|
|
304
|
+
exit_code: err._exitCode ?? renderResult?.exitCode ?? null,
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
tokenLedger.push({ agent: 'wb-render', model: 'haiku', phase_delta_tokens: budget.spent() - beforeRender })
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
tokenLedger.push({ agent: 'wb-validate', model: 'haiku', phase_delta_tokens: validateTokens })
|
|
311
|
+
tokenLedger.push({ agent: 'validate-work-breakdown-semantic', model: 'sonnet', phase_delta_tokens: semanticTokens })
|
|
312
|
+
|
|
313
|
+
// ── Step 5: Assemble gate2_payload ────────────────────────────────────────────
|
|
314
|
+
const gate2_payload = {
|
|
315
|
+
js_validator_report: wbValidatorReport,
|
|
316
|
+
js_validator_failed: validateFailed,
|
|
317
|
+
semantic_validator_result: semanticResult,
|
|
318
|
+
semantic_validator_failed: semanticFailed,
|
|
319
|
+
renderer_result: renderResult,
|
|
320
|
+
renderer_failed: renderFailed,
|
|
321
|
+
duration_bands: wbValidatorReport ? wbValidatorReport.durationBands : null,
|
|
322
|
+
domain_distribution: wbValidatorReport ? wbValidatorReport.domainDistribution : null,
|
|
323
|
+
warning_band_tasks: wbValidatorReport
|
|
324
|
+
? wbValidatorReport.warnings.filter(w => w.category === 'duration_warning')
|
|
325
|
+
.map(w => ({ taskId: w.taskId, agentMinutes: w.details ? w.details.agentMinutes : null }))
|
|
326
|
+
: [],
|
|
327
|
+
split_required_tasks: wbValidatorReport
|
|
328
|
+
? wbValidatorReport.errors.filter(e => e.category === 'split_required')
|
|
329
|
+
.map(e => ({ taskId: e.taskId, agentMinutes: e.details ? e.details.agentMinutes : null }))
|
|
330
|
+
: [],
|
|
331
|
+
must_ac_uncovered: wbValidatorReport
|
|
332
|
+
? wbValidatorReport.errors.filter(e => e.category === 'must_ac_uncovered')
|
|
333
|
+
.map(e => ({ acId: e.details ? e.details.acId : null }))
|
|
334
|
+
: [],
|
|
335
|
+
phase_unschedulable: wbValidatorReport
|
|
336
|
+
? (wbValidatorReport.dependencies ? wbValidatorReport.dependencies.phaseUnschedulable || [] : [])
|
|
337
|
+
: [],
|
|
338
|
+
gate2_blocked:
|
|
339
|
+
validateFailed ||
|
|
340
|
+
!wbValidatorPassed ||
|
|
341
|
+
semanticFailed ||
|
|
342
|
+
renderFailed ||
|
|
343
|
+
Boolean(semanticResult && semanticResult.findings && semanticResult.findings.some(f => f.blocking)),
|
|
344
|
+
}
|
|
345
|
+
log(`gate2_payload assembled — gate2_blocked: ${gate2_payload.gate2_blocked}`)
|
|
346
|
+
|
|
84
347
|
// ── Parse WB + write Effort-Estimate + Token-Estimate ────────────────────────
|
|
85
348
|
phase('Effort Estimate')
|
|
86
349
|
|
|
@@ -135,30 +398,27 @@ function formatEur(val) {
|
|
|
135
398
|
const metrics = await agent(
|
|
136
399
|
`You have four tasks:
|
|
137
400
|
|
|
138
|
-
TASK 1 — Read the Work Breakdown file.
|
|
401
|
+
TASK 1 — Read the Work Breakdown JSON file.
|
|
139
402
|
The feature.md is at: ${featurePath}
|
|
140
|
-
The
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
- task count
|
|
160
|
-
- domains involved
|
|
161
|
-
- estimated hours (from the Work Breakdown if present, otherwise estimate based on task count: ~2h per BE task, ~1h per TEST task, ~30min per INFRA task)
|
|
403
|
+
The JSON work breakdown is at: ${featureDir}/${prefix}-Work-Breakdown.json
|
|
404
|
+
Read the JSON file and extract:
|
|
405
|
+
- prefix (e.g. "FTR-009") — use "${prefix}"
|
|
406
|
+
- feature_dir (directory containing feature.md) — use "${featureDir}"
|
|
407
|
+
- feature_title — from wb.title or wb.feature field
|
|
408
|
+
- total_tasks (integer) — count all tasks across all phases
|
|
409
|
+
- user_stories (integer) — count phases whose id starts with "US-"
|
|
410
|
+
- domain_breakdown (string) — e.g. "BE: N, FE: N, DB: N, INFRA: N, TEST: N"
|
|
411
|
+
- implementation_phases (integer) — total number of phases
|
|
412
|
+
- human_estimate — compute as: (BE_tasks * 120 + TEST_tasks * 60 + INFRA_tasks * 30 + FE_tasks * 90 + DB_tasks * 90) minutes, format as "~Xh Ymin"
|
|
413
|
+
- agent_estimate — human_estimate / 4 (parallel dispatch), format as "~Xmin" or "~Xh Ymin"
|
|
414
|
+
- work_breakdown_path — ${featureDir}/${prefix}-Work-Breakdown.json
|
|
415
|
+
|
|
416
|
+
Also extract per-User-Story details for the Effort-Estimate:
|
|
417
|
+
- US ID (phase.id where it starts with "US-")
|
|
418
|
+
- US title (phase.title)
|
|
419
|
+
- task count (phase.tasks.length)
|
|
420
|
+
- domains involved (unique task.domain values in the phase)
|
|
421
|
+
- estimated hours (per the formula above)
|
|
162
422
|
|
|
163
423
|
TASK 2 — Write the Effort-Estimate.md file.
|
|
164
424
|
Write {feature_dir}/{PREFIX}-Effort-Estimate.md with this format:
|
|
@@ -245,7 +505,7 @@ For the "Est. cost €" column in Phase 3: use the formula tokens * (0.8 * 3.00
|
|
|
245
505
|
TASK 4 — Set return values.
|
|
246
506
|
Set effort_estimate_path to the full path of the written Effort-Estimate.md.
|
|
247
507
|
Set token_estimate_path to the full path of the written Token-Estimate.md.
|
|
248
|
-
Set tasks_csv_path to
|
|
508
|
+
Set tasks_csv_path to "${featureDir}/${prefix}-Work-Breakdown.csv" (written by wb-render in the same directory as feature.md).
|
|
249
509
|
|
|
250
510
|
Return the extracted metrics as structured output.`,
|
|
251
511
|
{
|
|
@@ -287,7 +547,7 @@ ${phase2Events}`,
|
|
|
287
547
|
)
|
|
288
548
|
|
|
289
549
|
return {
|
|
290
|
-
prefix: metrics.prefix,
|
|
550
|
+
prefix: metrics.prefix || prefix,
|
|
291
551
|
feature_path: featurePath,
|
|
292
552
|
user_stories: metrics.user_stories,
|
|
293
553
|
total_tasks: metrics.total_tasks,
|
|
@@ -301,4 +561,5 @@ return {
|
|
|
301
561
|
tasks_csv_path: metrics.tasks_csv_path || '',
|
|
302
562
|
token_ledger: tokenLedger,
|
|
303
563
|
errors: [],
|
|
564
|
+
gate2_payload,
|
|
304
565
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,71 @@
|
|
|
1
1
|
## 0.1.4 (2026-07-21)
|
|
2
|
+
## [0.10.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.9.0...v0.10.0) (2026-08-12)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **FTR-014:** Atomic Work Breakdown validation pipeline ([59a3be5](https://github.com/dtlab-tech/ai-toolkit/commit/59a3be511238229ca49f280262306b26e5ceb9a0))
|
|
8
|
+
* **FTR-014:** INFRA-T01 Create .claude/scripts/ directory structure ([27773ac](https://github.com/dtlab-tech/ai-toolkit/commit/27773ac03d916ddb64acba03e2f9a21985a59886))
|
|
9
|
+
* **FTR-014:** INFRA-T02 Define WB JSON schema v2 constants ([52c45d0](https://github.com/dtlab-tech/ai-toolkit/commit/52c45d06576e2a7717f98d056953ae6ad821cc75))
|
|
10
|
+
* **FTR-014:** INFRA-T03 Create AC table parser test fixture ([4e6a7a3](https://github.com/dtlab-tech/ai-toolkit/commit/4e6a7a3490f4de53354ac7ae384a00f0e4f98e34))
|
|
11
|
+
* **FTR-014:** INFRA-T04 Define WB_WRAPPER_SCHEMA and WB_RENDER_SCHEMA in pm-phase2.js ([b92e81c](https://github.com/dtlab-tech/ai-toolkit/commit/b92e81c348f3ec4fc2b3b8fe14fd6ecdbd60c3fa))
|
|
12
|
+
* **FTR-014:** INFRA-T05 Create valid and invalid WB JSON test fixtures ([ea5233c](https://github.com/dtlab-tech/ai-toolkit/commit/ea5233c4b1fccd782682533596ddd4febadd3c29))
|
|
13
|
+
* **FTR-014:** INFRA-T06 Extract buildWaves algorithm as shared test utility ([0b2fd25](https://github.com/dtlab-tech/ai-toolkit/commit/0b2fd25bf0d11679b0fef9140bf688b811316f8c))
|
|
14
|
+
* **FTR-014:** INFRA-T07 Define duration policy constants in wb-validate.js ([cd6cfee](https://github.com/dtlab-tech/ai-toolkit/commit/cd6cfee7bffb4dfd1d9ebbf1cc4732cc9def6acf))
|
|
15
|
+
* **FTR-014:** INFRA-T08 Define wb-validate error catalog constants ([06a9cd8](https://github.com/dtlab-tech/ai-toolkit/commit/06a9cd8d4f4e3583b84ac62ae94138184a8d96b8))
|
|
16
|
+
* **FTR-014:** US-01-T01 Update generate-work-breakdown.md to produce schema v2 JSON ([44a9674](https://github.com/dtlab-tech/ai-toolkit/commit/44a9674f897bcb70cbddd11cd9f72f220765e512))
|
|
17
|
+
* **FTR-014:** US-01-T02 Generate Work Breakdown JSON for a test feature ([8b1f3ed](https://github.com/dtlab-tech/ai-toolkit/commit/8b1f3ed65cb75350c2f2c181f5a1e8a8a7642a10))
|
|
18
|
+
* **FTR-014:** US-01-T03 Verify schema v2 compliance of generated JSON ([afdc774](https://github.com/dtlab-tech/ai-toolkit/commit/afdc774b0fc56466eaddbb462bb1230ad31b7792))
|
|
19
|
+
* **FTR-014:** US-02-T01 Implement wb-validate.js entry point and schema version check ([8ff1c30](https://github.com/dtlab-tech/ai-toolkit/commit/8ff1c308934830551c0bae6a1122e68af10dbcd9))
|
|
20
|
+
* **FTR-014:** US-02-T02 Implement unique ID and required field validation ([c426adc](https://github.com/dtlab-tech/ai-toolkit/commit/c426adc3796cc0705a40bb8ac34b1b79ca9f85d0))
|
|
21
|
+
* **FTR-014:** US-02-T03 Implement task ID format, domain, agentType validation ([a2166f7](https://github.com/dtlab-tech/ai-toolkit/commit/a2166f76a1b9e4f1cd7de0925e9ff20fe6d2cb36))
|
|
22
|
+
* **FTR-014:** US-02-T04 Implement task dependency resolution — refs, self-dep, phase consistency ([8fe5906](https://github.com/dtlab-tech/ai-toolkit/commit/8fe59060643a2704ce8aa1bd8a363c8a4a1794ef))
|
|
23
|
+
* **FTR-014:** US-02-T05 Implement task cycle detection (DFS gray/black coloring) ([94e18a3](https://github.com/dtlab-tech/ai-toolkit/commit/94e18a342ae025ff7574ee1fd9b038a6de4dea60))
|
|
24
|
+
* **FTR-014:** US-02-T06 Implement phase-level dependency projection and cycle detection ([a6e7469](https://github.com/dtlab-tech/ai-toolkit/commit/a6e7469541957a2dab3a3d525a87f060f64b2ecd))
|
|
25
|
+
* **FTR-014:** US-02-T07 Implement phase schedulability check (buildWaves) ([6a710a1](https://github.com/dtlab-tech/ai-toolkit/commit/6a710a13c4a4e3132f79f90f9f0d286120e64fb0))
|
|
26
|
+
* **FTR-014:** US-02-T08 Implement duration policy checks (four-band classification) ([44a61fb](https://github.com/dtlab-tech/ai-toolkit/commit/44a61fb62370e06eb7ac1f6e20d0b3e2ec628195))
|
|
27
|
+
* **FTR-014:** US-02-T09 Implement verification commands, commit subject, grouping rationale checks ([cbd64f0](https://github.com/dtlab-tech/ai-toolkit/commit/cbd64f00331e03ac9582b656c3e75b0921246630))
|
|
28
|
+
* **FTR-014:** US-02-T10 Implement AC table parser (Section 3.2 contract) ([317abe4](https://github.com/dtlab-tech/ai-toolkit/commit/317abe4f4a5848370a68d6ad3bae7e8a92755c53))
|
|
29
|
+
* **FTR-014:** US-02-T11 Implement AC existence and scope validation (checks 19-20) ([132e97a](https://github.com/dtlab-tech/ai-toolkit/commit/132e97a804175a2d5b2a97cfbc8816031ea81c87))
|
|
30
|
+
* **FTR-014:** US-02-T12 Implement AC priority derivation and Must coverage check (check 21) ([6483986](https://github.com/dtlab-tech/ai-toolkit/commit/6483986959a603ba1dc75c321db9b08d8808a2be))
|
|
31
|
+
* **FTR-014:** US-02-T13 Implement text field character validation and empty phase detection (checks 22-23) ([d93464e](https://github.com/dtlab-tech/ai-toolkit/commit/d93464e13444e0969a5cb9e58ad0a1c0fc3b865c))
|
|
32
|
+
* **FTR-014:** US-02-T14 Implement domain distribution in validator report output ([2d462f0](https://github.com/dtlab-tech/ai-toolkit/commit/2d462f07a00be04275d15608d2916e15b56df44b))
|
|
33
|
+
* **FTR-014:** US-02-T15 Add Jest suite for wb-validate checks 1-7 (schema, IDs, fields, format, domain, agentType) ([0affff6](https://github.com/dtlab-tech/ai-toolkit/commit/0affff6d986ab3da1b321e1baae91cc49fa402e1))
|
|
34
|
+
* **FTR-014:** US-02-T16 Add Jest suite for wb-validate checks 8-10 (task deps, self-dep, phase consistency) ([17427a2](https://github.com/dtlab-tech/ai-toolkit/commit/17427a2ab900238bf757a11040bb1380dc49fa4e))
|
|
35
|
+
* **FTR-014:** US-02-T17 Add Jest suite for wb-validate check 11 (task cycle detection) ([43e06aa](https://github.com/dtlab-tech/ai-toolkit/commit/43e06aa95fc7203be5d5594443d5dd8a891ba71a))
|
|
36
|
+
* **FTR-014:** US-02-T18 Add Jest suite for wb-validate checks 12-14 (phase graph, cycles, schedulability) ([a4f710e](https://github.com/dtlab-tech/ai-toolkit/commit/a4f710e6ab996251d98af5006f519a497d7ca850))
|
|
37
|
+
* **FTR-014:** US-02-T19 Add Jest suite for wb-validate checks 15-18 (duration policy, commands, commit, rationale) ([3bdfc81](https://github.com/dtlab-tech/ai-toolkit/commit/3bdfc810f4dea82e29493e06fb98e83991d21c9c))
|
|
38
|
+
* **FTR-014:** US-02-T20 Add Jest suite for wb-validate checks 19-21 (AC parsing, scope, priority, Must coverage) ([e237c8e](https://github.com/dtlab-tech/ai-toolkit/commit/e237c8e03f83a499e4f265c2f67ed51b629967f2))
|
|
39
|
+
* **FTR-014:** US-02-T21 Add Jest suite for wb-validate checks 22-23 and report output contract ([9cbb57a](https://github.com/dtlab-tech/ai-toolkit/commit/9cbb57a5f8d781fb20e84235d8c492032c102893))
|
|
40
|
+
* **FTR-014:** US-03-T01 Create validate-work-breakdown-semantic agent definition ([6b34656](https://github.com/dtlab-tech/ai-toolkit/commit/6b3465634632f03f9c79ab27f31a8e842971d66d))
|
|
41
|
+
* **FTR-014:** US-03-T02 US-03-T03 Semantic analysis logic and structured output (covered by T01 agent definition) ([762b648](https://github.com/dtlab-tech/ai-toolkit/commit/762b64853d52b42f9535f4531cb4010d66ae4da2))
|
|
42
|
+
* **FTR-014:** US-03-T04 Manual semantic validation on test features (7/7 schema checks PASS) ([db6ee3c](https://github.com/dtlab-tech/ai-toolkit/commit/db6ee3cefc698216876f479b5fdbe64120e7e2bf))
|
|
43
|
+
* **FTR-014:** US-04-T01 Implement wb-render.js entry point (arg parsing, JSON read, file path setup) ([3d82ca3](https://github.com/dtlab-tech/ai-toolkit/commit/3d82ca3d7427df5e6208c58d1d1625224bcd079f))
|
|
44
|
+
* **FTR-014:** US-04-T02 Implement phase-level depends_on aggregation in wb-render.js ([726a756](https://github.com/dtlab-tech/ai-toolkit/commit/726a756cfee589ea9a2879c7f5d2732ccea682c8))
|
|
45
|
+
* **FTR-014:** US-04-T03 Implement defensive character stripping and commit message construction ([d9262dc](https://github.com/dtlab-tech/ai-toolkit/commit/d9262dc25866100afe74fe468a963c5034745c9a))
|
|
46
|
+
* **FTR-014:** US-04-T04 Implement Markdown output generation in wb-render.js ([2a7a384](https://github.com/dtlab-tech/ai-toolkit/commit/2a7a3842f642cb514e6efd48d94d8f818a3a1414))
|
|
47
|
+
* **FTR-014:** US-04-T05 Implement CSV output generation in wb-render.js ([1c2a386](https://github.com/dtlab-tech/ai-toolkit/commit/1c2a386078ae54015048f32b76a3789efa82f03a))
|
|
48
|
+
* **FTR-014:** US-04-T06 Tests for Markdown structure, CSV structure, dependency aggregation ([03de433](https://github.com/dtlab-tech/ai-toolkit/commit/03de433b1643751d68abc5dc121715c75872775d))
|
|
49
|
+
* **FTR-014:** US-04-T07 Tests for character stripping, commit construction, output file paths ([ace21ba](https://github.com/dtlab-tech/ai-toolkit/commit/ace21ba2f1eff5777254a9174a3f4b2ff0383b97))
|
|
50
|
+
* **FTR-014:** US-04-T08 CSV regression test against pm-phase3 parser ([3a83ed5](https://github.com/dtlab-tech/ai-toolkit/commit/3a83ed5a1dd3f9bb8add57e8abc97b8f80303a62))
|
|
51
|
+
* **FTR-014:** US-05-T01..T06 Update pm-phase2.js with validation pipeline and gate2_payload ([711b71e](https://github.com/dtlab-tech/ai-toolkit/commit/711b71e8b3d3f78a7cac43841fc9b42625d4749b))
|
|
52
|
+
* **FTR-014:** US-05-T07 Update implement-feature SKILL.md Gate 2 presentation with validator results ([e617b3d](https://github.com/dtlab-tech/ai-toolkit/commit/e617b3d28036969adecff78c26df382e871e9ed2))
|
|
53
|
+
* **FTR-014:** US-05-T08 Tests for orchestration flow wb-validate to semantic to render ([c1341e2](https://github.com/dtlab-tech/ai-toolkit/commit/c1341e23566d6efaa33c58db368d52fdbbef9fe1))
|
|
54
|
+
* **FTR-014:** US-05-T09 Tests for Gate 2 blocking logic and gate2_payload assembly ([8421695](https://github.com/dtlab-tech/ai-toolkit/commit/8421695ff0aeee3f9fd0d01d21cfc863a9344c91))
|
|
55
|
+
* **FTR-014:** US-06-T01..T04 Ledger tracking for wb-validate, semantic, wb-render in pm-phase2 ([2f9a9f7](https://github.com/dtlab-tech/ai-toolkit/commit/2f9a9f7ff935314bb591a4d1bb8ea8eaf9ad19bc))
|
|
56
|
+
* **FTR-014:** US-07-T01..T02 Add .claude/scripts/ to installer distribution for wb-validate and wb-render ([bcba12a](https://github.com/dtlab-tech/ai-toolkit/commit/bcba12ad340d071b794dd6608318e34dea7e846b))
|
|
57
|
+
* **FTR-014:** US-07-T03..T05 Tests for installer script distribution verification ([2bb57bd](https://github.com/dtlab-tech/ai-toolkit/commit/2bb57bd4a918052ed05a421d310ae2124252a6f8))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Bug Fixes
|
|
61
|
+
|
|
62
|
+
* **FTR-014:** align warning/split task details field to agentMinutes ([ba7dbf8](https://github.com/dtlab-tech/ai-toolkit/commit/ba7dbf8f8dd5e34f55bc825bdefabdaaf2160eba))
|
|
63
|
+
* **FTR-014:** enforce groupingRationale as a required task field in check 4 ([e5cb733](https://github.com/dtlab-tech/ai-toolkit/commit/e5cb7336e91f44c653d979cfeba8f21de3da3d19))
|
|
64
|
+
* **FTR-014:** guard wb.phases in checks 22 and 23 against missing/non-array ([32ec505](https://github.com/dtlab-tech/ai-toolkit/commit/32ec50560c5e1fe855ac15bf9d40ec7b0ba3d8ae))
|
|
65
|
+
* **FTR-014:** preserve completed_at for skipped ledger entries (I6) ([389f280](https://github.com/dtlab-tech/ai-toolkit/commit/389f280d4ebdfc6a3d694e9c9e43e57042043493))
|
|
66
|
+
* **FTR-014:** relax symmetric-wrapping assertion in pm-phase2-source test ([8bc663e](https://github.com/dtlab-tech/ai-toolkit/commit/8bc663ec098d645cf3e6dcdd7793a08e44104784))
|
|
67
|
+
* **FTR-014:** use exit(2) for parseAcTable fatal errors (I5) ([1244066](https://github.com/dtlab-tech/ai-toolkit/commit/12440663cf57ddfea523535829ebbb9a66a300fd))
|
|
68
|
+
|
|
2
69
|
## [0.9.0](https://github.com/dtlab-tech/ai-toolkit/compare/v0.8.0...v0.9.0) (2026-08-05)
|
|
3
70
|
|
|
4
71
|
|
package/bin/cli.js
CHANGED
|
@@ -679,6 +679,7 @@ async function installGlobal(force) {
|
|
|
679
679
|
{ src: path.join(packageRoot, '.claude', 'skills'), dest: path.join(target, 'skills') },
|
|
680
680
|
{ src: path.join(packageRoot, '.claude', 'commands'), dest: path.join(target, 'commands') },
|
|
681
681
|
{ src: path.join(packageRoot, '.claude', 'workflows'), dest: path.join(target, 'workflows') },
|
|
682
|
+
{ src: path.join(packageRoot, '.claude', 'scripts'), dest: path.join(target, 'scripts') },
|
|
682
683
|
{ src: path.join(packageRoot, 'docs'), dest: path.join(target, 'docs') },
|
|
683
684
|
{ src: path.join(packageRoot, 'CLAUDE.global.md'), dest: path.join(target, 'CLAUDE.md') },
|
|
684
685
|
];
|