@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,251 @@
1
+ /**
2
+ * WU-1747: Merge Lock Module
3
+ *
4
+ * Provides atomic locking mechanism for wu:done merge operations
5
+ * to prevent race conditions during concurrent completions.
6
+ *
7
+ * Features:
8
+ * - File-based lock with PID and timestamp
9
+ * - Stale lock detection and auto-cleanup
10
+ * - Idempotent re-acquisition for same WU
11
+ * - Guaranteed cleanup via withMergeLock wrapper
12
+ *
13
+ * @module merge-lock
14
+ */
15
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
16
+ import path from 'node:path';
17
+ import crypto from 'node:crypto';
18
+ import { LOG_PREFIX, EMOJI } from './wu-constants.js';
19
+ import { createError, ErrorCodes } from './error-handler.js';
20
+ /**
21
+ * Default timeout for waiting to acquire lock (ms)
22
+ * After this time, acquisition fails if lock is held
23
+ */
24
+ export const MERGE_LOCK_TIMEOUT_MS = 30000; // 30 seconds
25
+ /**
26
+ * Time after which a lock is considered stale and can be forcibly released (ms)
27
+ * Should be greater than expected merge operation duration
28
+ */
29
+ export const MERGE_LOCK_STALE_MS = 60000; // 60 seconds
30
+ /** Lock file name within .beacon directory */
31
+ const LOCK_FILE_NAME = 'merge.lock';
32
+ /**
33
+ * Polling interval for lock acquisition retries
34
+ */
35
+ const LOCK_POLL_INTERVAL_MS = 500;
36
+ /**
37
+ * Get the path to the merge lock file
38
+ *
39
+ * @param {MergeLockBaseDirOptions} [options]
40
+ * @returns {string} Path to lock file
41
+ */
42
+ function getLockPath(options = {}) {
43
+ const baseDir = options.baseDir || process.cwd();
44
+ return path.join(baseDir, '.beacon', LOCK_FILE_NAME);
45
+ }
46
+ /**
47
+ * Read lock file contents
48
+ *
49
+ * @param {MergeLockBaseDirOptions} [options]
50
+ * @returns {LockInfo|null} Lock info or null if no lock
51
+ */
52
+ function readLockFile(options = {}) {
53
+ const lockPath = getLockPath(options);
54
+ if (!existsSync(lockPath)) {
55
+ return null;
56
+ }
57
+ try {
58
+ const content = readFileSync(lockPath, 'utf8');
59
+ return JSON.parse(content);
60
+ }
61
+ catch {
62
+ // Corrupted lock file - treat as no lock
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Write lock file
68
+ *
69
+ * @param {LockInfo} lockInfo - Lock information to write
70
+ * @param {MergeLockBaseDirOptions} [options]
71
+ */
72
+ function writeLockFile(lockInfo, options = {}) {
73
+ const lockPath = getLockPath(options);
74
+ const beaconDir = path.dirname(lockPath);
75
+ // Ensure .beacon directory exists
76
+ if (!existsSync(beaconDir)) {
77
+ mkdirSync(beaconDir, { recursive: true });
78
+ }
79
+ writeFileSync(lockPath, JSON.stringify(lockInfo, null, 2));
80
+ }
81
+ /**
82
+ * Delete lock file
83
+ *
84
+ * @param {MergeLockBaseDirOptions} [options]
85
+ */
86
+ function deleteLockFile(options = {}) {
87
+ const lockPath = getLockPath(options);
88
+ if (existsSync(lockPath)) {
89
+ unlinkSync(lockPath);
90
+ }
91
+ }
92
+ /**
93
+ * Check if a lock is stale (older than MERGE_LOCK_STALE_MS)
94
+ *
95
+ * @param {LockInfo} lockInfo - Lock info to check
96
+ * @returns {boolean} True if lock is stale
97
+ */
98
+ function isLockStale(lockInfo) {
99
+ if (!lockInfo || !lockInfo.createdAt) {
100
+ return true;
101
+ }
102
+ const lockTime = new Date(lockInfo.createdAt).getTime();
103
+ const age = Date.now() - lockTime;
104
+ return age > MERGE_LOCK_STALE_MS;
105
+ }
106
+ /**
107
+ * Generate a unique lock ID
108
+ *
109
+ * @returns {string} Unique lock ID
110
+ */
111
+ function generateLockId() {
112
+ return crypto.randomUUID();
113
+ }
114
+ /**
115
+ * Sleep for specified milliseconds
116
+ *
117
+ * @param {number} ms - Milliseconds to sleep
118
+ * @returns {Promise<void>}
119
+ */
120
+ function sleep(ms) {
121
+ return new Promise((resolve) => setTimeout(resolve, ms));
122
+ }
123
+ /**
124
+ * Check if merge lock is currently held
125
+ *
126
+ * @param {MergeLockBaseDirOptions} [options]
127
+ * @returns {boolean} True if lock is held (and not stale)
128
+ */
129
+ export function isMergeLocked(options = {}) {
130
+ const lockInfo = readLockFile(options);
131
+ if (!lockInfo) {
132
+ return false;
133
+ }
134
+ // Stale locks are treated as unlocked
135
+ return !isLockStale(lockInfo);
136
+ }
137
+ /**
138
+ * Get information about current merge lock
139
+ *
140
+ * @param {MergeLockBaseDirOptions} [options]
141
+ * @returns {LockInfo|null} Lock info or null if unlocked
142
+ */
143
+ export function getMergeLockInfo(options = {}) {
144
+ const lockInfo = readLockFile(options);
145
+ if (!lockInfo || isLockStale(lockInfo)) {
146
+ return null;
147
+ }
148
+ return lockInfo;
149
+ }
150
+ /**
151
+ * Attempt to acquire the merge lock
152
+ *
153
+ * Will wait up to waitMs for lock to become available.
154
+ * If the same WU already holds the lock, re-acquisition succeeds (idempotent).
155
+ * Stale locks are automatically cleaned up.
156
+ *
157
+ * @param {string} wuId - WU ID requesting the lock
158
+ * @param {AcquireMergeLockOptions} [options]
159
+ * @returns {Promise<AcquireResult>} Acquisition result
160
+ */
161
+ export async function acquireMergeLock(wuId, options = {}) {
162
+ const { baseDir, waitMs = MERGE_LOCK_TIMEOUT_MS } = options;
163
+ const startTime = Date.now();
164
+ while (true) {
165
+ const existingLock = readLockFile({ baseDir });
166
+ // No lock exists - acquire it
167
+ if (!existingLock) {
168
+ const lockId = generateLockId();
169
+ const lockInfo = {
170
+ wuId,
171
+ lockId,
172
+ createdAt: new Date().toISOString(),
173
+ pid: process.pid,
174
+ hostname: process.env.HOSTNAME || 'unknown',
175
+ };
176
+ writeLockFile(lockInfo, { baseDir });
177
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Acquired merge lock for ${wuId}`);
178
+ return { acquired: true, lockId };
179
+ }
180
+ // Lock is stale - clean it up and acquire
181
+ if (isLockStale(existingLock)) {
182
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cleaning up stale lock from ${existingLock.wuId}`);
183
+ deleteLockFile({ baseDir });
184
+ continue; // Retry acquisition
185
+ }
186
+ // Same WU already holds lock - return existing lock ID (idempotent)
187
+ if (existingLock.wuId === wuId) {
188
+ return { acquired: true, lockId: existingLock.lockId };
189
+ }
190
+ // Different WU holds lock - check if we should wait
191
+ const elapsed = Date.now() - startTime;
192
+ if (elapsed >= waitMs) {
193
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Merge lock held by ${existingLock.wuId} since ${existingLock.createdAt}`);
194
+ return {
195
+ acquired: false,
196
+ heldBy: existingLock.wuId,
197
+ heldSince: existingLock.createdAt,
198
+ };
199
+ }
200
+ // Wait and retry
201
+ await sleep(LOCK_POLL_INTERVAL_MS);
202
+ }
203
+ }
204
+ /**
205
+ * Release the merge lock
206
+ *
207
+ * Only releases if the provided lockId matches the current lock.
208
+ * This prevents accidentally releasing another WU's lock.
209
+ *
210
+ * @param {string} lockId - Lock ID to release
211
+ * @param {MergeLockBaseDirOptions} [options]
212
+ * @returns {boolean} True if lock was released
213
+ */
214
+ export function releaseMergeLock(lockId, options = {}) {
215
+ const existingLock = readLockFile(options);
216
+ if (!existingLock) {
217
+ return false;
218
+ }
219
+ if (existingLock.lockId !== lockId) {
220
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.WARNING} Cannot release lock - lockId mismatch`);
221
+ return false;
222
+ }
223
+ deleteLockFile(options);
224
+ console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Released merge lock for ${existingLock.wuId}`);
225
+ return true;
226
+ }
227
+ /**
228
+ * Execute a function while holding the merge lock
229
+ *
230
+ * Guarantees the lock is released after function completes,
231
+ * even if the function throws an error.
232
+ *
233
+ * @template T
234
+ * @param {string} wuId - WU ID requesting the lock
235
+ * @param {function(): Promise<T>} fn - Async function to execute
236
+ * @param {AcquireMergeLockOptions} [options]
237
+ * @returns {Promise<T>} Result of function execution
238
+ * @throws {Error} If lock cannot be acquired or function throws
239
+ */
240
+ export async function withMergeLock(wuId, fn, options = {}) {
241
+ const result = await acquireMergeLock(wuId, options);
242
+ if (!result.acquired) {
243
+ throw createError(ErrorCodes.LOCK_ERROR, `Cannot acquire merge lock - held by ${result.heldBy} since ${result.heldSince}`, { wuId, heldBy: result.heldBy, heldSince: result.heldSince });
244
+ }
245
+ try {
246
+ return await fn();
247
+ }
248
+ finally {
249
+ releaseMergeLock(result.lockId, options);
250
+ }
251
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Micro-Worktree Operations
3
+ *
4
+ * Race-safe micro-worktree isolation pattern extracted from wu-create.mjs (WU-1262).
5
+ * Provides shared infrastructure for commands that need to modify main branch
6
+ * atomically without switching checkout.
7
+ *
8
+ * Part of WU-1262: Race-safe WU creation using micro-worktrees
9
+ * Extended in WU-1274: Add wu:edit command for spec-only changes
10
+ * Extended in WU-1439: Migrate initiative:create and wu:create to shared helper
11
+ *
12
+ * Pattern:
13
+ * 1. Create temp branch without switching main checkout
14
+ * 2. Create micro-worktree in /tmp pointing to temp branch
15
+ * 3. Perform operations in micro-worktree
16
+ * 4. FF-only merge temp branch to main (retry with rebase if main moved)
17
+ * 5. Push to origin
18
+ * 6. Cleanup (always, even on failure)
19
+ *
20
+ * Benefits:
21
+ * - Main checkout never switches branches (no impact on other agents)
22
+ * - Race conditions handled via rebase+retry (up to MAX_MERGE_RETRIES attempts)
23
+ * - Cleanup guaranteed even on failure
24
+ *
25
+ * Consumers:
26
+ * @see {@link tools/wu-create.mjs} - WU creation (WU-1262, WU-1439)
27
+ * @see {@link tools/wu-edit.mjs} - Spec edits (WU-1274)
28
+ * @see {@link tools/initiative-create.mjs} - Initiative creation (WU-1439)
29
+ */
30
+ /**
31
+ * Maximum retry attempts for ff-only merge when main moves
32
+ *
33
+ * This handles race conditions when multiple agents run wu:create or wu:edit
34
+ * concurrently. Each retry fetches latest main and rebases.
35
+ */
36
+ export declare const MAX_MERGE_RETRIES = 3;
37
+ /**
38
+ * Default log prefix for micro-worktree operations
39
+ *
40
+ * Extracted to constant to satisfy sonarjs/no-duplicate-string rule.
41
+ */
42
+ export declare const DEFAULT_LOG_PREFIX = "[micro-wt]";
43
+ /**
44
+ * Temp branch prefix for micro-worktree operations
45
+ *
46
+ * @param {string} operation - Operation name (e.g., 'wu-create', 'wu-edit')
47
+ * @param {string} id - WU ID (e.g., 'wu-123')
48
+ * @returns {string} Temp branch name (e.g., 'tmp/wu-create/wu-123')
49
+ */
50
+ export declare function getTempBranchName(operation: any, id: any): string;
51
+ /**
52
+ * Create micro-worktree in /tmp directory
53
+ *
54
+ * @param {string} prefix - Directory prefix (e.g., 'wu-create-', 'wu-edit-')
55
+ * @returns {string} Path to created micro-worktree directory
56
+ */
57
+ export declare function createMicroWorktreeDir(prefix: any): string;
58
+ /**
59
+ * Parse git worktree list output to find worktrees by branch
60
+ *
61
+ * WU-2237: Helper to parse porcelain format output from `git worktree list --porcelain`
62
+ *
63
+ * @param {string} worktreeListOutput - Output from git worktree list --porcelain
64
+ * @param {string} branchName - Branch name to search for (e.g., 'tmp/wu-create/wu-123')
65
+ * @returns {string|null} Worktree path if found, null otherwise
66
+ */
67
+ export declare function findWorktreeByBranch(worktreeListOutput: any, branchName: any): any;
68
+ /**
69
+ * Clean up orphaned micro-worktree and temp branch from a previous interrupted operation
70
+ *
71
+ * WU-2237: Before creating a new micro-worktree, detect and clean any existing temp
72
+ * branch/worktree for the same operation+WU ID. This handles scenarios where:
73
+ * - A previous wu:create/wu:edit was interrupted (timeout/crash)
74
+ * - The temp branch and /tmp worktree were left behind
75
+ * - A subsequent operation would fail with 'branch already exists'
76
+ *
77
+ * This function is idempotent - safe to call even when no orphans exist.
78
+ *
79
+ * @param {string} operation - Operation name (e.g., 'wu-create', 'wu-edit')
80
+ * @param {string} id - WU ID (e.g., 'WU-123')
81
+ * @param {Object} gitAdapter - GitAdapter instance to use (for testability)
82
+ * @param {string} logPrefix - Log prefix for console output
83
+ * @returns {Promise<{cleanedWorktree: boolean, cleanedBranch: boolean}>} Cleanup status
84
+ */
85
+ export declare function cleanupOrphanedMicroWorktree(operation: any, id: any, gitAdapter: any, logPrefix?: string): Promise<{
86
+ cleanedWorktree: boolean;
87
+ cleanedBranch: boolean;
88
+ }>;
89
+ /**
90
+ * Cleanup micro-worktree and temp branch
91
+ *
92
+ * Runs even on failure to prevent orphaned resources.
93
+ * Safe to call multiple times (idempotent).
94
+ *
95
+ * WU-2237: Enhanced to also check git worktree list for registered worktrees
96
+ * on the temp branch, in case the worktree path differs from what was expected.
97
+ *
98
+ * @param {string} worktreePath - Path to micro-worktree
99
+ * @param {string} branchName - Temp branch name
100
+ * @param {string} logPrefix - Log prefix for console output
101
+ */
102
+ export declare function cleanupMicroWorktree(worktreePath: any, branchName: any, logPrefix?: string): Promise<void>;
103
+ /**
104
+ * Stage changes including deletions in micro-worktree
105
+ *
106
+ * WU-1813: Uses addWithDeletions to properly stage tracked file deletions.
107
+ * This replaces the previous pattern of using gitWorktree.add(files) which
108
+ * could miss deletions when files were removed.
109
+ *
110
+ * @param {Object} gitWorktree - GitAdapter instance for the worktree
111
+ * @param {string[]|undefined} files - Files to stage (undefined/empty = stage all)
112
+ * @returns {Promise<void>}
113
+ */
114
+ export declare function stageChangesWithDeletions(gitWorktree: any, files: any): Promise<void>;
115
+ /**
116
+ * Format files using prettier before committing
117
+ *
118
+ * WU-1435: Ensures committed files pass format gates.
119
+ * Runs prettier --write on specified files within the micro-worktree.
120
+ *
121
+ * @param {string[]} files - Relative file paths to format
122
+ * @param {string} worktreePath - Path to the micro-worktree
123
+ * @param {string} logPrefix - Log prefix for console output
124
+ */
125
+ export declare function formatFiles(files: any, worktreePath: any, logPrefix?: string): Promise<void>;
126
+ /**
127
+ * Merge temp branch to main with ff-only and retry logic
128
+ *
129
+ * Handles race conditions when main branch advances between operation start
130
+ * and merge attempt. Retries with rebase up to MAX_MERGE_RETRIES times.
131
+ *
132
+ * @param {string} tempBranchName - Temp branch to merge
133
+ * @param {string} microWorktreePath - Path to micro-worktree (for rebase)
134
+ * @param {string} logPrefix - Log prefix for console output
135
+ * @throws {Error} If merge fails after all retries
136
+ */
137
+ export declare function mergeWithRetry(tempBranchName: any, microWorktreePath: any, logPrefix?: string): Promise<void>;
138
+ /**
139
+ * Execute an operation in a micro-worktree with full isolation
140
+ *
141
+ * This is the main entry point for micro-worktree operations.
142
+ * Handles the full lifecycle: create temp branch, create worktree,
143
+ * execute operation, merge, push, and cleanup.
144
+ *
145
+ * WU-1435: Added pushOnly option to keep local main pristine.
146
+ * WU-2237: Added pre-creation cleanup of orphaned temp branches/worktrees.
147
+ *
148
+ * @param {Object} options - Options for the operation
149
+ * @param {string} options.operation - Operation name (e.g., 'wu-create', 'wu-edit')
150
+ * @param {string} options.id - WU ID (e.g., 'WU-123')
151
+ * @param {string} options.logPrefix - Log prefix for console output
152
+ * @param {boolean} [options.pushOnly=false] - Skip local main merge, push directly to origin/main
153
+ * @param {Function} options.execute - Async function to execute in micro-worktree
154
+ * Receives: { worktreePath: string, gitWorktree: GitAdapter }
155
+ * Should return: { commitMessage: string, files: string[] }
156
+ * @returns {Promise<Object>} Result with ref property for worktree creation
157
+ * @throws {Error} If any step fails (cleanup still runs)
158
+ */
159
+ export declare function withMicroWorktree(options: any): Promise<any>;