@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,139 @@
1
+ /**
2
+ * WU-2241: Cleanup Lock Module
3
+ *
4
+ * Provides atomic locking mechanism for wu:done cleanup operations
5
+ * to prevent race conditions during concurrent worktree/branch cleanup.
6
+ *
7
+ * Features:
8
+ * - File-based lock with PID, timestamp, and worktree path
9
+ * - Stale lock detection and auto-cleanup (5-minute threshold)
10
+ * - Zombie lock detection (PID not running)
11
+ * - Idempotent re-acquisition for same WU
12
+ * - Guaranteed cleanup via withCleanupLock wrapper
13
+ *
14
+ * Lock ordering (WU-2241):
15
+ * Lane lock (phase-scoped) -> Merge lock -> Cleanup lock -> State store lock
16
+ *
17
+ * Pattern: Follows lane-lock.mjs and merge-lock.mjs internal patterns.
18
+ *
19
+ * @module cleanup-lock
20
+ */
21
+ /**
22
+ * Default timeout for waiting to acquire lock (ms)
23
+ * After this time, acquisition fails if lock is held
24
+ */
25
+ export declare const CLEANUP_LOCK_TIMEOUT_MS = 30000;
26
+ /**
27
+ * Time after which a cleanup lock is considered stale (ms)
28
+ * Should be greater than expected cleanup operation duration
29
+ * Cleanup is slower than merge, so longer timeout
30
+ */
31
+ export declare const CLEANUP_LOCK_STALE_MS: number;
32
+ /**
33
+ * @typedef {Object} CleanupLockInfo
34
+ * @property {string} wuId - WU ID that holds the lock
35
+ * @property {string} lockId - Unique lock identifier
36
+ * @property {string} createdAt - ISO timestamp when lock was created
37
+ * @property {number} pid - Process ID of lock holder
38
+ * @property {string} hostname - Hostname of lock holder
39
+ * @property {string} [worktreePath] - Path to worktree being cleaned up
40
+ */
41
+ /**
42
+ * @typedef {Object} CleanupAcquireResult
43
+ * @property {boolean} acquired - Whether lock was acquired
44
+ * @property {string} [lockId] - Lock ID if acquired
45
+ * @property {string} [heldBy] - WU ID holding the lock if not acquired
46
+ * @property {string} [heldSince] - ISO timestamp if not acquired
47
+ */
48
+ /**
49
+ * Options for lock file operations
50
+ */
51
+ interface BaseDirOptions {
52
+ /** Base directory (defaults to cwd) */
53
+ baseDir?: string;
54
+ }
55
+ /**
56
+ * Check if a lock is stale (older than CLEANUP_LOCK_STALE_MS)
57
+ *
58
+ * @param {CleanupLockInfo} lockInfo - Lock info to check
59
+ * @returns {boolean} True if lock is stale
60
+ */
61
+ export declare function isCleanupLockStale(lockInfo: any): boolean;
62
+ /**
63
+ * Check if a lock is a zombie (PID not running)
64
+ *
65
+ * @param {CleanupLockInfo} lockInfo - Lock info to check
66
+ * @returns {boolean} True if lock is a zombie (PID not running)
67
+ */
68
+ export declare function isCleanupLockZombie(lockInfo: any): boolean;
69
+ /**
70
+ * Check if cleanup lock is currently held
71
+ *
72
+ * @param {BaseDirOptions} [options]
73
+ * @returns {boolean} True if lock is held (and not stale)
74
+ */
75
+ export declare function isCleanupLocked(options?: BaseDirOptions): boolean;
76
+ /**
77
+ * Get information about current cleanup lock
78
+ *
79
+ * @param {BaseDirOptions} [options]
80
+ * @returns {CleanupLockInfo|null} Lock info or null if unlocked
81
+ */
82
+ export declare function getCleanupLockInfo(options?: BaseDirOptions): any;
83
+ /**
84
+ * Options for acquiring cleanup lock
85
+ */
86
+ export interface AcquireCleanupLockOptions extends BaseDirOptions {
87
+ /** Max time to wait for lock (default: CLEANUP_LOCK_TIMEOUT_MS) */
88
+ waitMs?: number;
89
+ /** Path to worktree being cleaned up */
90
+ worktreePath?: string | null;
91
+ }
92
+ /**
93
+ * Attempt to acquire the cleanup lock
94
+ *
95
+ * Will wait up to waitMs for lock to become available.
96
+ * If the same WU already holds the lock, re-acquisition succeeds (idempotent).
97
+ * Stale and zombie locks are automatically cleaned up.
98
+ *
99
+ * @param {string} wuId - WU ID requesting the lock
100
+ * @param {AcquireCleanupLockOptions} [options]
101
+ * @returns {Promise<CleanupAcquireResult>} Acquisition result
102
+ */
103
+ export declare function acquireCleanupLock(wuId: any, options?: AcquireCleanupLockOptions): Promise<{
104
+ acquired: boolean;
105
+ lockId: any;
106
+ heldBy?: undefined;
107
+ heldSince?: undefined;
108
+ } | {
109
+ acquired: boolean;
110
+ heldBy: any;
111
+ heldSince: any;
112
+ lockId?: undefined;
113
+ }>;
114
+ /**
115
+ * Release the cleanup lock
116
+ *
117
+ * Only releases if the provided lockId matches the current lock.
118
+ * This prevents accidentally releasing another WU's lock.
119
+ *
120
+ * @param {string} lockId - Lock ID to release
121
+ * @param {BaseDirOptions} [options]
122
+ * @returns {boolean} True if lock was released
123
+ */
124
+ export declare function releaseCleanupLock(lockId: any, options?: BaseDirOptions): boolean;
125
+ /**
126
+ * Execute a function while holding the cleanup lock
127
+ *
128
+ * Guarantees the lock is released after function completes,
129
+ * even if the function throws an error.
130
+ *
131
+ * @template T
132
+ * @param {string} wuId - WU ID requesting the lock
133
+ * @param {function(): Promise<T>} fn - Async function to execute
134
+ * @param {AcquireCleanupLockOptions} [options]
135
+ * @returns {Promise<T>} Result of function execution
136
+ * @throws {Error} If lock cannot be acquired or function throws
137
+ */
138
+ export declare function withCleanupLock<T>(wuId: string, fn: () => Promise<T>, options?: AcquireCleanupLockOptions): Promise<T>;
139
+ export {};
@@ -0,0 +1,313 @@
1
+ /**
2
+ * WU-2241: Cleanup Lock Module
3
+ *
4
+ * Provides atomic locking mechanism for wu:done cleanup operations
5
+ * to prevent race conditions during concurrent worktree/branch cleanup.
6
+ *
7
+ * Features:
8
+ * - File-based lock with PID, timestamp, and worktree path
9
+ * - Stale lock detection and auto-cleanup (5-minute threshold)
10
+ * - Zombie lock detection (PID not running)
11
+ * - Idempotent re-acquisition for same WU
12
+ * - Guaranteed cleanup via withCleanupLock wrapper
13
+ *
14
+ * Lock ordering (WU-2241):
15
+ * Lane lock (phase-scoped) -> Merge lock -> Cleanup lock -> State store lock
16
+ *
17
+ * Pattern: Follows lane-lock.mjs and merge-lock.mjs internal patterns.
18
+ *
19
+ * @module cleanup-lock
20
+ */
21
+ /* eslint-disable security/detect-non-literal-fs-filename -- Lock file paths are computed from trusted sources */
22
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, closeSync, } from 'node:fs';
23
+ import path from 'node:path';
24
+ import crypto from 'node:crypto';
25
+ import { LOG_PREFIX, EMOJI } from './wu-constants.js';
26
+ import { createError, ErrorCodes } from './error-handler.js';
27
+ /**
28
+ * Default timeout for waiting to acquire lock (ms)
29
+ * After this time, acquisition fails if lock is held
30
+ */
31
+ export const CLEANUP_LOCK_TIMEOUT_MS = 30000; // 30 seconds
32
+ /**
33
+ * Time after which a cleanup lock is considered stale (ms)
34
+ * Should be greater than expected cleanup operation duration
35
+ * Cleanup is slower than merge, so longer timeout
36
+ */
37
+ export const CLEANUP_LOCK_STALE_MS = 5 * 60 * 1000; // 5 minutes
38
+ /** Lock file name within .beacon directory */
39
+ const LOCK_FILE_NAME = 'cleanup.lock';
40
+ /**
41
+ * Polling interval for lock acquisition retries
42
+ */
43
+ const LOCK_POLL_INTERVAL_MS = 500;
44
+ /**
45
+ * Get the path to the cleanup lock file
46
+ *
47
+ * @param {BaseDirOptions} [options]
48
+ * @returns {string} Path to lock file
49
+ */
50
+ function getLockPath(options = {}) {
51
+ const baseDir = options.baseDir || process.cwd();
52
+ return path.join(baseDir, '.beacon', LOCK_FILE_NAME);
53
+ }
54
+ /**
55
+ * Read lock file contents
56
+ *
57
+ * @param {BaseDirOptions} [options]
58
+ * @returns {CleanupLockInfo|null} Lock info or null if no lock
59
+ */
60
+ function readLockFile(options = {}) {
61
+ const lockPath = getLockPath(options);
62
+ if (!existsSync(lockPath)) {
63
+ return null;
64
+ }
65
+ try {
66
+ const content = readFileSync(lockPath, 'utf8');
67
+ return JSON.parse(content);
68
+ }
69
+ catch {
70
+ // Corrupted lock file - treat as no lock
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Delete lock file
76
+ *
77
+ * @param {BaseDirOptions} [options]
78
+ */
79
+ function deleteLockFile(options = {}) {
80
+ const lockPath = getLockPath(options);
81
+ if (existsSync(lockPath)) {
82
+ unlinkSync(lockPath);
83
+ }
84
+ }
85
+ /**
86
+ * Check if a lock is stale (older than CLEANUP_LOCK_STALE_MS)
87
+ *
88
+ * @param {CleanupLockInfo} lockInfo - Lock info to check
89
+ * @returns {boolean} True if lock is stale
90
+ */
91
+ export function isCleanupLockStale(lockInfo) {
92
+ if (!lockInfo || !lockInfo.createdAt) {
93
+ return true;
94
+ }
95
+ const lockTime = new Date(lockInfo.createdAt).getTime();
96
+ const age = Date.now() - lockTime;
97
+ return age > CLEANUP_LOCK_STALE_MS;
98
+ }
99
+ /**
100
+ * Check if a lock is a zombie (PID not running)
101
+ *
102
+ * @param {CleanupLockInfo} lockInfo - Lock info to check
103
+ * @returns {boolean} True if lock is a zombie (PID not running)
104
+ */
105
+ export function isCleanupLockZombie(lockInfo) {
106
+ if (!lockInfo || typeof lockInfo.pid !== 'number') {
107
+ return true;
108
+ }
109
+ try {
110
+ process.kill(lockInfo.pid, 0); // Signal 0 = check existence
111
+ return false; // Process exists
112
+ }
113
+ catch {
114
+ return true; // Process doesn't exist
115
+ }
116
+ }
117
+ /**
118
+ * Generate a unique lock ID
119
+ *
120
+ * @returns {string} Unique lock ID
121
+ */
122
+ function generateLockId() {
123
+ return crypto.randomUUID();
124
+ }
125
+ /**
126
+ * Sleep for specified milliseconds
127
+ *
128
+ * @param {number} ms - Milliseconds to sleep
129
+ * @returns {Promise<void>}
130
+ */
131
+ function sleep(ms) {
132
+ return new Promise((resolve) => setTimeout(resolve, ms));
133
+ }
134
+ /**
135
+ * Check if cleanup lock is currently held
136
+ *
137
+ * @param {BaseDirOptions} [options]
138
+ * @returns {boolean} True if lock is held (and not stale)
139
+ */
140
+ export function isCleanupLocked(options = {}) {
141
+ const lockInfo = readLockFile(options);
142
+ if (!lockInfo) {
143
+ return false;
144
+ }
145
+ // Stale or zombie locks are treated as unlocked
146
+ return !isCleanupLockStale(lockInfo) && !isCleanupLockZombie(lockInfo);
147
+ }
148
+ /**
149
+ * Get information about current cleanup lock
150
+ *
151
+ * @param {BaseDirOptions} [options]
152
+ * @returns {CleanupLockInfo|null} Lock info or null if unlocked
153
+ */
154
+ export function getCleanupLockInfo(options = {}) {
155
+ const lockInfo = readLockFile(options);
156
+ if (!lockInfo || isCleanupLockStale(lockInfo) || isCleanupLockZombie(lockInfo)) {
157
+ return null;
158
+ }
159
+ return lockInfo;
160
+ }
161
+ /**
162
+ * Attempt atomic lock file creation
163
+ * Returns 'acquired' | 'race' | throws on other errors
164
+ *
165
+ * @param {CleanupLockInfo} lockInfo - Lock info to write
166
+ * @param {Object} options - Options with baseDir
167
+ * @returns {'acquired' | 'race'} Result of acquisition attempt
168
+ */
169
+ function tryAtomicLockCreate(lockInfo, options) {
170
+ const lockPath = getLockPath(options);
171
+ const beaconDir = path.dirname(lockPath);
172
+ if (!existsSync(beaconDir)) {
173
+ mkdirSync(beaconDir, { recursive: true });
174
+ }
175
+ try {
176
+ // Use 'wx' flag for atomic creation
177
+ const fd = openSync(lockPath, 'wx');
178
+ writeFileSync(lockPath, JSON.stringify(lockInfo, null, 2));
179
+ closeSync(fd);
180
+ return 'acquired';
181
+ }
182
+ catch (err) {
183
+ if (err.code === 'EEXIST') {
184
+ return 'race';
185
+ }
186
+ throw err;
187
+ }
188
+ }
189
+ /**
190
+ * Handle stale or zombie lock cleanup
191
+ * Returns 'retry' if lock was cleaned, null if lock is valid
192
+ *
193
+ * @param {CleanupLockInfo} existingLock - Existing lock info
194
+ * @param {Object} options - Options with baseDir
195
+ * @returns {'retry' | null} Whether to retry acquisition
196
+ */
197
+ function handleStaleLock(existingLock, options) {
198
+ if (isCleanupLockStale(existingLock)) {
199
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cleaning up stale cleanup lock from ${existingLock.wuId}`);
200
+ deleteLockFile(options);
201
+ return 'retry';
202
+ }
203
+ if (isCleanupLockZombie(existingLock)) {
204
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cleaning up zombie cleanup lock from ${existingLock.wuId} (PID ${existingLock.pid} not running)`);
205
+ deleteLockFile(options);
206
+ return 'retry';
207
+ }
208
+ return null;
209
+ }
210
+ /**
211
+ * Attempt to acquire the cleanup lock
212
+ *
213
+ * Will wait up to waitMs for lock to become available.
214
+ * If the same WU already holds the lock, re-acquisition succeeds (idempotent).
215
+ * Stale and zombie locks are automatically cleaned up.
216
+ *
217
+ * @param {string} wuId - WU ID requesting the lock
218
+ * @param {AcquireCleanupLockOptions} [options]
219
+ * @returns {Promise<CleanupAcquireResult>} Acquisition result
220
+ */
221
+ export async function acquireCleanupLock(wuId, options = {}) {
222
+ const { baseDir, waitMs = CLEANUP_LOCK_TIMEOUT_MS, worktreePath = null } = options;
223
+ const startTime = Date.now();
224
+ while (true) {
225
+ const existingLock = readLockFile({ baseDir });
226
+ // No lock exists - acquire it
227
+ if (!existingLock) {
228
+ const lockId = generateLockId();
229
+ const lockInfo = {
230
+ wuId,
231
+ lockId,
232
+ createdAt: new Date().toISOString(),
233
+ pid: process.pid,
234
+ hostname: process.env.HOSTNAME || 'unknown',
235
+ worktreePath,
236
+ };
237
+ const result = tryAtomicLockCreate(lockInfo, { baseDir });
238
+ if (result === 'race') {
239
+ continue; // Race condition - retry
240
+ }
241
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Acquired cleanup lock for ${wuId}`);
242
+ return { acquired: true, lockId };
243
+ }
244
+ // Check for stale/zombie locks
245
+ if (handleStaleLock(existingLock, { baseDir }) === 'retry') {
246
+ continue;
247
+ }
248
+ // Same WU already holds lock - return existing lock ID (idempotent)
249
+ if (existingLock.wuId === wuId) {
250
+ return { acquired: true, lockId: existingLock.lockId };
251
+ }
252
+ // Different WU holds lock - check if we should wait
253
+ const elapsed = Date.now() - startTime;
254
+ if (elapsed >= waitMs) {
255
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cleanup lock held by ${existingLock.wuId} since ${existingLock.createdAt}`);
256
+ return {
257
+ acquired: false,
258
+ heldBy: existingLock.wuId,
259
+ heldSince: existingLock.createdAt,
260
+ };
261
+ }
262
+ // Wait and retry
263
+ await sleep(LOCK_POLL_INTERVAL_MS);
264
+ }
265
+ }
266
+ /**
267
+ * Release the cleanup lock
268
+ *
269
+ * Only releases if the provided lockId matches the current lock.
270
+ * This prevents accidentally releasing another WU's lock.
271
+ *
272
+ * @param {string} lockId - Lock ID to release
273
+ * @param {BaseDirOptions} [options]
274
+ * @returns {boolean} True if lock was released
275
+ */
276
+ export function releaseCleanupLock(lockId, options = {}) {
277
+ const existingLock = readLockFile(options);
278
+ if (!existingLock) {
279
+ return false;
280
+ }
281
+ if (existingLock.lockId !== lockId) {
282
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cannot release cleanup lock - lockId mismatch`);
283
+ return false;
284
+ }
285
+ deleteLockFile(options);
286
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Released cleanup lock for ${existingLock.wuId}`);
287
+ return true;
288
+ }
289
+ /**
290
+ * Execute a function while holding the cleanup lock
291
+ *
292
+ * Guarantees the lock is released after function completes,
293
+ * even if the function throws an error.
294
+ *
295
+ * @template T
296
+ * @param {string} wuId - WU ID requesting the lock
297
+ * @param {function(): Promise<T>} fn - Async function to execute
298
+ * @param {AcquireCleanupLockOptions} [options]
299
+ * @returns {Promise<T>} Result of function execution
300
+ * @throws {Error} If lock cannot be acquired or function throws
301
+ */
302
+ export async function withCleanupLock(wuId, fn, options = {}) {
303
+ const result = await acquireCleanupLock(wuId, options);
304
+ if (!result.acquired) {
305
+ throw createError(ErrorCodes.LOCK_ERROR, `Cannot acquire cleanup lock - held by ${result.heldBy} since ${result.heldSince}`, { wuId, heldBy: result.heldBy, heldSince: result.heldSince });
306
+ }
307
+ try {
308
+ return await fn();
309
+ }
310
+ finally {
311
+ releaseCleanupLock(result.lockId, options);
312
+ }
313
+ }
@@ -0,0 +1,146 @@
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
+ interface ExistValidationResult {
25
+ valid: boolean;
26
+ errors: string[];
27
+ missing: string[];
28
+ }
29
+ interface LaneValidationResult {
30
+ hasWarnings: boolean;
31
+ warnings: string[];
32
+ violations: string[];
33
+ skipped: boolean;
34
+ }
35
+ interface QualityValidationResult {
36
+ valid: boolean;
37
+ errors: string[];
38
+ warnings: string[];
39
+ }
40
+ interface QualityOptions {
41
+ allowTodos?: boolean;
42
+ worktreePath?: string | null;
43
+ }
44
+ interface ValidateOptions {
45
+ mode?: string;
46
+ worktreePath?: string;
47
+ targetBranch?: string;
48
+ lane?: string;
49
+ allowTodos?: boolean;
50
+ }
51
+ /**
52
+ * Validation modes for the unified validator
53
+ * @enum {string}
54
+ */
55
+ export declare const VALIDATION_MODES: Readonly<{
56
+ /** Check file existence - used by wu:done */
57
+ EXIST: "exist";
58
+ /** Check lane pattern matching - used by wu:claim */
59
+ LANE: "lane";
60
+ /** Check code quality (TODOs, mocks) - used by wu:done */
61
+ QUALITY: "quality";
62
+ }>;
63
+ /**
64
+ * Check if a file path is a test file
65
+ * @param {string} filePath - Path to check
66
+ * @returns {boolean} True if file is a test file
67
+ */
68
+ declare function isTestFile(filePath: string): boolean;
69
+ /**
70
+ * Check if a file path is a markdown file
71
+ * @param {string} filePath - Path to check
72
+ * @returns {boolean} True if file is a markdown file
73
+ */
74
+ declare function isMarkdownFile(filePath: string): boolean;
75
+ /**
76
+ * Scan a file for TODO/FIXME/HACK/XXX comments
77
+ * @param {string} filePath - Path to file to scan
78
+ * @returns {{found: boolean, matches: Array<{line: number, text: string, pattern: string}>}}
79
+ */
80
+ declare function scanFileForTODOs(filePath: string): {
81
+ found: boolean;
82
+ matches: Array<{
83
+ line: number;
84
+ text: string;
85
+ pattern: string | null;
86
+ }>;
87
+ };
88
+ /**
89
+ * Scan a file for Mock/Stub/Fake class/function names
90
+ * @param {string} filePath - Path to file to scan
91
+ * @returns {{found: boolean, matches: Array<{line: number, text: string, type: string}>}}
92
+ */
93
+ declare function scanFileForMocks(filePath: string): {
94
+ found: boolean;
95
+ matches: Array<{
96
+ line: number;
97
+ text: string;
98
+ type: string;
99
+ }>;
100
+ };
101
+ /**
102
+ * Unified code path validation function
103
+ *
104
+ * @param {string[]} codePaths - Array of file paths to validate
105
+ * @param {object} options - Validation options
106
+ * @param {string} options.mode - Validation mode: 'exist', 'lane', or 'quality'
107
+ * @param {string} [options.worktreePath] - Worktree path (for 'exist' and 'quality' modes)
108
+ * @param {string} [options.targetBranch] - Target branch (for 'exist' mode, branch-only)
109
+ * @param {string} [options.lane] - Lane name (for 'lane' mode)
110
+ * @param {boolean} [options.allowTodos] - Allow TODO comments (for 'quality' mode)
111
+ * @returns {Promise<ExistValidationResult|LaneValidationResult|QualityValidationResult>}
112
+ */
113
+ export declare function validate(codePaths: string[], options?: ValidateOptions): Promise<ExistValidationResult | LaneValidationResult | QualityValidationResult>;
114
+ interface WUDoc {
115
+ code_paths?: string[];
116
+ }
117
+ interface ExistOptions {
118
+ targetBranch?: string;
119
+ worktreePath?: string | null;
120
+ }
121
+ /**
122
+ * @deprecated Use validate(paths, { mode: 'exist' }) instead
123
+ * Backward-compatible wrapper for validateCodePathsExist
124
+ */
125
+ export declare function validateCodePathsExist(doc: WUDoc, _id: string, options?: ExistOptions): Promise<ExistValidationResult>;
126
+ /**
127
+ * @deprecated Use validate(paths, { mode: 'lane', lane }) instead
128
+ * Backward-compatible wrapper for validateLaneCodePaths
129
+ * NOTE: This must remain SYNCHRONOUS for backward compatibility
130
+ */
131
+ export declare function validateLaneCodePaths(doc: WUDoc, lane: string): LaneValidationResult;
132
+ /**
133
+ * @deprecated Use validate(paths, { mode: 'quality' }) instead
134
+ * Backward-compatible wrapper for validateWUCodePaths
135
+ * NOTE: This must remain SYNCHRONOUS for backward compatibility
136
+ */
137
+ export declare function validateWUCodePaths(codePaths: string[], options?: QualityOptions): QualityValidationResult;
138
+ /**
139
+ * Log lane validation warnings to console.
140
+ * Helper function to format and display warnings consistently.
141
+ *
142
+ * @param {LaneValidationResult} result - Result from validateLaneCodePaths
143
+ * @param {string} logPrefix - Log prefix (e.g., "[wu-claim]")
144
+ */
145
+ export declare function logLaneValidationWarnings(result: LaneValidationResult, logPrefix?: string): void;
146
+ export { isTestFile, isMarkdownFile, scanFileForTODOs, scanFileForMocks };