@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.
Files changed (129) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +116 -0
  3. package/dist/gates.d.ts +41 -0
  4. package/dist/gates.d.ts.map +1 -0
  5. package/dist/gates.js +684 -0
  6. package/dist/gates.js.map +1 -0
  7. package/dist/initiative-add-wu.d.ts +22 -0
  8. package/dist/initiative-add-wu.d.ts.map +1 -0
  9. package/dist/initiative-add-wu.js +234 -0
  10. package/dist/initiative-add-wu.js.map +1 -0
  11. package/dist/initiative-create.d.ts +28 -0
  12. package/dist/initiative-create.d.ts.map +1 -0
  13. package/dist/initiative-create.js +172 -0
  14. package/dist/initiative-create.js.map +1 -0
  15. package/dist/initiative-edit.d.ts +34 -0
  16. package/dist/initiative-edit.d.ts.map +1 -0
  17. package/dist/initiative-edit.js +440 -0
  18. package/dist/initiative-edit.js.map +1 -0
  19. package/dist/initiative-list.d.ts +12 -0
  20. package/dist/initiative-list.d.ts.map +1 -0
  21. package/dist/initiative-list.js +101 -0
  22. package/dist/initiative-list.js.map +1 -0
  23. package/dist/initiative-status.d.ts +11 -0
  24. package/dist/initiative-status.d.ts.map +1 -0
  25. package/dist/initiative-status.js +221 -0
  26. package/dist/initiative-status.js.map +1 -0
  27. package/dist/mem-checkpoint.d.ts +16 -0
  28. package/dist/mem-checkpoint.d.ts.map +1 -0
  29. package/dist/mem-checkpoint.js +237 -0
  30. package/dist/mem-checkpoint.js.map +1 -0
  31. package/dist/mem-cleanup.d.ts +29 -0
  32. package/dist/mem-cleanup.d.ts.map +1 -0
  33. package/dist/mem-cleanup.js +267 -0
  34. package/dist/mem-cleanup.js.map +1 -0
  35. package/dist/mem-create.d.ts +17 -0
  36. package/dist/mem-create.d.ts.map +1 -0
  37. package/dist/mem-create.js +265 -0
  38. package/dist/mem-create.js.map +1 -0
  39. package/dist/mem-inbox.d.ts +35 -0
  40. package/dist/mem-inbox.d.ts.map +1 -0
  41. package/dist/mem-inbox.js +373 -0
  42. package/dist/mem-inbox.js.map +1 -0
  43. package/dist/mem-init.d.ts +15 -0
  44. package/dist/mem-init.d.ts.map +1 -0
  45. package/dist/mem-init.js +146 -0
  46. package/dist/mem-init.js.map +1 -0
  47. package/dist/mem-ready.d.ts +16 -0
  48. package/dist/mem-ready.d.ts.map +1 -0
  49. package/dist/mem-ready.js +224 -0
  50. package/dist/mem-ready.js.map +1 -0
  51. package/dist/mem-signal.d.ts +16 -0
  52. package/dist/mem-signal.d.ts.map +1 -0
  53. package/dist/mem-signal.js +204 -0
  54. package/dist/mem-signal.js.map +1 -0
  55. package/dist/mem-start.d.ts +16 -0
  56. package/dist/mem-start.d.ts.map +1 -0
  57. package/dist/mem-start.js +158 -0
  58. package/dist/mem-start.js.map +1 -0
  59. package/dist/mem-summarize.d.ts +22 -0
  60. package/dist/mem-summarize.d.ts.map +1 -0
  61. package/dist/mem-summarize.js +213 -0
  62. package/dist/mem-summarize.js.map +1 -0
  63. package/dist/mem-triage.d.ts +22 -0
  64. package/dist/mem-triage.d.ts.map +1 -0
  65. package/dist/mem-triage.js +328 -0
  66. package/dist/mem-triage.js.map +1 -0
  67. package/dist/spawn-list.d.ts +16 -0
  68. package/dist/spawn-list.d.ts.map +1 -0
  69. package/dist/spawn-list.js +140 -0
  70. package/dist/spawn-list.js.map +1 -0
  71. package/dist/wu-block.d.ts +16 -0
  72. package/dist/wu-block.d.ts.map +1 -0
  73. package/dist/wu-block.js +241 -0
  74. package/dist/wu-block.js.map +1 -0
  75. package/dist/wu-claim.d.ts +32 -0
  76. package/dist/wu-claim.d.ts.map +1 -0
  77. package/dist/wu-claim.js +1106 -0
  78. package/dist/wu-claim.js.map +1 -0
  79. package/dist/wu-cleanup.d.ts +17 -0
  80. package/dist/wu-cleanup.d.ts.map +1 -0
  81. package/dist/wu-cleanup.js +194 -0
  82. package/dist/wu-cleanup.js.map +1 -0
  83. package/dist/wu-create.d.ts +38 -0
  84. package/dist/wu-create.d.ts.map +1 -0
  85. package/dist/wu-create.js +520 -0
  86. package/dist/wu-create.js.map +1 -0
  87. package/dist/wu-deps.d.ts +13 -0
  88. package/dist/wu-deps.d.ts.map +1 -0
  89. package/dist/wu-deps.js +119 -0
  90. package/dist/wu-deps.js.map +1 -0
  91. package/dist/wu-done.d.ts +153 -0
  92. package/dist/wu-done.d.ts.map +1 -0
  93. package/dist/wu-done.js +2096 -0
  94. package/dist/wu-done.js.map +1 -0
  95. package/dist/wu-edit.d.ts +29 -0
  96. package/dist/wu-edit.d.ts.map +1 -0
  97. package/dist/wu-edit.js +852 -0
  98. package/dist/wu-edit.js.map +1 -0
  99. package/dist/wu-infer-lane.d.ts +17 -0
  100. package/dist/wu-infer-lane.d.ts.map +1 -0
  101. package/dist/wu-infer-lane.js +135 -0
  102. package/dist/wu-infer-lane.js.map +1 -0
  103. package/dist/wu-preflight.d.ts +47 -0
  104. package/dist/wu-preflight.d.ts.map +1 -0
  105. package/dist/wu-preflight.js +167 -0
  106. package/dist/wu-preflight.js.map +1 -0
  107. package/dist/wu-prune.d.ts +16 -0
  108. package/dist/wu-prune.d.ts.map +1 -0
  109. package/dist/wu-prune.js +259 -0
  110. package/dist/wu-prune.js.map +1 -0
  111. package/dist/wu-repair.d.ts +60 -0
  112. package/dist/wu-repair.d.ts.map +1 -0
  113. package/dist/wu-repair.js +226 -0
  114. package/dist/wu-repair.js.map +1 -0
  115. package/dist/wu-spawn-completion.d.ts +10 -0
  116. package/dist/wu-spawn-completion.js +30 -0
  117. package/dist/wu-spawn.d.ts +168 -0
  118. package/dist/wu-spawn.d.ts.map +1 -0
  119. package/dist/wu-spawn.js +1327 -0
  120. package/dist/wu-spawn.js.map +1 -0
  121. package/dist/wu-unblock.d.ts +16 -0
  122. package/dist/wu-unblock.d.ts.map +1 -0
  123. package/dist/wu-unblock.js +234 -0
  124. package/dist/wu-unblock.js.map +1 -0
  125. package/dist/wu-validate.d.ts +16 -0
  126. package/dist/wu-validate.d.ts.map +1 -0
  127. package/dist/wu-validate.js +193 -0
  128. package/dist/wu-validate.js.map +1 -0
  129. package/package.json +92 -0
@@ -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, '&amp;')
1087
+ .replace(/</g, '&lt;')
1088
+ .replace(/>/g, '&gt;');
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
+ }