@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,550 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readWU } from './wu-yaml.js';
4
+ import { WU_PATHS } from './wu-paths.js';
5
+ import { STRING_LITERALS, WU_STATUS } from './wu-constants.js';
6
+ // Optional import from @lumenflow/initiatives - if not available, provide stub
7
+ let detectCycles;
8
+ try {
9
+ // Dynamic import for optional peer dependency
10
+ const module = await import('@lumenflow/initiatives');
11
+ detectCycles = module.detectCycles;
12
+ }
13
+ catch {
14
+ // Fallback stub if @lumenflow/initiatives is not available
15
+ detectCycles = () => ({ hasCycle: false, cycles: [] });
16
+ }
17
+ /**
18
+ * Dependency Graph Module (WU-1247, WU-1568)
19
+ *
20
+ * Provides graph building, visualization, and analysis for WU dependencies.
21
+ * Supports ASCII tree and Mermaid diagram output formats.
22
+ * Includes graph algorithms for operational insights: topological sort,
23
+ * critical path, impact scoring, and bottleneck detection.
24
+ *
25
+ * @example
26
+ * import { buildDependencyGraph, renderASCII, renderMermaid, topologicalSort, criticalPath, bottlenecks } from './lib/dependency-graph.js';
27
+ *
28
+ * const graph = buildDependencyGraph();
29
+ * console.log(renderASCII(graph, 'WU-1247'));
30
+ * console.log(renderMermaid(graph, { direction: 'TD' }));
31
+ *
32
+ * // Graph analysis (WU-1568)
33
+ * const sorted = topologicalSort(graph);
34
+ * const critical = criticalPath(graph);
35
+ * const topBottlenecks = bottlenecks(graph, 10);
36
+ */
37
+ /**
38
+ * Build a dependency graph from all WU YAML files.
39
+ *
40
+ * @returns {Map<string, {id: string, title: string, status: string, blocks: string[], blockedBy: string[]}>}
41
+ */
42
+ export function buildDependencyGraph() {
43
+ const wuDir = path.dirname(WU_PATHS.WU('dummy'));
44
+ const graph = new Map();
45
+ if (!existsSync(wuDir)) {
46
+ return graph;
47
+ }
48
+ const files = readdirSync(wuDir).filter((f) => f.endsWith('.yaml') && f.startsWith('WU-'));
49
+ for (const f of files) {
50
+ const filePath = path.join(wuDir, f);
51
+ const id = f.replace('.yaml', '');
52
+ try {
53
+ const doc = readWU(filePath, id);
54
+ graph.set(id, {
55
+ id,
56
+ title: doc.title || id,
57
+ status: doc.status || 'unknown',
58
+ blocks: Array.isArray(doc.blocks) ? doc.blocks : [],
59
+ blockedBy: Array.isArray(doc.blocked_by) ? doc.blocked_by : [],
60
+ });
61
+ }
62
+ catch {
63
+ // Skip invalid files
64
+ }
65
+ }
66
+ return graph;
67
+ }
68
+ /**
69
+ * Get all dependencies (upstream: blocked_by) for a WU.
70
+ *
71
+ * @param {Map} graph - Dependency graph
72
+ * @param {string} wuId - WU ID to get dependencies for
73
+ * @param {number} [maxDepth=10] - Maximum traversal depth
74
+ * @returns {Array<{id: string, depth: number, path: string[]}>}
75
+ */
76
+ export function getUpstreamDependencies(graph, wuId, maxDepth = 10) {
77
+ const visited = new Set();
78
+ const result = [];
79
+ function traverse(id, depth, pathSoFar) {
80
+ if (depth > maxDepth || visited.has(id))
81
+ return;
82
+ visited.add(id);
83
+ const node = graph.get(id);
84
+ if (!node)
85
+ return;
86
+ for (const dep of node.blockedBy) {
87
+ if (!visited.has(dep)) {
88
+ result.push({ id: dep, depth, path: [...pathSoFar, dep] });
89
+ traverse(dep, depth + 1, [...pathSoFar, dep]);
90
+ }
91
+ }
92
+ }
93
+ traverse(wuId, 1, [wuId]);
94
+ return result;
95
+ }
96
+ /**
97
+ * Get all dependents (downstream: blocks) for a WU.
98
+ *
99
+ * @param {Map} graph - Dependency graph
100
+ * @param {string} wuId - WU ID to get dependents for
101
+ * @param {number} [maxDepth=10] - Maximum traversal depth
102
+ * @returns {Array<{id: string, depth: number, path: string[]}>}
103
+ */
104
+ export function getDownstreamDependents(graph, wuId, maxDepth = 10) {
105
+ const visited = new Set();
106
+ const result = [];
107
+ function traverse(id, depth, pathSoFar) {
108
+ if (depth > maxDepth || visited.has(id))
109
+ return;
110
+ visited.add(id);
111
+ const node = graph.get(id);
112
+ if (!node)
113
+ return;
114
+ for (const dep of node.blocks) {
115
+ if (!visited.has(dep)) {
116
+ result.push({ id: dep, depth, path: [...pathSoFar, dep] });
117
+ traverse(dep, depth + 1, [...pathSoFar, dep]);
118
+ }
119
+ }
120
+ }
121
+ traverse(wuId, 1, [wuId]);
122
+ return result;
123
+ }
124
+ /**
125
+ * Render dependency graph as ASCII tree.
126
+ *
127
+ * @param {Map} graph - Dependency graph
128
+ * @param {string} rootId - Root WU ID
129
+ * @param {RenderASCIIOptions} [options] - Render options
130
+ * @returns {string} ASCII tree representation
131
+ */
132
+ export function renderASCII(graph, rootId, options = {}) {
133
+ const { direction = 'both', depth: maxDepth = 3 } = options;
134
+ const lines = [];
135
+ const root = graph.get(rootId);
136
+ if (!root) {
137
+ return `WU not found: ${rootId}`;
138
+ }
139
+ // Header
140
+ lines.push(`${rootId}: ${truncate(root.title, 50)}`);
141
+ lines.push('');
142
+ // Upstream (blocked_by)
143
+ if (direction === 'up' || direction === 'both') {
144
+ const upstream = getUpstreamDependencies(graph, rootId, maxDepth);
145
+ if (upstream.length > 0) {
146
+ lines.push('Dependencies (blocked by):');
147
+ for (const dep of upstream) {
148
+ const node = graph.get(dep.id);
149
+ const status = node ? `[${node.status}]` : '[unknown]';
150
+ const title = node ? truncate(node.title, 40) : '';
151
+ const indent = ' '.repeat(dep.depth);
152
+ lines.push(`${indent}+-- ${dep.id}: ${title} ${status}`);
153
+ }
154
+ lines.push('');
155
+ }
156
+ }
157
+ // Downstream (blocks)
158
+ if (direction === 'down' || direction === 'both') {
159
+ const downstream = getDownstreamDependents(graph, rootId, maxDepth);
160
+ if (downstream.length > 0) {
161
+ lines.push('Dependents (blocks):');
162
+ for (const dep of downstream) {
163
+ const node = graph.get(dep.id);
164
+ const status = node ? `[${node.status}]` : '[unknown]';
165
+ const title = node ? truncate(node.title, 40) : '';
166
+ const indent = ' '.repeat(dep.depth);
167
+ lines.push(`${indent}+-- ${dep.id}: ${title} ${status}`);
168
+ }
169
+ lines.push('');
170
+ }
171
+ }
172
+ // Check for cycles
173
+ const cycleResult = detectCycles(graph);
174
+ if (cycleResult.hasCycle) {
175
+ lines.push('⚠️ Circular dependencies detected:');
176
+ for (const cycle of cycleResult.cycles) {
177
+ lines.push(` ${cycle.join(' → ')}`);
178
+ }
179
+ lines.push('');
180
+ }
181
+ return lines.join(STRING_LITERALS.NEWLINE);
182
+ }
183
+ /**
184
+ * Render dependency graph as Mermaid diagram.
185
+ *
186
+ * @param {Map} graph - Dependency graph
187
+ * @param {RenderMermaidOptions} [options] - Render options
188
+ * @returns {string} Mermaid diagram syntax
189
+ */
190
+ export function renderMermaid(graph, options = {}) {
191
+ const { rootId, direction = 'TD', depth: maxDepth = 3 } = options;
192
+ const lines = [];
193
+ const nodes = new Set();
194
+ const edges = [];
195
+ // Collect nodes and edges
196
+ if (rootId) {
197
+ // Focus on specific WU
198
+ const upstream = getUpstreamDependencies(graph, rootId, maxDepth);
199
+ const downstream = getDownstreamDependents(graph, rootId, maxDepth);
200
+ nodes.add(rootId);
201
+ for (const dep of upstream) {
202
+ nodes.add(dep.id);
203
+ }
204
+ for (const dep of downstream) {
205
+ nodes.add(dep.id);
206
+ }
207
+ // Build edges for focused view
208
+ for (const nodeId of nodes) {
209
+ const node = graph.get(nodeId);
210
+ if (!node)
211
+ continue;
212
+ for (const blockedBy of node.blockedBy) {
213
+ if (nodes.has(blockedBy)) {
214
+ edges.push({ from: blockedBy, to: nodeId });
215
+ }
216
+ }
217
+ }
218
+ }
219
+ else {
220
+ // Full graph
221
+ for (const [nodeId, node] of graph.entries()) {
222
+ nodes.add(nodeId);
223
+ for (const blockedBy of node.blockedBy) {
224
+ if (graph.has(blockedBy)) {
225
+ edges.push({ from: blockedBy, to: nodeId });
226
+ }
227
+ }
228
+ }
229
+ }
230
+ // Generate Mermaid
231
+ lines.push(`flowchart ${direction}`);
232
+ // Node definitions with labels
233
+ for (const nodeId of nodes) {
234
+ const node = graph.get(nodeId);
235
+ if (node) {
236
+ const shortTitle = truncate(node.title, 30);
237
+ lines.push(` ${nodeId}["${nodeId}: ${shortTitle}<br/>${node.status}"]`);
238
+ }
239
+ }
240
+ lines.push('');
241
+ // Edges
242
+ for (const { from, to } of edges) {
243
+ lines.push(` ${from} --> ${to}`);
244
+ }
245
+ lines.push('');
246
+ // Status styling
247
+ lines.push(' classDef done fill:#86efac,stroke:#22c55e');
248
+ lines.push(' classDef in_progress fill:#93c5fd,stroke:#3b82f6');
249
+ lines.push(' classDef ready fill:#fde68a,stroke:#f59e0b');
250
+ lines.push(' classDef blocked fill:#fca5a5,stroke:#ef4444');
251
+ // Apply classes
252
+ const statusGroups = { done: [], in_progress: [], ready: [], blocked: [] };
253
+ for (const nodeId of nodes) {
254
+ const node = graph.get(nodeId);
255
+ if (node && statusGroups[node.status]) {
256
+ statusGroups[node.status].push(nodeId);
257
+ }
258
+ }
259
+ for (const [status, nodeIds] of Object.entries(statusGroups)) {
260
+ if (nodeIds.length > 0) {
261
+ lines.push(` class ${nodeIds.join(',')} ${status}`);
262
+ }
263
+ }
264
+ return lines.join(STRING_LITERALS.NEWLINE);
265
+ }
266
+ /**
267
+ * Validate dependency graph for cycles and orphans.
268
+ *
269
+ * @param {Map} graph - Dependency graph
270
+ * @returns {{hasCycle: boolean, cycles: string[][], orphans: Array<{wuId: string, ref: string}>}}
271
+ */
272
+ export function validateGraph(graph) {
273
+ const allIds = new Set(graph.keys());
274
+ const orphans = [];
275
+ // Check for orphan references
276
+ for (const [wuId, node] of graph.entries()) {
277
+ for (const ref of [...node.blocks, ...node.blockedBy]) {
278
+ if (!allIds.has(ref)) {
279
+ orphans.push({ wuId, ref });
280
+ }
281
+ }
282
+ }
283
+ // Transform graph to snake_case for detectCycles compatibility
284
+ // (dependency-graph uses camelCase internally, initiative-validator uses snake_case)
285
+ const snakeCaseGraph = new Map();
286
+ for (const [id, node] of graph.entries()) {
287
+ snakeCaseGraph.set(id, {
288
+ id: node.id,
289
+ blocked_by: node.blockedBy || [],
290
+ blocks: node.blocks || [],
291
+ });
292
+ }
293
+ // Check for cycles
294
+ const cycleResult = detectCycles(snakeCaseGraph);
295
+ return {
296
+ hasCycle: cycleResult.hasCycle,
297
+ cycles: cycleResult.cycles,
298
+ orphans,
299
+ };
300
+ }
301
+ // =============================================================================
302
+ // Graph Analysis Functions (WU-1568)
303
+ // =============================================================================
304
+ /**
305
+ * Filter graph to active (non-done) WUs only.
306
+ *
307
+ * @param {Map} graph - Full dependency graph
308
+ * @returns {Map} Graph containing only non-done WUs
309
+ */
310
+ function filterActiveGraph(graph) {
311
+ const activeGraph = new Map();
312
+ for (const [id, node] of graph.entries()) {
313
+ if (node.status !== WU_STATUS.DONE) {
314
+ activeGraph.set(id, node);
315
+ }
316
+ }
317
+ return activeGraph;
318
+ }
319
+ /**
320
+ * Check if a WU's dependencies are satisfied (done or not in active graph).
321
+ *
322
+ * @param {Map} activeGraph - Active subgraph
323
+ * @param {Map} fullGraph - Full graph (includes done WUs)
324
+ * @param {object} node - Node to check
325
+ * @returns {boolean} True if all dependencies are satisfied
326
+ */
327
+ function areDependenciesSatisfied(activeGraph, fullGraph, node) {
328
+ for (const depId of node.blockedBy) {
329
+ // Dependency is satisfied if:
330
+ // 1. It doesn't exist in the graph (orphan reference, treat as satisfied)
331
+ // 2. It's done
332
+ // 3. It's not in the active graph (already filtered out)
333
+ const depNode = fullGraph.get(depId);
334
+ if (depNode && depNode.status !== WU_STATUS.DONE && activeGraph.has(depId)) {
335
+ return false;
336
+ }
337
+ }
338
+ return true;
339
+ }
340
+ /**
341
+ * Perform topological sort on non-done WUs using Kahn's algorithm.
342
+ * Returns valid execution ordering where dependencies come before dependents.
343
+ * Handles cycles gracefully by returning partial ordering with warning.
344
+ *
345
+ * @param {Map} graph - Dependency graph
346
+ * @returns {string[]|{order: string[], warning: string, cycleNodes: string[]}} Sorted WU IDs or warning object
347
+ */
348
+ export function topologicalSort(graph) {
349
+ const activeGraph = filterActiveGraph(graph);
350
+ if (activeGraph.size === 0) {
351
+ return [];
352
+ }
353
+ // Build in-degree map (count of unsatisfied dependencies)
354
+ const inDegree = new Map();
355
+ for (const [id, node] of activeGraph.entries()) {
356
+ // Count only dependencies that are in the active graph
357
+ let count = 0;
358
+ for (const depId of node.blockedBy) {
359
+ if (activeGraph.has(depId)) {
360
+ count++;
361
+ }
362
+ }
363
+ inDegree.set(id, count);
364
+ }
365
+ // Start with nodes that have no active dependencies
366
+ const queue = [];
367
+ for (const [id, degree] of inDegree.entries()) {
368
+ if (degree === 0) {
369
+ queue.push(id);
370
+ }
371
+ }
372
+ const sorted = [];
373
+ while (queue.length > 0) {
374
+ const current = queue.shift();
375
+ sorted.push(current);
376
+ const node = activeGraph.get(current);
377
+ if (!node)
378
+ continue;
379
+ // Decrease in-degree of dependents
380
+ for (const depId of node.blocks) {
381
+ if (!activeGraph.has(depId))
382
+ continue;
383
+ const newDegree = inDegree.get(depId) - 1;
384
+ inDegree.set(depId, newDegree);
385
+ if (newDegree === 0) {
386
+ queue.push(depId);
387
+ }
388
+ }
389
+ }
390
+ // Check for cycles (not all nodes processed)
391
+ if (sorted.length < activeGraph.size) {
392
+ const cycleNodes = [];
393
+ for (const [id, degree] of inDegree.entries()) {
394
+ if (degree > 0) {
395
+ cycleNodes.push(id);
396
+ }
397
+ }
398
+ return {
399
+ order: sorted,
400
+ warning: 'Cycle detected: some WUs have circular dependencies',
401
+ cycleNodes,
402
+ };
403
+ }
404
+ return sorted;
405
+ }
406
+ /**
407
+ * Find the critical path (longest dependency chain) in the graph.
408
+ * Uses dynamic programming on the DAG to find the longest path.
409
+ * Excludes done WUs from the path.
410
+ *
411
+ * @param {Map} graph - Dependency graph
412
+ * @returns {{path: string[], length: number, warning?: string}} Critical path info
413
+ */
414
+ export function criticalPath(graph) {
415
+ const activeGraph = filterActiveGraph(graph);
416
+ if (activeGraph.size === 0) {
417
+ return { path: [], length: 0 };
418
+ }
419
+ // First, get topological order
420
+ const topoResult = topologicalSort(graph);
421
+ // Handle cycle case
422
+ if (!Array.isArray(topoResult)) {
423
+ return {
424
+ path: [],
425
+ length: 0,
426
+ warning: topoResult.warning,
427
+ };
428
+ }
429
+ // Distance and predecessor maps for longest path
430
+ const distance = new Map();
431
+ const predecessor = new Map();
432
+ // Initialise distances
433
+ for (const id of topoResult) {
434
+ distance.set(id, 1); // Each node has length 1
435
+ predecessor.set(id, null);
436
+ }
437
+ // Process in topological order
438
+ for (const current of topoResult) {
439
+ const node = activeGraph.get(current);
440
+ if (!node)
441
+ continue;
442
+ // Update distances for dependents
443
+ for (const depId of node.blocks) {
444
+ if (!activeGraph.has(depId))
445
+ continue;
446
+ const newDistance = distance.get(current) + 1;
447
+ if (newDistance > distance.get(depId)) {
448
+ distance.set(depId, newDistance);
449
+ predecessor.set(depId, current);
450
+ }
451
+ }
452
+ }
453
+ // Find the node with maximum distance
454
+ let maxDistance = 0;
455
+ let endNode = null;
456
+ for (const [id, dist] of distance.entries()) {
457
+ if (dist > maxDistance) {
458
+ maxDistance = dist;
459
+ endNode = id;
460
+ }
461
+ }
462
+ // Reconstruct path
463
+ const path = [];
464
+ let current = endNode;
465
+ while (current !== null) {
466
+ path.unshift(current);
467
+ current = predecessor.get(current);
468
+ }
469
+ return {
470
+ path,
471
+ length: path.length,
472
+ };
473
+ }
474
+ /**
475
+ * Calculate the impact score for a WU.
476
+ * Impact score is the count of all downstream dependents (recursive).
477
+ * Excludes done WUs from the count.
478
+ *
479
+ * @param {Map} graph - Dependency graph
480
+ * @param {string} wuId - WU ID to score
481
+ * @returns {number} Count of downstream dependents
482
+ */
483
+ export function impactScore(graph, wuId) {
484
+ const activeGraph = filterActiveGraph(graph);
485
+ if (!activeGraph.has(wuId)) {
486
+ return 0;
487
+ }
488
+ // BFS to count all downstream dependents
489
+ const visited = new Set();
490
+ const queue = [wuId];
491
+ visited.add(wuId);
492
+ while (queue.length > 0) {
493
+ const current = queue.shift();
494
+ const node = activeGraph.get(current);
495
+ if (!node)
496
+ continue;
497
+ for (const depId of node.blocks) {
498
+ if (!visited.has(depId) && activeGraph.has(depId)) {
499
+ visited.add(depId);
500
+ queue.push(depId);
501
+ }
502
+ }
503
+ }
504
+ // Score is count minus 1 (exclude the starting node)
505
+ return visited.size - 1;
506
+ }
507
+ /**
508
+ * Find the top N bottleneck WUs by impact score.
509
+ * A bottleneck is a WU that blocks many other WUs.
510
+ * Excludes done WUs from results.
511
+ *
512
+ * @param {Map} graph - Dependency graph
513
+ * @param {number} limit - Maximum number of bottlenecks to return
514
+ * @returns {Array<{id: string, score: number, title?: string}>} Bottlenecks sorted by score descending
515
+ */
516
+ export function bottlenecks(graph, limit) {
517
+ const activeGraph = filterActiveGraph(graph);
518
+ if (activeGraph.size === 0) {
519
+ return [];
520
+ }
521
+ // Calculate impact score for each active WU
522
+ const scores = [];
523
+ for (const [id, node] of activeGraph.entries()) {
524
+ const score = impactScore(graph, id);
525
+ scores.push({
526
+ id,
527
+ score,
528
+ title: node.title,
529
+ });
530
+ }
531
+ // Sort by score descending
532
+ scores.sort((a, b) => b.score - a.score);
533
+ // Return top N
534
+ return scores.slice(0, limit);
535
+ }
536
+ // =============================================================================
537
+ // Helper Functions
538
+ // =============================================================================
539
+ /**
540
+ * Truncate string with ellipsis.
541
+ *
542
+ * @param {string} str - String to truncate
543
+ * @param {number} maxLen - Maximum length
544
+ * @returns {string} Truncated string
545
+ */
546
+ function truncate(str, maxLen) {
547
+ if (!str)
548
+ return '';
549
+ return str.length > maxLen ? `${str.substring(0, maxLen - 3)}...` : str;
550
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Dependency Guard (WU-1783)
3
+ *
4
+ * Detects dependency-mutating pnpm commands and provides blocking/guidance
5
+ * for worktree discipline enforcement.
6
+ *
7
+ * Used by:
8
+ * - pre-tool-use-hook.sh to block dependency mutations on main
9
+ * - deps:add and deps:remove wrapper commands
10
+ *
11
+ * @see {@link .claude/hooks/pre-tool-use-hook.sh} - PreToolUse hook
12
+ * @see {@link tools/deps-add.mjs} - Safe wrapper for pnpm add
13
+ * @see {@link tools/deps-remove.mjs} - Safe wrapper for pnpm remove
14
+ */
15
+ /**
16
+ * pnpm subcommands that mutate dependencies.
17
+ *
18
+ * These commands modify package.json, pnpm-lock.yaml, and/or node_modules.
19
+ * Running them on main checkout violates worktree isolation.
20
+ *
21
+ * Includes both full names and shorthand aliases:
22
+ * - add: Add packages to dependencies
23
+ * - install/i: Install packages from lockfile
24
+ * - remove/rm/uninstall: Remove packages from dependencies
25
+ * - update/up: Update packages to latest
26
+ */
27
+ export declare const DEPENDENCY_MUTATING_COMMANDS: string[];
28
+ /**
29
+ * Check if a command is a dependency-mutating pnpm command.
30
+ *
31
+ * @param {string|null|undefined} command - Command string to check
32
+ * @returns {boolean} True if the command mutates dependencies
33
+ *
34
+ * @example
35
+ * isDependencyMutatingCommand('pnpm add react'); // true
36
+ * isDependencyMutatingCommand('pnpm run test'); // false
37
+ * isDependencyMutatingCommand('npm install'); // false (not pnpm)
38
+ */
39
+ export declare function isDependencyMutatingCommand(command: any): boolean;
40
+ /**
41
+ * Build a blocking message for dependency-mutating commands on main.
42
+ *
43
+ * @param {string} command - The blocked command
44
+ * @returns {string} Formatted error message with guidance
45
+ *
46
+ * @example
47
+ * const message = buildDependencyBlockMessage('pnpm add react');
48
+ * // Returns multi-line message with guidance
49
+ */
50
+ export declare function buildDependencyBlockMessage(command: any): string;
51
+ /**
52
+ * Log prefix for dependency guard output
53
+ */
54
+ export declare const DEPS_LOG_PREFIX = "[deps-guard]";