@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,285 @@
1
+ /**
2
+ * Spawn Tree Builder (WU-1950)
3
+ *
4
+ * Builds and formats spawn trees for visualization.
5
+ * Used by spawn:list command to display parent-child relationships.
6
+ *
7
+ * Note: Domain-specific tree visualization for spawn registry.
8
+ * Integrates with spawn-registry-store and spawn-registry-schema.
9
+ * No external tree library needed - logic is tightly coupled to spawn data model.
10
+ *
11
+ * @see {@link tools/__tests__/spawn-list.test.mjs} - Tests
12
+ * @see {@link tools/spawn-list.mjs} - CLI command
13
+ * @see {@link tools/lib/spawn-registry-store.mjs} - Data source
14
+ */
15
+ import fs from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ import { SpawnRegistryStore } from './spawn-registry-store.js';
18
+ import { SpawnStatus } from './spawn-registry-schema.js';
19
+ import { parse as parseYAML } from 'yaml';
20
+ /**
21
+ * Status indicators for terminal output.
22
+ * Using unicode symbols for clear visual distinction.
23
+ */
24
+ export const STATUS_INDICATORS = Object.freeze({
25
+ [SpawnStatus.PENDING]: '\u25CB', // ○ (white circle)
26
+ [SpawnStatus.COMPLETED]: '\u2713', // ✓ (check mark)
27
+ [SpawnStatus.TIMEOUT]: '\u23F1', // ⏱ (stopwatch)
28
+ [SpawnStatus.CRASHED]: '\u2717', // ✗ (x mark)
29
+ });
30
+ /**
31
+ * Tree node structure
32
+ * @typedef {object} SpawnTreeNode
33
+ * @property {string} wuId - WU ID for this node
34
+ * @property {string|null} spawnId - Spawn ID (null for root)
35
+ * @property {string|null} status - Spawn status (null for root)
36
+ * @property {string|null} lane - Lane (null for root)
37
+ * @property {string|null} spawnedAt - Spawn timestamp (null for root)
38
+ * @property {SpawnTreeNode[]} children - Child nodes
39
+ */
40
+ /**
41
+ * Builds a spawn tree from flat spawn events.
42
+ *
43
+ * @param {import('./spawn-registry-schema.js').SpawnEvent[]} spawns - Array of spawn events
44
+ * @param {string} rootWuId - Root WU ID to build tree from
45
+ * @returns {SpawnTreeNode} Tree rooted at rootWuId
46
+ *
47
+ * @example
48
+ * const tree = buildSpawnTree(spawns, 'WU-1000');
49
+ * // { wuId: 'WU-1000', children: [{ wuId: 'WU-1001', ... }] }
50
+ */
51
+ export function buildSpawnTree(spawns, rootWuId) {
52
+ // Create root node
53
+ /** @type {SpawnTreeNode} */
54
+ const root = {
55
+ wuId: rootWuId,
56
+ spawnId: null,
57
+ status: null,
58
+ lane: null,
59
+ spawnedAt: null,
60
+ children: [],
61
+ };
62
+ if (spawns.length === 0) {
63
+ return root;
64
+ }
65
+ // Build index of spawns by parent WU ID for efficient lookup
66
+ /** @type {Map<string, import('./spawn-registry-schema.js').SpawnEvent[]>} */
67
+ const spawnsByParent = new Map();
68
+ for (const spawn of spawns) {
69
+ const existing = spawnsByParent.get(spawn.parentWuId) ?? [];
70
+ existing.push(spawn);
71
+ spawnsByParent.set(spawn.parentWuId, existing);
72
+ }
73
+ // Recursive function to build tree
74
+ /**
75
+ * @param {string} parentWuId
76
+ * @returns {SpawnTreeNode[]}
77
+ */
78
+ function buildChildren(parentWuId) {
79
+ const childSpawns = spawnsByParent.get(parentWuId) ?? [];
80
+ return childSpawns.map((spawn) => ({
81
+ wuId: spawn.targetWuId,
82
+ spawnId: spawn.id,
83
+ status: spawn.status,
84
+ lane: spawn.lane,
85
+ spawnedAt: spawn.spawnedAt,
86
+ children: buildChildren(spawn.targetWuId),
87
+ }));
88
+ }
89
+ root.children = buildChildren(rootWuId);
90
+ return root;
91
+ }
92
+ /**
93
+ * Tree branch characters for formatting
94
+ */
95
+ const TREE_CHARS = Object.freeze({
96
+ VERTICAL: '\u2502', // │
97
+ BRANCH: '\u251C', // ├
98
+ LAST_BRANCH: '\u2514', // └
99
+ HORIZONTAL: '\u2500', // ─
100
+ SPACE: ' ',
101
+ });
102
+ /**
103
+ * Formats a spawn tree for terminal display with indentation and tree characters.
104
+ *
105
+ * @param {SpawnTreeNode} tree - Tree to format
106
+ * @returns {string} Formatted tree string
107
+ *
108
+ * @example
109
+ * const formatted = formatSpawnTree(tree);
110
+ * // WU-1000 (root)
111
+ * // ├── ○ WU-1001 [spawn-1111] (Operations: Tooling)
112
+ * // │ └── ✓ WU-1002 [spawn-2222] (Core: Backend)
113
+ * // └── ○ WU-1003 [spawn-3333] (Experience: Web)
114
+ */
115
+ export function formatSpawnTree(tree) {
116
+ const lines = [];
117
+ // Root line
118
+ lines.push(`${tree.wuId} (root)`);
119
+ if (tree.children.length === 0) {
120
+ lines.push(` (no spawns)`);
121
+ return lines.join('\n');
122
+ }
123
+ // Recursive formatting
124
+ /**
125
+ * @param {SpawnTreeNode[]} children
126
+ * @param {string} prefix - Indentation prefix for this level
127
+ */
128
+ function formatChildren(children, prefix) {
129
+ children.forEach((child, index) => {
130
+ const isLast = index === children.length - 1;
131
+ const branch = isLast
132
+ ? `${TREE_CHARS.LAST_BRANCH}${TREE_CHARS.HORIZONTAL}${TREE_CHARS.HORIZONTAL}`
133
+ : `${TREE_CHARS.BRANCH}${TREE_CHARS.HORIZONTAL}${TREE_CHARS.HORIZONTAL}`;
134
+ const indicator = STATUS_INDICATORS[child.status] ?? '?';
135
+ const spawnInfo = child.spawnId ? ` [${child.spawnId}]` : '';
136
+ const laneInfo = child.lane ? ` (${child.lane})` : '';
137
+ lines.push(`${prefix}${branch} ${indicator} ${child.wuId}${spawnInfo}${laneInfo}`);
138
+ // Child prefix: use vertical bar if not last, space if last
139
+ const childPrefix = prefix + (isLast ? ' ' : `${TREE_CHARS.VERTICAL} `);
140
+ formatChildren(child.children, childPrefix);
141
+ });
142
+ }
143
+ formatChildren(tree.children, '');
144
+ return lines.join('\n');
145
+ }
146
+ /**
147
+ * Gets all spawns for a WU (both where WU is parent and descendants).
148
+ *
149
+ * Returns all spawns needed to build the full tree from this WU.
150
+ *
151
+ * @param {string} wuId - WU ID to get spawns for
152
+ * @param {string} baseDir - Directory containing spawn-registry.jsonl
153
+ * @returns {Promise<import('./spawn-registry-schema.js').SpawnEvent[]>} Array of spawn events
154
+ *
155
+ * @example
156
+ * const spawns = await getSpawnsByWU('WU-1000', '.beacon/state');
157
+ */
158
+ export async function getSpawnsByWU(wuId, baseDir) {
159
+ const store = new SpawnRegistryStore(baseDir);
160
+ try {
161
+ await store.load();
162
+ }
163
+ catch (error) {
164
+ // Registry doesn't exist or is invalid
165
+ if (error.code === 'ENOENT') {
166
+ return [];
167
+ }
168
+ throw error;
169
+ }
170
+ // Get all spawns
171
+ const allSpawns = store.getAllSpawns();
172
+ if (allSpawns.length === 0) {
173
+ return [];
174
+ }
175
+ // Find all spawns in the tree rooted at wuId
176
+ // Start with direct children of wuId
177
+ const result = [];
178
+ const visited = new Set();
179
+ const queue = [wuId];
180
+ while (queue.length > 0) {
181
+ const currentWuId = queue.shift();
182
+ if (visited.has(currentWuId))
183
+ continue;
184
+ visited.add(currentWuId);
185
+ // Find spawns where current WU is the parent
186
+ const childSpawns = store.getByParent(currentWuId);
187
+ for (const spawn of childSpawns) {
188
+ if (!result.some((s) => s.id === spawn.id)) {
189
+ result.push(spawn);
190
+ queue.push(spawn.targetWuId);
191
+ }
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+ /**
197
+ * Gets all spawns for an initiative.
198
+ *
199
+ * Reads WU YAML files to find which WUs belong to the initiative,
200
+ * then returns all spawns where parent or target WU belongs to initiative.
201
+ *
202
+ * @param {string} initiativeId - Initiative ID (e.g., 'INIT-001')
203
+ * @param {string} registryDir - Directory containing spawn-registry.jsonl
204
+ * @param {string} wuDir - Directory containing WU YAML files
205
+ * @returns {Promise<import('./spawn-registry-schema.js').SpawnEvent[]>} Array of spawn events
206
+ *
207
+ * @example
208
+ * const spawns = await getSpawnsByInitiative('INIT-001', '.beacon/state', 'docs/04-operations/tasks/wu');
209
+ */
210
+ export async function getSpawnsByInitiative(initiativeId, registryDir, wuDir) {
211
+ // Get all WUs belonging to initiative
212
+ const initiativeWuIds = await getWUsForInitiative(initiativeId, wuDir);
213
+ if (initiativeWuIds.size === 0) {
214
+ return [];
215
+ }
216
+ // Load spawn registry
217
+ const store = new SpawnRegistryStore(registryDir);
218
+ try {
219
+ await store.load();
220
+ }
221
+ catch (error) {
222
+ if (error.code === 'ENOENT') {
223
+ return [];
224
+ }
225
+ throw error;
226
+ }
227
+ // Filter spawns where parent belongs to initiative
228
+ const allSpawns = store.getAllSpawns();
229
+ return allSpawns.filter((spawn) => initiativeWuIds.has(spawn.parentWuId));
230
+ }
231
+ /**
232
+ * Reads WU YAML files to find WUs belonging to an initiative.
233
+ *
234
+ * @param {string} initiativeId - Initiative ID
235
+ * @param {string} wuDir - Directory containing WU YAML files
236
+ * @returns {Promise<Set<string>>} Set of WU IDs belonging to initiative
237
+ */
238
+ async function getWUsForInitiative(initiativeId, wuDir) {
239
+ const wuIds = new Set();
240
+ let files;
241
+ try {
242
+ files = await fs.readdir(wuDir);
243
+ }
244
+ catch (error) {
245
+ if (error.code === 'ENOENT') {
246
+ return wuIds;
247
+ }
248
+ throw error;
249
+ }
250
+ const wuFiles = files.filter((f) => f.startsWith('WU-') && f.endsWith('.yaml'));
251
+ for (const file of wuFiles) {
252
+ try {
253
+ const content = await fs.readFile(path.join(wuDir, file), 'utf-8');
254
+ const doc = parseYAML(content);
255
+ if (doc.initiative === initiativeId) {
256
+ wuIds.add(doc.id);
257
+ }
258
+ }
259
+ catch {
260
+ // Skip files that can't be parsed
261
+ continue;
262
+ }
263
+ }
264
+ return wuIds;
265
+ }
266
+ /**
267
+ * Converts a spawn tree to JSON format.
268
+ *
269
+ * @param {SpawnTreeNode} tree - Tree to convert
270
+ * @returns {object} JSON-serializable tree
271
+ *
272
+ * @example
273
+ * const json = treeToJSON(tree);
274
+ * console.log(JSON.stringify(json, null, 2));
275
+ */
276
+ export function treeToJSON(tree) {
277
+ return {
278
+ wuId: tree.wuId,
279
+ spawnId: tree.spawnId,
280
+ status: tree.status,
281
+ lane: tree.lane,
282
+ spawnedAt: tree.spawnedAt,
283
+ children: tree.children.map((child) => treeToJSON(child)),
284
+ };
285
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @fileoverview Stamp-status validation utilities
3
+ *
4
+ * WU-1781: Provides utilities to detect and handle legacy WUs with stamp-status mismatches.
5
+ *
6
+ * The problem: WUs with .done stamps but non-done status (e.g., cancelled, ready) cause
7
+ * tasks:validate to fail, which blocks husky pre-push and prevents wu:done from completing.
8
+ *
9
+ * The solution: Detect these legacy artifacts and exempt them from validation to prevent
10
+ * deadlocks, while still enforcing validation on new/active WUs.
11
+ */
12
+ /**
13
+ * List of parent-only lanes that are deprecated (legacy).
14
+ * WUs with these lanes are considered legacy artifacts and may have stamp-status mismatches.
15
+ * Modern WUs should use sub-lanes like "Operations: Tooling".
16
+ *
17
+ * @type {readonly string[]}
18
+ */
19
+ export declare const LEGACY_PARENT_ONLY_LANES: readonly string[];
20
+ /**
21
+ * Check if a lane is a deprecated parent-only lane (no sub-lane specified).
22
+ *
23
+ * @param {string|null|undefined} lane - The lane to check
24
+ * @returns {boolean} True if the lane is a parent-only legacy lane
25
+ *
26
+ * @example
27
+ * isLegacyParentOnlyLane('Intelligence') // true
28
+ * isLegacyParentOnlyLane('Operations: Tooling') // false
29
+ */
30
+ export declare function isLegacyParentOnlyLane(lane: any): boolean;
31
+ /**
32
+ * Check if a WU is a legacy artifact with stamp-status mismatch.
33
+ *
34
+ * A legacy stamped WU is one that:
35
+ * - Has a .done stamp file
36
+ * - Has a status that is NOT 'done'
37
+ *
38
+ * These WUs are historical artifacts from before validation was strict,
39
+ * and should be exempted from the stamp-status consistency check.
40
+ *
41
+ * @param {object} wu - The parsed WU YAML object
42
+ * @param {string} wu.id - WU ID (e.g., 'WU-100')
43
+ * @param {string} wu.status - WU status (e.g., 'done', 'cancelled', 'ready')
44
+ * @param {Set<string>} stampedIds - Set of WU IDs that have .done stamp files
45
+ * @returns {boolean} True if this is a legacy stamped WU with mismatch
46
+ *
47
+ * @example
48
+ * const stampedIds = new Set(['WU-100', 'WU-101']);
49
+ * isLegacyStampedWU({ id: 'WU-100', status: 'cancelled' }, stampedIds) // true
50
+ * isLegacyStampedWU({ id: 'WU-100', status: 'done' }, stampedIds) // false (no mismatch)
51
+ * isLegacyStampedWU({ id: 'WU-999', status: 'cancelled' }, stampedIds) // false (no stamp)
52
+ */
53
+ export declare function isLegacyStampedWU(wu: any, stampedIds: any): boolean;
54
+ /**
55
+ * Check if a WU ID is exempted from stamp-status validation via config.
56
+ *
57
+ * The .lumenflow.config.yaml can specify exemptions for known historical
58
+ * artifacts that cannot be fixed (e.g., WU-307, WU-311, WU-1152).
59
+ *
60
+ * @param {string} id - WU ID to check
61
+ * @param {string[]|null|undefined} exemptions - List of exempted WU IDs from config
62
+ * @returns {boolean} True if the WU is exempted
63
+ *
64
+ * @example
65
+ * isExemptedFromStampStatusCheck('WU-307', ['WU-307', 'WU-311']) // true
66
+ * isExemptedFromStampStatusCheck('WU-999', ['WU-307', 'WU-311']) // false
67
+ */
68
+ export declare function isExemptedFromStampStatusCheck(id: any, exemptions: any): boolean;
69
+ /**
70
+ * Determine if a WU should be exempted from stamp-status validation.
71
+ *
72
+ * A WU is exempted if:
73
+ * 1. It's explicitly listed in the config exemptions, OR
74
+ * 2. It's a legacy stamped WU (has stamp but status != done) AND has a legacy parent-only lane
75
+ *
76
+ * @param {object} wu - The parsed WU YAML object
77
+ * @param {Set<string>} stampedIds - Set of WU IDs that have .done stamp files
78
+ * @param {string[]} exemptions - List of exempted WU IDs from config
79
+ * @returns {{ exempted: boolean, reason: string|null }} Whether exempted and why
80
+ */
81
+ export declare function shouldExemptFromStampStatusCheck(wu: any, stampedIds: any, exemptions?: any[]): {
82
+ exempted: boolean;
83
+ reason: string;
84
+ };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @fileoverview Stamp-status validation utilities
3
+ *
4
+ * WU-1781: Provides utilities to detect and handle legacy WUs with stamp-status mismatches.
5
+ *
6
+ * The problem: WUs with .done stamps but non-done status (e.g., cancelled, ready) cause
7
+ * tasks:validate to fail, which blocks husky pre-push and prevents wu:done from completing.
8
+ *
9
+ * The solution: Detect these legacy artifacts and exempt them from validation to prevent
10
+ * deadlocks, while still enforcing validation on new/active WUs.
11
+ */
12
+ /**
13
+ * List of parent-only lanes that are deprecated (legacy).
14
+ * WUs with these lanes are considered legacy artifacts and may have stamp-status mismatches.
15
+ * Modern WUs should use sub-lanes like "Operations: Tooling".
16
+ *
17
+ * @type {readonly string[]}
18
+ */
19
+ export const LEGACY_PARENT_ONLY_LANES = Object.freeze([
20
+ 'Intelligence',
21
+ 'Core Systems',
22
+ 'Operations',
23
+ 'Experience',
24
+ 'Discovery',
25
+ ]);
26
+ /**
27
+ * Check if a lane is a deprecated parent-only lane (no sub-lane specified).
28
+ *
29
+ * @param {string|null|undefined} lane - The lane to check
30
+ * @returns {boolean} True if the lane is a parent-only legacy lane
31
+ *
32
+ * @example
33
+ * isLegacyParentOnlyLane('Intelligence') // true
34
+ * isLegacyParentOnlyLane('Operations: Tooling') // false
35
+ */
36
+ export function isLegacyParentOnlyLane(lane) {
37
+ if (!lane || typeof lane !== 'string') {
38
+ return false;
39
+ }
40
+ const normalizedLane = lane.trim();
41
+ // Check if it's an exact match to a parent-only lane (case-insensitive)
42
+ const lowerLane = normalizedLane.toLowerCase();
43
+ return LEGACY_PARENT_ONLY_LANES.some((parentLane) => parentLane.toLowerCase() === lowerLane);
44
+ }
45
+ /**
46
+ * Check if a WU is a legacy artifact with stamp-status mismatch.
47
+ *
48
+ * A legacy stamped WU is one that:
49
+ * - Has a .done stamp file
50
+ * - Has a status that is NOT 'done'
51
+ *
52
+ * These WUs are historical artifacts from before validation was strict,
53
+ * and should be exempted from the stamp-status consistency check.
54
+ *
55
+ * @param {object} wu - The parsed WU YAML object
56
+ * @param {string} wu.id - WU ID (e.g., 'WU-100')
57
+ * @param {string} wu.status - WU status (e.g., 'done', 'cancelled', 'ready')
58
+ * @param {Set<string>} stampedIds - Set of WU IDs that have .done stamp files
59
+ * @returns {boolean} True if this is a legacy stamped WU with mismatch
60
+ *
61
+ * @example
62
+ * const stampedIds = new Set(['WU-100', 'WU-101']);
63
+ * isLegacyStampedWU({ id: 'WU-100', status: 'cancelled' }, stampedIds) // true
64
+ * isLegacyStampedWU({ id: 'WU-100', status: 'done' }, stampedIds) // false (no mismatch)
65
+ * isLegacyStampedWU({ id: 'WU-999', status: 'cancelled' }, stampedIds) // false (no stamp)
66
+ */
67
+ export function isLegacyStampedWU(wu, stampedIds) {
68
+ if (!wu || !wu.id || !stampedIds) {
69
+ return false;
70
+ }
71
+ // Must have a stamp
72
+ const hasStamp = stampedIds.has(wu.id);
73
+ if (!hasStamp) {
74
+ return false;
75
+ }
76
+ // Must have a status that is NOT done
77
+ const status = wu.status;
78
+ if (!status || typeof status !== 'string') {
79
+ return false;
80
+ }
81
+ // If status is 'done', this is a normal WU (no mismatch)
82
+ if (status.toLowerCase() === 'done') {
83
+ return false;
84
+ }
85
+ // Has stamp + status != done = legacy mismatch
86
+ return true;
87
+ }
88
+ /**
89
+ * Check if a WU ID is exempted from stamp-status validation via config.
90
+ *
91
+ * The .lumenflow.config.yaml can specify exemptions for known historical
92
+ * artifacts that cannot be fixed (e.g., WU-307, WU-311, WU-1152).
93
+ *
94
+ * @param {string} id - WU ID to check
95
+ * @param {string[]|null|undefined} exemptions - List of exempted WU IDs from config
96
+ * @returns {boolean} True if the WU is exempted
97
+ *
98
+ * @example
99
+ * isExemptedFromStampStatusCheck('WU-307', ['WU-307', 'WU-311']) // true
100
+ * isExemptedFromStampStatusCheck('WU-999', ['WU-307', 'WU-311']) // false
101
+ */
102
+ export function isExemptedFromStampStatusCheck(id, exemptions) {
103
+ if (!id || !exemptions || !Array.isArray(exemptions)) {
104
+ return false;
105
+ }
106
+ return exemptions.includes(id);
107
+ }
108
+ /**
109
+ * Determine if a WU should be exempted from stamp-status validation.
110
+ *
111
+ * A WU is exempted if:
112
+ * 1. It's explicitly listed in the config exemptions, OR
113
+ * 2. It's a legacy stamped WU (has stamp but status != done) AND has a legacy parent-only lane
114
+ *
115
+ * @param {object} wu - The parsed WU YAML object
116
+ * @param {Set<string>} stampedIds - Set of WU IDs that have .done stamp files
117
+ * @param {string[]} exemptions - List of exempted WU IDs from config
118
+ * @returns {{ exempted: boolean, reason: string|null }} Whether exempted and why
119
+ */
120
+ export function shouldExemptFromStampStatusCheck(wu, stampedIds, exemptions = []) {
121
+ const id = wu?.id;
122
+ // Check config exemption first
123
+ if (isExemptedFromStampStatusCheck(id, exemptions)) {
124
+ return { exempted: true, reason: 'config exemption' };
125
+ }
126
+ // Check if it's a legacy stamped WU with a legacy lane
127
+ if (isLegacyStampedWU(wu, stampedIds)) {
128
+ const lane = wu.lane;
129
+ if (isLegacyParentOnlyLane(lane)) {
130
+ return { exempted: true, reason: 'legacy parent-only lane with stamp' };
131
+ }
132
+ }
133
+ return { exempted: false, reason: null };
134
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Stamp File Utilities
3
+ *
4
+ * Centralized stamp file operations (create, validate)
5
+ * Eliminates magic string for stamp body template
6
+ *
7
+ * WU-2242: Added format validation for corrupted stamp detection
8
+ *
9
+ * Stamp files (.beacon/stamps/WU-{id}.done) serve as completion markers
10
+ * Used by wu:done, wu:recovery, and validation tools
11
+ */
12
+ /**
13
+ * Stamp format error types (WU-2242)
14
+ * @readonly
15
+ * @enum {string}
16
+ */
17
+ export declare const STAMP_FORMAT_ERRORS: Readonly<{
18
+ /** Stamp file is empty or contains only whitespace */
19
+ EMPTY_FILE: "EMPTY_FILE";
20
+ /** Missing WU identifier line (format: WU WU-123 (em dash) Title) */
21
+ MISSING_WU_LINE: "MISSING_WU_LINE";
22
+ /** Missing Completed: YYYY-MM-DD line */
23
+ MISSING_COMPLETED_LINE: "MISSING_COMPLETED_LINE";
24
+ /** Date is not in valid YYYY-MM-DD format or is invalid */
25
+ INVALID_DATE_FORMAT: "INVALID_DATE_FORMAT";
26
+ /** WU ID in stamp does not match expected ID */
27
+ WU_ID_MISMATCH: "WU_ID_MISMATCH";
28
+ }>;
29
+ /**
30
+ * Create stamp file (idempotent - safe to call multiple times)
31
+ *
32
+ * @param {object} params - Parameters
33
+ * @param {string} params.id - WU ID (e.g., 'WU-123')
34
+ * @param {string} params.title - WU title
35
+ * @returns {object} Result { created: boolean, path: string, reason?: string }
36
+ */
37
+ export declare function createStamp({ id, title }: {
38
+ id: any;
39
+ title: any;
40
+ }): {
41
+ created: boolean;
42
+ path: string;
43
+ reason: string;
44
+ } | {
45
+ created: boolean;
46
+ path: string;
47
+ reason?: undefined;
48
+ };
49
+ /**
50
+ * Validate stamp exists
51
+ *
52
+ * @param {string} stampPath - Path to stamp file
53
+ * @returns {boolean} True if stamp exists
54
+ */
55
+ export declare function validateStamp(stampPath: any): boolean;
56
+ /**
57
+ * Get stamp path using WU_PATHS (consistent with codebase)
58
+ *
59
+ * @param {string} id - WU ID
60
+ * @returns {string} Absolute path to stamp file
61
+ */
62
+ export declare function getStampPath(id: any): string;
63
+ /**
64
+ * Validate stamp file format (WU-2242)
65
+ *
66
+ * Expected format:
67
+ * ```
68
+ * WU WU-123 (em dash) Title here
69
+ * Completed: 2025-12-31
70
+ * ```
71
+ *
72
+ * @param {string} wuId - WU ID (e.g., 'WU-123')
73
+ * @param {string} [projectRoot=process.cwd()] - Project root directory
74
+ * @returns {Promise<{valid: boolean, errors: string[], missing?: boolean}>}
75
+ */
76
+ export declare function validateStampFormat(wuId: any, projectRoot?: string): Promise<{
77
+ valid: boolean;
78
+ errors: any[];
79
+ missing: boolean;
80
+ } | {
81
+ valid: boolean;
82
+ errors: any[];
83
+ missing?: undefined;
84
+ }>;
85
+ /**
86
+ * Parsed stamp metadata
87
+ */
88
+ interface StampMetadata {
89
+ wuId?: string;
90
+ title?: string;
91
+ completedDate?: string;
92
+ }
93
+ /**
94
+ * Parse stamp content to extract metadata
95
+ *
96
+ * @param {string} content - Stamp file content
97
+ * @returns {StampMetadata}
98
+ */
99
+ export declare function parseStampContent(content: string): StampMetadata;
100
+ export {};