@lumenflow/cli 1.0.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/LICENSE +190 -0
- package/README.md +116 -0
- package/dist/gates.d.ts +41 -0
- package/dist/gates.d.ts.map +1 -0
- package/dist/gates.js +684 -0
- package/dist/gates.js.map +1 -0
- package/dist/initiative-add-wu.d.ts +22 -0
- package/dist/initiative-add-wu.d.ts.map +1 -0
- package/dist/initiative-add-wu.js +234 -0
- package/dist/initiative-add-wu.js.map +1 -0
- package/dist/initiative-create.d.ts +28 -0
- package/dist/initiative-create.d.ts.map +1 -0
- package/dist/initiative-create.js +172 -0
- package/dist/initiative-create.js.map +1 -0
- package/dist/initiative-edit.d.ts +34 -0
- package/dist/initiative-edit.d.ts.map +1 -0
- package/dist/initiative-edit.js +440 -0
- package/dist/initiative-edit.js.map +1 -0
- package/dist/initiative-list.d.ts +12 -0
- package/dist/initiative-list.d.ts.map +1 -0
- package/dist/initiative-list.js +101 -0
- package/dist/initiative-list.js.map +1 -0
- package/dist/initiative-status.d.ts +11 -0
- package/dist/initiative-status.d.ts.map +1 -0
- package/dist/initiative-status.js +221 -0
- package/dist/initiative-status.js.map +1 -0
- package/dist/mem-checkpoint.d.ts +16 -0
- package/dist/mem-checkpoint.d.ts.map +1 -0
- package/dist/mem-checkpoint.js +237 -0
- package/dist/mem-checkpoint.js.map +1 -0
- package/dist/mem-cleanup.d.ts +29 -0
- package/dist/mem-cleanup.d.ts.map +1 -0
- package/dist/mem-cleanup.js +267 -0
- package/dist/mem-cleanup.js.map +1 -0
- package/dist/mem-create.d.ts +17 -0
- package/dist/mem-create.d.ts.map +1 -0
- package/dist/mem-create.js +265 -0
- package/dist/mem-create.js.map +1 -0
- package/dist/mem-inbox.d.ts +35 -0
- package/dist/mem-inbox.d.ts.map +1 -0
- package/dist/mem-inbox.js +373 -0
- package/dist/mem-inbox.js.map +1 -0
- package/dist/mem-init.d.ts +15 -0
- package/dist/mem-init.d.ts.map +1 -0
- package/dist/mem-init.js +146 -0
- package/dist/mem-init.js.map +1 -0
- package/dist/mem-ready.d.ts +16 -0
- package/dist/mem-ready.d.ts.map +1 -0
- package/dist/mem-ready.js +224 -0
- package/dist/mem-ready.js.map +1 -0
- package/dist/mem-signal.d.ts +16 -0
- package/dist/mem-signal.d.ts.map +1 -0
- package/dist/mem-signal.js +204 -0
- package/dist/mem-signal.js.map +1 -0
- package/dist/mem-start.d.ts +16 -0
- package/dist/mem-start.d.ts.map +1 -0
- package/dist/mem-start.js +158 -0
- package/dist/mem-start.js.map +1 -0
- package/dist/mem-summarize.d.ts +22 -0
- package/dist/mem-summarize.d.ts.map +1 -0
- package/dist/mem-summarize.js +213 -0
- package/dist/mem-summarize.js.map +1 -0
- package/dist/mem-triage.d.ts +22 -0
- package/dist/mem-triage.d.ts.map +1 -0
- package/dist/mem-triage.js +328 -0
- package/dist/mem-triage.js.map +1 -0
- package/dist/spawn-list.d.ts +16 -0
- package/dist/spawn-list.d.ts.map +1 -0
- package/dist/spawn-list.js +140 -0
- package/dist/spawn-list.js.map +1 -0
- package/dist/wu-block.d.ts +16 -0
- package/dist/wu-block.d.ts.map +1 -0
- package/dist/wu-block.js +241 -0
- package/dist/wu-block.js.map +1 -0
- package/dist/wu-claim.d.ts +32 -0
- package/dist/wu-claim.d.ts.map +1 -0
- package/dist/wu-claim.js +1106 -0
- package/dist/wu-claim.js.map +1 -0
- package/dist/wu-cleanup.d.ts +17 -0
- package/dist/wu-cleanup.d.ts.map +1 -0
- package/dist/wu-cleanup.js +194 -0
- package/dist/wu-cleanup.js.map +1 -0
- package/dist/wu-create.d.ts +38 -0
- package/dist/wu-create.d.ts.map +1 -0
- package/dist/wu-create.js +520 -0
- package/dist/wu-create.js.map +1 -0
- package/dist/wu-deps.d.ts +13 -0
- package/dist/wu-deps.d.ts.map +1 -0
- package/dist/wu-deps.js +119 -0
- package/dist/wu-deps.js.map +1 -0
- package/dist/wu-done.d.ts +153 -0
- package/dist/wu-done.d.ts.map +1 -0
- package/dist/wu-done.js +2096 -0
- package/dist/wu-done.js.map +1 -0
- package/dist/wu-edit.d.ts +29 -0
- package/dist/wu-edit.d.ts.map +1 -0
- package/dist/wu-edit.js +852 -0
- package/dist/wu-edit.js.map +1 -0
- package/dist/wu-infer-lane.d.ts +17 -0
- package/dist/wu-infer-lane.d.ts.map +1 -0
- package/dist/wu-infer-lane.js +135 -0
- package/dist/wu-infer-lane.js.map +1 -0
- package/dist/wu-preflight.d.ts +47 -0
- package/dist/wu-preflight.d.ts.map +1 -0
- package/dist/wu-preflight.js +167 -0
- package/dist/wu-preflight.js.map +1 -0
- package/dist/wu-prune.d.ts +16 -0
- package/dist/wu-prune.d.ts.map +1 -0
- package/dist/wu-prune.js +259 -0
- package/dist/wu-prune.js.map +1 -0
- package/dist/wu-repair.d.ts +60 -0
- package/dist/wu-repair.d.ts.map +1 -0
- package/dist/wu-repair.js +226 -0
- package/dist/wu-repair.js.map +1 -0
- package/dist/wu-spawn-completion.d.ts +10 -0
- package/dist/wu-spawn-completion.js +30 -0
- package/dist/wu-spawn.d.ts +168 -0
- package/dist/wu-spawn.d.ts.map +1 -0
- package/dist/wu-spawn.js +1327 -0
- package/dist/wu-spawn.js.map +1 -0
- package/dist/wu-unblock.d.ts +16 -0
- package/dist/wu-unblock.d.ts.map +1 -0
- package/dist/wu-unblock.js +234 -0
- package/dist/wu-unblock.js.map +1 -0
- package/dist/wu-validate.d.ts +16 -0
- package/dist/wu-validate.d.ts.map +1 -0
- package/dist/wu-validate.js +193 -0
- package/dist/wu-validate.js.map +1 -0
- package/package.json +92 -0
package/dist/wu-spawn.js
ADDED
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* WU Spawn Helper
|
|
4
|
+
*
|
|
5
|
+
* Generates ready-to-use Task tool invocations for sub-agent WU execution.
|
|
6
|
+
* Includes context loading preamble, skills selection guidance, and constraints block.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* pnpm wu:spawn --id WU-123
|
|
10
|
+
* pnpm wu:spawn --id WU-123 --codex
|
|
11
|
+
*
|
|
12
|
+
* Output:
|
|
13
|
+
* A complete Task tool invocation block with:
|
|
14
|
+
* - Context loading preamble (CLAUDE-core.md, README, lumenflow, WU YAML)
|
|
15
|
+
* - WU details and acceptance criteria
|
|
16
|
+
* - Skills Selection section (sub-agent reads catalogue and selects at runtime)
|
|
17
|
+
* - Mandatory agent advisory
|
|
18
|
+
* - Constraints block at end (Lost in the Middle research)
|
|
19
|
+
*
|
|
20
|
+
* Skills Selection:
|
|
21
|
+
* This command is AGENT-FACING. Unlike /wu-prompt (human-facing, skills selected
|
|
22
|
+
* at generation time), wu:spawn instructs the sub-agent to read the skill catalogue
|
|
23
|
+
* and select skills at execution time based on WU context.
|
|
24
|
+
*
|
|
25
|
+
* Codex Mode:
|
|
26
|
+
* When --codex is used, outputs a Codex/GPT-friendly Markdown prompt (no antml/XML escaping).
|
|
27
|
+
*
|
|
28
|
+
* @see {@link ai/onboarding/agent-invocation-guide.md} - Context loading templates
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
33
|
+
import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
|
|
34
|
+
import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
|
|
35
|
+
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
36
|
+
import { WU_STATUS, PATTERNS, FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
|
|
37
|
+
// WU-1603: Check lane lock status before spawning
|
|
38
|
+
import { checkLaneLock } from '@lumenflow/core/dist/lane-lock.js';
|
|
39
|
+
import { minimatch } from 'minimatch';
|
|
40
|
+
// WU-2252: Import invariants loader for spawn output injection
|
|
41
|
+
import { loadInvariants, INVARIANT_TYPES } from '@lumenflow/core/dist/invariants-runner.js';
|
|
42
|
+
import { validateSpawnArgs, generateExecutionModeSection, generateThinkToolGuidance, recordSpawnToRegistry, formatSpawnRecordedMessage, } from '@lumenflow/core/dist/wu-spawn-helpers.js';
|
|
43
|
+
import { validateSpawnDependencies, formatDependencyError, } from '@lumenflow/core/dist/dependency-validator.js';
|
|
44
|
+
/**
|
|
45
|
+
* Mandatory agent trigger patterns.
|
|
46
|
+
* Mirrors MANDATORY_TRIGGERS from orchestration-advisory-loader.mjs.
|
|
47
|
+
*/
|
|
48
|
+
const MANDATORY_TRIGGERS = {
|
|
49
|
+
'security-auditor': ['supabase/migrations/**', '**/auth/**', '**/rls/**', '**/permissions/**'],
|
|
50
|
+
'beacon-guardian': ['**/prompts/**', '**/classification/**', '**/detector/**', '**/llm/**'],
|
|
51
|
+
};
|
|
52
|
+
const LOG_PREFIX = '[wu:spawn]';
|
|
53
|
+
/** @type {string} */
|
|
54
|
+
const AGENTS_DIR = '.claude/agents';
|
|
55
|
+
/**
|
|
56
|
+
* Load skills configured in agent's frontmatter
|
|
57
|
+
*
|
|
58
|
+
* @param {string} agentName - Agent name (e.g., 'general-purpose')
|
|
59
|
+
* @returns {string[]} Array of skill names or empty array if not found
|
|
60
|
+
*/
|
|
61
|
+
function loadAgentConfiguredSkills(agentName) {
|
|
62
|
+
const agentPath = `${AGENTS_DIR}/${agentName}.md`;
|
|
63
|
+
if (!existsSync(agentPath)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const content = readFileSync(agentPath, { encoding: FILE_SYSTEM.UTF8 });
|
|
68
|
+
return []; // Skills loading removed - vendor agnostic
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Detect mandatory agents based on code paths.
|
|
76
|
+
*
|
|
77
|
+
* @param {string[]} codePaths - Array of file paths
|
|
78
|
+
* @returns {string[]} Array of mandatory agent names
|
|
79
|
+
*/
|
|
80
|
+
function detectMandatoryAgents(codePaths) {
|
|
81
|
+
if (!codePaths || codePaths.length === 0) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const triggeredAgents = new Set();
|
|
85
|
+
for (const [agentName, patterns] of Object.entries(MANDATORY_TRIGGERS)) {
|
|
86
|
+
const isTriggered = codePaths.some((filePath) => patterns.some((pattern) => minimatch(filePath, pattern)));
|
|
87
|
+
if (isTriggered) {
|
|
88
|
+
triggeredAgents.add(agentName);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Array.from(triggeredAgents);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format acceptance criteria as markdown list
|
|
95
|
+
*
|
|
96
|
+
* @param {string[]|undefined} acceptance - Acceptance criteria array
|
|
97
|
+
* @returns {string} Formatted acceptance criteria
|
|
98
|
+
*/
|
|
99
|
+
function formatAcceptance(acceptance) {
|
|
100
|
+
if (!acceptance || acceptance.length === 0) {
|
|
101
|
+
return '- No acceptance criteria defined';
|
|
102
|
+
}
|
|
103
|
+
return acceptance.map((item) => `- [ ] ${item}`).join('\n');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Format spec_refs as markdown links
|
|
107
|
+
*
|
|
108
|
+
* @param {string[]|undefined} specRefs - Spec references array
|
|
109
|
+
* @returns {string} Formatted references or empty string if none
|
|
110
|
+
*/
|
|
111
|
+
function formatSpecRefs(specRefs) {
|
|
112
|
+
if (!specRefs || specRefs.length === 0) {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
return specRefs.map((ref) => `- ${ref}`).join('\n');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Format risks as markdown list
|
|
119
|
+
*
|
|
120
|
+
* @param {string[]|undefined} risks - Risks array
|
|
121
|
+
* @returns {string} Formatted risks or empty string if none
|
|
122
|
+
*/
|
|
123
|
+
function formatRisks(risks) {
|
|
124
|
+
if (!risks || risks.length === 0) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
return risks.map((risk) => `- ${risk}`).join('\n');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Format manual tests as markdown checklist
|
|
131
|
+
*
|
|
132
|
+
* @param {string[]|undefined} manualTests - Manual test steps
|
|
133
|
+
* @returns {string} Formatted tests or empty string if none
|
|
134
|
+
*/
|
|
135
|
+
function formatManualTests(manualTests) {
|
|
136
|
+
if (!manualTests || manualTests.length === 0) {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
return manualTests.map((test) => `- [ ] ${test}`).join('\n');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Generate implementation context section (WU-1833)
|
|
143
|
+
*
|
|
144
|
+
* Includes spec_refs, notes, risks, and tests.manual if present.
|
|
145
|
+
* Sections with no content are omitted to keep prompts lean.
|
|
146
|
+
*
|
|
147
|
+
* @param {object} doc - WU YAML document
|
|
148
|
+
* @returns {string} Implementation context section or empty string
|
|
149
|
+
*/
|
|
150
|
+
function generateImplementationContext(doc) {
|
|
151
|
+
const sections = [];
|
|
152
|
+
// References (spec_refs)
|
|
153
|
+
const refs = formatSpecRefs(doc.spec_refs);
|
|
154
|
+
if (refs) {
|
|
155
|
+
sections.push(`## References\n\n${refs}`);
|
|
156
|
+
}
|
|
157
|
+
// Implementation Notes
|
|
158
|
+
if (doc.notes && doc.notes.trim()) {
|
|
159
|
+
sections.push(`## Implementation Notes\n\n${doc.notes.trim()}`);
|
|
160
|
+
}
|
|
161
|
+
// Risks
|
|
162
|
+
const risks = formatRisks(doc.risks);
|
|
163
|
+
if (risks) {
|
|
164
|
+
sections.push(`## Risks\n\n${risks}`);
|
|
165
|
+
}
|
|
166
|
+
// Manual Verification (tests.manual)
|
|
167
|
+
const manualTests = formatManualTests(doc.tests?.manual);
|
|
168
|
+
if (manualTests) {
|
|
169
|
+
sections.push(`## Manual Verification\n\n${manualTests}`);
|
|
170
|
+
}
|
|
171
|
+
if (sections.length === 0) {
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
return sections.join('\n\n---\n\n');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if a code path matches an invariant based on type
|
|
178
|
+
*
|
|
179
|
+
* @param {object} invariant - Invariant definition
|
|
180
|
+
* @param {string[]} codePaths - Array of code paths
|
|
181
|
+
* @returns {boolean} True if code paths match the invariant
|
|
182
|
+
*/
|
|
183
|
+
function codePathMatchesInvariant(invariant, codePaths) {
|
|
184
|
+
switch (invariant.type) {
|
|
185
|
+
case INVARIANT_TYPES.FORBIDDEN_FILE:
|
|
186
|
+
case INVARIANT_TYPES.REQUIRED_FILE:
|
|
187
|
+
return codePaths.some((p) => p === invariant.path || minimatch(p, invariant.path) || minimatch(invariant.path, p));
|
|
188
|
+
case INVARIANT_TYPES.MUTUAL_EXCLUSIVITY:
|
|
189
|
+
return codePaths.some((p) => invariant.paths.some((invPath) => p === invPath || minimatch(p, invPath)));
|
|
190
|
+
case INVARIANT_TYPES.FORBIDDEN_PATTERN:
|
|
191
|
+
case INVARIANT_TYPES.REQUIRED_PATTERN:
|
|
192
|
+
return (invariant.scope?.some((scopePattern) => codePaths.some((p) => minimatch(p, scopePattern))) ?? false);
|
|
193
|
+
// WU-2254: forbidden-import uses 'from' glob instead of 'scope'
|
|
194
|
+
case INVARIANT_TYPES.FORBIDDEN_IMPORT:
|
|
195
|
+
return invariant.from ? codePaths.some((p) => minimatch(p, invariant.from)) : false;
|
|
196
|
+
default:
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Format a single invariant for output
|
|
202
|
+
*
|
|
203
|
+
* @param {object} inv - Invariant definition
|
|
204
|
+
* @returns {string[]} Lines of formatted output
|
|
205
|
+
*/
|
|
206
|
+
function formatInvariantForOutput(inv) {
|
|
207
|
+
const lines = [`### ${inv.id} (${inv.type})`, '', inv.description, ''];
|
|
208
|
+
if (inv.message) {
|
|
209
|
+
lines.push(`**Action:** ${inv.message}`, '');
|
|
210
|
+
}
|
|
211
|
+
if (inv.path) {
|
|
212
|
+
lines.push(`**Path:** \`${inv.path}\``);
|
|
213
|
+
}
|
|
214
|
+
if (inv.paths) {
|
|
215
|
+
lines.push(`**Paths:** ${inv.paths.map((p) => `\`${p}\``).join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
// WU-2254: forbidden-import specific fields
|
|
218
|
+
if (inv.from) {
|
|
219
|
+
lines.push(`**From:** \`${inv.from}\``);
|
|
220
|
+
}
|
|
221
|
+
if (inv.cannot_import && Array.isArray(inv.cannot_import)) {
|
|
222
|
+
lines.push(`**Cannot Import:** ${inv.cannot_import.map((m) => `\`${m}\``).join(', ')}`);
|
|
223
|
+
}
|
|
224
|
+
// WU-2254: required-pattern specific fields
|
|
225
|
+
if (inv.pattern &&
|
|
226
|
+
(inv.type === INVARIANT_TYPES.REQUIRED_PATTERN ||
|
|
227
|
+
inv.type === INVARIANT_TYPES.FORBIDDEN_PATTERN)) {
|
|
228
|
+
lines.push(`**Pattern:** \`${inv.pattern}\``);
|
|
229
|
+
}
|
|
230
|
+
if (inv.scope && Array.isArray(inv.scope)) {
|
|
231
|
+
lines.push(`**Scope:** ${inv.scope.map((s) => `\`${s}\``).join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
lines.push('');
|
|
234
|
+
return lines;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* WU-2252: Generate invariants/prior-art section for code_paths
|
|
238
|
+
*
|
|
239
|
+
* Loads relevant invariants from invariants.yml and generates a section
|
|
240
|
+
* that surfaces constraints and prior-art for the WU's code_paths.
|
|
241
|
+
*
|
|
242
|
+
* @param {string[]} codePaths - Array of code paths from the WU
|
|
243
|
+
* @returns {string} Invariants/prior-art section or empty string if none relevant
|
|
244
|
+
*/
|
|
245
|
+
function generateInvariantsPriorArtSection(codePaths) {
|
|
246
|
+
if (!codePaths || codePaths.length === 0) {
|
|
247
|
+
return '';
|
|
248
|
+
}
|
|
249
|
+
// Try to load tools/invariants.yml
|
|
250
|
+
const invariantsPath = path.resolve('tools/invariants.yml');
|
|
251
|
+
if (!existsSync(invariantsPath)) {
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
let invariants;
|
|
255
|
+
try {
|
|
256
|
+
invariants = loadInvariants(invariantsPath);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return '';
|
|
260
|
+
}
|
|
261
|
+
if (!invariants || invariants.length === 0) {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
// Find relevant invariants based on code_paths
|
|
265
|
+
const relevantInvariants = invariants.filter((inv) => codePathMatchesInvariant(inv, codePaths));
|
|
266
|
+
if (relevantInvariants.length === 0) {
|
|
267
|
+
return '';
|
|
268
|
+
}
|
|
269
|
+
// Format the section
|
|
270
|
+
const lines = [
|
|
271
|
+
'## Invariants/Prior-Art (WU-2252)',
|
|
272
|
+
'',
|
|
273
|
+
'The following repo invariants are relevant to your code_paths:',
|
|
274
|
+
'',
|
|
275
|
+
...relevantInvariants.flatMap(formatInvariantForOutput),
|
|
276
|
+
'**IMPORTANT:** Do not create specs or acceptance criteria that conflict with these invariants.',
|
|
277
|
+
];
|
|
278
|
+
return lines.join('\n');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Generate the TDD directive section (WU-1585)
|
|
282
|
+
*
|
|
283
|
+
* Positioned immediately after </task> preamble per "Lost in the Middle" research.
|
|
284
|
+
* Critical instructions at START and END of prompt improve adherence.
|
|
285
|
+
*
|
|
286
|
+
* @returns {string} TDD directive section
|
|
287
|
+
*/
|
|
288
|
+
function generateTDDDirective() {
|
|
289
|
+
return `## ⛔ TDD DIRECTIVE — READ BEFORE CODING
|
|
290
|
+
|
|
291
|
+
**IF YOU WRITE IMPLEMENTATION CODE BEFORE A FAILING TEST, YOU HAVE FAILED THIS WU.**
|
|
292
|
+
|
|
293
|
+
### Test-First Workflow (MANDATORY)
|
|
294
|
+
|
|
295
|
+
1. Write a failing test for the acceptance criteria
|
|
296
|
+
2. Run the test to confirm it fails (RED)
|
|
297
|
+
3. Implement the minimum code to pass the test
|
|
298
|
+
4. Run the test to confirm it passes (GREEN)
|
|
299
|
+
5. Refactor if needed, keeping tests green
|
|
300
|
+
|
|
301
|
+
### Why This Matters
|
|
302
|
+
|
|
303
|
+
- Tests document expected behavior BEFORE implementation
|
|
304
|
+
- Prevents scope creep and over-engineering
|
|
305
|
+
- Ensures every feature has verification
|
|
306
|
+
- Failing tests prove the test actually tests something`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Generate the context loading preamble
|
|
310
|
+
*
|
|
311
|
+
* Follows AGENTS.md context loading protocol (WU-2247):
|
|
312
|
+
* 1. CLAUDE.md for workflow fundamentals
|
|
313
|
+
* 2. README.md for project structure
|
|
314
|
+
* 3. lumenflow-complete.md sections 1-7 (TDD, gates, DoD)
|
|
315
|
+
* 4. WU YAML for specific task
|
|
316
|
+
*
|
|
317
|
+
* Includes context recovery section for session resumption (WU-1589).
|
|
318
|
+
*
|
|
319
|
+
* @param {string} id - WU ID
|
|
320
|
+
* @returns {string} Context loading preamble
|
|
321
|
+
*/
|
|
322
|
+
function generatePreamble(id) {
|
|
323
|
+
return `Load the following context in this order:
|
|
324
|
+
|
|
325
|
+
1. Read CLAUDE.md (workflow fundamentals and critical rules)
|
|
326
|
+
2. Read README.md (project structure and tech stack)
|
|
327
|
+
3. Read docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md sections 1-7 (TDD, gates, Definition of Done)
|
|
328
|
+
4. Read docs/04-operations/tasks/wu/${id}.yaml (the specific WU you're working on)
|
|
329
|
+
|
|
330
|
+
## WIP=1 Lane Check (BEFORE claiming)
|
|
331
|
+
|
|
332
|
+
Before running wu:claim, check docs/04-operations/tasks/status.md to ensure the lane is free.
|
|
333
|
+
Only ONE WU can be in_progress per lane at any time.
|
|
334
|
+
|
|
335
|
+
## Context Recovery (Session Resumption)
|
|
336
|
+
|
|
337
|
+
Before starting work, check for prior context from previous sessions:
|
|
338
|
+
|
|
339
|
+
1. \`pnpm mem:ready --wu ${id}\` — Query pending nodes (what's next?)
|
|
340
|
+
2. \`pnpm mem:inbox --wu ${id}\` — Check coordination signals from parallel agents
|
|
341
|
+
|
|
342
|
+
If prior context exists, resume from the last checkpoint. Otherwise, proceed with the task below.`;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Generate the constraints block (appended at end per Lost in the Middle research)
|
|
346
|
+
*
|
|
347
|
+
* WU-2247: Aligned with LumenFlow §7.2 (stop-and-ask) and §7.3 (anti-loop guard).
|
|
348
|
+
* Includes item 6: MEMORY LAYER COORDINATION (WU-1589).
|
|
349
|
+
*
|
|
350
|
+
* @param {string} id - WU ID
|
|
351
|
+
* @returns {string} Constraints block
|
|
352
|
+
*/
|
|
353
|
+
function generateConstraints(id) {
|
|
354
|
+
return `---
|
|
355
|
+
|
|
356
|
+
<constraints>
|
|
357
|
+
CRITICAL RULES - ENFORCE BEFORE EVERY ACTION:
|
|
358
|
+
|
|
359
|
+
1. TDD CHECKPOINT (VERIFY BEFORE IMPLEMENTATION)
|
|
360
|
+
- Did you write tests BEFORE implementation?
|
|
361
|
+
- Is there at least one failing test for each acceptance criterion?
|
|
362
|
+
- Never skip the RED phase — failing tests prove the test works
|
|
363
|
+
|
|
364
|
+
2. ANTI-LOOP GUARD (LumenFlow §7.3)
|
|
365
|
+
- Max 3 attempts per unique error before escalating
|
|
366
|
+
- If same error repeats 3x, STOP and report with full context
|
|
367
|
+
- Retry with different approach, not same command
|
|
368
|
+
|
|
369
|
+
3. STOP-AND-ASK TRIGGERS (LumenFlow §7.2 - narrow scope)
|
|
370
|
+
- Policy changes, auth/permissions modifications
|
|
371
|
+
- PII/PHI/safety issues, cloud spend, secrets, backups
|
|
372
|
+
- Same error repeats 3x
|
|
373
|
+
- For ordinary errors: fix and retry autonomously (up to 3 attempts)
|
|
374
|
+
|
|
375
|
+
4. VERIFY COMPLETION before reporting success
|
|
376
|
+
- Run: node tools/lib/agent-verification.mjs ${id} (from shared checkout)
|
|
377
|
+
- Exit 0 = passed, Exit 1 = INCOMPLETE
|
|
378
|
+
- Never report "done" if verification fails
|
|
379
|
+
|
|
380
|
+
5. NEVER FABRICATE COMPLETION
|
|
381
|
+
- If blockers remain, report INCOMPLETE
|
|
382
|
+
- If verification fails, summarize failures
|
|
383
|
+
- Honesty over false completion
|
|
384
|
+
|
|
385
|
+
6. GIT WORKFLOW (CRITICAL - GitHub rules reject merge commits)
|
|
386
|
+
- GitHub REJECTS merge commits on main
|
|
387
|
+
- ALWAYS use \`git rebase origin/main\` before push
|
|
388
|
+
- Push to main via \`git push origin lane/...:main\` (fast-forward only)
|
|
389
|
+
- NEVER use \`git merge\` on main branch
|
|
390
|
+
- Let \`pnpm wu:done\` handle the merge workflow
|
|
391
|
+
|
|
392
|
+
7. MEMORY LAYER COORDINATION (INIT-007)
|
|
393
|
+
- Use \`pnpm mem:checkpoint --wu ${id}\` to save progress before risky operations
|
|
394
|
+
- Check \`pnpm mem:inbox --wu ${id}\` periodically for parallel signals from other agents
|
|
395
|
+
- Checkpoint triggers (WU-1943): checkpoint after each acceptance criterion completed, checkpoint before gates, checkpoint every 30 tool calls
|
|
396
|
+
</constraints>`;
|
|
397
|
+
}
|
|
398
|
+
function generateCodexConstraints(id) {
|
|
399
|
+
return `## Constraints (Critical)
|
|
400
|
+
|
|
401
|
+
1. **TDD checkpoint**: tests BEFORE implementation; never skip RED
|
|
402
|
+
2. **Stop on errors**: if any command fails, report BLOCKED (never DONE) with the error
|
|
403
|
+
3. **Verify before success**: run \`pnpm gates\` in the worktree, then run \`node tools/lib/agent-verification.mjs ${id}\` (from the shared checkout)
|
|
404
|
+
4. **No fabrication**: if blockers remain or verification fails, report INCOMPLETE
|
|
405
|
+
5. **Git workflow**: avoid merge commits; let \`pnpm wu:done\` handle completion
|
|
406
|
+
6. **Scope discipline**: stay within \`code_paths\`; capture out-of-scope issues via \`pnpm mem:create\``;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Generate mandatory agent advisory section
|
|
410
|
+
*
|
|
411
|
+
* @param {string[]} mandatoryAgents - Array of mandatory agent names
|
|
412
|
+
* @param {string} id - WU ID
|
|
413
|
+
* @returns {string} Mandatory agent section or empty string
|
|
414
|
+
*/
|
|
415
|
+
function generateMandatoryAgentSection(mandatoryAgents, id) {
|
|
416
|
+
if (mandatoryAgents.length === 0) {
|
|
417
|
+
return '';
|
|
418
|
+
}
|
|
419
|
+
const agentList = mandatoryAgents.map((agent) => ` - ${agent}`).join('\n');
|
|
420
|
+
return `
|
|
421
|
+
## Mandatory Agents (MUST invoke before wu:done)
|
|
422
|
+
|
|
423
|
+
Based on code_paths, the following agents MUST be invoked:
|
|
424
|
+
|
|
425
|
+
${agentList}
|
|
426
|
+
|
|
427
|
+
Run: pnpm orchestrate:suggest --wu ${id}
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Generate effort scaling rules section (WU-1986)
|
|
432
|
+
*
|
|
433
|
+
* Based on Anthropic multi-agent research: helps agents decide when to
|
|
434
|
+
* spawn sub-agents vs handle inline.
|
|
435
|
+
*
|
|
436
|
+
* @returns {string} Effort scaling section
|
|
437
|
+
*/
|
|
438
|
+
export function generateEffortScalingRules() {
|
|
439
|
+
return `## Effort Scaling (When to Spawn Sub-Agents)
|
|
440
|
+
|
|
441
|
+
Use this heuristic to decide complexity:
|
|
442
|
+
|
|
443
|
+
| Complexity | Approach | Tool Calls |
|
|
444
|
+
|------------|----------|------------|
|
|
445
|
+
| **Simple** (single file, <50 lines) | Handle inline | 3-10 |
|
|
446
|
+
| **Moderate** (2-3 files, clear scope) | Handle inline | 10-20 |
|
|
447
|
+
| **Complex** (4+ files, exploration needed) | Spawn Explore agent first | 20+ |
|
|
448
|
+
| **Multi-domain** (cross-cutting concerns) | Spawn specialized sub-agents | Varies |
|
|
449
|
+
|
|
450
|
+
**Rule**: If you need >30 tool calls for a subtask, consider spawning a sub-agent with a focused scope.`;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Generate parallel tool call guidance (WU-1986)
|
|
454
|
+
*
|
|
455
|
+
* Based on Anthropic research: 3+ parallel tool calls significantly improve performance.
|
|
456
|
+
*
|
|
457
|
+
* @returns {string} Parallel tool call guidance
|
|
458
|
+
*/
|
|
459
|
+
export function generateParallelToolCallGuidance() {
|
|
460
|
+
return `## Parallel Tool Calls (Performance)
|
|
461
|
+
|
|
462
|
+
**IMPORTANT**: Make 3+ tool calls in parallel when operations are independent.
|
|
463
|
+
|
|
464
|
+
Good examples:
|
|
465
|
+
- Reading multiple files simultaneously
|
|
466
|
+
- Running independent grep searches
|
|
467
|
+
- Spawning multiple Explore agents for different areas
|
|
468
|
+
|
|
469
|
+
Bad examples:
|
|
470
|
+
- Reading a file then editing it (sequential dependency)
|
|
471
|
+
- Running tests then checking results (sequential)
|
|
472
|
+
|
|
473
|
+
Parallelism reduces latency by 50-90% for complex tasks.`;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Generate iterative search heuristics (WU-1986)
|
|
477
|
+
*
|
|
478
|
+
* Based on Anthropic research: start broad, narrow focus.
|
|
479
|
+
*
|
|
480
|
+
* @returns {string} Search heuristics section
|
|
481
|
+
*/
|
|
482
|
+
export function generateIterativeSearchHeuristics() {
|
|
483
|
+
return `## Search Strategy (Broad to Narrow)
|
|
484
|
+
|
|
485
|
+
When exploring the codebase:
|
|
486
|
+
|
|
487
|
+
1. **Start broad**: Use Explore agent or glob patterns to understand structure
|
|
488
|
+
2. **Evaluate findings**: What patterns exist? What's relevant?
|
|
489
|
+
3. **Narrow focus**: Target specific files/functions based on findings
|
|
490
|
+
4. **Iterate**: Refine if initial approach misses the target
|
|
491
|
+
|
|
492
|
+
Avoid: Jumping directly to specific file edits without understanding context.`;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Generate token budget awareness section (WU-1986)
|
|
496
|
+
*
|
|
497
|
+
* @param {string} id - WU ID
|
|
498
|
+
* @returns {string} Token budget section
|
|
499
|
+
*/
|
|
500
|
+
export function generateTokenBudgetAwareness(id) {
|
|
501
|
+
return `## Token Budget Awareness
|
|
502
|
+
|
|
503
|
+
Context limit is ~200K tokens. Monitor your usage:
|
|
504
|
+
|
|
505
|
+
- **At 50+ tool calls**: Create a checkpoint (\`pnpm mem:checkpoint --wu ${id}\`)
|
|
506
|
+
- **At 100+ tool calls**: Consider spawning fresh sub-agent with focused scope
|
|
507
|
+
- **Before risky operations**: Always checkpoint first
|
|
508
|
+
|
|
509
|
+
If approaching limits, summarize progress and spawn continuation agent.`;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Generate structured completion format (WU-1986)
|
|
513
|
+
*
|
|
514
|
+
* @param {string} id - WU ID
|
|
515
|
+
* @returns {string} Completion format section
|
|
516
|
+
*/
|
|
517
|
+
export function generateCompletionFormat(_id) {
|
|
518
|
+
return `## Completion Report Format
|
|
519
|
+
|
|
520
|
+
When finishing, provide structured output:
|
|
521
|
+
|
|
522
|
+
\`\`\`
|
|
523
|
+
## Summary
|
|
524
|
+
<1-3 sentences describing what was accomplished>
|
|
525
|
+
|
|
526
|
+
## Artifacts
|
|
527
|
+
- Files modified: <list>
|
|
528
|
+
- Tests added: <list>
|
|
529
|
+
- Documentation updated: <list>
|
|
530
|
+
|
|
531
|
+
## Verification
|
|
532
|
+
- Gates: <pass/fail>
|
|
533
|
+
- Tests: <X passing, Y failing>
|
|
534
|
+
|
|
535
|
+
## Blockers (if any)
|
|
536
|
+
- <blocker description>
|
|
537
|
+
|
|
538
|
+
## Follow-up (if needed)
|
|
539
|
+
- <suggested next WU or action>
|
|
540
|
+
\`\`\`
|
|
541
|
+
|
|
542
|
+
This format enables orchestrator to track progress across waves.`;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Generate agent coordination section (WU-1987)
|
|
546
|
+
*
|
|
547
|
+
* Provides guidance on mem:signal for parallel agent coordination,
|
|
548
|
+
* orchestrate:status for dashboard checks, and abandoned WU handling.
|
|
549
|
+
*
|
|
550
|
+
* @param {string} id - WU ID
|
|
551
|
+
* @returns {string} Agent coordination section
|
|
552
|
+
*/
|
|
553
|
+
export function generateAgentCoordinationSection(id) {
|
|
554
|
+
return `## Agent Coordination (Parallel Work)
|
|
555
|
+
|
|
556
|
+
### ⚠️ CRITICAL: Use mem:signal, NOT TaskOutput
|
|
557
|
+
|
|
558
|
+
**DO NOT** use TaskOutput to check agent progress - it returns full transcripts
|
|
559
|
+
and causes "prompt too long" errors. Always use the memory layer instead:
|
|
560
|
+
|
|
561
|
+
\`\`\`bash
|
|
562
|
+
# ✅ CORRECT: Compact signals (~6 lines)
|
|
563
|
+
pnpm mem:inbox --since 30m
|
|
564
|
+
|
|
565
|
+
# ❌ WRONG: Full transcripts (context explosion)
|
|
566
|
+
# TaskOutput with block=false <-- NEVER DO THIS FOR MONITORING
|
|
567
|
+
\`\`\`
|
|
568
|
+
|
|
569
|
+
### Automatic Completion Signals
|
|
570
|
+
|
|
571
|
+
\`wu:done\` automatically broadcasts completion signals. You do not need to
|
|
572
|
+
manually signal completion - just run \`wu:done\` and orchestrators will
|
|
573
|
+
see your signal via \`mem:inbox\`.
|
|
574
|
+
|
|
575
|
+
### Progress Signals (Optional)
|
|
576
|
+
|
|
577
|
+
For long-running work, send progress signals at milestones:
|
|
578
|
+
|
|
579
|
+
\`\`\`bash
|
|
580
|
+
pnpm mem:signal "50% complete: tests passing, implementing adapter" --wu ${id}
|
|
581
|
+
pnpm mem:signal "Blocked: waiting for WU-XXX dependency" --wu ${id}
|
|
582
|
+
\`\`\`
|
|
583
|
+
|
|
584
|
+
### Checking Status
|
|
585
|
+
|
|
586
|
+
\`\`\`bash
|
|
587
|
+
pnpm orchestrate:init-status -i INIT-XXX # Initiative progress (compact)
|
|
588
|
+
pnpm mem:inbox --since 1h # Recent signals from all agents
|
|
589
|
+
pnpm mem:inbox --lane "Experience: Web" # Lane-specific signals
|
|
590
|
+
\`\`\``;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Generate quick fix commands section (WU-1987)
|
|
594
|
+
*
|
|
595
|
+
* Provides format/lint/typecheck commands for quick fixes before gates.
|
|
596
|
+
*
|
|
597
|
+
* @returns {string} Quick fix commands section
|
|
598
|
+
*/
|
|
599
|
+
export function generateQuickFixCommands() {
|
|
600
|
+
return `## Quick Fix Commands
|
|
601
|
+
|
|
602
|
+
If gates fail, try these before investigating:
|
|
603
|
+
|
|
604
|
+
\`\`\`bash
|
|
605
|
+
pnpm format # Auto-fix formatting issues
|
|
606
|
+
pnpm lint # Check linting (use --fix for auto-fix)
|
|
607
|
+
pnpm typecheck # Check TypeScript types
|
|
608
|
+
\`\`\`
|
|
609
|
+
|
|
610
|
+
**Use before gates** to catch simple issues early. These are faster than full \`pnpm gates\`.`;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Generate Lane Selection section (WU-2107)
|
|
614
|
+
*
|
|
615
|
+
* Provides guidance on lane selection when creating new WUs.
|
|
616
|
+
* Points agents to wu:infer-lane for automated lane suggestions.
|
|
617
|
+
*
|
|
618
|
+
* @returns {string} Lane Selection section
|
|
619
|
+
*/
|
|
620
|
+
export function generateLaneSelectionSection() {
|
|
621
|
+
return `## Lane Selection
|
|
622
|
+
|
|
623
|
+
When creating new WUs, use the correct lane to enable parallelization:
|
|
624
|
+
|
|
625
|
+
\`\`\`bash
|
|
626
|
+
# Get lane suggestion based on code paths and description
|
|
627
|
+
pnpm wu:infer-lane --id WU-XXX
|
|
628
|
+
|
|
629
|
+
# Or infer from manual inputs
|
|
630
|
+
pnpm wu:infer-lane --paths "tools/**" --desc "CLI improvements"
|
|
631
|
+
\`\`\`
|
|
632
|
+
|
|
633
|
+
**Lane taxonomy**: See \`.lumenflow.lane-inference.yaml\` for valid lanes and patterns.
|
|
634
|
+
|
|
635
|
+
**Why lanes matter**: WIP=1 per lane means correct lane selection enables parallel work across lanes.`;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Generate Worktree Path Guidance section (WU-2362)
|
|
639
|
+
*
|
|
640
|
+
* Provides guidance for sub-agents on working within worktrees, including
|
|
641
|
+
* how to determine the worktree root and where to create stamps.
|
|
642
|
+
*
|
|
643
|
+
* Problem: CLAUDE_PROJECT_DIR is hook-only; sub-agents inherit parent cwd (main).
|
|
644
|
+
* Solution: Use git rev-parse --show-toplevel to determine actual worktree root.
|
|
645
|
+
*
|
|
646
|
+
* @param {string|undefined} worktreePath - Worktree path from WU YAML
|
|
647
|
+
* @returns {string} Worktree path guidance section
|
|
648
|
+
*/
|
|
649
|
+
export function generateWorktreePathGuidance(worktreePath) {
|
|
650
|
+
if (!worktreePath) {
|
|
651
|
+
return '';
|
|
652
|
+
}
|
|
653
|
+
return `## Worktree Path Guidance (WU-2362)
|
|
654
|
+
|
|
655
|
+
**Your worktree:** \`${worktreePath}\`
|
|
656
|
+
|
|
657
|
+
### Finding the Worktree Root
|
|
658
|
+
|
|
659
|
+
Sub-agents may inherit the parent's cwd (main checkout). To find the actual worktree root:
|
|
660
|
+
|
|
661
|
+
\`\`\`bash
|
|
662
|
+
# Get the worktree root (not main checkout)
|
|
663
|
+
git rev-parse --show-toplevel
|
|
664
|
+
\`\`\`
|
|
665
|
+
|
|
666
|
+
### Stamp Creation
|
|
667
|
+
|
|
668
|
+
When creating \`.beacon/\` stamps or other artifacts:
|
|
669
|
+
|
|
670
|
+
1. **ALWAYS** create stamps in the **worktree**, not main
|
|
671
|
+
2. Use \`git rev-parse --show-toplevel\` to get the correct base path
|
|
672
|
+
3. Stamps created on main will be lost when the worktree merges
|
|
673
|
+
|
|
674
|
+
\`\`\`bash
|
|
675
|
+
# CORRECT: Create stamp in worktree
|
|
676
|
+
WORKTREE_ROOT=$(git rev-parse --show-toplevel)
|
|
677
|
+
mkdir -p "$WORKTREE_ROOT/.beacon/agent-runs"
|
|
678
|
+
touch "$WORKTREE_ROOT/.beacon/agent-runs/beacon-guardian.stamp"
|
|
679
|
+
|
|
680
|
+
# WRONG: Hardcoded path to main
|
|
681
|
+
# touch /path/to/main/.beacon/agent-runs/beacon-guardian.stamp
|
|
682
|
+
\`\`\`
|
|
683
|
+
|
|
684
|
+
### Why This Matters
|
|
685
|
+
|
|
686
|
+
- Stamps on main get overwritten by worktree merge
|
|
687
|
+
- \`wu:done\` validates stamps exist in the worktree branch
|
|
688
|
+
- Parallel WUs in other lanes won't see your stamps if on main`;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Generate the Bug Discovery section (WU-1592, WU-2284)
|
|
692
|
+
*
|
|
693
|
+
* Instructs sub-agents to capture bugs found mid-WU via mem:create.
|
|
694
|
+
* This enables scope-creep tracking and ensures discovered bugs
|
|
695
|
+
* are not lost when agents encounter issues outside their WU scope.
|
|
696
|
+
*
|
|
697
|
+
* WU-2284: Added explicit prohibition against using wu:create directly
|
|
698
|
+
* for discovered issues. Agents must use mem:create for capture, then
|
|
699
|
+
* human triage decides whether to promote to a WU.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} id - WU ID
|
|
702
|
+
* @returns {string} Bug Discovery section
|
|
703
|
+
*/
|
|
704
|
+
function generateBugDiscoverySection(id) {
|
|
705
|
+
return `## Bug Discovery (Mid-WU Issue Capture)
|
|
706
|
+
|
|
707
|
+
If you discover a bug or issue **outside the scope of this WU**:
|
|
708
|
+
|
|
709
|
+
1. **Capture it immediately** using:
|
|
710
|
+
\`\`\`bash
|
|
711
|
+
pnpm mem:create 'Bug: <description>' --type discovery --tags bug,scope-creep --wu ${id}
|
|
712
|
+
\`\`\`
|
|
713
|
+
|
|
714
|
+
2. **Continue with your WU** — do not fix bugs outside your scope
|
|
715
|
+
3. **Reference in notes** — mention the mem node ID in your completion notes
|
|
716
|
+
|
|
717
|
+
### NEVER use wu:create for discovered issues
|
|
718
|
+
|
|
719
|
+
**Do NOT use \`wu:create\` directly for bugs discovered mid-WU.**
|
|
720
|
+
|
|
721
|
+
- \`mem:create\` = **capture** (immediate, no human approval needed)
|
|
722
|
+
- \`wu:create\` = **planned work** (requires human triage and approval)
|
|
723
|
+
|
|
724
|
+
Discovered issues MUST go through human triage before becoming WUs.
|
|
725
|
+
Using \`wu:create\` directly bypasses the triage workflow and creates
|
|
726
|
+
unreviewed work items.
|
|
727
|
+
|
|
728
|
+
### When to Capture
|
|
729
|
+
|
|
730
|
+
- Found a bug in code NOT in your \`code_paths\`
|
|
731
|
+
- Discovered an issue that would require >10 lines to fix
|
|
732
|
+
- Encountered broken behaviour unrelated to your acceptance criteria
|
|
733
|
+
|
|
734
|
+
### Triage Workflow
|
|
735
|
+
|
|
736
|
+
After WU completion, bugs can be promoted to Bug WUs by humans:
|
|
737
|
+
\`\`\`bash
|
|
738
|
+
pnpm mem:triage --wu ${id} # List discoveries for this WU
|
|
739
|
+
pnpm mem:triage --promote <node-id> --lane "<lane>" # Create Bug WU (human action)
|
|
740
|
+
\`\`\`
|
|
741
|
+
|
|
742
|
+
See: ai/onboarding/agent-invocation-guide.md §Bug Discovery`;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Generate lane-specific guidance
|
|
746
|
+
*
|
|
747
|
+
* @param {string} lane - Lane name
|
|
748
|
+
* @returns {string} Lane-specific guidance or empty string
|
|
749
|
+
*/
|
|
750
|
+
function generateLaneGuidance(lane) {
|
|
751
|
+
if (!lane)
|
|
752
|
+
return '';
|
|
753
|
+
const laneParent = lane.split(':')[0].trim();
|
|
754
|
+
const guidance = {
|
|
755
|
+
Operations: `## Lane-Specific: Tooling
|
|
756
|
+
|
|
757
|
+
- Update tool documentation in tools/README.md or relevant docs if adding new CLI commands`,
|
|
758
|
+
Intelligence: `## Lane-Specific: Intelligence
|
|
759
|
+
|
|
760
|
+
- All prompt changes require golden dataset evaluation (pnpm prompts:eval)
|
|
761
|
+
- Follow prompt versioning guidelines in ai/prompts/README.md`,
|
|
762
|
+
Experience: `## Lane-Specific: Experience
|
|
763
|
+
|
|
764
|
+
- Follow design system tokens in packages/@patientpath/design-system
|
|
765
|
+
- Ensure accessibility compliance (WCAG 2.1 AA)`,
|
|
766
|
+
Core: `## Lane-Specific: Core
|
|
767
|
+
|
|
768
|
+
- Maintain hexagonal architecture boundaries
|
|
769
|
+
- Update domain model documentation if changing entities`,
|
|
770
|
+
};
|
|
771
|
+
return guidance[laneParent] || '';
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Generate the Action section based on WU claim status (WU-1745).
|
|
775
|
+
*
|
|
776
|
+
* If WU is already claimed (has claimed_at and worktree_path), tells agent
|
|
777
|
+
* to continue in the existing worktree.
|
|
778
|
+
*
|
|
779
|
+
* If WU is unclaimed (status: ready), tells agent to run wu:claim first.
|
|
780
|
+
*
|
|
781
|
+
* @param {object} doc - WU YAML document
|
|
782
|
+
* @param {string} id - WU ID
|
|
783
|
+
* @returns {string} Action section content
|
|
784
|
+
*/
|
|
785
|
+
export function generateActionSection(doc, id) {
|
|
786
|
+
const isAlreadyClaimed = doc.claimed_at && doc.worktree_path;
|
|
787
|
+
if (isAlreadyClaimed) {
|
|
788
|
+
return `This WU is already claimed. Continue implementation in worktree following all standards above.
|
|
789
|
+
|
|
790
|
+
cd ${doc.worktree_path}`;
|
|
791
|
+
}
|
|
792
|
+
// WU is unclaimed - agent needs to claim first
|
|
793
|
+
const laneSlug = (doc.lane || 'unknown')
|
|
794
|
+
.toLowerCase()
|
|
795
|
+
.replace(/[:\s]+/g, '-')
|
|
796
|
+
.replace(/-+/g, '-');
|
|
797
|
+
return `**FIRST: Claim this WU before starting work:**
|
|
798
|
+
|
|
799
|
+
\`\`\`bash
|
|
800
|
+
pnpm wu:claim --id ${id} --lane "${doc.lane}"
|
|
801
|
+
cd worktrees/${laneSlug}-${id.toLowerCase()}
|
|
802
|
+
\`\`\`
|
|
803
|
+
|
|
804
|
+
Then implement following all standards above.
|
|
805
|
+
|
|
806
|
+
**CRITICAL:** Never use \`git worktree add\` directly. Always use \`pnpm wu:claim\` to ensure:
|
|
807
|
+
- Event tracking in .beacon/state/wu-events.jsonl
|
|
808
|
+
- Lane lock acquisition (WIP=1 enforcement)
|
|
809
|
+
- Session tracking for context recovery`;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Generate the Completion Workflow section for sub-agents (WU-2682).
|
|
813
|
+
*
|
|
814
|
+
* Explicitly instructs sub-agents to run wu:done autonomously after gates pass.
|
|
815
|
+
* This prevents agents from asking permission instead of completing.
|
|
816
|
+
*
|
|
817
|
+
* @param {string} id - WU ID
|
|
818
|
+
* @returns {string} Completion Workflow section
|
|
819
|
+
*/
|
|
820
|
+
export function generateCompletionWorkflowSection(id) {
|
|
821
|
+
return `## Completion Workflow
|
|
822
|
+
|
|
823
|
+
**CRITICAL: Complete autonomously. Do NOT ask for permission.**
|
|
824
|
+
|
|
825
|
+
After all acceptance criteria are satisfied:
|
|
826
|
+
|
|
827
|
+
1. Run gates in the worktree: \`pnpm gates\`
|
|
828
|
+
2. If gates pass, cd back to main checkout
|
|
829
|
+
3. Run: \`pnpm wu:done --id ${id}\`
|
|
830
|
+
|
|
831
|
+
\`\`\`bash
|
|
832
|
+
# From worktree, after gates pass:
|
|
833
|
+
cd /path/to/main # NOT the worktree
|
|
834
|
+
pnpm wu:done --id ${id}
|
|
835
|
+
\`\`\`
|
|
836
|
+
|
|
837
|
+
**wu:done** handles: merge to main, stamp creation, worktree cleanup.
|
|
838
|
+
|
|
839
|
+
**Do not ask** "should I run wu:done?" — just run it when gates pass.`;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Generate the Skills Selection section for sub-agents.
|
|
843
|
+
*
|
|
844
|
+
* Unlike /wu-prompt (human-facing, skills selected at generation time),
|
|
845
|
+
* wu:spawn instructs the sub-agent to read the catalogue and select skills
|
|
846
|
+
* at execution time based on WU context.
|
|
847
|
+
*
|
|
848
|
+
* If an agentName is provided, that agent's configured skills (from frontmatter)
|
|
849
|
+
* are auto-loaded at the top.
|
|
850
|
+
*
|
|
851
|
+
* @param {object} doc - WU YAML document
|
|
852
|
+
* @param {string} [agentName='general-purpose'] - Agent to spawn
|
|
853
|
+
* @returns {string} Skills Selection section
|
|
854
|
+
*/
|
|
855
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- WU-2025: Pre-existing complexity, refactor tracked
|
|
856
|
+
function generateSkillsSection(doc, agentName = 'general-purpose') {
|
|
857
|
+
const lane = doc.lane || '';
|
|
858
|
+
const type = doc.type || 'feature';
|
|
859
|
+
const laneParent = lane.split(':')[0].trim();
|
|
860
|
+
// Load agent's configured skills from frontmatter
|
|
861
|
+
const agentSkills = loadAgentConfiguredSkills(agentName);
|
|
862
|
+
const hasAgentSkills = agentSkills.length > 0;
|
|
863
|
+
// Build auto-load section if agent has configured skills
|
|
864
|
+
const autoLoadSection = hasAgentSkills
|
|
865
|
+
? `### Auto-Loaded Skills (from ${agentName} agent config)
|
|
866
|
+
|
|
867
|
+
These skills are pre-configured for this agent and should be loaded first:
|
|
868
|
+
|
|
869
|
+
${agentSkills.map((s) => `- \`${s}\` — Load via \`/skill ${s}\``).join('\n')}
|
|
870
|
+
|
|
871
|
+
`
|
|
872
|
+
: '';
|
|
873
|
+
// Build context hints for the sub-agent
|
|
874
|
+
const contextHints = [];
|
|
875
|
+
// Universal baselines (only if not already in agent skills)
|
|
876
|
+
if (!agentSkills.includes('wu-lifecycle')) {
|
|
877
|
+
contextHints.push('- `wu-lifecycle` — ALL WUs need workflow automation');
|
|
878
|
+
}
|
|
879
|
+
if (!agentSkills.includes('worktree-discipline')) {
|
|
880
|
+
contextHints.push('- `worktree-discipline` — ALL WUs need path safety');
|
|
881
|
+
}
|
|
882
|
+
// Type-based hints
|
|
883
|
+
if ((type === 'feature' || type === 'enhancement') && !agentSkills.includes('tdd-workflow')) {
|
|
884
|
+
contextHints.push('- `tdd-workflow` — TDD is mandatory for feature/enhancement WUs');
|
|
885
|
+
}
|
|
886
|
+
if (type === 'bug' && !agentSkills.includes('bug-classification')) {
|
|
887
|
+
contextHints.push('- `bug-classification` — Bug severity assessment');
|
|
888
|
+
}
|
|
889
|
+
// Lane-based hints
|
|
890
|
+
if (laneParent === 'Operations' &&
|
|
891
|
+
lane.includes('Tooling') &&
|
|
892
|
+
!agentSkills.includes('lumenflow-gates')) {
|
|
893
|
+
contextHints.push('- `lumenflow-gates` — Tooling often affects gates');
|
|
894
|
+
}
|
|
895
|
+
if (laneParent === 'Intelligence') {
|
|
896
|
+
if (!agentSkills.includes('beacon-compliance')) {
|
|
897
|
+
contextHints.push('- `beacon-compliance` — Intelligence lane requires Beacon validation');
|
|
898
|
+
}
|
|
899
|
+
if (!agentSkills.includes('prompt-management')) {
|
|
900
|
+
contextHints.push('- `prompt-management` — For prompt template work');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (laneParent === 'Experience' && !agentSkills.includes('frontend-design')) {
|
|
904
|
+
contextHints.push('- `frontend-design` — For UI component work');
|
|
905
|
+
}
|
|
906
|
+
const softPolicySection = contextHints.length > 0
|
|
907
|
+
? `### Soft Policy (baselines for this WU)
|
|
908
|
+
|
|
909
|
+
Based on WU context, consider loading:
|
|
910
|
+
|
|
911
|
+
${contextHints.join('\n')}
|
|
912
|
+
|
|
913
|
+
`
|
|
914
|
+
: '';
|
|
915
|
+
return `## Skills Selection
|
|
916
|
+
|
|
917
|
+
**IMPORTANT**: Before starting work, select and load relevant skills.
|
|
918
|
+
|
|
919
|
+
${autoLoadSection}### How to Select Skills
|
|
920
|
+
|
|
921
|
+
1. Read the skill catalogue frontmatter from \`.claude/skills/*/SKILL.md\`
|
|
922
|
+
2. Match skills to WU context (lane, type, code_paths, description)
|
|
923
|
+
3. Load selected skills via \`/skill <skill-name>\`
|
|
924
|
+
|
|
925
|
+
${softPolicySection}### Additional Skills (load if needed)
|
|
926
|
+
|
|
927
|
+
| Skill | Use When |
|
|
928
|
+
|-------|----------|
|
|
929
|
+
| lumenflow-gates | Gates fail, debugging format/lint/typecheck errors |
|
|
930
|
+
| bug-classification | Bug discovered mid-WU, need priority classification |
|
|
931
|
+
| beacon-compliance | Code touches LLM, prompts, classification |
|
|
932
|
+
| prompt-management | Working with prompt templates, golden datasets |
|
|
933
|
+
| frontend-design | Building UI components, pages |
|
|
934
|
+
| initiative-management | Multi-phase projects, INIT-XXX coordination |
|
|
935
|
+
| multi-agent-coordination | Spawning sub-agents, parallel WU work |
|
|
936
|
+
| orchestration | Agent coordination, mandatory agent checks |
|
|
937
|
+
| ops-maintenance | Metrics, validation, health checks |
|
|
938
|
+
|
|
939
|
+
### Graceful Degradation
|
|
940
|
+
|
|
941
|
+
If the skill catalogue is missing or invalid:
|
|
942
|
+
- Load baseline skills: \`/skill wu-lifecycle\`, \`/skill tdd-workflow\` (for features)
|
|
943
|
+
- Continue with implementation using Mandatory Standards below
|
|
944
|
+
`;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Generate the complete Task tool invocation
|
|
948
|
+
*
|
|
949
|
+
* @param {object} doc - WU YAML document
|
|
950
|
+
* @param {string} id - WU ID
|
|
951
|
+
* @param {object} [options={}] - Thinking mode options
|
|
952
|
+
* @param {boolean} [options.thinking] - Whether extended thinking is enabled
|
|
953
|
+
* @param {boolean} [options.noThinking] - Whether thinking is explicitly disabled
|
|
954
|
+
* @param {string} [options.budget] - Token budget for thinking
|
|
955
|
+
* @returns {string} Complete Task tool invocation
|
|
956
|
+
*/
|
|
957
|
+
export function generateTaskInvocation(doc, id, options = {}) {
|
|
958
|
+
const codePaths = doc.code_paths || [];
|
|
959
|
+
const mandatoryAgents = detectMandatoryAgents(codePaths);
|
|
960
|
+
const preamble = generatePreamble(id);
|
|
961
|
+
const tddDirective = generateTDDDirective();
|
|
962
|
+
const skillsSection = generateSkillsSection(doc);
|
|
963
|
+
const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
|
|
964
|
+
const laneGuidance = generateLaneGuidance(doc.lane);
|
|
965
|
+
const bugDiscoverySection = generateBugDiscoverySection(id);
|
|
966
|
+
const constraints = generateConstraints(id);
|
|
967
|
+
const implementationContext = generateImplementationContext(doc);
|
|
968
|
+
// WU-2252: Generate invariants/prior-art section for code_paths
|
|
969
|
+
const invariantsPriorArt = generateInvariantsPriorArtSection(codePaths);
|
|
970
|
+
// WU-1986: Anthropic multi-agent best practices sections
|
|
971
|
+
const effortScaling = generateEffortScalingRules();
|
|
972
|
+
const parallelToolCalls = generateParallelToolCallGuidance();
|
|
973
|
+
const searchHeuristics = generateIterativeSearchHeuristics();
|
|
974
|
+
const tokenBudget = generateTokenBudgetAwareness(id);
|
|
975
|
+
const completionFormat = generateCompletionFormat(id);
|
|
976
|
+
// WU-1987: Agent coordination and quick fix sections
|
|
977
|
+
const agentCoordination = generateAgentCoordinationSection(id);
|
|
978
|
+
const quickFix = generateQuickFixCommands();
|
|
979
|
+
// WU-2107: Lane selection guidance
|
|
980
|
+
const laneSelection = generateLaneSelectionSection();
|
|
981
|
+
// WU-2362: Worktree path guidance for sub-agents
|
|
982
|
+
const worktreeGuidance = generateWorktreePathGuidance(doc.worktree_path);
|
|
983
|
+
// Generate thinking mode sections if applicable
|
|
984
|
+
const executionModeSection = generateExecutionModeSection(options);
|
|
985
|
+
const thinkToolGuidance = generateThinkToolGuidance(options);
|
|
986
|
+
// Build optional sections string
|
|
987
|
+
const thinkingSections = [executionModeSection, thinkToolGuidance]
|
|
988
|
+
.filter((section) => section.length > 0)
|
|
989
|
+
.join('\n\n---\n\n');
|
|
990
|
+
const thinkingBlock = thinkingSections ? `${thinkingSections}\n\n---\n\n` : '';
|
|
991
|
+
// Build the task prompt
|
|
992
|
+
// TDD directive appears immediately after </task> per "Lost in the Middle" research (WU-1585)
|
|
993
|
+
const taskPrompt = `<task>
|
|
994
|
+
${preamble}
|
|
995
|
+
</task>
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
${tddDirective}
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
# ${id}: ${doc.title || 'Untitled'}
|
|
1004
|
+
|
|
1005
|
+
## WU Details
|
|
1006
|
+
|
|
1007
|
+
- **ID:** ${id}
|
|
1008
|
+
- **Lane:** ${doc.lane || 'Unknown'}
|
|
1009
|
+
- **Type:** ${doc.type || 'feature'}
|
|
1010
|
+
- **Status:** ${doc.status || 'unknown'}
|
|
1011
|
+
- **Worktree:** ${doc.worktree_path || `worktrees/<lane>-${id.toLowerCase()}`}
|
|
1012
|
+
|
|
1013
|
+
## Description
|
|
1014
|
+
|
|
1015
|
+
${doc.description || 'No description provided.'}
|
|
1016
|
+
|
|
1017
|
+
## Acceptance Criteria
|
|
1018
|
+
|
|
1019
|
+
${formatAcceptance(doc.acceptance)}
|
|
1020
|
+
|
|
1021
|
+
## Code Paths
|
|
1022
|
+
|
|
1023
|
+
${codePaths.length > 0 ? codePaths.map((p) => `- ${p}`).join('\n') : '- No code paths defined'}
|
|
1024
|
+
${mandatorySection}${invariantsPriorArt ? `---\n\n${invariantsPriorArt}\n\n` : ''}${implementationContext ? `---\n\n${implementationContext}\n\n` : ''}---
|
|
1025
|
+
|
|
1026
|
+
${thinkingBlock}${skillsSection}
|
|
1027
|
+
---
|
|
1028
|
+
|
|
1029
|
+
## Mandatory Standards
|
|
1030
|
+
|
|
1031
|
+
- **LumenFlow**: Follow trunk-based flow, WIP=1, worktree discipline
|
|
1032
|
+
- **TDD**: Failing test first, then implementation, then passing test. 90%+ coverage on new application code
|
|
1033
|
+
- **Hexagonal Architecture**: Ports-first design. No application -> infrastructure imports
|
|
1034
|
+
- **SOLID/DRY/YAGNI/KISS**: No over-engineering, no premature abstraction
|
|
1035
|
+
- **Library-First**: Search context7 before writing custom code. No reinventing wheels
|
|
1036
|
+
- **Code Quality**: No string literals, no magic numbers, no brittle regexes when libraries exist
|
|
1037
|
+
- **Worktree Discipline**: ALWAYS use \`pnpm wu:claim\` to create worktrees (never \`git worktree add\` directly). Work ONLY in the worktree, never edit main
|
|
1038
|
+
- **Documentation**: Update tooling docs if changing tools. Keep docs in sync with code
|
|
1039
|
+
- **Sub-agents**: Use Explore agent for codebase investigation. Activate mandatory agents (security-auditor for PHI/auth, beacon-guardian for LLM/prompts)
|
|
1040
|
+
|
|
1041
|
+
${worktreeGuidance ? `---\n\n${worktreeGuidance}\n\n` : ''}---
|
|
1042
|
+
|
|
1043
|
+
${bugDiscoverySection}
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
${effortScaling}
|
|
1048
|
+
|
|
1049
|
+
---
|
|
1050
|
+
|
|
1051
|
+
${parallelToolCalls}
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
${searchHeuristics}
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
${tokenBudget}
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
${completionFormat}
|
|
1064
|
+
|
|
1065
|
+
---
|
|
1066
|
+
|
|
1067
|
+
${agentCoordination}
|
|
1068
|
+
|
|
1069
|
+
---
|
|
1070
|
+
|
|
1071
|
+
${quickFix}
|
|
1072
|
+
|
|
1073
|
+
---
|
|
1074
|
+
|
|
1075
|
+
${laneSelection}
|
|
1076
|
+
|
|
1077
|
+
---
|
|
1078
|
+
|
|
1079
|
+
${laneGuidance}${laneGuidance ? '\n\n---\n\n' : ''}## Action
|
|
1080
|
+
|
|
1081
|
+
${generateActionSection(doc, id)}
|
|
1082
|
+
|
|
1083
|
+
${constraints}`;
|
|
1084
|
+
// Escape special characters for XML output
|
|
1085
|
+
const escapedPrompt = taskPrompt
|
|
1086
|
+
.replace(/&/g, '&')
|
|
1087
|
+
.replace(/</g, '<')
|
|
1088
|
+
.replace(/>/g, '>');
|
|
1089
|
+
// Build the Task tool invocation block using antml format
|
|
1090
|
+
// Using array join to avoid XML parsing issues
|
|
1091
|
+
const openTag = '<' + 'antml:invoke name="Task">';
|
|
1092
|
+
const closeTag = '</' + 'antml:invoke>';
|
|
1093
|
+
const paramOpen = '<' + 'antml:parameter name="';
|
|
1094
|
+
const paramClose = '</' + 'antml:parameter>';
|
|
1095
|
+
const invocation = [
|
|
1096
|
+
'<' + 'antml:function_calls>',
|
|
1097
|
+
openTag,
|
|
1098
|
+
`${paramOpen}subagent_type">general-purpose${paramClose}`,
|
|
1099
|
+
`${paramOpen}description">Execute ${id}${paramClose}`,
|
|
1100
|
+
`${paramOpen}prompt">${escapedPrompt}${paramClose}`,
|
|
1101
|
+
closeTag,
|
|
1102
|
+
'</' + 'antml:function_calls>',
|
|
1103
|
+
].join('\n');
|
|
1104
|
+
return invocation;
|
|
1105
|
+
}
|
|
1106
|
+
export function generateCodexPrompt(doc, id, options = {}) {
|
|
1107
|
+
const codePaths = doc.code_paths || [];
|
|
1108
|
+
const mandatoryAgents = detectMandatoryAgents(codePaths);
|
|
1109
|
+
const preamble = generatePreamble(id);
|
|
1110
|
+
const tddDirective = generateTDDDirective();
|
|
1111
|
+
const mandatorySection = generateMandatoryAgentSection(mandatoryAgents, id);
|
|
1112
|
+
const laneGuidance = generateLaneGuidance(doc.lane);
|
|
1113
|
+
const bugDiscoverySection = generateBugDiscoverySection(id);
|
|
1114
|
+
const implementationContext = generateImplementationContext(doc);
|
|
1115
|
+
const action = generateActionSection(doc, id);
|
|
1116
|
+
const constraints = generateCodexConstraints(id);
|
|
1117
|
+
const executionModeSection = generateExecutionModeSection(options);
|
|
1118
|
+
const thinkToolGuidance = generateThinkToolGuidance(options);
|
|
1119
|
+
const thinkingSections = [executionModeSection, thinkToolGuidance]
|
|
1120
|
+
.filter((section) => section.length > 0)
|
|
1121
|
+
.join('\n\n---\n\n');
|
|
1122
|
+
const thinkingBlock = thinkingSections ? `${thinkingSections}\n\n---\n\n` : '';
|
|
1123
|
+
return `# ${id}: ${doc.title || 'Untitled'}
|
|
1124
|
+
|
|
1125
|
+
${tddDirective}
|
|
1126
|
+
|
|
1127
|
+
---
|
|
1128
|
+
|
|
1129
|
+
## Context
|
|
1130
|
+
|
|
1131
|
+
${preamble}
|
|
1132
|
+
|
|
1133
|
+
---
|
|
1134
|
+
|
|
1135
|
+
## WU Details
|
|
1136
|
+
|
|
1137
|
+
- **ID:** ${id}
|
|
1138
|
+
- **Lane:** ${doc.lane || 'Unknown'}
|
|
1139
|
+
- **Type:** ${doc.type || 'feature'}
|
|
1140
|
+
- **Status:** ${doc.status || 'unknown'}
|
|
1141
|
+
- **Worktree:** ${doc.worktree_path || `worktrees/<lane>-${id.toLowerCase()}`}
|
|
1142
|
+
|
|
1143
|
+
## Description
|
|
1144
|
+
|
|
1145
|
+
${doc.description || 'No description provided.'}
|
|
1146
|
+
|
|
1147
|
+
## Scope (code_paths)
|
|
1148
|
+
|
|
1149
|
+
Only change files within these paths:
|
|
1150
|
+
|
|
1151
|
+
${codePaths.length > 0 ? codePaths.map((p) => `- ${p}`).join('\n') : '- No code paths defined'}
|
|
1152
|
+
|
|
1153
|
+
## Acceptance Criteria
|
|
1154
|
+
|
|
1155
|
+
${formatAcceptance(doc.acceptance)}
|
|
1156
|
+
|
|
1157
|
+
---
|
|
1158
|
+
|
|
1159
|
+
## Action
|
|
1160
|
+
|
|
1161
|
+
${action}
|
|
1162
|
+
|
|
1163
|
+
---
|
|
1164
|
+
|
|
1165
|
+
## Verification
|
|
1166
|
+
|
|
1167
|
+
- Run in worktree: \`pnpm gates\`
|
|
1168
|
+
- From shared checkout: \`node tools/lib/agent-verification.mjs ${id}\`
|
|
1169
|
+
|
|
1170
|
+
---
|
|
1171
|
+
|
|
1172
|
+
${mandatorySection}${implementationContext ? `${implementationContext}\n\n---\n\n` : ''}${thinkingBlock}${bugDiscoverySection}
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
${laneGuidance}${laneGuidance ? '\n\n---\n\n' : ''}${constraints}
|
|
1177
|
+
`;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* WU-1603: Check if a lane is currently occupied by another WU
|
|
1181
|
+
*
|
|
1182
|
+
* @param {string} lane - Lane name (e.g., "Operations: Tooling")
|
|
1183
|
+
* @returns {import('@lumenflow/core/dist/lane-lock.js').LockMetadata|null} Lock metadata if occupied, null otherwise
|
|
1184
|
+
*/
|
|
1185
|
+
export function checkLaneOccupation(lane) {
|
|
1186
|
+
const lockStatus = checkLaneLock(lane);
|
|
1187
|
+
if (lockStatus.locked && lockStatus.metadata) {
|
|
1188
|
+
return lockStatus.metadata;
|
|
1189
|
+
}
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
export function generateLaneOccupationWarning(lockMetadata, targetWuId, options = {}) {
|
|
1193
|
+
const { isStale = false } = options;
|
|
1194
|
+
let warning = `⚠️ Lane "${lockMetadata.lane}" is occupied by ${lockMetadata.wuId}\n`;
|
|
1195
|
+
warning += ` This violates WIP=1 (Work In Progress limit of 1 per lane).\n\n`;
|
|
1196
|
+
if (isStale) {
|
|
1197
|
+
warning += ` ⏰ This lock is STALE (>24 hours old) - the WU may be abandoned.\n`;
|
|
1198
|
+
warning += ` Consider using pnpm wu:block --id ${lockMetadata.wuId} if work is stalled.\n\n`;
|
|
1199
|
+
}
|
|
1200
|
+
warning += ` Options:\n`;
|
|
1201
|
+
warning += ` 1. Wait for ${lockMetadata.wuId} to complete or block\n`;
|
|
1202
|
+
warning += ` 2. Choose a different lane for ${targetWuId}\n`;
|
|
1203
|
+
warning += ` 3. Block ${lockMetadata.wuId} if work is stalled: pnpm wu:block --id ${lockMetadata.wuId}`;
|
|
1204
|
+
return warning;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Main entry point
|
|
1208
|
+
*/
|
|
1209
|
+
async function main() {
|
|
1210
|
+
// WU-2202: Validate dependencies BEFORE any other operation
|
|
1211
|
+
// This prevents false lane occupancy reports when yaml package is missing
|
|
1212
|
+
const depResult = await validateSpawnDependencies();
|
|
1213
|
+
if (!depResult.valid) {
|
|
1214
|
+
die(formatDependencyError('wu:spawn', depResult.missing));
|
|
1215
|
+
}
|
|
1216
|
+
const args = createWUParser({
|
|
1217
|
+
name: 'wu-spawn',
|
|
1218
|
+
description: 'Generate Task tool invocation for sub-agent WU execution',
|
|
1219
|
+
options: [
|
|
1220
|
+
WU_OPTIONS.id,
|
|
1221
|
+
WU_OPTIONS.thinking,
|
|
1222
|
+
WU_OPTIONS.noThinking,
|
|
1223
|
+
WU_OPTIONS.budget,
|
|
1224
|
+
WU_OPTIONS.codex,
|
|
1225
|
+
WU_OPTIONS.parentWu, // WU-1945: Parent WU for spawn registry tracking
|
|
1226
|
+
],
|
|
1227
|
+
required: ['id'],
|
|
1228
|
+
allowPositionalId: true,
|
|
1229
|
+
});
|
|
1230
|
+
// Validate thinking mode options
|
|
1231
|
+
try {
|
|
1232
|
+
validateSpawnArgs(args);
|
|
1233
|
+
}
|
|
1234
|
+
catch (e) {
|
|
1235
|
+
die(e.message);
|
|
1236
|
+
}
|
|
1237
|
+
const id = args.id.toUpperCase();
|
|
1238
|
+
if (!PATTERNS.WU_ID.test(id)) {
|
|
1239
|
+
die(`Invalid WU id '${args.id}'. Expected format WU-123`);
|
|
1240
|
+
}
|
|
1241
|
+
const WU_PATH = WU_PATHS.WU(id);
|
|
1242
|
+
// Check if WU file exists
|
|
1243
|
+
if (!existsSync(WU_PATH)) {
|
|
1244
|
+
die(`WU file not found: ${WU_PATH}\n\n` +
|
|
1245
|
+
`Cannot spawn a sub-agent for a WU that doesn't exist.\n\n` +
|
|
1246
|
+
`Options:\n` +
|
|
1247
|
+
` 1. Create the WU first: pnpm wu:create --id ${id} --lane <lane> --title "..."\n` +
|
|
1248
|
+
` 2. Check if the WU ID is correct`);
|
|
1249
|
+
}
|
|
1250
|
+
// Read and parse WU YAML
|
|
1251
|
+
let doc;
|
|
1252
|
+
let text;
|
|
1253
|
+
try {
|
|
1254
|
+
text = readFileSync(WU_PATH, { encoding: FILE_SYSTEM.UTF8 });
|
|
1255
|
+
}
|
|
1256
|
+
catch (e) {
|
|
1257
|
+
die(`Failed to read WU file: ${WU_PATH}\n\n` +
|
|
1258
|
+
`Error: ${e.message}\n\n` +
|
|
1259
|
+
`Options:\n` +
|
|
1260
|
+
` 1. Check file permissions: ls -la ${WU_PATH}\n` +
|
|
1261
|
+
` 2. Ensure the file exists and is readable`);
|
|
1262
|
+
}
|
|
1263
|
+
try {
|
|
1264
|
+
doc = parseYAML(text);
|
|
1265
|
+
}
|
|
1266
|
+
catch (e) {
|
|
1267
|
+
die(`Failed to parse WU YAML ${WU_PATH}\n\n` +
|
|
1268
|
+
`Error: ${e.message}\n\n` +
|
|
1269
|
+
`Options:\n` +
|
|
1270
|
+
` 1. Validate YAML syntax: pnpm wu:validate --id ${id}\n` +
|
|
1271
|
+
` 2. Fix YAML errors manually and retry`);
|
|
1272
|
+
}
|
|
1273
|
+
// Warn if WU is not in ready or in_progress status
|
|
1274
|
+
const validStatuses = [WU_STATUS.READY, WU_STATUS.IN_PROGRESS];
|
|
1275
|
+
if (!validStatuses.includes(doc.status)) {
|
|
1276
|
+
console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Warning: ${id} has status '${doc.status}'.`);
|
|
1277
|
+
console.warn(`${LOG_PREFIX} ${EMOJI.WARNING} Sub-agents typically work on ready or in_progress WUs.`);
|
|
1278
|
+
console.warn('');
|
|
1279
|
+
}
|
|
1280
|
+
// WU-1603: Check if lane is already occupied and warn
|
|
1281
|
+
const lane = doc.lane;
|
|
1282
|
+
if (lane) {
|
|
1283
|
+
const existingLock = checkLaneOccupation(lane);
|
|
1284
|
+
if (existingLock && existingLock.wuId !== id) {
|
|
1285
|
+
// Lane is occupied by a different WU
|
|
1286
|
+
const { isLockStale } = await import('@lumenflow/core/dist/lane-lock.js');
|
|
1287
|
+
const isStale = isLockStale(existingLock);
|
|
1288
|
+
const warning = generateLaneOccupationWarning(existingLock, id, { isStale });
|
|
1289
|
+
console.warn(`${LOG_PREFIX} ${EMOJI.WARNING}\n${warning}\n`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
// Build thinking mode options for task invocation
|
|
1293
|
+
const thinkingOptions = {
|
|
1294
|
+
thinking: args.thinking,
|
|
1295
|
+
noThinking: args.noThinking,
|
|
1296
|
+
budget: args.budget,
|
|
1297
|
+
};
|
|
1298
|
+
if (args.codex) {
|
|
1299
|
+
const prompt = generateCodexPrompt(doc, id, thinkingOptions);
|
|
1300
|
+
console.log(`${LOG_PREFIX} Generated Codex/GPT prompt for ${id}`);
|
|
1301
|
+
console.log(`${LOG_PREFIX} Copy the Markdown below:\n`);
|
|
1302
|
+
console.log(prompt.trimEnd());
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
// Generate and output the Task invocation
|
|
1306
|
+
const invocation = generateTaskInvocation(doc, id, thinkingOptions);
|
|
1307
|
+
console.log(`${LOG_PREFIX} Generated Task tool invocation for ${id}`);
|
|
1308
|
+
console.log(`${LOG_PREFIX} Copy the block below to spawn a sub-agent:\n`);
|
|
1309
|
+
console.log(invocation);
|
|
1310
|
+
// WU-1945: Record spawn event to registry (non-blocking)
|
|
1311
|
+
// Only record if --parent-wu is provided (orchestrator context)
|
|
1312
|
+
if (args.parentWu) {
|
|
1313
|
+
const registryResult = await recordSpawnToRegistry({
|
|
1314
|
+
parentWuId: args.parentWu,
|
|
1315
|
+
targetWuId: id,
|
|
1316
|
+
lane: doc.lane || 'Unknown',
|
|
1317
|
+
baseDir: '.beacon/state',
|
|
1318
|
+
});
|
|
1319
|
+
const registryMessage = formatSpawnRecordedMessage(registryResult.spawnId, registryResult.error);
|
|
1320
|
+
console.log(`\n${registryMessage}`);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
// Guard main() for testability
|
|
1324
|
+
import { fileURLToPath } from 'node:url';
|
|
1325
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
1326
|
+
main();
|
|
1327
|
+
}
|