@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,48 @@
1
+ import { getGitForCwd } from './git-adapter.js';
2
+ import { die } from './error-handler.js';
3
+ /**
4
+ * Git Staged Files Validator
5
+ *
6
+ * Centralized validation for staged files requirement.
7
+ * Extracted from duplicate implementations in wu-block and wu-unblock (WU-1341).
8
+ *
9
+ * Used in --no-auto mode to enforce that required files are staged before commit.
10
+ *
11
+ * @module git-staged-validator
12
+ */
13
+ /**
14
+ * Ensure all required paths are staged in git index
15
+ *
16
+ * Validates that specified files/directories are staged for commit.
17
+ * Supports exact path matching and directory prefix matching.
18
+ *
19
+ * @param {Array<string|null|undefined>} paths - Paths to check (null/undefined values filtered out)
20
+ * @throws {Error} If any required paths are not staged
21
+ * @returns {Array<string>} List of all staged files
22
+ *
23
+ * @example
24
+ * // All files staged - success
25
+ * ensureStaged(['docs/file.md', 'tools/script.js']);
26
+ *
27
+ * // Directory prefix - matches all files under directory
28
+ * ensureStaged(['docs/04-operations/']);
29
+ *
30
+ * // Missing files - throws error
31
+ * ensureStaged(['docs/file1.md', 'docs/file2.md']);
32
+ * // Error: Stage updates for: docs/file2.md
33
+ */
34
+ export function ensureStaged(paths) {
35
+ const git = getGitForCwd();
36
+ const raw = git.run('git diff --cached --name-only');
37
+ const staged = raw ? raw.split(/\r?\n/).filter((s) => Boolean(s)) : [];
38
+ // Filter out null/undefined and check each path
39
+ const missing = paths.filter(Boolean).filter((p) => {
40
+ // Normalize path: remove trailing slash for directory checks
41
+ const pathToCheck = p.endsWith('/') ? p.slice(0, -1) : p;
42
+ return !staged.some((name) => name === pathToCheck || name.startsWith(`${pathToCheck}/`));
43
+ });
44
+ if (missing.length) {
45
+ die(`Stage updates for: ${missing.join(', ')}`);
46
+ }
47
+ return staged;
48
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Hardcoded String Detection Module
3
+ *
4
+ * Provides classification of hardcoded path strings to distinguish between:
5
+ * - Route paths (API endpoints, webhooks, etc.) - should use endpoint constants
6
+ * - Filesystem paths (absolute paths, config files) - should use path.join()
7
+ *
8
+ * This enables Gate 13 to provide accurate, actionable remediation messages.
9
+ *
10
+ * @see WU-1788 - Improve hardcoded-string gate messaging for route paths
11
+ */
12
+ /**
13
+ * Path type constants
14
+ * @type {Object}
15
+ */
16
+ export declare const PATH_TYPES: Readonly<{
17
+ ROUTE: "route";
18
+ FILESYSTEM: "filesystem";
19
+ UNKNOWN: "unknown";
20
+ }>;
21
+ /**
22
+ * Classify a path string as route, filesystem, or unknown
23
+ *
24
+ * @param {string} pathStr - The path string to classify
25
+ * @returns {string} One of PATH_TYPES values
26
+ */
27
+ export declare function classifyPath(pathStr: any): "unknown" | "route" | "filesystem";
28
+ /**
29
+ * Get the appropriate remediation message for a path type
30
+ *
31
+ * @param {string} pathType - One of PATH_TYPES values
32
+ * @returns {string} Remediation message
33
+ */
34
+ export declare function getRemediation(pathType: any): any;
35
+ /**
36
+ * Options for hardcoded path violation detection
37
+ */
38
+ export interface FindHardcodedPathViolationsOptions {
39
+ /** Whether this is a test file (skip detection) */
40
+ isTestFile?: boolean;
41
+ /** Whether this is a config file (skip detection) */
42
+ isConfigFile?: boolean;
43
+ }
44
+ /**
45
+ * Find hardcoded path violations in a line of code
46
+ *
47
+ * @param {string} line - The line of code to check
48
+ * @param {FindHardcodedPathViolationsOptions} options - Options for detection
49
+ * @returns {Array<{line: string, fix: string, pathType: string, path: string}>} Array of violations
50
+ */
51
+ export declare function findHardcodedPathViolations(line: any, options?: FindHardcodedPathViolationsOptions): any[];
52
+ /**
53
+ * Legacy function for backwards compatibility with gates-pre-commit.mjs
54
+ * Detects all hardcoded string patterns (not just paths)
55
+ *
56
+ * @deprecated Use findHardcodedPathViolations for path-specific detection
57
+ */
58
+ export declare const HARDCODED_PATTERNS: {
59
+ pattern: RegExp;
60
+ message: string;
61
+ }[];
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Hardcoded String Detection Module
3
+ *
4
+ * Provides classification of hardcoded path strings to distinguish between:
5
+ * - Route paths (API endpoints, webhooks, etc.) - should use endpoint constants
6
+ * - Filesystem paths (absolute paths, config files) - should use path.join()
7
+ *
8
+ * This enables Gate 13 to provide accurate, actionable remediation messages.
9
+ *
10
+ * @see WU-1788 - Improve hardcoded-string gate messaging for route paths
11
+ */
12
+ /**
13
+ * Path type constants
14
+ * @type {Object}
15
+ */
16
+ export const PATH_TYPES = Object.freeze({
17
+ ROUTE: 'route',
18
+ FILESYSTEM: 'filesystem',
19
+ UNKNOWN: 'unknown',
20
+ });
21
+ /**
22
+ * Route path prefixes that indicate API/web endpoints
23
+ * These should use endpoint constants, not path.join()
24
+ */
25
+ const ROUTE_PREFIXES = [
26
+ '/api/',
27
+ '/api?', // API with query params
28
+ '/auth/',
29
+ '/v1/',
30
+ '/v2/',
31
+ '/v3/',
32
+ '/graphql',
33
+ '/webhook/',
34
+ '/webhooks/',
35
+ '/trpc/',
36
+ '/rest/',
37
+ '/rpc/',
38
+ ];
39
+ /**
40
+ * Filesystem path prefixes that indicate absolute paths
41
+ * These should use path.join() with constants
42
+ */
43
+ const FILESYSTEM_PREFIXES = [
44
+ '/home/',
45
+ '/usr/',
46
+ '/etc/',
47
+ '/var/',
48
+ '/tmp/',
49
+ '/opt/',
50
+ '/bin/',
51
+ '/sbin/',
52
+ '/lib/',
53
+ '/proc/',
54
+ '/sys/',
55
+ '/dev/',
56
+ '/mnt/',
57
+ '/media/',
58
+ '/srv/',
59
+ '/root/',
60
+ ];
61
+ /**
62
+ * Common file extensions that indicate filesystem paths
63
+ */
64
+ const FILE_EXTENSIONS = [
65
+ '.json',
66
+ '.yaml',
67
+ '.yml',
68
+ '.xml',
69
+ '.conf',
70
+ '.config',
71
+ '.cfg',
72
+ '.ini',
73
+ '.env',
74
+ '.log',
75
+ '.txt',
76
+ '.md',
77
+ '.csv',
78
+ '.tsv',
79
+ '.sql',
80
+ '.sh',
81
+ '.bash',
82
+ '.zsh',
83
+ '.py',
84
+ '.rb',
85
+ '.pl',
86
+ '.js',
87
+ '.ts',
88
+ '.jsx',
89
+ '.tsx',
90
+ '.js',
91
+ '.cjs',
92
+ '.png',
93
+ '.jpg',
94
+ '.jpeg',
95
+ '.gif',
96
+ '.svg',
97
+ '.ico',
98
+ '.pdf',
99
+ '.doc',
100
+ '.docx',
101
+ '.xls',
102
+ '.xlsx',
103
+ '.pem',
104
+ '.key',
105
+ '.crt',
106
+ '.cer',
107
+ ];
108
+ /**
109
+ * Patterns that indicate filesystem paths
110
+ */
111
+ const FILESYSTEM_PATTERNS = [
112
+ /^[A-Z]:[/\\]/, // Windows drive letter (C:\, D:/)
113
+ /node_modules/, // Node.js modules directory
114
+ /\.\.\//, // Relative path traversal
115
+ /~\//, // Home directory shorthand
116
+ ];
117
+ /**
118
+ * Classify a path string as route, filesystem, or unknown
119
+ *
120
+ * @param {string} pathStr - The path string to classify
121
+ * @returns {string} One of PATH_TYPES values
122
+ */
123
+ export function classifyPath(pathStr) {
124
+ if (!pathStr || pathStr === '/') {
125
+ return PATH_TYPES.UNKNOWN;
126
+ }
127
+ const normalizedPath = pathStr.toLowerCase();
128
+ // Check for route path indicators first (more specific)
129
+ for (const prefix of ROUTE_PREFIXES) {
130
+ if (normalizedPath.startsWith(prefix) || normalizedPath.includes(prefix)) {
131
+ return PATH_TYPES.ROUTE;
132
+ }
133
+ }
134
+ // Check for query parameters (indicates a route/URL)
135
+ if (pathStr.includes('?') && pathStr.startsWith('/')) {
136
+ return PATH_TYPES.ROUTE;
137
+ }
138
+ // Check for filesystem path prefixes
139
+ for (const prefix of FILESYSTEM_PREFIXES) {
140
+ if (normalizedPath.startsWith(prefix)) {
141
+ return PATH_TYPES.FILESYSTEM;
142
+ }
143
+ }
144
+ // Check for file extensions
145
+ for (const ext of FILE_EXTENSIONS) {
146
+ if (normalizedPath.endsWith(ext)) {
147
+ return PATH_TYPES.FILESYSTEM;
148
+ }
149
+ }
150
+ // Check for filesystem patterns
151
+ for (const pattern of FILESYSTEM_PATTERNS) {
152
+ if (pattern.test(pathStr)) {
153
+ return PATH_TYPES.FILESYSTEM;
154
+ }
155
+ }
156
+ // Default to unknown for ambiguous paths
157
+ return PATH_TYPES.UNKNOWN;
158
+ }
159
+ /**
160
+ * Remediation messages for each path type
161
+ */
162
+ const REMEDIATION_MESSAGES = Object.freeze({
163
+ [PATH_TYPES.ROUTE]: 'Route path - use an endpoint constant or config (e.g., BEACON_API_ENDPOINT, API_ROUTES.ASSISTANT)',
164
+ [PATH_TYPES.FILESYSTEM]: 'File path - use path.join() with constants',
165
+ [PATH_TYPES.UNKNOWN]: 'Path string - use a constant or configuration value',
166
+ });
167
+ /**
168
+ * Get the appropriate remediation message for a path type
169
+ *
170
+ * @param {string} pathType - One of PATH_TYPES values
171
+ * @returns {string} Remediation message
172
+ */
173
+ export function getRemediation(pathType) {
174
+ // Validate pathType to prevent object injection
175
+ const validTypes = Object.values(PATH_TYPES);
176
+ if (validTypes.includes(pathType)) {
177
+ // eslint-disable-next-line security/detect-object-injection -- Safe: pathType validated against known values above
178
+ return REMEDIATION_MESSAGES[pathType];
179
+ }
180
+ return REMEDIATION_MESSAGES[PATH_TYPES.UNKNOWN];
181
+ }
182
+ /**
183
+ * Pattern to match path-like strings in code
184
+ * Matches quoted strings that:
185
+ * - Start with /
186
+ * - Have at least one path segment after the initial /
187
+ * - May include alphanumeric characters, underscores, hyphens, dots
188
+ * - May include query parameters
189
+ *
190
+ * Note: Uses non-greedy quantifiers and avoids nested quantifiers to prevent ReDoS
191
+ */
192
+ // eslint-disable-next-line security/detect-unsafe-regex -- Intentionally simplified pattern, not user-controlled
193
+ const PATH_PATTERN = /['"](\/[\w./-]+(?:\?[^'"]*)?)['"]/gi;
194
+ /**
195
+ * Check if a line contains a constant definition (SCREAMING_SNAKE_CASE)
196
+ *
197
+ * @param {string} line - The line to check
198
+ * @returns {boolean} True if line defines a constant
199
+ */
200
+ function isConstantDefinition(line) {
201
+ if (!line.includes('const '))
202
+ return false;
203
+ const afterConst = line.split('const ')[1];
204
+ if (!afterConst)
205
+ return false;
206
+ const identifier = afterConst.trim().split(/[\s=]/)[0];
207
+ return identifier && identifier === identifier.toUpperCase() && identifier.length > 1;
208
+ }
209
+ /**
210
+ * Find hardcoded path violations in a line of code
211
+ *
212
+ * @param {string} line - The line of code to check
213
+ * @param {FindHardcodedPathViolationsOptions} options - Options for detection
214
+ * @returns {Array<{line: string, fix: string, pathType: string, path: string}>} Array of violations
215
+ */
216
+ export function findHardcodedPathViolations(line, options = {}) {
217
+ const { isTestFile = false, isConfigFile = false } = options;
218
+ // Skip test files and config files
219
+ if (isTestFile || isConfigFile) {
220
+ return [];
221
+ }
222
+ // Skip constant definitions (they ARE the constants we want people to create)
223
+ if (isConstantDefinition(line)) {
224
+ return [];
225
+ }
226
+ // Skip lines that look like test context
227
+ if (line.includes('.test.') || line.includes('.spec.')) {
228
+ return [];
229
+ }
230
+ const violations = [];
231
+ let match;
232
+ // Reset regex lastIndex for global matching
233
+ PATH_PATTERN.lastIndex = 0;
234
+ while ((match = PATH_PATTERN.exec(line)) !== null) {
235
+ const pathStr = match[1];
236
+ // Only flag paths that start with /
237
+ if (!pathStr.startsWith('/')) {
238
+ continue;
239
+ }
240
+ const pathType = classifyPath(pathStr);
241
+ const remediation = getRemediation(pathType);
242
+ violations.push({
243
+ line: line.substring(0, 80),
244
+ fix: remediation,
245
+ pathType,
246
+ path: pathStr,
247
+ });
248
+ }
249
+ return violations;
250
+ }
251
+ /**
252
+ * Legacy function for backwards compatibility with gates-pre-commit.mjs
253
+ * Detects all hardcoded string patterns (not just paths)
254
+ *
255
+ * @deprecated Use findHardcodedPathViolations for path-specific detection
256
+ */
257
+ export const HARDCODED_PATTERNS = [
258
+ {
259
+ pattern: /['"]https?:\/\/[^'"]+\/api\//i,
260
+ message: 'API endpoint - use environment variable or config',
261
+ },
262
+ {
263
+ pattern: /throw new Error\s*\(\s*['"][^'"]{50,}['"]\s*\)/i,
264
+ message: 'Long error message - use error constants',
265
+ },
266
+ // WU-1788: Path pattern now uses classifier for accurate messages
267
+ // The old pattern is replaced by findHardcodedPathViolations
268
+ { pattern: /['"]#[0-9a-fA-F]{6}['"]/i, message: 'Color value - use theme/design tokens' },
269
+ { pattern: /if\s*\(\s*['"]feature_/i, message: 'Feature flag - use feature flag service' },
270
+ ];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @file incremental-lint.mjs
3
+ * @description Incremental linting utilities for gates
4
+ * WU-1304: Optimise ESLint gates performance
5
+ *
6
+ * Provides utilities to determine which files need linting based on
7
+ * changes since branching from main, enabling faster gate execution.
8
+ */
9
+ /**
10
+ * File extensions that should be linted by ESLint
11
+ * @type {string[]}
12
+ */
13
+ export declare const LINTABLE_EXTENSIONS: string[];
14
+ /**
15
+ * Check if a file path should be linted
16
+ * @param {string} filePath - File path to check
17
+ * @returns {boolean} True if file should be linted
18
+ */
19
+ export declare function isLintableFile(filePath: any): boolean;
20
+ /**
21
+ * Git adapter interface for testing
22
+ */
23
+ interface GitAdapter {
24
+ mergeBase: (ref1: string, ref2: string) => Promise<string>;
25
+ raw: (args: string[]) => Promise<string>;
26
+ }
27
+ /**
28
+ * Options for getting changed lintable files
29
+ */
30
+ export interface GetChangedLintableFilesOptions {
31
+ /** GitAdapter instance (for testing) */
32
+ git?: GitAdapter;
33
+ /** Base branch to compare against */
34
+ baseBranch?: string;
35
+ /** Optional path prefix to filter files */
36
+ filterPath?: string;
37
+ }
38
+ /**
39
+ * Get list of changed files that should be linted
40
+ * Includes:
41
+ * - Committed changes since branching from main (git diff merge-base...HEAD)
42
+ * - Modified but unstaged files (git diff --name-only)
43
+ * - Untracked files (git ls-files --others --exclude-standard)
44
+ *
45
+ * WU-1784: Extended to include untracked and unstaged files to prevent
46
+ * lint gaps when gates are run before staging/committing changes.
47
+ *
48
+ * @param {GetChangedLintableFilesOptions} [options] - Options
49
+ * @returns {Promise<string[]>} List of file paths to lint
50
+ *
51
+ * @example
52
+ * // Get all changed lintable files
53
+ * const files = await getChangedLintableFiles();
54
+ *
55
+ * @example
56
+ * // Get only files in apps/web/
57
+ * const files = await getChangedLintableFiles({ filterPath: 'apps/web/' });
58
+ */
59
+ export declare function getChangedLintableFiles(options?: GetChangedLintableFilesOptions): Promise<any[]>;
60
+ /**
61
+ * Convert repo-relative file paths to package-relative paths
62
+ *
63
+ * WU-2571: ESLint runs from package directory (e.g., apps/web/) where
64
+ * repo-relative paths (e.g., apps/web/src/file.ts) don't exist.
65
+ * This function strips the package prefix to produce paths that
66
+ * work correctly when ESLint is run from the package directory.
67
+ *
68
+ * @param {string[]} repoRelativePaths - Paths relative to repo root (e.g., ['apps/web/src/app.ts'])
69
+ * @param {string} packagePrefix - Package directory prefix (e.g., 'apps/web/' or 'apps/web')
70
+ * @returns {string[]} Paths relative to package directory (e.g., ['src/app.ts'])
71
+ *
72
+ * @example
73
+ * const repoRelative = ['apps/web/src/app.ts', 'apps/web/src/lib.tsx'];
74
+ * const packageRelative = convertToPackageRelativePaths(repoRelative, 'apps/web/');
75
+ * // Returns: ['src/app.ts', 'src/lib.tsx']
76
+ */
77
+ export declare function convertToPackageRelativePaths(repoRelativePaths: any, packagePrefix: any): any;
78
+ export {};
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @file incremental-lint.mjs
3
+ * @description Incremental linting utilities for gates
4
+ * WU-1304: Optimise ESLint gates performance
5
+ *
6
+ * Provides utilities to determine which files need linting based on
7
+ * changes since branching from main, enabling faster gate execution.
8
+ */
9
+ import { getGitForCwd } from './git-adapter.js';
10
+ import { STRING_LITERALS } from './wu-constants.js';
11
+ /**
12
+ * File extensions that should be linted by ESLint
13
+ * @type {string[]}
14
+ */
15
+ export const LINTABLE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.js'];
16
+ /**
17
+ * Directory patterns that should be ignored
18
+ * Matches ESLint ignores in apps/web/eslint.config.mjs
19
+ * @type {string[]}
20
+ */
21
+ const IGNORED_DIRECTORIES = [
22
+ 'node_modules/',
23
+ '.next/',
24
+ '.expo/',
25
+ 'dist/',
26
+ 'build/',
27
+ '.turbo/',
28
+ 'coverage/',
29
+ 'worktrees/',
30
+ ];
31
+ /**
32
+ * Check if a file path should be linted
33
+ * @param {string} filePath - File path to check
34
+ * @returns {boolean} True if file should be linted
35
+ */
36
+ export function isLintableFile(filePath) {
37
+ // Check if in ignored directory
38
+ for (const ignored of IGNORED_DIRECTORIES) {
39
+ if (filePath.includes(ignored)) {
40
+ return false;
41
+ }
42
+ }
43
+ // Check if has lintable extension
44
+ for (const ext of LINTABLE_EXTENSIONS) {
45
+ if (filePath.endsWith(ext)) {
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ /**
52
+ * Parse git output into a list of file paths
53
+ * @param {string} output - Git command output
54
+ * @returns {string[]} List of file paths
55
+ */
56
+ function parseGitFileList(output) {
57
+ return output
58
+ .split(STRING_LITERALS.NEWLINE)
59
+ .map((f) => f.trim())
60
+ .filter((f) => f.length > 0);
61
+ }
62
+ /**
63
+ * Get list of changed files that should be linted
64
+ * Includes:
65
+ * - Committed changes since branching from main (git diff merge-base...HEAD)
66
+ * - Modified but unstaged files (git diff --name-only)
67
+ * - Untracked files (git ls-files --others --exclude-standard)
68
+ *
69
+ * WU-1784: Extended to include untracked and unstaged files to prevent
70
+ * lint gaps when gates are run before staging/committing changes.
71
+ *
72
+ * @param {GetChangedLintableFilesOptions} [options] - Options
73
+ * @returns {Promise<string[]>} List of file paths to lint
74
+ *
75
+ * @example
76
+ * // Get all changed lintable files
77
+ * const files = await getChangedLintableFiles();
78
+ *
79
+ * @example
80
+ * // Get only files in apps/web/
81
+ * const files = await getChangedLintableFiles({ filterPath: 'apps/web/' });
82
+ */
83
+ export async function getChangedLintableFiles(options = {}) {
84
+ const { git = getGitForCwd(), baseBranch = 'origin/main', filterPath } = options;
85
+ // Get the merge base (common ancestor) with the base branch
86
+ const mergeBase = await git.mergeBase('HEAD', baseBranch);
87
+ // 1. Get committed changes since the merge base
88
+ const committedOutput = await git.raw(['diff', '--name-only', `${mergeBase}...HEAD`]);
89
+ const committedFiles = parseGitFileList(committedOutput);
90
+ // 2. Get modified but unstaged files (WU-1784)
91
+ const unstagedOutput = await git.raw(['diff', '--name-only']);
92
+ const unstagedFiles = parseGitFileList(unstagedOutput);
93
+ // 3. Get untracked files (WU-1784)
94
+ const untrackedOutput = await git.raw(['ls-files', '--others', '--exclude-standard']);
95
+ const untrackedFiles = parseGitFileList(untrackedOutput);
96
+ // Combine all sources and deduplicate using Set
97
+ const allFiles = [...new Set([...committedFiles, ...unstagedFiles, ...untrackedFiles])];
98
+ // Filter to lintable files
99
+ let lintableFiles = allFiles.filter(isLintableFile);
100
+ // Apply path filter if specified
101
+ if (filterPath) {
102
+ lintableFiles = lintableFiles.filter((f) => f.startsWith(filterPath));
103
+ }
104
+ return lintableFiles;
105
+ }
106
+ /**
107
+ * Convert repo-relative file paths to package-relative paths
108
+ *
109
+ * WU-2571: ESLint runs from package directory (e.g., apps/web/) where
110
+ * repo-relative paths (e.g., apps/web/src/file.ts) don't exist.
111
+ * This function strips the package prefix to produce paths that
112
+ * work correctly when ESLint is run from the package directory.
113
+ *
114
+ * @param {string[]} repoRelativePaths - Paths relative to repo root (e.g., ['apps/web/src/app.ts'])
115
+ * @param {string} packagePrefix - Package directory prefix (e.g., 'apps/web/' or 'apps/web')
116
+ * @returns {string[]} Paths relative to package directory (e.g., ['src/app.ts'])
117
+ *
118
+ * @example
119
+ * const repoRelative = ['apps/web/src/app.ts', 'apps/web/src/lib.tsx'];
120
+ * const packageRelative = convertToPackageRelativePaths(repoRelative, 'apps/web/');
121
+ * // Returns: ['src/app.ts', 'src/lib.tsx']
122
+ */
123
+ export function convertToPackageRelativePaths(repoRelativePaths, packagePrefix) {
124
+ // Normalize the prefix to ensure it ends with a slash
125
+ const normalizedPrefix = packagePrefix.endsWith('/') ? packagePrefix : `${packagePrefix}/`;
126
+ return repoRelativePaths
127
+ .filter((filePath) => filePath.startsWith(normalizedPrefix))
128
+ .map((filePath) => filePath.slice(normalizedPrefix.length));
129
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @file incremental-test.mjs
3
+ * @description Helpers for Vitest --changed execution
4
+ *
5
+ * WU-1920: Add incremental test execution to gates
6
+ * WU-2504: Use Vitest --changed instead of custom file diff logic
7
+ */
8
+ /**
9
+ * Glob patterns to exclude slow integration/golden tests from changed runs.
10
+ * @type {string[]}
11
+ */
12
+ export declare const VITEST_CHANGED_EXCLUDES: readonly string[];
13
+ /**
14
+ * File extensions considered executable code for test runs.
15
+ * @type {string[]}
16
+ */
17
+ export declare const CODE_FILE_EXTENSIONS: readonly string[];
18
+ /**
19
+ * Check if a path points to a code file that should trigger full tests.
20
+ *
21
+ * @param {string} filePath - Path to check
22
+ * @returns {boolean} True if the path has a code extension
23
+ */
24
+ export declare function isCodeFilePath(filePath: any): boolean;
25
+ /**
26
+ * Options for building Vitest changed args
27
+ */
28
+ interface BuildVitestChangedArgsOptions {
29
+ /** Base branch for diff */
30
+ baseBranch?: string;
31
+ }
32
+ /**
33
+ * Build Vitest CLI args for --changed runs.
34
+ *
35
+ * @param {BuildVitestChangedArgsOptions} [options]
36
+ * @returns {string[]} Vitest args for changed test runs
37
+ */
38
+ export declare function buildVitestChangedArgs(options?: BuildVitestChangedArgsOptions): string[];
39
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @file incremental-test.mjs
3
+ * @description Helpers for Vitest --changed execution
4
+ *
5
+ * WU-1920: Add incremental test execution to gates
6
+ * WU-2504: Use Vitest --changed instead of custom file diff logic
7
+ */
8
+ import { GIT_REFS } from './wu-constants.js';
9
+ /**
10
+ * Glob patterns to exclude slow integration/golden tests from changed runs.
11
+ * @type {string[]}
12
+ */
13
+ export const VITEST_CHANGED_EXCLUDES = Object.freeze(['**/*.integration.*', '**/golden-*.test.*']);
14
+ /**
15
+ * File extensions considered executable code for test runs.
16
+ * @type {string[]}
17
+ */
18
+ export const CODE_FILE_EXTENSIONS = Object.freeze([
19
+ '.ts',
20
+ '.tsx',
21
+ '.js',
22
+ '.jsx',
23
+ '.js',
24
+ '.cjs',
25
+ '.mts',
26
+ '.cts',
27
+ ]);
28
+ /**
29
+ * Check if a path points to a code file that should trigger full tests.
30
+ *
31
+ * @param {string} filePath - Path to check
32
+ * @returns {boolean} True if the path has a code extension
33
+ */
34
+ export function isCodeFilePath(filePath) {
35
+ if (!filePath || typeof filePath !== 'string') {
36
+ return false;
37
+ }
38
+ const normalized = filePath.replace(/\\/g, '/').toLowerCase();
39
+ return CODE_FILE_EXTENSIONS.some((ext) => normalized.endsWith(ext));
40
+ }
41
+ /**
42
+ * Build Vitest CLI args for --changed runs.
43
+ *
44
+ * @param {BuildVitestChangedArgsOptions} [options]
45
+ * @returns {string[]} Vitest args for changed test runs
46
+ */
47
+ export function buildVitestChangedArgs(options = {}) {
48
+ const { baseBranch = GIT_REFS.ORIGIN_MAIN } = options;
49
+ const args = [
50
+ '--changed',
51
+ baseBranch,
52
+ '--run',
53
+ '--passWithNoTests',
54
+ '--maxWorkers=1',
55
+ '--teardownTimeout=30000',
56
+ ];
57
+ for (const pattern of VITEST_CHANGED_EXCLUDES) {
58
+ args.push(`--exclude='${pattern}'`);
59
+ }
60
+ return args;
61
+ }