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