@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,131 @@
1
+ /**
2
+ * Orphan Worktree Detector (WU-1476)
3
+ *
4
+ * Detects orphan directories in worktrees/ that are not tracked by git worktree.
5
+ * Orphan directories can occur when wu:done fails mid-workflow due to:
6
+ * - Backlog sync issues
7
+ * - Formatting errors
8
+ * - Typecheck failures
9
+ * - Recovery mode interruptions
10
+ *
11
+ * Multi-layer defense strategy:
12
+ * - Layer 1: Explicit cleanup in git-adapter.worktreeRemove()
13
+ * - Layer 2: Orphan detection in wu:prune (this module)
14
+ * - Layer 3: Pre-flight check in wu:claim
15
+ * - Layer 4: Manual utility wu:cleanup-orphans
16
+ *
17
+ * @see {@link tools/wu-prune.mjs} - Primary consumer
18
+ * @see {@link tools/wu-claim.mjs} - Pre-flight orphan check
19
+ * @see {@link tools/lib/git-adapter.mjs} - worktreeRemove with cleanup
20
+ */
21
+ /**
22
+ * Result of orphan detection
23
+ * @typedef {object} OrphanDetectionResult
24
+ * @property {string[]} orphans - List of orphan directory paths (absolute)
25
+ * @property {string[]} tracked - List of tracked worktree paths (absolute)
26
+ * @property {string[]} errors - List of error messages encountered during detection
27
+ */
28
+ /**
29
+ * Parsed worktree entry from git worktree list --porcelain
30
+ */
31
+ interface WorktreeEntry {
32
+ /** Absolute path to worktree */
33
+ path: string;
34
+ /** HEAD commit SHA */
35
+ head?: string;
36
+ /** Branch name (without refs/heads/ prefix) */
37
+ branch?: string;
38
+ }
39
+ /**
40
+ * Parse git worktree list --porcelain output into structured entries
41
+ *
42
+ * @param {string} porcelainOutput - Output from git worktree list --porcelain
43
+ * @returns {WorktreeEntry[]} Parsed worktree entries
44
+ */
45
+ export declare function parseWorktreeList(porcelainOutput: string): WorktreeEntry[];
46
+ /**
47
+ * Get set of tracked worktree paths from git
48
+ *
49
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
50
+ * @returns {Promise<Set<string>>} Set of absolute paths tracked by git worktree
51
+ */
52
+ export declare function getTrackedWorktreePaths(projectRoot: any): Promise<Set<string>>;
53
+ /**
54
+ * Get list of directories in worktrees/ folder
55
+ *
56
+ * @param {string} projectRoot - Absolute path to project root
57
+ * @returns {string[]} List of absolute paths to directories in worktrees/
58
+ */
59
+ export declare function getWorktreeDirectories(projectRoot: any): string[];
60
+ /**
61
+ * Detect orphan worktree directories
62
+ *
63
+ * Compares directories in worktrees/ against git worktree list to find
64
+ * directories that exist on disk but are not tracked by git.
65
+ *
66
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
67
+ * @returns {Promise<OrphanDetectionResult>} Detection result with orphans and tracked paths
68
+ */
69
+ export declare function detectOrphanWorktrees(projectRoot: any): Promise<{
70
+ orphans: string[];
71
+ tracked: any[];
72
+ errors: any[];
73
+ }>;
74
+ /**
75
+ * Check if a specific worktree path is an orphan
76
+ *
77
+ * Used by wu:claim for pre-flight checks before creating a worktree.
78
+ *
79
+ * @param {string} worktreePath - Absolute path to check
80
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
81
+ * @returns {Promise<boolean>} True if the path exists but is not tracked by git
82
+ */
83
+ export declare function isOrphanWorktree(worktreePath: any, projectRoot: any): Promise<boolean>;
84
+ /**
85
+ * Options for removing orphan directories
86
+ */
87
+ export interface RemoveOrphanDirectoryOptions {
88
+ /** If true, don't actually remove */
89
+ dryRun?: boolean;
90
+ }
91
+ /**
92
+ * Remove an orphan directory
93
+ *
94
+ * Safely removes an orphan worktree directory from disk.
95
+ * Only removes directories that are confirmed orphans.
96
+ *
97
+ * @param {string} orphanPath - Absolute path to orphan directory
98
+ * @param {RemoveOrphanDirectoryOptions} [options] - Options
99
+ * @returns {Promise<{removed: boolean, path: string, error?: string}>} Result
100
+ */
101
+ export declare function removeOrphanDirectory(orphanPath: any, options?: RemoveOrphanDirectoryOptions): Promise<{
102
+ removed: boolean;
103
+ path: any;
104
+ error: string;
105
+ dryRun?: undefined;
106
+ } | {
107
+ removed: boolean;
108
+ path: any;
109
+ dryRun: boolean;
110
+ error?: undefined;
111
+ } | {
112
+ removed: boolean;
113
+ path: any;
114
+ error?: undefined;
115
+ dryRun?: undefined;
116
+ }>;
117
+ /**
118
+ * Clean up all orphan directories
119
+ *
120
+ * Detects and removes all orphan worktree directories.
121
+ *
122
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
123
+ * @param {RemoveOrphanDirectoryOptions} [options] - Options
124
+ * @returns {Promise<{detected: number, removed: number, errors: string[]}>} Summary
125
+ */
126
+ export declare function cleanupOrphanDirectories(projectRoot: any, options?: RemoveOrphanDirectoryOptions): Promise<{
127
+ detected: number;
128
+ removed: number;
129
+ errors: any[];
130
+ }>;
131
+ export {};
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Orphan Worktree Detector (WU-1476)
3
+ *
4
+ * Detects orphan directories in worktrees/ that are not tracked by git worktree.
5
+ * Orphan directories can occur when wu:done fails mid-workflow due to:
6
+ * - Backlog sync issues
7
+ * - Formatting errors
8
+ * - Typecheck failures
9
+ * - Recovery mode interruptions
10
+ *
11
+ * Multi-layer defense strategy:
12
+ * - Layer 1: Explicit cleanup in git-adapter.worktreeRemove()
13
+ * - Layer 2: Orphan detection in wu:prune (this module)
14
+ * - Layer 3: Pre-flight check in wu:claim
15
+ * - Layer 4: Manual utility wu:cleanup-orphans
16
+ *
17
+ * @see {@link tools/wu-prune.mjs} - Primary consumer
18
+ * @see {@link tools/wu-claim.mjs} - Pre-flight orphan check
19
+ * @see {@link tools/lib/git-adapter.mjs} - worktreeRemove with cleanup
20
+ */
21
+ import { readdirSync, statSync, existsSync, rmSync } from 'node:fs';
22
+ import path from 'node:path';
23
+ import { getGitForCwd, createGitForPath } from './git-adapter.js';
24
+ import { STRING_LITERALS, DEFAULTS } from './wu-constants.js';
25
+ /**
26
+ * Parse git worktree list --porcelain output into structured entries
27
+ *
28
+ * @param {string} porcelainOutput - Output from git worktree list --porcelain
29
+ * @returns {WorktreeEntry[]} Parsed worktree entries
30
+ */
31
+ export function parseWorktreeList(porcelainOutput) {
32
+ if (!porcelainOutput || porcelainOutput.trim() === '') {
33
+ return [];
34
+ }
35
+ const worktrees = [];
36
+ const lines = porcelainOutput.split(STRING_LITERALS.NEWLINE);
37
+ let current = {};
38
+ for (const line of lines) {
39
+ if (line.startsWith('worktree ')) {
40
+ if (current.path) {
41
+ worktrees.push(current);
42
+ }
43
+ current = { path: line.substring(9).trim() };
44
+ }
45
+ else if (line.startsWith('HEAD ')) {
46
+ current.head = line.substring(5).trim();
47
+ }
48
+ else if (line.startsWith('branch ')) {
49
+ const fullRef = line.substring(7).trim();
50
+ // Extract branch name from refs/heads/...
51
+ current.branch = fullRef.replace(/^refs\/heads\//, '');
52
+ }
53
+ else if (line === '') {
54
+ if (current.path) {
55
+ worktrees.push(current);
56
+ }
57
+ current = {};
58
+ }
59
+ }
60
+ // Handle final entry without trailing newline
61
+ if (current.path) {
62
+ worktrees.push(current);
63
+ }
64
+ return worktrees;
65
+ }
66
+ /**
67
+ * Get set of tracked worktree paths from git
68
+ *
69
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
70
+ * @returns {Promise<Set<string>>} Set of absolute paths tracked by git worktree
71
+ */
72
+ export async function getTrackedWorktreePaths(projectRoot) {
73
+ const git = projectRoot ? createGitForPath(projectRoot) : getGitForCwd();
74
+ const output = await git.worktreeList();
75
+ const entries = parseWorktreeList(output);
76
+ return new Set(entries.map((e) => e.path));
77
+ }
78
+ /**
79
+ * Get list of directories in worktrees/ folder
80
+ *
81
+ * @param {string} projectRoot - Absolute path to project root
82
+ * @returns {string[]} List of absolute paths to directories in worktrees/
83
+ */
84
+ export function getWorktreeDirectories(projectRoot) {
85
+ const worktreesDir = path.join(projectRoot, DEFAULTS.WORKTREES_DIR);
86
+ if (!existsSync(worktreesDir)) {
87
+ return [];
88
+ }
89
+ try {
90
+ const entries = readdirSync(worktreesDir, { withFileTypes: true });
91
+ return entries
92
+ .filter((entry) => entry.isDirectory())
93
+ .map((entry) => path.join(worktreesDir, entry.name));
94
+ }
95
+ catch {
96
+ return [];
97
+ }
98
+ }
99
+ /**
100
+ * Detect orphan worktree directories
101
+ *
102
+ * Compares directories in worktrees/ against git worktree list to find
103
+ * directories that exist on disk but are not tracked by git.
104
+ *
105
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
106
+ * @returns {Promise<OrphanDetectionResult>} Detection result with orphans and tracked paths
107
+ */
108
+ export async function detectOrphanWorktrees(projectRoot) {
109
+ const errors = [];
110
+ const root = projectRoot || process.cwd();
111
+ // Get paths tracked by git worktree
112
+ let trackedPaths;
113
+ try {
114
+ trackedPaths = await getTrackedWorktreePaths(root);
115
+ }
116
+ catch (err) {
117
+ const message = err instanceof Error ? err.message : String(err);
118
+ errors.push(`Failed to get git worktree list: ${message}`);
119
+ return { orphans: [], tracked: [], errors };
120
+ }
121
+ // Get directories on disk
122
+ const diskDirectories = getWorktreeDirectories(root);
123
+ // Find orphans: directories that exist but aren't tracked
124
+ const orphans = diskDirectories.filter((dir) => !trackedPaths.has(dir));
125
+ return {
126
+ orphans,
127
+ tracked: [...trackedPaths],
128
+ errors,
129
+ };
130
+ }
131
+ /**
132
+ * Check if a specific worktree path is an orphan
133
+ *
134
+ * Used by wu:claim for pre-flight checks before creating a worktree.
135
+ *
136
+ * @param {string} worktreePath - Absolute path to check
137
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
138
+ * @returns {Promise<boolean>} True if the path exists but is not tracked by git
139
+ */
140
+ export async function isOrphanWorktree(worktreePath, projectRoot) {
141
+ // If directory doesn't exist, it's not an orphan
142
+ if (!existsSync(worktreePath)) {
143
+ return false;
144
+ }
145
+ // Check if path is in the tracked set
146
+ const trackedPaths = await getTrackedWorktreePaths(projectRoot);
147
+ return !trackedPaths.has(worktreePath);
148
+ }
149
+ /**
150
+ * Remove an orphan directory
151
+ *
152
+ * Safely removes an orphan worktree directory from disk.
153
+ * Only removes directories that are confirmed orphans.
154
+ *
155
+ * @param {string} orphanPath - Absolute path to orphan directory
156
+ * @param {RemoveOrphanDirectoryOptions} [options] - Options
157
+ * @returns {Promise<{removed: boolean, path: string, error?: string}>} Result
158
+ */
159
+ export async function removeOrphanDirectory(orphanPath, options = {}) {
160
+ const { dryRun = false } = options;
161
+ // Verify it exists
162
+ if (!existsSync(orphanPath)) {
163
+ return { removed: false, path: orphanPath, error: 'Directory does not exist' };
164
+ }
165
+ // Verify it's a directory
166
+ try {
167
+ const stat = statSync(orphanPath);
168
+ if (!stat.isDirectory()) {
169
+ return { removed: false, path: orphanPath, error: 'Path is not a directory' };
170
+ }
171
+ }
172
+ catch (err) {
173
+ const message = err instanceof Error ? err.message : String(err);
174
+ return { removed: false, path: orphanPath, error: `Failed to stat: ${message}` };
175
+ }
176
+ // Verify it's in worktrees/ directory (safety check)
177
+ const basename = path.basename(path.dirname(orphanPath));
178
+ if (basename !== DEFAULTS.WORKTREES_DIR) {
179
+ return {
180
+ removed: false,
181
+ path: orphanPath,
182
+ error: `Safety check failed: not in ${DEFAULTS.WORKTREES_DIR}/ directory`,
183
+ };
184
+ }
185
+ if (dryRun) {
186
+ return { removed: false, path: orphanPath, dryRun: true };
187
+ }
188
+ // Remove the directory
189
+ try {
190
+ rmSync(orphanPath, { recursive: true, force: true });
191
+ return { removed: true, path: orphanPath };
192
+ }
193
+ catch (err) {
194
+ const message = err instanceof Error ? err.message : String(err);
195
+ return { removed: false, path: orphanPath, error: `Failed to remove: ${message}` };
196
+ }
197
+ }
198
+ /**
199
+ * Clean up all orphan directories
200
+ *
201
+ * Detects and removes all orphan worktree directories.
202
+ *
203
+ * @param {string} [projectRoot] - Project root directory (defaults to cwd)
204
+ * @param {RemoveOrphanDirectoryOptions} [options] - Options
205
+ * @returns {Promise<{detected: number, removed: number, errors: string[]}>} Summary
206
+ */
207
+ export async function cleanupOrphanDirectories(projectRoot, options = {}) {
208
+ const { dryRun = false } = options;
209
+ const result = await detectOrphanWorktrees(projectRoot);
210
+ const errors = [...result.errors];
211
+ let removed = 0;
212
+ for (const orphanPath of result.orphans) {
213
+ const removeResult = await removeOrphanDirectory(orphanPath, { dryRun });
214
+ if (removeResult.removed) {
215
+ removed++;
216
+ }
217
+ else if (removeResult.error) {
218
+ errors.push(`${orphanPath}: ${removeResult.error}`);
219
+ }
220
+ }
221
+ return {
222
+ detected: result.orphans.length,
223
+ removed,
224
+ errors,
225
+ };
226
+ }
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Path Classification Utilities for WU Tooling
4
+ *
5
+ * WU-1255: Classifies file paths to determine test scoping.
6
+ * Uses string methods (no regex) for path prefix matching.
7
+ *
8
+ * Paths that skip web app tests:
9
+ * - Documentation: docs/, ai/, .claude/, README*, CLAUDE*.md
10
+ * - Tooling: tools/, scripts/
11
+ *
12
+ * @see {@link tools/lib/wu-done-validators.mjs} - Consumer for detectDocsOnlyByPaths
13
+ */
14
+ /**
15
+ * Prefixes for paths that should skip web app tests.
16
+ * These are paths for docs, tooling, and configuration that don't affect app functionality.
17
+ *
18
+ * @constant {string[]}
19
+ */
20
+ export declare const SKIP_TESTS_PREFIXES: readonly string[];
21
+ /**
22
+ * Root file patterns that should skip web app tests.
23
+ * These are files in the root directory that are documentation.
24
+ *
25
+ * @constant {string[]}
26
+ */
27
+ export declare const SKIP_TESTS_ROOT_FILES: readonly string[];
28
+ /**
29
+ * Check if a single file path should skip web app tests.
30
+ *
31
+ * Uses string methods for matching:
32
+ * - `startsWith` for directory prefixes (docs/, tools/, etc.)
33
+ * - `toLowerCase` for case-insensitive root file matching
34
+ *
35
+ * @param {string|null|undefined} filePath - File path to check
36
+ * @returns {boolean} True if path should skip web app tests
37
+ *
38
+ * @example
39
+ * isSkipWebTestsPath('docs/README.md') // true
40
+ * isSkipWebTestsPath('tools/wu-done.js') // true
41
+ * isSkipWebTestsPath('apps/web/src/page.tsx') // false
42
+ */
43
+ export declare function isSkipWebTestsPath(filePath: any): boolean;
44
+ /**
45
+ * Check if ALL code paths should skip web app tests.
46
+ *
47
+ * Returns true only if EVERY path in the array is a skip-tests path.
48
+ * This is the aggregate check for WU YAML code_paths array.
49
+ *
50
+ * @param {string[]|null|undefined} codePaths - Array of file paths from WU YAML
51
+ * @returns {boolean} True if all paths should skip web app tests
52
+ *
53
+ * @example
54
+ * shouldSkipWebTests(['docs/README.md', 'tools/wu-done.js']) // true
55
+ * shouldSkipWebTests(['docs/README.md', 'apps/web/src/page.tsx']) // false
56
+ */
57
+ export declare function shouldSkipWebTests(codePaths: any): boolean;
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Path Classification Utilities for WU Tooling
4
+ *
5
+ * WU-1255: Classifies file paths to determine test scoping.
6
+ * Uses string methods (no regex) for path prefix matching.
7
+ *
8
+ * Paths that skip web app tests:
9
+ * - Documentation: docs/, ai/, .claude/, README*, CLAUDE*.md
10
+ * - Tooling: tools/, scripts/
11
+ *
12
+ * @see {@link tools/lib/wu-done-validators.mjs} - Consumer for detectDocsOnlyByPaths
13
+ */
14
+ /**
15
+ * Prefixes for paths that should skip web app tests.
16
+ * These are paths for docs, tooling, and configuration that don't affect app functionality.
17
+ *
18
+ * @constant {string[]}
19
+ */
20
+ export const SKIP_TESTS_PREFIXES = Object.freeze([
21
+ 'docs/',
22
+ 'ai/',
23
+ '.claude/',
24
+ 'tools/', // WU-1255: Tooling WUs
25
+ 'scripts/', // WU-1255: Scripts WUs
26
+ ]);
27
+ /**
28
+ * Root file patterns that should skip web app tests.
29
+ * These are files in the root directory that are documentation.
30
+ *
31
+ * @constant {string[]}
32
+ */
33
+ export const SKIP_TESTS_ROOT_FILES = Object.freeze([
34
+ 'readme', // Case-insensitive match
35
+ 'claude', // CLAUDE.md, CLAUDE-core.md, etc.
36
+ ]);
37
+ /**
38
+ * Check if a single file path should skip web app tests.
39
+ *
40
+ * Uses string methods for matching:
41
+ * - `startsWith` for directory prefixes (docs/, tools/, etc.)
42
+ * - `toLowerCase` for case-insensitive root file matching
43
+ *
44
+ * @param {string|null|undefined} filePath - File path to check
45
+ * @returns {boolean} True if path should skip web app tests
46
+ *
47
+ * @example
48
+ * isSkipWebTestsPath('docs/README.md') // true
49
+ * isSkipWebTestsPath('tools/wu-done.js') // true
50
+ * isSkipWebTestsPath('apps/web/src/page.tsx') // false
51
+ */
52
+ export function isSkipWebTestsPath(filePath) {
53
+ if (!filePath || typeof filePath !== 'string') {
54
+ return false;
55
+ }
56
+ const path = filePath.trim();
57
+ if (path.length === 0) {
58
+ return false;
59
+ }
60
+ // Check directory prefixes (docs/, ai/, .claude/, tools/, scripts/)
61
+ for (const prefix of SKIP_TESTS_PREFIXES) {
62
+ if (path.startsWith(prefix)) {
63
+ return true;
64
+ }
65
+ }
66
+ // Check root file patterns (README*, CLAUDE*.md)
67
+ const lowerPath = path.toLowerCase();
68
+ for (const pattern of SKIP_TESTS_ROOT_FILES) {
69
+ if (lowerPath.startsWith(pattern)) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+ /**
76
+ * Check if ALL code paths should skip web app tests.
77
+ *
78
+ * Returns true only if EVERY path in the array is a skip-tests path.
79
+ * This is the aggregate check for WU YAML code_paths array.
80
+ *
81
+ * @param {string[]|null|undefined} codePaths - Array of file paths from WU YAML
82
+ * @returns {boolean} True if all paths should skip web app tests
83
+ *
84
+ * @example
85
+ * shouldSkipWebTests(['docs/README.md', 'tools/wu-done.js']) // true
86
+ * shouldSkipWebTests(['docs/README.md', 'apps/web/src/page.tsx']) // false
87
+ */
88
+ export function shouldSkipWebTests(codePaths) {
89
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
90
+ return false;
91
+ }
92
+ return codePaths.every((path) => isSkipWebTestsPath(path));
93
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * WU-2278: Piped Command Detection
3
+ *
4
+ * Detects when pnpm dependency commands are being executed with piped input,
5
+ * which can bypass interactive prompts and cause security issues.
6
+ *
7
+ * Note: No external library exists for this specific shell command analysis.
8
+ *
9
+ * @module piped-command-detector
10
+ */
11
+ /**
12
+ * Check if a command is a piped pnpm dependency command
13
+ *
14
+ * Detects patterns like:
15
+ * - echo "y" | pnpm add foo
16
+ * - yes | pnpm install
17
+ * - pnpm add foo < /dev/null
18
+ * - pnpm install <<< "y"
19
+ *
20
+ * Does NOT flag:
21
+ * - pnpm test | grep foo (pnpm not receiving input)
22
+ * - pnpm add foo (no pipe)
23
+ *
24
+ * @param {string} command - Shell command to analyze
25
+ * @returns {boolean} True if command is a piped pnpm dependency command
26
+ */
27
+ export declare function isPipedPnpmCommand(command: any): boolean;
28
+ /**
29
+ * Check if a command contains any dependency-mutating pnpm subcommand
30
+ *
31
+ * @param {string} command - Shell command to analyze
32
+ * @returns {boolean} True if command contains pnpm dependency mutation
33
+ */
34
+ export declare function containsPnpmDependencyCommand(command: any): boolean;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * WU-2278: Piped Command Detection
3
+ *
4
+ * Detects when pnpm dependency commands are being executed with piped input,
5
+ * which can bypass interactive prompts and cause security issues.
6
+ *
7
+ * Note: No external library exists for this specific shell command analysis.
8
+ *
9
+ * @module piped-command-detector
10
+ */
11
+ /**
12
+ * List of pnpm commands that mutate dependencies
13
+ * @constant {string[]}
14
+ */
15
+ const DEPENDENCY_COMMANDS = ['add', 'install', 'i', 'remove', 'rm', 'uninstall', 'update', 'up'];
16
+ /**
17
+ * Check if a command is a piped pnpm dependency command
18
+ *
19
+ * Detects patterns like:
20
+ * - echo "y" | pnpm add foo
21
+ * - yes | pnpm install
22
+ * - pnpm add foo < /dev/null
23
+ * - pnpm install <<< "y"
24
+ *
25
+ * Does NOT flag:
26
+ * - pnpm test | grep foo (pnpm not receiving input)
27
+ * - pnpm add foo (no pipe)
28
+ *
29
+ * @param {string} command - Shell command to analyze
30
+ * @returns {boolean} True if command is a piped pnpm dependency command
31
+ */
32
+ export function isPipedPnpmCommand(command) {
33
+ if (!command || typeof command !== 'string') {
34
+ return false;
35
+ }
36
+ // Pattern 1: Something piped TO pnpm (before pnpm in command)
37
+ // e.g., "echo y | pnpm add", "yes | pnpm install"
38
+ const pipeToPattern = /\|\s*pnpm\s+(add|install|i|remove|rm|uninstall|update|up)\b/i;
39
+ if (pipeToPattern.test(command)) {
40
+ return true;
41
+ }
42
+ // Pattern 2: Input redirection (< or <<<)
43
+ // e.g., "pnpm add foo < /dev/null", "pnpm install <<< y"
44
+ const redirectPattern = /pnpm\s+(add|install|i|remove|rm|uninstall|update|up)\b[^|]*<(?!<)/i;
45
+ const heredocPattern = /pnpm\s+(add|install|i|remove|rm|uninstall|update|up)\b[^|]*<<</i;
46
+ if (redirectPattern.test(command) || heredocPattern.test(command)) {
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+ /**
52
+ * Check if a command contains any dependency-mutating pnpm subcommand
53
+ *
54
+ * @param {string} command - Shell command to analyze
55
+ * @returns {boolean} True if command contains pnpm dependency mutation
56
+ */
57
+ export function containsPnpmDependencyCommand(command) {
58
+ if (!command || typeof command !== 'string') {
59
+ return false;
60
+ }
61
+ // eslint-disable-next-line security/detect-non-literal-regexp -- Pattern built from const array, not user input
62
+ const pattern = new RegExp(`pnpm\\s+(${DEPENDENCY_COMMANDS.join('|')})\\b`, 'i');
63
+ return pattern.test(command);
64
+ }