@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,245 @@
1
+ /**
2
+ * Code Paths Overlap Detection
3
+ *
4
+ * Two-step algorithm for detecting code path conflicts between Work Units:
5
+ * 1. Static glob containment check (fast pre-filter using pattern matching)
6
+ * 2. Concrete file intersection (authoritative using filesystem)
7
+ *
8
+ * @module code-paths-overlap
9
+ */
10
+ import { readFileSync, existsSync } from 'fs';
11
+ import path from 'path';
12
+ import yaml from 'js-yaml';
13
+ import fg from 'fast-glob';
14
+ import micromatch from 'micromatch';
15
+ import { STATUS_SECTIONS, BACKLOG_SECTIONS, STRING_LITERALS } from './wu-constants.js';
16
+ /**
17
+ * Check for code path overlap between two sets of glob patterns
18
+ *
19
+ * Uses two-step algorithm:
20
+ * - Static check: Fast pattern analysis to detect obvious containment
21
+ * - Concrete check: Filesystem-based expansion to find actual file intersection
22
+ *
23
+ * @param {string[]} claimingPaths - Glob patterns from WU being claimed
24
+ * @param {string[]} existingPaths - Glob patterns from in-progress WU
25
+ * @returns {{
26
+ * overlaps: boolean,
27
+ * type: 'none'|'concrete'|'ambiguous',
28
+ * files: string[]
29
+ * }} Overlap detection result
30
+ *
31
+ * @example
32
+ * checkOverlap(['apps/web/**'], ['apps/web/prompts/**'])
33
+ * // => { overlaps: true, type: 'concrete', files: [...] }
34
+ */
35
+ export function checkOverlap(claimingPaths, existingPaths) {
36
+ // Handle empty inputs
37
+ if (!claimingPaths || claimingPaths.length === 0) {
38
+ return { overlaps: false, type: 'none', files: [] };
39
+ }
40
+ if (!existingPaths || existingPaths.length === 0) {
41
+ return { overlaps: false, type: 'none', files: [] };
42
+ }
43
+ // Step 1: Static check (fast pre-filter)
44
+ // Check if any pattern pair has static containment
45
+ let hasStaticOverlap = false;
46
+ for (const claiming of claimingPaths) {
47
+ for (const existing of existingPaths) {
48
+ // Bidirectional check: A contains B OR B contains A
49
+ if (staticGlobContainment(claiming, existing) || staticGlobContainment(existing, claiming)) {
50
+ hasStaticOverlap = true;
51
+ break;
52
+ }
53
+ }
54
+ if (hasStaticOverlap)
55
+ break;
56
+ }
57
+ // Step 2: Concrete check (authoritative)
58
+ // Expand globs and find actual file intersection
59
+ const allFiles = new Set();
60
+ for (const claiming of claimingPaths) {
61
+ for (const existing of existingPaths) {
62
+ const result = concreteFileIntersection(claiming, existing);
63
+ if (result.overlaps) {
64
+ result.files.forEach((f) => allFiles.add(f));
65
+ }
66
+ }
67
+ }
68
+ const hasConcreteOverlap = allFiles.size > 0;
69
+ // Decision logic:
70
+ // - Both static and concrete → BLOCK (concrete overlap)
71
+ // - Static only, no concrete → WARN (ambiguous)
72
+ // - Neither → ALLOW (none)
73
+ if (hasConcreteOverlap) {
74
+ return {
75
+ overlaps: true,
76
+ type: 'concrete',
77
+ files: [...allFiles].sort(),
78
+ };
79
+ }
80
+ else if (hasStaticOverlap && !hasConcreteOverlap) {
81
+ return {
82
+ overlaps: false,
83
+ type: 'ambiguous',
84
+ files: [],
85
+ };
86
+ }
87
+ else {
88
+ return {
89
+ overlaps: false,
90
+ type: 'none',
91
+ files: [],
92
+ };
93
+ }
94
+ }
95
+ /**
96
+ * Find all in-progress WUs with overlapping code paths
97
+ *
98
+ * Reads status.md to find in-progress WUs, loads their code_paths,
99
+ * and checks for overlaps with the claiming WU's paths.
100
+ *
101
+ * @param {string} statusPath - Path to status.md file
102
+ * @param {string[]} claimingPaths - Glob patterns from WU being claimed
103
+ * @param {string} claimingWU - WU ID being claimed (excluded from check)
104
+ * @returns {{
105
+ * conflicts: Array<{wuid: string, overlaps: string[]}>,
106
+ * hasBlocker: boolean
107
+ * }} List of conflicting WUs and whether to block claim
108
+ *
109
+ * @example
110
+ * detectConflicts('docs/04-operations/tasks/status.md', ['apps/**'], 'WU-901')
111
+ * // => { conflicts: [{wuid: 'WU-900', overlaps: ['apps/web/foo.ts']}], hasBlocker: true }
112
+ */
113
+ export function detectConflicts(statusPath, claimingPaths, claimingWU) {
114
+ // Handle empty claiming paths
115
+ if (!claimingPaths || claimingPaths.length === 0) {
116
+ return { conflicts: [], hasBlocker: false };
117
+ }
118
+ // Read status.md
119
+ const content = readFileSync(statusPath, { encoding: 'utf-8' });
120
+ const lines = content.split(STRING_LITERALS.NEWLINE);
121
+ // Find "## In Progress" section (handles both status.md and backlog.md formats)
122
+ const inProgressIdx = lines.findIndex((l) => {
123
+ const normalized = l.trim().toLowerCase();
124
+ return (normalized === STATUS_SECTIONS.IN_PROGRESS.toLowerCase() ||
125
+ normalized === BACKLOG_SECTIONS.IN_PROGRESS.toLowerCase() ||
126
+ normalized.startsWith('## in progress'));
127
+ });
128
+ if (inProgressIdx === -1) {
129
+ return { conflicts: [], hasBlocker: false };
130
+ }
131
+ // Find end of In Progress section (next ## heading or end of file)
132
+ let endIdx = lines.slice(inProgressIdx + 1).findIndex((l) => l.startsWith('## '));
133
+ if (endIdx === -1)
134
+ endIdx = lines.length - inProgressIdx - 1;
135
+ else
136
+ endIdx = inProgressIdx + 1 + endIdx;
137
+ // Extract section content
138
+ const section = lines.slice(inProgressIdx + 1, endIdx).join(STRING_LITERALS.NEWLINE);
139
+ // Check for "No items" marker
140
+ if (section.includes('No items currently in progress')) {
141
+ return { conflicts: [], hasBlocker: false };
142
+ }
143
+ // Extract WU IDs from links like [WU-334 — Title](wu/WU-334.yaml)
144
+ const wuLinkPattern = /\[([A-Z]+-\d+)\s*—\s*[^\]]+\]\([^)]+\)/gi;
145
+ const matches = [...section.matchAll(wuLinkPattern)];
146
+ if (matches.length === 0) {
147
+ return { conflicts: [], hasBlocker: false };
148
+ }
149
+ // Compute project root from statusPath
150
+ // statusPath is at docs/04-operations/tasks/status.md
151
+ const tasksDir = path.dirname(statusPath); // docs/04-operations/tasks
152
+ const operationsDir = path.dirname(tasksDir); // docs/04-operations
153
+ const docsDir = path.dirname(operationsDir); // docs
154
+ const projectRoot = path.dirname(docsDir); // project root
155
+ // Check each in-progress WU for overlaps
156
+ const conflicts = [];
157
+ for (const match of matches) {
158
+ const activeWuid = match[1]; // e.g., "WU-334"
159
+ // Skip claiming WU (shouldn't conflict with itself)
160
+ if (activeWuid === claimingWU) {
161
+ continue;
162
+ }
163
+ // Read WU YAML
164
+ const wuPath = path.join(projectRoot, 'docs', '04-operations', 'tasks', 'wu', `${activeWuid}.yaml`);
165
+ if (!existsSync(wuPath)) {
166
+ continue; // Skip if YAML doesn't exist
167
+ }
168
+ const wuContent = readFileSync(wuPath, { encoding: 'utf-8' });
169
+ const wuDoc = yaml.load(wuContent);
170
+ // Extract code_paths (skip if not defined)
171
+ const existingPaths = wuDoc?.code_paths;
172
+ if (!existingPaths || existingPaths.length === 0) {
173
+ continue;
174
+ }
175
+ // Check for overlap
176
+ const overlapResult = checkOverlap(claimingPaths, existingPaths);
177
+ // Only record concrete overlaps (block on real conflicts)
178
+ if (overlapResult.overlaps && overlapResult.type === 'concrete') {
179
+ conflicts.push({
180
+ wuid: activeWuid,
181
+ overlaps: overlapResult.files,
182
+ });
183
+ }
184
+ }
185
+ return {
186
+ conflicts,
187
+ hasBlocker: conflicts.length > 0,
188
+ };
189
+ }
190
+ /**
191
+ * Static glob containment check (internal helper)
192
+ *
193
+ * Tests if patternA contains patternB using glob semantics.
194
+ * Uses micromatch to convert globs to regex and test containment.
195
+ *
196
+ * @private
197
+ * @param {string} patternA - First glob pattern
198
+ * @param {string} patternB - Second glob pattern
199
+ * @returns {boolean} True if patternA contains patternB
200
+ *
201
+ * @example
202
+ * staticGlobContainment('apps/**', 'apps/web/**') // => true
203
+ * staticGlobContainment('apps/web/**', 'packages/**') // => false
204
+ */
205
+ function staticGlobContainment(patternA, patternB) {
206
+ // Convert patternB to a test path by replacing wildcards
207
+ // Example: 'apps/web/**' → 'apps/web/test/file.ts'
208
+ const testPath = patternB.replace(/\*\*/g, 'test/nested').replace(/\*/g, 'testfile');
209
+ // Use micromatch to test if patternA would match this test path
210
+ // If patternA matches the test path, it contains patternB
211
+ return micromatch.isMatch(testPath, patternA);
212
+ }
213
+ /**
214
+ * Concrete file intersection using filesystem (internal helper)
215
+ *
216
+ * Expands glob patterns to real files using fast-glob,
217
+ * then computes Set intersection to find overlapping files.
218
+ *
219
+ * @private
220
+ * @param {string} patternA - First glob pattern
221
+ * @param {string} patternB - Second glob pattern
222
+ * @returns {{overlaps: boolean, files: string[]}} Intersection result
223
+ *
224
+ * @example
225
+ * concreteFileIntersection('apps/web/**', 'apps/web/prompts/**')
226
+ * // => { overlaps: true, files: ['apps/web/prompts/base.yaml'] }
227
+ */
228
+ function concreteFileIntersection(patternA, patternB) {
229
+ // Expand globs to real files using fast-glob
230
+ // Use sync for simplicity (wu:claim is not performance-critical)
231
+ const filesA = new Set(fg.sync(patternA, {
232
+ dot: true, // Include dotfiles
233
+ ignore: ['node_modules/**', '.git/**'], // Exclude common bloat
234
+ }));
235
+ const filesB = new Set(fg.sync(patternB, {
236
+ dot: true,
237
+ ignore: ['node_modules/**', '.git/**'],
238
+ }));
239
+ // Compute intersection: files in both sets
240
+ const intersection = [...filesA].filter((file) => filesB.has(file));
241
+ return {
242
+ overlaps: intersection.length > 0,
243
+ files: intersection,
244
+ };
245
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Git Commands Logger
3
+ * Part of WU-630: Detective layer (Layer 3 of 4-layer defense)
4
+ * Part of WU-1552: Complete logging integration with user/outcome tracking
5
+ *
6
+ * Logs all git commands to .beacon/commands.log for post-execution analysis.
7
+ * Provides defense-in-depth even if git shim is bypassed.
8
+ *
9
+ * Log format (v2): timestamp | command | branch | worktree | user | outcome
10
+ * Example: 2025-10-24T10:30:00.000Z | git status | lane/operations/wu-630 | worktrees/operations-wu-630 | agent | allowed
11
+ *
12
+ * Legacy format (v1): timestamp | command | branch | worktree
13
+ * - Backward compatible: old entries are parsed with user='unknown' and outcome='unknown'
14
+ */
15
+ /**
16
+ * Command logging constants (WU-1552)
17
+ *
18
+ * User types and outcome values for audit trail logging.
19
+ */
20
+ export declare const COMMAND_LOG: {
21
+ /** User types: who initiated the command */
22
+ USER: {
23
+ AGENT: string;
24
+ HUMAN: string;
25
+ UNKNOWN: string;
26
+ };
27
+ /** Outcome values: what happened to the command */
28
+ OUTCOME: {
29
+ ALLOWED: string;
30
+ BLOCKED: string;
31
+ UNKNOWN: string;
32
+ };
33
+ };
34
+ /**
35
+ * Options for logging git commands (WU-1552)
36
+ */
37
+ export interface LogGitCommandOptions {
38
+ /** User type: 'agent' | 'human' | 'unknown' */
39
+ user?: string;
40
+ /** Command outcome: 'allowed' | 'blocked' */
41
+ outcome?: string;
42
+ }
43
+ /**
44
+ * Log a git command to the commands log
45
+ * @param {string[]} args - Git command arguments (e.g., ['status'], ['add', '.'])
46
+ * @param {string} logPath - Path to log file (defaults to .beacon/commands.log)
47
+ * @param {LogGitCommandOptions} options - Additional logging options (WU-1552)
48
+ */
49
+ export declare function logGitCommand(args: any, logPath?: string, options?: LogGitCommandOptions): void;
50
+ /**
51
+ * Parse a log entry line into structured data
52
+ * Supports both v1 (4 fields) and v2 (6 fields) formats.
53
+ *
54
+ * @param {string} line - Log line to parse
55
+ * @returns {{timestamp: string, command: string, branch: string, worktree: string, user: string, outcome: string} | null}
56
+ */
57
+ export declare function parseLogEntry(line: any): {
58
+ timestamp: any;
59
+ command: any;
60
+ branch: any;
61
+ worktree: any;
62
+ user: any;
63
+ outcome: any;
64
+ };
65
+ /**
66
+ * Scan log for violations within a session window
67
+ * @param {string} logPath - Path to commands log
68
+ * @param {number} windowMinutes - Session window in minutes (default 60)
69
+ * @returns {Array<{timestamp: string, command: string, branch: string, worktree: string}>}
70
+ */
71
+ export declare function scanLogForViolations(logPath?: string, windowMinutes?: number): any[];
72
+ /**
73
+ * Rotate log by removing entries older than retention period
74
+ * @param {string} logPath - Path to commands log
75
+ * @param {number} retentionDays - Number of days to keep (default 7)
76
+ */
77
+ export declare function rotateLog(logPath?: string, retentionDays?: number): void;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Git Commands Logger
3
+ * Part of WU-630: Detective layer (Layer 3 of 4-layer defense)
4
+ * Part of WU-1552: Complete logging integration with user/outcome tracking
5
+ *
6
+ * Logs all git commands to .beacon/commands.log for post-execution analysis.
7
+ * Provides defense-in-depth even if git shim is bypassed.
8
+ *
9
+ * Log format (v2): timestamp | command | branch | worktree | user | outcome
10
+ * Example: 2025-10-24T10:30:00.000Z | git status | lane/operations/wu-630 | worktrees/operations-wu-630 | agent | allowed
11
+ *
12
+ * Legacy format (v1): timestamp | command | branch | worktree
13
+ * - Backward compatible: old entries are parsed with user='unknown' and outcome='unknown'
14
+ */
15
+ /* eslint-disable security/detect-non-literal-fs-filename */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { getCurrentBranch, isMainWorktree } from './wu-helpers.js';
20
+ import { BEACON_PATHS, GIT_FLAGS, STRING_LITERALS } from './wu-constants.js';
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ // Default log path (can be overridden for testing)
24
+ const DEFAULT_LOG_PATH = path.resolve(__dirname, '../..', BEACON_PATHS.COMMANDS_LOG);
25
+ // Banned patterns (same as git shim)
26
+ const BANNED_PATTERNS = [
27
+ { command: 'reset', flags: [GIT_FLAGS.HARD] },
28
+ { command: 'stash' },
29
+ { command: 'clean', flags: [GIT_FLAGS.FD_SHORT, GIT_FLAGS.DF_SHORT] },
30
+ { command: 'checkout', flags: [GIT_FLAGS.FORCE_SHORT, GIT_FLAGS.FORCE] },
31
+ { command: 'push', flags: [GIT_FLAGS.FORCE, GIT_FLAGS.FORCE_SHORT] },
32
+ ];
33
+ const BANNED_FLAGS = [GIT_FLAGS.NO_VERIFY, GIT_FLAGS.NO_GPG_SIGN];
34
+ /**
35
+ * Command logging constants (WU-1552)
36
+ *
37
+ * User types and outcome values for audit trail logging.
38
+ */
39
+ export const COMMAND_LOG = {
40
+ /** User types: who initiated the command */
41
+ USER: {
42
+ AGENT: 'agent',
43
+ HUMAN: 'human',
44
+ UNKNOWN: 'unknown',
45
+ },
46
+ /** Outcome values: what happened to the command */
47
+ OUTCOME: {
48
+ ALLOWED: 'allowed',
49
+ BLOCKED: 'blocked',
50
+ UNKNOWN: 'unknown',
51
+ },
52
+ };
53
+ /**
54
+ * Log a git command to the commands log
55
+ * @param {string[]} args - Git command arguments (e.g., ['status'], ['add', '.'])
56
+ * @param {string} logPath - Path to log file (defaults to .beacon/commands.log)
57
+ * @param {LogGitCommandOptions} options - Additional logging options (WU-1552)
58
+ */
59
+ export function logGitCommand(args, logPath = DEFAULT_LOG_PATH, options = {}) {
60
+ try {
61
+ const timestamp = new Date().toISOString();
62
+ const command = `git ${args.join(' ')}`;
63
+ // Get current context
64
+ let branch, worktree;
65
+ if (process.env.TEST_MODE === 'true') {
66
+ branch = process.env.TEST_BRANCH || COMMAND_LOG.USER.UNKNOWN;
67
+ worktree = process.env.TEST_WORKTREE || COMMAND_LOG.USER.UNKNOWN;
68
+ }
69
+ else {
70
+ branch = getCurrentBranch();
71
+ worktree = isMainWorktree() ? '.' : process.cwd();
72
+ }
73
+ // WU-1552: Extract user and outcome from options with defaults
74
+ const user = options.user || COMMAND_LOG.USER.UNKNOWN;
75
+ const outcome = options.outcome || COMMAND_LOG.OUTCOME.ALLOWED;
76
+ // New format (v2): timestamp | command | branch | worktree | user | outcome
77
+ const logEntry = `${timestamp} | ${command} | ${branch} | ${worktree} | ${user} | ${outcome}\n`;
78
+ // Ensure .beacon directory exists
79
+ const logDir = path.dirname(logPath);
80
+ if (!fs.existsSync(logDir)) {
81
+ fs.mkdirSync(logDir, { recursive: true });
82
+ }
83
+ // Append to log file
84
+ fs.appendFileSync(logPath, logEntry, { encoding: 'utf-8' });
85
+ }
86
+ catch (error) {
87
+ // Don't fail git commands if logging fails
88
+ console.error(`[commands-logger] Warning: Failed to log command: ${error.message}`);
89
+ }
90
+ }
91
+ /**
92
+ * Parse a log entry line into structured data
93
+ * Supports both v1 (4 fields) and v2 (6 fields) formats.
94
+ *
95
+ * @param {string} line - Log line to parse
96
+ * @returns {{timestamp: string, command: string, branch: string, worktree: string, user: string, outcome: string} | null}
97
+ */
98
+ export function parseLogEntry(line) {
99
+ if (!line || !line.trim()) {
100
+ return null;
101
+ }
102
+ // Split on " | " (with spaces) to avoid splitting commands that contain pipes
103
+ const parts = line.split(' | ');
104
+ // Minimum 4 fields required (v1 format)
105
+ if (parts.length < 4) {
106
+ return null;
107
+ }
108
+ // v2 format: timestamp | command | branch | worktree | user | outcome (6 fields)
109
+ // v1 format: timestamp | command | branch | worktree (4 fields)
110
+ // Commands may contain pipes, so we need to handle variable field counts
111
+ const hasUserOutcome = parts.length >= 6;
112
+ if (hasUserOutcome) {
113
+ // v2 format: last 2 fields are user and outcome
114
+ const outcome = parts[parts.length - 1].trim();
115
+ const user = parts[parts.length - 2].trim();
116
+ const worktree = parts[parts.length - 3].trim();
117
+ const branch = parts[parts.length - 4].trim();
118
+ // Everything between timestamp and branch is the command (may contain pipes)
119
+ const command = parts
120
+ .slice(1, parts.length - 4)
121
+ .join(' | ')
122
+ .trim();
123
+ return {
124
+ timestamp: parts[0].trim(),
125
+ command,
126
+ branch,
127
+ worktree,
128
+ user,
129
+ outcome,
130
+ };
131
+ }
132
+ else {
133
+ // v1 format (backward compatibility): no user/outcome fields
134
+ const worktree = parts[parts.length - 1].trim();
135
+ const branch = parts[parts.length - 2].trim();
136
+ const command = parts
137
+ .slice(1, parts.length - 2)
138
+ .join(' | ')
139
+ .trim();
140
+ return {
141
+ timestamp: parts[0].trim(),
142
+ command,
143
+ branch,
144
+ worktree,
145
+ user: COMMAND_LOG.USER.UNKNOWN,
146
+ outcome: COMMAND_LOG.OUTCOME.UNKNOWN,
147
+ };
148
+ }
149
+ }
150
+ /**
151
+ * Check if a command+context is a violation
152
+ * @param {string} command - The git command
153
+ * @param {string} branch - The branch it was run on
154
+ * @param {string} worktree - The worktree it was run in
155
+ * @returns {boolean}
156
+ */
157
+ function isViolation(command, branch, worktree) {
158
+ // Protected context: main branch OR main worktree (.)
159
+ const isProtected = branch === 'main' || worktree === '.';
160
+ if (!isProtected) {
161
+ return false; // Allowed on lane branches in worktrees
162
+ }
163
+ // Parse command into args
164
+ const args = command.replace(/^git\s+/, '').split(/\s+/);
165
+ const commandName = args[0]?.toLowerCase();
166
+ const flags = args.slice(1).map((a) => a.toLowerCase());
167
+ // Check banned flags
168
+ for (const bannedFlag of BANNED_FLAGS) {
169
+ if (flags.includes(bannedFlag)) {
170
+ return true;
171
+ }
172
+ }
173
+ // Check banned command patterns
174
+ for (const pattern of BANNED_PATTERNS) {
175
+ if (commandName !== pattern.command)
176
+ continue;
177
+ // If no specific flags required, ban the command entirely
178
+ if (!pattern.flags) {
179
+ return true;
180
+ }
181
+ // Check if any required flag is present
182
+ const hasRequiredFlag = pattern.flags.some((reqFlag) => flags.includes(reqFlag));
183
+ if (hasRequiredFlag) {
184
+ return true;
185
+ }
186
+ }
187
+ return false;
188
+ }
189
+ /**
190
+ * Scan log for violations within a session window
191
+ * @param {string} logPath - Path to commands log
192
+ * @param {number} windowMinutes - Session window in minutes (default 60)
193
+ * @returns {Array<{timestamp: string, command: string, branch: string, worktree: string}>}
194
+ */
195
+ export function scanLogForViolations(logPath = DEFAULT_LOG_PATH, windowMinutes = 60) {
196
+ if (!fs.existsSync(logPath)) {
197
+ return [];
198
+ }
199
+ try {
200
+ const content = fs.readFileSync(logPath, { encoding: 'utf-8' });
201
+ const lines = content.trim().split(STRING_LITERALS.NEWLINE).filter(Boolean);
202
+ const now = new Date();
203
+ const windowStart = new Date(now.getTime() - windowMinutes * 60 * 1000);
204
+ const violations = [];
205
+ for (const line of lines) {
206
+ const entry = parseLogEntry(line);
207
+ if (!entry)
208
+ continue;
209
+ // Filter by session window
210
+ const entryTime = new Date(entry.timestamp);
211
+ if (entryTime < windowStart) {
212
+ continue;
213
+ }
214
+ // Check if this is a violation
215
+ if (isViolation(entry.command, entry.branch, entry.worktree)) {
216
+ violations.push(entry);
217
+ }
218
+ }
219
+ return violations;
220
+ }
221
+ catch (error) {
222
+ console.error(`[commands-logger] Warning: Failed to scan log: ${error.message}`);
223
+ return [];
224
+ }
225
+ }
226
+ /**
227
+ * Rotate log by removing entries older than retention period
228
+ * @param {string} logPath - Path to commands log
229
+ * @param {number} retentionDays - Number of days to keep (default 7)
230
+ */
231
+ export function rotateLog(logPath = DEFAULT_LOG_PATH, retentionDays = 7) {
232
+ if (!fs.existsSync(logPath)) {
233
+ return;
234
+ }
235
+ try {
236
+ const content = fs.readFileSync(logPath, { encoding: 'utf-8' });
237
+ const lines = content.trim().split(STRING_LITERALS.NEWLINE).filter(Boolean);
238
+ const now = new Date();
239
+ const cutoffDate = new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1000);
240
+ const recentLines = lines.filter((line) => {
241
+ const entry = parseLogEntry(line);
242
+ if (!entry)
243
+ return false;
244
+ const entryTime = new Date(entry.timestamp);
245
+ return entryTime >= cutoffDate;
246
+ });
247
+ // Write back only recent entries
248
+ fs.writeFileSync(logPath, recentLines.join(STRING_LITERALS.NEWLINE) +
249
+ (recentLines.length > 0 ? STRING_LITERALS.NEWLINE : ''), { encoding: 'utf-8' });
250
+ }
251
+ catch (error) {
252
+ console.error(`[commands-logger] Warning: Failed to rotate log: ${error.message}`);
253
+ }
254
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * WU-2278: Commit Message Utilities
3
+ *
4
+ * Utilities for processing commit messages according to commitlint rules.
5
+ * Specifically handles lowercasing of commit subjects.
6
+ *
7
+ * Note: This is internal LumenFlow tooling - no external library applies.
8
+ *
9
+ * @module commit-message-utils
10
+ */
11
+ /**
12
+ * Lowercase the entire subject of a conventional commit message
13
+ *
14
+ * commitlint requires lowercase subjects, but the prepare-commit-msg hook
15
+ * was only lowercasing the first character. This function lowercases
16
+ * the entire subject portion.
17
+ *
18
+ * Examples:
19
+ * "feat(wu-100): Add Supabase integration" -> "feat(wu-100): add supabase integration"
20
+ * "fix: Fix OpenAI API call" -> "fix: fix openai api call"
21
+ *
22
+ * @param {string} message - Commit message (first line only)
23
+ * @returns {string} Message with lowercased subject
24
+ */
25
+ export declare function lowercaseCommitSubject(message: any): any;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * WU-2278: Commit Message Utilities
3
+ *
4
+ * Utilities for processing commit messages according to commitlint rules.
5
+ * Specifically handles lowercasing of commit subjects.
6
+ *
7
+ * Note: This is internal LumenFlow tooling - no external library applies.
8
+ *
9
+ * @module commit-message-utils
10
+ */
11
+ /**
12
+ * Lowercase the entire subject of a conventional commit message
13
+ *
14
+ * commitlint requires lowercase subjects, but the prepare-commit-msg hook
15
+ * was only lowercasing the first character. This function lowercases
16
+ * the entire subject portion.
17
+ *
18
+ * Examples:
19
+ * "feat(wu-100): Add Supabase integration" -> "feat(wu-100): add supabase integration"
20
+ * "fix: Fix OpenAI API call" -> "fix: fix openai api call"
21
+ *
22
+ * @param {string} message - Commit message (first line only)
23
+ * @returns {string} Message with lowercased subject
24
+ */
25
+ export function lowercaseCommitSubject(message) {
26
+ if (!message || typeof message !== 'string') {
27
+ return message;
28
+ }
29
+ // Match conventional commit format: type(scope): subject
30
+ // or type: subject
31
+ // Using indexOf for simple parsing - safer than complex regex
32
+ const colonIndex = message.indexOf(': ');
33
+ if (colonIndex > 0) {
34
+ const prefix = message.slice(0, colonIndex);
35
+ const subject = message.slice(colonIndex + 2);
36
+ const lowercaseSubject = subject.toLowerCase();
37
+ return `${prefix.toLowerCase()}: ${lowercaseSubject}`;
38
+ }
39
+ // No conventional format - lowercase entire message
40
+ return message.toLowerCase();
41
+ }