@dtlabs/ai-toolkit 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/bin/cli.js +371 -2
- package/docs/task-executor-bootstrap.md +195 -0
- package/docs/task-executor-cli.md +319 -0
- package/docs/workflow-control-plane.md +102 -0
- package/lib/execution-ledger.js +186 -4
- package/lib/task-executor/claude-process.js +563 -0
- package/lib/task-executor/git.js +1362 -0
- package/lib/task-executor/index.js +3729 -0
- package/lib/task-executor/ownership.js +590 -0
- package/lib/task-executor/plan.js +725 -0
- package/lib/task-executor/store.js +1164 -0
- package/lib/workflow-artifacts.js +102 -0
- package/lib/workflow-control.js +225 -0
- package/lib/workflow-runner.js +39 -0
- package/package.json +1 -1
- package/src/claude/agents/gaia-intervention-documentation-standard.md +9 -9
- package/src/claude/agents/gaia-validate-feature-docs.md +31 -28
- package/src/claude/skills/assess-codebase/SKILL.md +9 -103
- package/src/claude/skills/implement-feature/SKILL.md +74 -159
- package/src/claude/workflows/am-phase1.js +43 -294
- package/src/claude/workflows/am-phase2.js +10 -164
- package/src/claude/workflows/pm-phase1.js +45 -358
- package/src/claude/workflows/pm-phase2.js +50 -571
- package/src/claude/workflows/pm-phase3.js +5 -824
|
@@ -1,364 +1,51 @@
|
|
|
1
1
|
export const meta = {
|
|
2
|
+
controlPlaneVersion: 1,
|
|
2
3
|
name: 'pm-phase1',
|
|
3
|
-
description: '
|
|
4
|
-
phases: [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
exitNonZero: { type: 'boolean' },
|
|
22
|
-
error: { type: 'string' },
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function resolveAgentTier2(agentId, label, phaseName) {
|
|
27
|
-
const result = await agent(
|
|
28
|
-
`Run this command via Bash:\n\nai-toolkit agents resolve --project . --id ${agentId} --require-verified\n\nIf the command exits 0: parse the JSON from stdout and return it with exitNonZero: false.\nIf the command exits non-zero: return {"exitNonZero": true, "error": "<stderr text>", "agentId": "${agentId}"}.`,
|
|
29
|
-
{ label, phase: phaseName, model: 'haiku', schema: RESOLVE_SCHEMA }
|
|
30
|
-
)
|
|
31
|
-
if (!result || result.exitNonZero || !result.nativeName) {
|
|
32
|
-
throw new Error(`HARD STOP — Tier 2 resolution failed for ${agentId}: ${result && result.error ? result.error : 'no nativeName returned'}`)
|
|
33
|
-
}
|
|
34
|
-
return result.nativeName
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ── Tier 3 — self-ledger helper ───────────────────────────────────────────────
|
|
38
|
-
const SELF_LEDGER_SCHEMA = {
|
|
39
|
-
type: 'object',
|
|
40
|
-
properties: {
|
|
41
|
-
exitCode: { type: 'number' },
|
|
42
|
-
stdout: { type: 'string' },
|
|
43
|
-
stderr: { type: 'string' },
|
|
44
|
-
},
|
|
45
|
-
required: ['exitCode'],
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function selfLedgerOp(cmd, label, phaseName) {
|
|
49
|
-
const result = await agent(
|
|
50
|
-
`Run this shell command via Bash and return the exit code as structured output.\n\nCommand: ${cmd}\n\nCapture: exitCode (integer), stdout (string), stderr (string). Return all three.`,
|
|
51
|
-
{ label, phase: phaseName, model: 'haiku', schema: SELF_LEDGER_SCHEMA }
|
|
52
|
-
)
|
|
53
|
-
const status = (result && typeof result.exitCode === 'number') ? result.exitCode : 1
|
|
54
|
-
if (status !== 0) {
|
|
55
|
-
throw new Error(`HARD STOP — self-ledger operation failed. Exit code: ${status}. Command: ${cmd}`)
|
|
4
|
+
description: 'Deterministic documentation workflow; returns Gate 1 evidence.',
|
|
5
|
+
phases: [{ title: 'Discovery' }, { title: 'Requirements' }, { title: 'Tech-Spec' }, { title: 'Validation' }],
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const c = control
|
|
9
|
+
const { featurePath, featureDir, prefix } = c.feature(args[0])
|
|
10
|
+
const force = args.includes('--force')
|
|
11
|
+
return c.run(meta.name, featureDir, prefix, async () => {
|
|
12
|
+
const file = suffix => `${featureDir}/${prefix}-${suffix}.md`
|
|
13
|
+
const fresh = (output, inputs = [featurePath]) => c.exists(output) && c.read(output).trim().length > 0 && inputs.every(input => c.exists(input) && c.mtime(output) >= c.mtime(input))
|
|
14
|
+
const reqId = 'gaia.agent.planner.requirements'
|
|
15
|
+
const specId = 'gaia.agent.planner.tech-spec'
|
|
16
|
+
const valId = 'gaia.agent.planner.validate-feature-docs'
|
|
17
|
+
let changed = false
|
|
18
|
+
if (force || !fresh(file('Requirements'))) {
|
|
19
|
+
await c.worker(reqId, featurePath)
|
|
20
|
+
if (!c.exists(file('Requirements'))) throw new Error('Requirements output missing')
|
|
21
|
+
changed = true
|
|
56
22
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const featurePath = args.split(/\s+/)[0]
|
|
62
|
-
const force = typeof args === 'string' && args.includes('--force')
|
|
63
|
-
|
|
64
|
-
// Derive prefix and feature dir early (before Discovery agent) for self-registration
|
|
65
|
-
const featureDirEarly = featurePath.replace(/[/\\][^/\\]+$/, '')
|
|
66
|
-
const prefixEarlyMatch = featureDirEarly.match(/([A-Z]+-\d+)/)
|
|
67
|
-
const prefixEarly = prefixEarlyMatch ? prefixEarlyMatch[1] : 'FTR-000'
|
|
68
|
-
|
|
69
|
-
// ── Tier 3 self-registration (open BEFORE any main logic, fail-closed) ────────
|
|
70
|
-
await selfLedgerOp(
|
|
71
|
-
`ai-toolkit ledger open --prefix ${prefixEarly} --agent pm-phase1:self --phase phase1 --dir "${featureDirEarly}" --attempt 1`,
|
|
72
|
-
'ledger-open-pm-phase1-self', 'Discovery'
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
// ── Discovery ─────────────────────────────────────────────────────────────────
|
|
76
|
-
phase('Discovery')
|
|
77
|
-
|
|
78
|
-
const discoveryResult = await agent(
|
|
79
|
-
`You are a discovery agent for the feature delivery pipeline.
|
|
80
|
-
|
|
81
|
-
Read the feature.md file at: ${featurePath}
|
|
82
|
-
|
|
83
|
-
Extract:
|
|
84
|
-
1. The feature PREFIX from the directory name (pattern [A-Z]+-[0-9]+, e.g. FTR-009)
|
|
85
|
-
2. The feature directory path (parent of feature.md)
|
|
86
|
-
3. Whether these files exist and are NOT stale vs feature.md (stale = older mtime):
|
|
87
|
-
- {PREFIX}-Requirements.md
|
|
88
|
-
- {PREFIX}-Tech-Spec.md
|
|
89
|
-
- {PREFIX}-Validation-Report.md
|
|
90
|
-
|
|
91
|
-
Return a JSON object:
|
|
92
|
-
{
|
|
93
|
-
"prefix": "FTR-009",
|
|
94
|
-
"feature_dir": "internal_docs/features/FTR-009-workflow-orchestrators",
|
|
95
|
-
"needs_requirements": true,
|
|
96
|
-
"needs_tech_spec": true,
|
|
97
|
-
"needs_validation": true,
|
|
98
|
-
"force": ${force}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
If force=true, set all needs_* to true regardless of file state.
|
|
102
|
-
If feature.md does not exist, return { "error": "feature.md not found: ${featurePath}" }.`,
|
|
103
|
-
{
|
|
104
|
-
label: 'discovery',
|
|
105
|
-
phase: 'Discovery',
|
|
106
|
-
schema: {
|
|
107
|
-
type: 'object',
|
|
108
|
-
properties: {
|
|
109
|
-
prefix: { type: 'string' },
|
|
110
|
-
feature_dir: { type: 'string' },
|
|
111
|
-
needs_requirements: { type: 'boolean' },
|
|
112
|
-
needs_tech_spec: { type: 'boolean' },
|
|
113
|
-
needs_validation: { type: 'boolean' },
|
|
114
|
-
force: { type: 'boolean' },
|
|
115
|
-
error: { type: 'string' },
|
|
116
|
-
},
|
|
117
|
-
required: ['prefix', 'feature_dir'],
|
|
118
|
-
},
|
|
23
|
+
if (force || changed || !fresh(file('Tech-Spec'), [featurePath, file('Requirements')])) {
|
|
24
|
+
await c.worker(specId, featurePath)
|
|
25
|
+
if (!c.exists(file('Tech-Spec'))) throw new Error('Tech-Spec output missing')
|
|
26
|
+
changed = true
|
|
119
27
|
}
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Touch it with an empty array so the ledger facade can always assume a valid base.
|
|
134
|
-
const ledgerFilePath = `${feature_dir}/${prefix}-token-ledger.json`
|
|
135
|
-
await agent(
|
|
136
|
-
`Check whether the file ${ledgerFilePath} exists.\n\n1. Try to read the file using the Read tool.\n2. If the file does NOT exist: write it now using the Write tool with contents: []\n3. If the file already exists and is a valid JSON array: do nothing.\n4. If the file exists but is not valid JSON: overwrite it with: []\n5. Return no output.`,
|
|
137
|
-
{ label: 'ensure-ledger', phase: 'Discovery', model: 'haiku' }
|
|
138
|
-
)
|
|
139
|
-
log(`Ledger ensured at ${ledgerFilePath}`)
|
|
140
|
-
|
|
141
|
-
// ── generate-requirements ─────────────────────────────────────────────────────
|
|
142
|
-
phase('Requirements')
|
|
143
|
-
|
|
144
|
-
const tokenLedger = []
|
|
145
|
-
const errors = []
|
|
146
|
-
|
|
147
|
-
if (discoveryResult.needs_requirements) {
|
|
148
|
-
const reqNativeName = await resolveAgentTier2('gaia.agent.planner.requirements', 'resolve-generate-requirements', 'Requirements')
|
|
149
|
-
const reqKey = 'generate-requirements:phase1'
|
|
150
|
-
await agent(
|
|
151
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${feature_dir} --prefix ${prefix} --agent ${reqKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
|
|
152
|
-
{ label: 'ledger-open-requirements', phase: 'Requirements', model: 'haiku' }
|
|
153
|
-
)
|
|
154
|
-
const beforeReq = budget.spent()
|
|
155
|
-
const reqResult = await agent(featurePath, { agentType: reqNativeName, label: 'generate-requirements', phase: 'Requirements' })
|
|
156
|
-
const reqTokens = budget.spent() - beforeReq
|
|
157
|
-
await agent(
|
|
158
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${reqKey} --tokens ${reqTokens} --attempt 1\n\nReturn no output.`,
|
|
159
|
-
{ label: 'ledger-close-requirements', phase: 'Requirements', model: 'haiku' }
|
|
160
|
-
)
|
|
161
|
-
tokenLedger.push({ agent: 'generate-requirements', model: 'haiku', phase_delta_tokens: reqTokens })
|
|
162
|
-
log(`generate-requirements done — phase delta: ${reqTokens} tokens`)
|
|
163
|
-
} else {
|
|
164
|
-
log('generate-requirements: fresh — skipped')
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ── generate-tech-spec ────────────────────────────────────────────────────────
|
|
168
|
-
phase('Tech-Spec')
|
|
169
|
-
|
|
170
|
-
if (discoveryResult.needs_tech_spec) {
|
|
171
|
-
const specNativeName = await resolveAgentTier2('gaia.agent.planner.tech-spec', 'resolve-generate-tech-spec', 'Tech-Spec')
|
|
172
|
-
const specKey = 'generate-tech-spec:phase1'
|
|
173
|
-
await agent(
|
|
174
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${feature_dir} --prefix ${prefix} --agent ${specKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
|
|
175
|
-
{ label: 'ledger-open-tech-spec', phase: 'Tech-Spec', model: 'haiku' }
|
|
176
|
-
)
|
|
177
|
-
const beforeSpec = budget.spent()
|
|
178
|
-
const specResult = await agent(featurePath, { agentType: specNativeName, label: 'generate-tech-spec', phase: 'Tech-Spec' })
|
|
179
|
-
const specTokens = budget.spent() - beforeSpec
|
|
180
|
-
await agent(
|
|
181
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${specKey} --tokens ${specTokens} --attempt 1\n\nReturn no output.`,
|
|
182
|
-
{ label: 'ledger-close-tech-spec', phase: 'Tech-Spec', model: 'haiku' }
|
|
183
|
-
)
|
|
184
|
-
tokenLedger.push({ agent: 'generate-tech-spec', model: 'haiku', phase_delta_tokens: specTokens })
|
|
185
|
-
log(`generate-tech-spec done — phase delta: ${specTokens} tokens`)
|
|
186
|
-
} else {
|
|
187
|
-
log('generate-tech-spec: fresh — skipped')
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── validate-feature-docs (revision loop, max 3 cycles) ──────────────────────
|
|
191
|
-
phase('Validation')
|
|
192
|
-
|
|
193
|
-
// Resolve all validation-phase agents once before the loop (Tier 2 guard)
|
|
194
|
-
const valNativeName = await resolveAgentTier2('gaia.agent.planner.validate-feature-docs', 'resolve-validate-feature-docs', 'Validation')
|
|
195
|
-
const reqRevNativeName = await resolveAgentTier2('gaia.agent.planner.requirements', 'resolve-generate-requirements-rev', 'Validation')
|
|
196
|
-
const specRevNativeName = await resolveAgentTier2('gaia.agent.planner.tech-spec', 'resolve-generate-tech-spec-rev', 'Validation')
|
|
197
|
-
|
|
198
|
-
let validationSummary = 'skipped'
|
|
199
|
-
let lastValText = ''
|
|
200
|
-
const MAX_CYCLES = 3
|
|
201
|
-
|
|
202
|
-
for (let cycle = 1; cycle <= MAX_CYCLES; cycle++) {
|
|
203
|
-
log(`validate-feature-docs cycle ${cycle}`)
|
|
204
|
-
const valKey = `validate-feature-docs:phase1:cycle${cycle}`
|
|
205
|
-
await agent(
|
|
206
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger open --dir ${feature_dir} --prefix ${prefix} --agent ${valKey} --phase phase1 --model haiku --attempt 1\n\nReturn no output.`,
|
|
207
|
-
{ label: `ledger-open-validate-cycle-${cycle}`, phase: 'Validation', model: 'haiku' }
|
|
208
|
-
)
|
|
209
|
-
const beforeVal = budget.spent()
|
|
210
|
-
const valResult = await agent(featurePath, { agentType: valNativeName, label: `validate-feature-docs (cycle ${cycle})`, phase: 'Validation' })
|
|
211
|
-
const valTokens = budget.spent() - beforeVal
|
|
212
|
-
await agent(
|
|
213
|
-
`Run this shell command via Bash. If the --dir path contains spaces, enclose it in double quotes.\n\nai-toolkit ledger close --dir ${feature_dir} --prefix ${prefix} --agent ${valKey} --tokens ${valTokens} --attempt 1\n\nReturn no output.`,
|
|
214
|
-
{ label: `ledger-close-validate-cycle-${cycle}`, phase: 'Validation', model: 'haiku' }
|
|
215
|
-
)
|
|
216
|
-
tokenLedger.push({ agent: `validate-feature-docs (cycle ${cycle})`, model: 'haiku', phase_delta_tokens: valTokens })
|
|
217
|
-
|
|
218
|
-
// valResult is the agent's text output — check for gap indicators
|
|
219
|
-
// Use only 'MISSING:' as the gap signal: 'gaps found' also matches 'ZERO GAPS FOUND'
|
|
220
|
-
const resultText = typeof valResult === 'string' ? valResult : JSON.stringify(valResult)
|
|
221
|
-
lastValText = resultText
|
|
222
|
-
const hasGaps = resultText.includes('MISSING:')
|
|
223
|
-
|
|
224
|
-
if (!hasGaps) {
|
|
225
|
-
validationSummary = `0 gaps (clean on cycle ${cycle})`
|
|
226
|
-
log(`Validation clean on cycle ${cycle}`)
|
|
227
|
-
break
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
log(`Validation cycle ${cycle}: gaps found`)
|
|
231
|
-
|
|
232
|
-
if (cycle < MAX_CYCLES) {
|
|
233
|
-
// Re-run failing docs before next validation cycle
|
|
234
|
-
if (resultText.includes('Requirements')) {
|
|
235
|
-
await agent(featurePath, { agentType: reqRevNativeName, label: `generate-requirements (revision ${cycle})`, phase: 'Validation' })
|
|
28
|
+
let validationSummary = 'skipped (fresh)'
|
|
29
|
+
if (force || changed || !fresh(file('Validation-Report'), [featurePath, file('Requirements'), file('Tech-Spec')])) {
|
|
30
|
+
for (let cycle = 1; cycle <= 3; cycle++) {
|
|
31
|
+
const result = await c.worker(valId, featurePath + '\nValidate only; the host owns the revision loop. Write the validation report and return valid plus a findings array (one string per remaining gap). Do not dispatch other agents.', { label: `validation:${cycle}`, schema: { type: 'object', properties: { valid: { type: 'boolean' }, findings: { type: 'array', items: { type: 'string' } } }, required: ['valid', 'findings'] } })
|
|
32
|
+
const text = result.findings.join('\n')
|
|
33
|
+
if (result.valid && result.findings.length === 0) {
|
|
34
|
+
if (!fresh(file('Validation-Report'), [featurePath, file('Requirements'), file('Tech-Spec')])) throw new Error('Validation agent did not produce its report')
|
|
35
|
+
validationSummary = `0 gaps (clean on cycle ${cycle})`
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
if (cycle === 3) throw new Error('Validation gaps remain after 3 cycles')
|
|
39
|
+
if (!text.includes('Tech-Spec') || text.includes('Requirements')) await c.worker(reqId, featurePath)
|
|
40
|
+
if (!text.includes('Requirements') || text.includes('Tech-Spec')) await c.worker(specId, featurePath)
|
|
236
41
|
}
|
|
237
|
-
if (resultText.includes('Tech-Spec')) {
|
|
238
|
-
await agent(featurePath, { agentType: specRevNativeName, label: `generate-tech-spec (revision ${cycle})`, phase: 'Validation' })
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
validationSummary = 'gaps remain after 3 cycles'
|
|
242
|
-
errors.push('validate-feature-docs: gaps remain after 3 revision cycles')
|
|
243
42
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
await agent(
|
|
254
|
-
`Ensure the Validation Report file exists on disk for this feature delivery run.
|
|
255
|
-
|
|
256
|
-
File path: ${validationReportPath}
|
|
257
|
-
|
|
258
|
-
Steps:
|
|
259
|
-
1. Check whether the file already exists (use Read or Glob).
|
|
260
|
-
2. If it ALREADY EXISTS: do nothing, return "exists".
|
|
261
|
-
3. If it DOES NOT EXIST: write it now using the Write tool, based on the validation outcome below.
|
|
262
|
-
Get the current date via Bash: run \`date -u +"%Y-%m-%d"\`.
|
|
263
|
-
|
|
264
|
-
Validation outcome summary: ${validationSummary}
|
|
265
|
-
|
|
266
|
-
Validation agent output (source of truth for gaps found/resolved):
|
|
267
|
-
──────────────────────────────────────────────────
|
|
268
|
-
${lastValText.slice(0, 4000)}
|
|
269
|
-
──────────────────────────────────────────────────
|
|
270
|
-
|
|
271
|
-
File format to write (fill from the outcome above; if the run was clean, mark both documents Clean with 0 gaps):
|
|
272
|
-
|
|
273
|
-
# Validation Report — ${prefix}
|
|
274
|
-
|
|
275
|
-
## Summary
|
|
276
|
-
| Document | Gaps found | Gaps resolved | Status |
|
|
277
|
-
|----------|-----------|--------------|--------|
|
|
278
|
-
| ${prefix}-Requirements.md | N | N | Clean / Gaps remain |
|
|
279
|
-
| ${prefix}-Tech-Spec.md | N | N | Clean / Gaps remain |
|
|
280
|
-
|
|
281
|
-
## Gaps found and resolved
|
|
282
|
-
(derive from the validation output above; write "(none)" if clean)
|
|
283
|
-
|
|
284
|
-
## Remaining gaps (if any)
|
|
285
|
-
(list any unresolved gaps, or "(none)")
|
|
286
|
-
|
|
287
|
-
## Validation date
|
|
288
|
-
{date from Bash}
|
|
289
|
-
|
|
290
|
-
Return "written" if you created the file, or "exists" if it was already present.`,
|
|
291
|
-
{ label: 'ensure-validation-report', phase: 'Validation' }
|
|
292
|
-
)
|
|
293
|
-
log(`Validation report ensured at ${validationReportPath}`)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// ── Write process-log (phase 1 snapshot) ─────────────────────────────────────
|
|
297
|
-
const reqEntry = tokenLedger.find(e => e.agent === 'generate-requirements')
|
|
298
|
-
const specEntry = tokenLedger.find(e => e.agent === 'generate-tech-spec')
|
|
299
|
-
const valEntries = tokenLedger.filter(e => e.agent.startsWith('validate-feature-docs'))
|
|
300
|
-
const phase1Events = [
|
|
301
|
-
`pm-phase1 START — documentation phase`,
|
|
302
|
-
reqEntry
|
|
303
|
-
? `Agent DONE: generate-requirements — tokens: ${reqEntry.phase_delta_tokens}`
|
|
304
|
-
: `generate-requirements: skipped (fresh)`,
|
|
305
|
-
specEntry
|
|
306
|
-
? `Agent DONE: generate-tech-spec — tokens: ${specEntry.phase_delta_tokens}`
|
|
307
|
-
: `generate-tech-spec: skipped (fresh)`,
|
|
308
|
-
...valEntries.map(e => `Agent DONE: ${e.agent} — tokens: ${e.phase_delta_tokens}`),
|
|
309
|
-
`Validation result: ${validationSummary}`,
|
|
310
|
-
`APPROVAL REQUESTED — Gate 1`,
|
|
311
|
-
].join('\n')
|
|
312
|
-
|
|
313
|
-
await agent(
|
|
314
|
-
`Create the process-log file for this feature delivery run.
|
|
315
|
-
|
|
316
|
-
File path: ${feature_dir}/${prefix}-process-log.txt
|
|
317
|
-
|
|
318
|
-
Steps:
|
|
319
|
-
1. Get current UTC datetime via Bash: run \`date -u +"%Y-%m-%dT%H:%M:%S"\`
|
|
320
|
-
2. Read feature.md at: ${featurePath} — extract the feature title (first H1 heading after the frontmatter).
|
|
321
|
-
3. Write the file using the Write tool. Use the datetime from step 1 for ALL timestamps.
|
|
322
|
-
Format each event line as: [{datetime}] {event text}
|
|
323
|
-
|
|
324
|
-
File structure:
|
|
325
|
-
════════════════════════════════════════════════════════
|
|
326
|
-
RUN STARTED — {datetime}
|
|
327
|
-
Feature: ${prefix} — {title from step 2}
|
|
328
|
-
════════════════════════════════════════════════════════
|
|
329
|
-
{one line per event below, each prefixed with [{datetime}]}
|
|
330
|
-
|
|
331
|
-
Events to log:
|
|
332
|
-
${phase1Events}`,
|
|
333
|
-
{ label: 'write-process-log', phase: 'Validation' }
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
// ── Build gate1_payload ───────────────────────────────────────────────────────
|
|
337
|
-
const gate1Payload = {
|
|
338
|
-
prefix,
|
|
339
|
-
feature_dir,
|
|
340
|
-
feature_path: featurePath,
|
|
341
|
-
requirements: {
|
|
342
|
-
path: `${feature_dir}/${prefix}-Requirements.md`,
|
|
343
|
-
summary: `${prefix}-Requirements.md — generated`,
|
|
344
|
-
},
|
|
345
|
-
tech_spec: {
|
|
346
|
-
path: `${feature_dir}/${prefix}-Tech-Spec.md`,
|
|
347
|
-
summary: `${prefix}-Tech-Spec.md — generated`,
|
|
348
|
-
},
|
|
349
|
-
validation: {
|
|
350
|
-
path: `${feature_dir}/${prefix}-Validation-Report.md`,
|
|
351
|
-
summary: validationSummary,
|
|
352
|
-
},
|
|
353
|
-
token_ledger: tokenLedger,
|
|
354
|
-
errors,
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ── Tier 3 self-registration — close on successful completion ─────────────────
|
|
358
|
-
await selfLedgerOp(
|
|
359
|
-
`ai-toolkit ledger close --prefix ${prefix} --agent pm-phase1:self --dir "${feature_dir}" --attempt 1`,
|
|
360
|
-
'ledger-close-pm-phase1-self', 'Validation'
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
log(`pm-phase1 complete — gate1_payload ready`)
|
|
364
|
-
return gate1Payload
|
|
43
|
+
c.append(`${featureDir}/${prefix}-process-log.txt`, `[${new Date().toISOString()}] pm-phase1: ${validationSummary}; Gate 1 approval requested\n`)
|
|
44
|
+
return {
|
|
45
|
+
prefix, feature_dir: featureDir, feature_path: featurePath,
|
|
46
|
+
requirements: { path: file('Requirements'), summary: 'Requirements ready' },
|
|
47
|
+
tech_spec: { path: file('Tech-Spec'), summary: 'Tech-Spec ready' },
|
|
48
|
+
validation: { path: file('Validation-Report'), summary: validationSummary },
|
|
49
|
+
token_ledger: c.tokenLedger, errors: [],
|
|
50
|
+
}
|
|
51
|
+
})
|