@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,537 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Unified Code Path Validator (WU-1825)
4
+ *
5
+ * Consolidates three separate code path validators into one module:
6
+ * - validateCodePathsExist (wu-done-validators.mjs) - file existence for wu:done
7
+ * - validateLaneCodePaths (lane-validator.mjs) - lane pattern matching
8
+ * - validateWUCodePaths (wu-validator.mjs) - code quality (TODOs, mocks)
9
+ *
10
+ * Usage:
11
+ * import { validate } from './code-path-validator.js';
12
+ *
13
+ * // Mode: 'exist' - check file existence (wu:done workflow)
14
+ * const result = await validate(paths, { mode: 'exist', worktreePath, targetBranch });
15
+ *
16
+ * // Mode: 'lane' - check lane pattern matching (wu:claim workflow)
17
+ * const result = validate(paths, { mode: 'lane', lane: 'Operations: Tooling' });
18
+ *
19
+ * // Mode: 'quality' - check code quality (TODOs, mocks)
20
+ * const result = validate(paths, { mode: 'quality', worktreePath, allowTodos: false });
21
+ *
22
+ * Part of INIT-023: Workflow Integrity initiative.
23
+ */
24
+ /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
25
+ import path from 'node:path';
26
+ import { existsSync, readFileSync } from 'node:fs';
27
+ import { execSync } from 'node:child_process';
28
+ import micromatch from 'micromatch';
29
+ import { getGitForCwd } from './git-adapter.js';
30
+ import { extractParent } from './lane-checker.js';
31
+ import { LANE_PATH_PATTERNS, BRANCHES, STRING_LITERALS, GIT_COMMANDS, LOG_PREFIX, EMOJI, } from './wu-constants.js';
32
+ // ============================================================================
33
+ // VALIDATION MODE CONSTANTS
34
+ // ============================================================================
35
+ /**
36
+ * Validation modes for the unified validator
37
+ * @enum {string}
38
+ */
39
+ export const VALIDATION_MODES = Object.freeze({
40
+ /** Check file existence - used by wu:done */
41
+ EXIST: 'exist',
42
+ /** Check lane pattern matching - used by wu:claim */
43
+ LANE: 'lane',
44
+ /** Check code quality (TODOs, mocks) - used by wu:done */
45
+ QUALITY: 'quality',
46
+ });
47
+ // ============================================================================
48
+ // FILE EXISTENCE VALIDATION (MODE: 'exist')
49
+ // ============================================================================
50
+ /**
51
+ * Check if a file path is a test file
52
+ * @param {string} filePath - Path to check
53
+ * @returns {boolean} True if file is a test file
54
+ */
55
+ function isTestFile(filePath) {
56
+ const normalized = filePath.replace(/\\/g, '/');
57
+ const testPatterns = [
58
+ /\.test\.(ts|tsx|js|jsx|mjs)$/,
59
+ /\.spec\.(ts|tsx|js|jsx|mjs)$/,
60
+ /__tests__\//,
61
+ /\.test-utils\./,
62
+ /\.mock\./,
63
+ ];
64
+ return testPatterns.some((pattern) => pattern.test(normalized));
65
+ }
66
+ /**
67
+ * Check if a file path is a markdown file
68
+ * @param {string} filePath - Path to check
69
+ * @returns {boolean} True if file is a markdown file
70
+ */
71
+ function isMarkdownFile(filePath) {
72
+ const normalized = filePath.replace(/\\/g, '/');
73
+ return /\.md$/i.test(normalized);
74
+ }
75
+ /**
76
+ * Get the repo root directory
77
+ * @returns {string} Absolute path to repo root
78
+ */
79
+ function getRepoRoot() {
80
+ try {
81
+ return execSync('git rev-parse --show-toplevel', {
82
+ encoding: 'utf-8',
83
+ stdio: ['pipe', 'pipe', 'ignore'],
84
+ }).trim();
85
+ }
86
+ catch {
87
+ return process.cwd();
88
+ }
89
+ }
90
+ /**
91
+ * Validate that files exist (worktree mode)
92
+ * @param {string[]} codePaths - Array of file paths
93
+ * @param {string} worktreePath - Worktree directory path
94
+ * @returns {ExistValidationResult} Validation result
95
+ */
96
+ function validateExistenceInWorktree(codePaths, worktreePath) {
97
+ const missing = [];
98
+ const errors = [];
99
+ for (const filePath of codePaths) {
100
+ const fullPath = path.join(worktreePath, filePath);
101
+ if (!existsSync(fullPath)) {
102
+ missing.push(filePath);
103
+ }
104
+ }
105
+ if (missing.length > 0) {
106
+ errors.push(`code_paths validation failed - ${missing.length} file(s) not found in worktree:\n${missing
107
+ .map((p) => ` - ${p}`)
108
+ .join(STRING_LITERALS.NEWLINE)}\n\nEnsure all files listed in code_paths exist before running wu:done.`);
109
+ }
110
+ return { valid: errors.length === 0, errors, missing };
111
+ }
112
+ /**
113
+ * Validate that files exist on a git branch (branch-only mode)
114
+ * @param {string[]} codePaths - Array of file paths
115
+ * @param {string} targetBranch - Branch to check files against
116
+ * @returns {Promise<ExistValidationResult>} Validation result
117
+ */
118
+ async function validateExistenceOnBranch(codePaths, targetBranch) {
119
+ const missing = [];
120
+ const errors = [];
121
+ try {
122
+ const gitAdapter = getGitForCwd();
123
+ for (const filePath of codePaths) {
124
+ try {
125
+ const result = await gitAdapter.raw([GIT_COMMANDS.LS_TREE, targetBranch, '--', filePath]);
126
+ if (!result || result.trim() === '') {
127
+ missing.push(filePath);
128
+ }
129
+ }
130
+ catch {
131
+ missing.push(filePath);
132
+ }
133
+ }
134
+ if (missing.length > 0) {
135
+ errors.push(`code_paths validation failed - ${missing.length} file(s) not found on ${targetBranch}:\n${missing
136
+ .map((p) => ` - ${p}`)
137
+ .join(STRING_LITERALS.NEWLINE)}\n\n❌ POTENTIAL FALSE COMPLETION DETECTED\n\n` +
138
+ `These files are listed in code_paths but do not exist on ${targetBranch}.\n` +
139
+ `This prevents creating a stamp for incomplete work.\n\n` +
140
+ `Fix options:\n` +
141
+ ` 1. Ensure all code is committed and merged to ${targetBranch}\n` +
142
+ ` 2. Update code_paths in WU YAML to match actual files\n` +
143
+ ` 3. Remove files that were intentionally not created\n\n` +
144
+ `Context: WU-1351 prevents false completions from INIT-WORKFLOW-INTEGRITY`);
145
+ }
146
+ }
147
+ catch (err) {
148
+ const errMessage = err instanceof Error ? err.message : String(err);
149
+ console.warn(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Could not validate code_paths: ${errMessage}`);
150
+ return { valid: true, errors: [], missing: [] };
151
+ }
152
+ return { valid: errors.length === 0, errors, missing };
153
+ }
154
+ // ============================================================================
155
+ // LANE PATTERN VALIDATION (MODE: 'lane')
156
+ // ============================================================================
157
+ /**
158
+ * Validate code paths against lane patterns
159
+ * @param {string[]} codePaths - Array of file paths
160
+ * @param {string} lane - Lane name (e.g., "Operations: Tooling")
161
+ * @returns {LaneValidationResult} Validation result
162
+ */
163
+ function validateLanePatterns(codePaths, lane) {
164
+ // Skip validation if no code_paths
165
+ if (!codePaths || codePaths.length === 0) {
166
+ return {
167
+ hasWarnings: false,
168
+ warnings: [],
169
+ violations: [],
170
+ skipped: true,
171
+ };
172
+ }
173
+ // Extract parent lane (e.g., "Operations" from "Operations: Tooling")
174
+ const parentLane = extractParent(lane);
175
+ // Get patterns for this lane parent
176
+ const patterns = LANE_PATH_PATTERNS[parentLane];
177
+ // Skip validation if no patterns defined for this lane
178
+ if (!patterns) {
179
+ return {
180
+ hasWarnings: false,
181
+ warnings: [],
182
+ violations: [],
183
+ skipped: true,
184
+ };
185
+ }
186
+ const { exclude = [], allowExceptions = [] } = patterns;
187
+ // Find violations: paths that match exclude patterns but NOT exception patterns
188
+ const violations = codePaths.filter((codePath) => {
189
+ const matchesExclude = micromatch.isMatch(codePath, exclude, { nocase: true });
190
+ if (!matchesExclude)
191
+ return false;
192
+ if (allowExceptions.length > 0) {
193
+ const matchesException = micromatch.isMatch(codePath, allowExceptions, { nocase: true });
194
+ if (matchesException)
195
+ return false;
196
+ }
197
+ return true;
198
+ });
199
+ if (violations.length === 0) {
200
+ return {
201
+ hasWarnings: false,
202
+ warnings: [],
203
+ violations: [],
204
+ skipped: false,
205
+ };
206
+ }
207
+ // Build warning messages
208
+ const warnings = violations.map((violatingPath) => {
209
+ return `Lane "${lane}" typically doesn't include "${violatingPath}" (expected for different lane)`;
210
+ });
211
+ return {
212
+ hasWarnings: true,
213
+ warnings,
214
+ violations,
215
+ skipped: false,
216
+ };
217
+ }
218
+ // ============================================================================
219
+ // CODE QUALITY VALIDATION (MODE: 'quality')
220
+ // ============================================================================
221
+ /**
222
+ * Scan a file for TODO/FIXME/HACK/XXX comments
223
+ * @param {string} filePath - Path to file to scan
224
+ * @returns {{found: boolean, matches: Array<{line: number, text: string, pattern: string}>}}
225
+ */
226
+ function scanFileForTODOs(filePath) {
227
+ if (!existsSync(filePath)) {
228
+ return { found: false, matches: [] };
229
+ }
230
+ if (isTestFile(filePath)) {
231
+ return { found: false, matches: [] };
232
+ }
233
+ if (isMarkdownFile(filePath)) {
234
+ return { found: false, matches: [] };
235
+ }
236
+ try {
237
+ const content = readFileSync(filePath, { encoding: 'utf-8' });
238
+ const lines = content.split(/\r?\n/);
239
+ const matches = [];
240
+ const checkForActionableMarker = (line) => {
241
+ const trimmed = line.trim();
242
+ // Skip documentation lines
243
+ if (trimmed.includes('// TODO:,') || trimmed.includes('/* TODO */')) {
244
+ return { found: false, pattern: null };
245
+ }
246
+ if (trimmed.includes('@todo,') || trimmed.includes('@-prefixed:')) {
247
+ return { found: false, pattern: null };
248
+ }
249
+ // Pattern 1: @-prefixed tags at start of JSDoc comment line
250
+ const atTagMatch = trimmed.match(/^\*\s+@(todo|fixme|hack|xxx)\b/i);
251
+ if (atTagMatch) {
252
+ return { found: true, pattern: atTagMatch[1].toUpperCase() };
253
+ }
254
+ // Pattern 2: Keyword at start of comment content
255
+ const commentStartMatch = trimmed.match(/^(?:\/\/|\/\*+|\*|<!--|#)\s*(TODO|FIXME|HACK|XXX)(?::|[\s]|$)/i);
256
+ if (commentStartMatch) {
257
+ const afterKeyword = trimmed.slice(trimmed.indexOf(commentStartMatch[1]) + commentStartMatch[1].length);
258
+ if (!afterKeyword.startsWith('/')) {
259
+ return { found: true, pattern: commentStartMatch[1].toUpperCase() };
260
+ }
261
+ }
262
+ // Pattern 3: Keyword in inline comment after code
263
+ const inlineCommentMatch = line.match(/\/\/\s*(TODO|FIXME|HACK|XXX)(?::|[\s]|$)/i);
264
+ if (inlineCommentMatch && !line.match(/\/\/\s*(TODO|FIXME|HACK|XXX)\//i)) {
265
+ const doubleSlashIndex = line.indexOf('//');
266
+ const beforeSlash = line.slice(0, doubleSlashIndex);
267
+ const singleQuotes = (beforeSlash.match(/(?<!\\)'/g) || []).length;
268
+ const doubleQuotes = (beforeSlash.match(/(?<!\\)"/g) || []).length;
269
+ const backticks = (beforeSlash.match(/(?<!\\)`/g) || []).length;
270
+ if (singleQuotes % 2 !== 0 || doubleQuotes % 2 !== 0 || backticks % 2 !== 0) {
271
+ return { found: false, pattern: null };
272
+ }
273
+ const commentPart = line.slice(doubleSlashIndex);
274
+ const keywordIndex = commentPart.search(/\b(TODO|FIXME|HACK|XXX)\b/i);
275
+ if (keywordIndex >= 0 && keywordIndex <= 10) {
276
+ return { found: true, pattern: inlineCommentMatch[1].toUpperCase() };
277
+ }
278
+ }
279
+ // Exclude WU-XXX placeholders
280
+ if (trimmed.match(/\bWU-XXX\b/i)) {
281
+ return { found: false, pattern: null };
282
+ }
283
+ return { found: false, pattern: null };
284
+ };
285
+ lines.forEach((line, index) => {
286
+ const lineNumber = index + 1;
287
+ const trimmed = line.trim();
288
+ const isComment = /^(\/\/|\/\*|\*|<!--|#)/.test(trimmed) || line.includes('//') || line.includes('/*');
289
+ if (isComment) {
290
+ const result = checkForActionableMarker(line);
291
+ if (result.found) {
292
+ matches.push({
293
+ line: lineNumber,
294
+ text: trimmed,
295
+ pattern: result.pattern,
296
+ });
297
+ }
298
+ }
299
+ });
300
+ return { found: matches.length > 0, matches };
301
+ }
302
+ catch {
303
+ return { found: false, matches: [] };
304
+ }
305
+ }
306
+ /**
307
+ * Scan a file for Mock/Stub/Fake class/function names
308
+ * @param {string} filePath - Path to file to scan
309
+ * @returns {{found: boolean, matches: Array<{line: number, text: string, type: string}>}}
310
+ */
311
+ function scanFileForMocks(filePath) {
312
+ if (!existsSync(filePath)) {
313
+ return { found: false, matches: [] };
314
+ }
315
+ if (isTestFile(filePath)) {
316
+ return { found: false, matches: [] };
317
+ }
318
+ try {
319
+ const content = readFileSync(filePath, { encoding: 'utf-8' });
320
+ const lines = content.split(/\r?\n/);
321
+ const matches = [];
322
+ const mockPatterns = [
323
+ { name: 'Mock', regex: /\b(class|export\s+class)\s+(\w*Mock\w*)/i },
324
+ { name: 'Stub', regex: /\b(class|export\s+class)\s+(\w*Stub\w*)/i },
325
+ { name: 'Fake', regex: /\b(class|export\s+class)\s+(\w*Fake\w*)/i },
326
+ { name: 'Placeholder', regex: /\b(class|export\s+class)\s+(\w*Placeholder\w*)/i },
327
+ { name: 'Mock', regex: /\b(function|const|let|var)\s+(\w*mock\w*)/i },
328
+ { name: 'Stub', regex: /\b(function|const|let|var)\s+(\w*stub\w*)/i },
329
+ { name: 'Fake', regex: /\b(function|const|let|var)\s+(\w*fake\w*)/i },
330
+ { name: 'Placeholder', regex: /\b(function|const|let|var)\s+(\w*placeholder\w*)/i },
331
+ ];
332
+ lines.forEach((line, index) => {
333
+ const lineNumber = index + 1;
334
+ mockPatterns.forEach(({ name, regex }) => {
335
+ const match = regex.exec(line);
336
+ if (match) {
337
+ matches.push({
338
+ line: lineNumber,
339
+ text: line.trim(),
340
+ type: name,
341
+ });
342
+ }
343
+ });
344
+ });
345
+ return { found: matches.length > 0, matches };
346
+ }
347
+ catch {
348
+ return { found: false, matches: [] };
349
+ }
350
+ }
351
+ /**
352
+ * Format TODO findings for display
353
+ * @param {Array} findings - TODO findings
354
+ * @returns {string} Formatted message
355
+ */
356
+ function formatTODOFindings(findings) {
357
+ let msg = '\n❌ TODO/FIXME/HACK/XXX comments found in production code:\n';
358
+ findings.forEach(({ path: filePath, matches }) => {
359
+ msg += `\n ${filePath}:\n`;
360
+ matches.forEach(({ line, text }) => {
361
+ msg += ` Line ${line}: ${text}\n`;
362
+ });
363
+ });
364
+ msg += '\nThese indicate incomplete work and must be resolved before WU completion.';
365
+ msg += '\nEither complete the work or use --allow-todo with justification in WU notes.';
366
+ return msg;
367
+ }
368
+ /**
369
+ * Format Mock findings for display
370
+ * @param {Array} findings - Mock findings
371
+ * @returns {string} Formatted message
372
+ */
373
+ function formatMockFindings(findings) {
374
+ let msg = '\n⚠️ Mock/Stub/Fake/Placeholder classes found in production code:\n';
375
+ findings.forEach(({ path: filePath, matches }) => {
376
+ msg += `\n ${filePath}:\n`;
377
+ matches.forEach(({ line, text }) => {
378
+ msg += ` Line ${line}: ${text}\n`;
379
+ });
380
+ });
381
+ msg += '\nThese suggest incomplete implementation (interface ≠ implementation).';
382
+ msg += '\nVerify these are actual implementations, not placeholder code.';
383
+ return msg;
384
+ }
385
+ /**
386
+ * Validate code quality (TODOs, mocks)
387
+ * @param {string[]} codePaths - Array of file paths
388
+ * @param {object} options - Options
389
+ * @param {boolean} options.allowTodos - Allow TODO comments (with warning)
390
+ * @param {string} options.worktreePath - Worktree path for file lookups
391
+ * @returns {QualityValidationResult} Validation result
392
+ */
393
+ function validateCodeQuality(codePaths, options = {}) {
394
+ const { allowTodos = false, worktreePath = null } = options;
395
+ const errors = [];
396
+ const warnings = [];
397
+ const repoRoot = worktreePath || getRepoRoot();
398
+ if (!codePaths || codePaths.length === 0) {
399
+ return { valid: true, errors, warnings };
400
+ }
401
+ const todoFindings = [];
402
+ const mockFindings = [];
403
+ for (const codePath of codePaths) {
404
+ const absolutePath = path.join(repoRoot, codePath);
405
+ if (!existsSync(absolutePath)) {
406
+ errors.push(`\n❌ Code path validation failed: File does not exist: ${codePath}\n\n` +
407
+ `This indicates the WU claims to have created/modified a file that doesn't exist.\n` +
408
+ `Either create the file, or remove it from code_paths in the WU YAML.\n`);
409
+ continue;
410
+ }
411
+ const todoResult = scanFileForTODOs(absolutePath);
412
+ if (todoResult.found) {
413
+ todoFindings.push({ path: codePath, ...todoResult });
414
+ }
415
+ const mockResult = scanFileForMocks(absolutePath);
416
+ if (mockResult.found) {
417
+ mockFindings.push({ path: codePath, ...mockResult });
418
+ }
419
+ }
420
+ if (todoFindings.length > 0) {
421
+ const message = formatTODOFindings(todoFindings);
422
+ if (allowTodos) {
423
+ warnings.push(message);
424
+ }
425
+ else {
426
+ errors.push(message);
427
+ }
428
+ }
429
+ if (mockFindings.length > 0) {
430
+ warnings.push(formatMockFindings(mockFindings));
431
+ }
432
+ return { valid: errors.length === 0, errors, warnings };
433
+ }
434
+ // ============================================================================
435
+ // UNIFIED VALIDATE API
436
+ // ============================================================================
437
+ /**
438
+ * Unified code path validation function
439
+ *
440
+ * @param {string[]} codePaths - Array of file paths to validate
441
+ * @param {object} options - Validation options
442
+ * @param {string} options.mode - Validation mode: 'exist', 'lane', or 'quality'
443
+ * @param {string} [options.worktreePath] - Worktree path (for 'exist' and 'quality' modes)
444
+ * @param {string} [options.targetBranch] - Target branch (for 'exist' mode, branch-only)
445
+ * @param {string} [options.lane] - Lane name (for 'lane' mode)
446
+ * @param {boolean} [options.allowTodos] - Allow TODO comments (for 'quality' mode)
447
+ * @returns {Promise<ExistValidationResult|LaneValidationResult|QualityValidationResult>}
448
+ */
449
+ export async function validate(codePaths, options = {}) {
450
+ const { mode = VALIDATION_MODES.EXIST } = options;
451
+ switch (mode) {
452
+ case VALIDATION_MODES.EXIST: {
453
+ const { worktreePath, targetBranch = BRANCHES.MAIN } = options;
454
+ if (!codePaths || codePaths.length === 0) {
455
+ return { valid: true, errors: [], missing: [] };
456
+ }
457
+ if (worktreePath && existsSync(worktreePath)) {
458
+ return validateExistenceInWorktree(codePaths, worktreePath);
459
+ }
460
+ return validateExistenceOnBranch(codePaths, targetBranch);
461
+ }
462
+ case VALIDATION_MODES.LANE: {
463
+ const { lane } = options;
464
+ if (!lane) {
465
+ throw new Error('Lane name is required for lane validation mode');
466
+ }
467
+ return validateLanePatterns(codePaths, lane);
468
+ }
469
+ case VALIDATION_MODES.QUALITY: {
470
+ const { worktreePath, allowTodos } = options;
471
+ return validateCodeQuality(codePaths, { worktreePath, allowTodos });
472
+ }
473
+ default:
474
+ throw new Error(`Unknown validation mode: ${mode}`);
475
+ }
476
+ }
477
+ /**
478
+ * @deprecated Use validate(paths, { mode: 'exist' }) instead
479
+ * Backward-compatible wrapper for validateCodePathsExist
480
+ */
481
+ export async function validateCodePathsExist(doc, _id, options = {}) {
482
+ const codePaths = doc.code_paths || [];
483
+ const { targetBranch = BRANCHES.MAIN, worktreePath = null } = options;
484
+ if (codePaths.length === 0) {
485
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.INFO} No code_paths to validate for ${_id}`);
486
+ return { valid: true, errors: [], missing: [] };
487
+ }
488
+ console.log(`${LOG_PREFIX.DONE} Validating ${codePaths.length} code_paths exist...`);
489
+ const result = (await validate(codePaths, {
490
+ mode: VALIDATION_MODES.EXIST,
491
+ worktreePath: worktreePath ?? undefined,
492
+ targetBranch,
493
+ }));
494
+ if (result.valid) {
495
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} All ${codePaths.length} code_paths verified`);
496
+ }
497
+ return result;
498
+ }
499
+ /**
500
+ * @deprecated Use validate(paths, { mode: 'lane', lane }) instead
501
+ * Backward-compatible wrapper for validateLaneCodePaths
502
+ * NOTE: This must remain SYNCHRONOUS for backward compatibility
503
+ */
504
+ export function validateLaneCodePaths(doc, lane) {
505
+ const codePaths = doc.code_paths || [];
506
+ // Call the sync internal function directly to maintain sync behavior
507
+ return validateLanePatterns(codePaths, lane);
508
+ }
509
+ /**
510
+ * @deprecated Use validate(paths, { mode: 'quality' }) instead
511
+ * Backward-compatible wrapper for validateWUCodePaths
512
+ * NOTE: This must remain SYNCHRONOUS for backward compatibility
513
+ */
514
+ export function validateWUCodePaths(codePaths, options = {}) {
515
+ const { allowTodos = false, worktreePath = null } = options;
516
+ // Call the sync internal function directly to maintain sync behavior
517
+ return validateCodeQuality(codePaths, { worktreePath, allowTodos });
518
+ }
519
+ /**
520
+ * Log lane validation warnings to console.
521
+ * Helper function to format and display warnings consistently.
522
+ *
523
+ * @param {LaneValidationResult} result - Result from validateLaneCodePaths
524
+ * @param {string} logPrefix - Log prefix (e.g., "[wu-claim]")
525
+ */
526
+ export function logLaneValidationWarnings(result, logPrefix = '[wu-claim]') {
527
+ if (!result.hasWarnings) {
528
+ return;
529
+ }
530
+ console.warn(`${logPrefix} Lane/code_paths mismatch detected (advisory only):`);
531
+ for (const warning of result.warnings) {
532
+ console.warn(`${logPrefix} ${warning}`);
533
+ }
534
+ console.warn(`${logPrefix} This is a warning only - proceeding with claim.`);
535
+ }
536
+ // Re-export helper functions for consumers that need them
537
+ export { isTestFile, isMarkdownFile, scanFileForTODOs, scanFileForMocks };
@@ -0,0 +1,55 @@
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
+ /**
11
+ * Check for code path overlap between two sets of glob patterns
12
+ *
13
+ * Uses two-step algorithm:
14
+ * - Static check: Fast pattern analysis to detect obvious containment
15
+ * - Concrete check: Filesystem-based expansion to find actual file intersection
16
+ *
17
+ * @param {string[]} claimingPaths - Glob patterns from WU being claimed
18
+ * @param {string[]} existingPaths - Glob patterns from in-progress WU
19
+ * @returns {{
20
+ * overlaps: boolean,
21
+ * type: 'none'|'concrete'|'ambiguous',
22
+ * files: string[]
23
+ * }} Overlap detection result
24
+ *
25
+ * @example
26
+ * checkOverlap(['apps/web/**'], ['apps/web/prompts/**'])
27
+ * // => { overlaps: true, type: 'concrete', files: [...] }
28
+ */
29
+ export declare function checkOverlap(claimingPaths: any, existingPaths: any): {
30
+ overlaps: boolean;
31
+ type: string;
32
+ files: unknown[];
33
+ };
34
+ /**
35
+ * Find all in-progress WUs with overlapping code paths
36
+ *
37
+ * Reads status.md to find in-progress WUs, loads their code_paths,
38
+ * and checks for overlaps with the claiming WU's paths.
39
+ *
40
+ * @param {string} statusPath - Path to status.md file
41
+ * @param {string[]} claimingPaths - Glob patterns from WU being claimed
42
+ * @param {string} claimingWU - WU ID being claimed (excluded from check)
43
+ * @returns {{
44
+ * conflicts: Array<{wuid: string, overlaps: string[]}>,
45
+ * hasBlocker: boolean
46
+ * }} List of conflicting WUs and whether to block claim
47
+ *
48
+ * @example
49
+ * detectConflicts('docs/04-operations/tasks/status.md', ['apps/**'], 'WU-901')
50
+ * // => { conflicts: [{wuid: 'WU-900', overlaps: ['apps/web/foo.ts']}], hasBlocker: true }
51
+ */
52
+ export declare function detectConflicts(statusPath: any, claimingPaths: any, claimingWU: any): {
53
+ conflicts: any[];
54
+ hasBlocker: boolean;
55
+ };