@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.
- package/LICENSE +190 -0
- package/README.md +119 -0
- package/dist/active-wu-detector.d.ts +33 -0
- package/dist/active-wu-detector.js +106 -0
- package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
- package/dist/adapters/filesystem-metrics.adapter.js +519 -0
- package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
- package/dist/adapters/terminal-renderer.adapter.js +337 -0
- package/dist/arg-parser.d.ts +63 -0
- package/dist/arg-parser.js +560 -0
- package/dist/backlog-editor.d.ts +98 -0
- package/dist/backlog-editor.js +179 -0
- package/dist/backlog-generator.d.ts +111 -0
- package/dist/backlog-generator.js +381 -0
- package/dist/backlog-parser.d.ts +45 -0
- package/dist/backlog-parser.js +102 -0
- package/dist/backlog-sync-validator.d.ts +78 -0
- package/dist/backlog-sync-validator.js +294 -0
- package/dist/branch-drift.d.ts +34 -0
- package/dist/branch-drift.js +51 -0
- package/dist/cleanup-install-config.d.ts +33 -0
- package/dist/cleanup-install-config.js +37 -0
- package/dist/cleanup-lock.d.ts +139 -0
- package/dist/cleanup-lock.js +313 -0
- package/dist/code-path-validator.d.ts +146 -0
- package/dist/code-path-validator.js +537 -0
- package/dist/code-paths-overlap.d.ts +55 -0
- package/dist/code-paths-overlap.js +245 -0
- package/dist/commands-logger.d.ts +77 -0
- package/dist/commands-logger.js +254 -0
- package/dist/commit-message-utils.d.ts +25 -0
- package/dist/commit-message-utils.js +41 -0
- package/dist/compliance-parser.d.ts +150 -0
- package/dist/compliance-parser.js +507 -0
- package/dist/constants/backlog-patterns.d.ts +20 -0
- package/dist/constants/backlog-patterns.js +23 -0
- package/dist/constants/dora-constants.d.ts +49 -0
- package/dist/constants/dora-constants.js +53 -0
- package/dist/constants/gate-constants.d.ts +15 -0
- package/dist/constants/gate-constants.js +15 -0
- package/dist/constants/linter-constants.d.ts +16 -0
- package/dist/constants/linter-constants.js +16 -0
- package/dist/constants/tokenizer-constants.d.ts +15 -0
- package/dist/constants/tokenizer-constants.js +15 -0
- package/dist/core/scope-checker.d.ts +97 -0
- package/dist/core/scope-checker.js +163 -0
- package/dist/core/tool-runner.d.ts +161 -0
- package/dist/core/tool-runner.js +393 -0
- package/dist/core/tool.constants.d.ts +105 -0
- package/dist/core/tool.constants.js +101 -0
- package/dist/core/tool.schemas.d.ts +226 -0
- package/dist/core/tool.schemas.js +226 -0
- package/dist/core/worktree-guard.d.ts +130 -0
- package/dist/core/worktree-guard.js +242 -0
- package/dist/coverage-gate.d.ts +108 -0
- package/dist/coverage-gate.js +196 -0
- package/dist/date-utils.d.ts +75 -0
- package/dist/date-utils.js +140 -0
- package/dist/dependency-graph.d.ts +142 -0
- package/dist/dependency-graph.js +550 -0
- package/dist/dependency-guard.d.ts +54 -0
- package/dist/dependency-guard.js +142 -0
- package/dist/dependency-validator.d.ts +105 -0
- package/dist/dependency-validator.js +154 -0
- package/dist/docs-path-validator.d.ts +36 -0
- package/dist/docs-path-validator.js +95 -0
- package/dist/domain/orchestration.constants.d.ts +99 -0
- package/dist/domain/orchestration.constants.js +97 -0
- package/dist/domain/orchestration.schemas.d.ts +280 -0
- package/dist/domain/orchestration.schemas.js +211 -0
- package/dist/domain/orchestration.types.d.ts +133 -0
- package/dist/domain/orchestration.types.js +12 -0
- package/dist/error-handler.d.ts +116 -0
- package/dist/error-handler.js +136 -0
- package/dist/file-classifiers.d.ts +62 -0
- package/dist/file-classifiers.js +108 -0
- package/dist/gates-agent-mode.d.ts +81 -0
- package/dist/gates-agent-mode.js +94 -0
- package/dist/generate-traceability.d.ts +107 -0
- package/dist/generate-traceability.js +411 -0
- package/dist/git-adapter.d.ts +395 -0
- package/dist/git-adapter.js +649 -0
- package/dist/git-staged-validator.d.ts +32 -0
- package/dist/git-staged-validator.js +48 -0
- package/dist/hardcoded-strings.d.ts +61 -0
- package/dist/hardcoded-strings.js +270 -0
- package/dist/incremental-lint.d.ts +78 -0
- package/dist/incremental-lint.js +129 -0
- package/dist/incremental-test.d.ts +39 -0
- package/dist/incremental-test.js +61 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +61 -0
- package/dist/invariants/check-automated-tests.d.ts +50 -0
- package/dist/invariants/check-automated-tests.js +166 -0
- package/dist/invariants-runner.d.ts +103 -0
- package/dist/invariants-runner.js +527 -0
- package/dist/lane-checker.d.ts +50 -0
- package/dist/lane-checker.js +319 -0
- package/dist/lane-inference.d.ts +39 -0
- package/dist/lane-inference.js +195 -0
- package/dist/lane-lock.d.ts +211 -0
- package/dist/lane-lock.js +474 -0
- package/dist/lane-validator.d.ts +48 -0
- package/dist/lane-validator.js +114 -0
- package/dist/logs-lib.d.ts +104 -0
- package/dist/logs-lib.js +207 -0
- package/dist/lumenflow-config-schema.d.ts +272 -0
- package/dist/lumenflow-config-schema.js +207 -0
- package/dist/lumenflow-config.d.ts +95 -0
- package/dist/lumenflow-config.js +236 -0
- package/dist/manual-test-validator.d.ts +80 -0
- package/dist/manual-test-validator.js +200 -0
- package/dist/merge-lock.d.ts +115 -0
- package/dist/merge-lock.js +251 -0
- package/dist/micro-worktree.d.ts +159 -0
- package/dist/micro-worktree.js +427 -0
- package/dist/migration-deployer.d.ts +69 -0
- package/dist/migration-deployer.js +151 -0
- package/dist/orchestration-advisory-loader.d.ts +28 -0
- package/dist/orchestration-advisory-loader.js +87 -0
- package/dist/orchestration-advisory.d.ts +58 -0
- package/dist/orchestration-advisory.js +94 -0
- package/dist/orchestration-di.d.ts +48 -0
- package/dist/orchestration-di.js +57 -0
- package/dist/orchestration-rules.d.ts +57 -0
- package/dist/orchestration-rules.js +201 -0
- package/dist/orphan-detector.d.ts +131 -0
- package/dist/orphan-detector.js +226 -0
- package/dist/path-classifiers.d.ts +57 -0
- package/dist/path-classifiers.js +93 -0
- package/dist/piped-command-detector.d.ts +34 -0
- package/dist/piped-command-detector.js +64 -0
- package/dist/ports/dashboard-renderer.port.d.ts +112 -0
- package/dist/ports/dashboard-renderer.port.js +25 -0
- package/dist/ports/metrics-collector.port.d.ts +132 -0
- package/dist/ports/metrics-collector.port.js +26 -0
- package/dist/process-detector.d.ts +84 -0
- package/dist/process-detector.js +172 -0
- package/dist/prompt-linter.d.ts +72 -0
- package/dist/prompt-linter.js +312 -0
- package/dist/prompt-monitor.d.ts +15 -0
- package/dist/prompt-monitor.js +205 -0
- package/dist/rebase-artifact-cleanup.d.ts +145 -0
- package/dist/rebase-artifact-cleanup.js +433 -0
- package/dist/retry-strategy.d.ts +189 -0
- package/dist/retry-strategy.js +283 -0
- package/dist/risk-detector.d.ts +108 -0
- package/dist/risk-detector.js +252 -0
- package/dist/rollback-utils.d.ts +76 -0
- package/dist/rollback-utils.js +104 -0
- package/dist/section-headings.d.ts +43 -0
- package/dist/section-headings.js +49 -0
- package/dist/spawn-escalation.d.ts +90 -0
- package/dist/spawn-escalation.js +253 -0
- package/dist/spawn-monitor.d.ts +229 -0
- package/dist/spawn-monitor.js +672 -0
- package/dist/spawn-recovery.d.ts +82 -0
- package/dist/spawn-recovery.js +298 -0
- package/dist/spawn-registry-schema.d.ts +98 -0
- package/dist/spawn-registry-schema.js +108 -0
- package/dist/spawn-registry-store.d.ts +146 -0
- package/dist/spawn-registry-store.js +273 -0
- package/dist/spawn-tree.d.ts +121 -0
- package/dist/spawn-tree.js +285 -0
- package/dist/stamp-status-validator.d.ts +84 -0
- package/dist/stamp-status-validator.js +134 -0
- package/dist/stamp-utils.d.ts +100 -0
- package/dist/stamp-utils.js +229 -0
- package/dist/state-machine.d.ts +26 -0
- package/dist/state-machine.js +83 -0
- package/dist/system-map-validator.d.ts +80 -0
- package/dist/system-map-validator.js +272 -0
- package/dist/telemetry.d.ts +80 -0
- package/dist/telemetry.js +213 -0
- package/dist/token-counter.d.ts +51 -0
- package/dist/token-counter.js +145 -0
- package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
- package/dist/usecases/get-dashboard-data.usecase.js +61 -0
- package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
- package/dist/usecases/get-suggestions.usecase.js +153 -0
- package/dist/user-normalizer.d.ts +41 -0
- package/dist/user-normalizer.js +141 -0
- package/dist/validators/phi-constants.d.ts +97 -0
- package/dist/validators/phi-constants.js +152 -0
- package/dist/validators/phi-scanner.d.ts +58 -0
- package/dist/validators/phi-scanner.js +215 -0
- package/dist/worktree-ownership.d.ts +50 -0
- package/dist/worktree-ownership.js +74 -0
- package/dist/worktree-scanner.d.ts +103 -0
- package/dist/worktree-scanner.js +168 -0
- package/dist/worktree-symlink.d.ts +99 -0
- package/dist/worktree-symlink.js +359 -0
- package/dist/wu-backlog-updater.d.ts +17 -0
- package/dist/wu-backlog-updater.js +37 -0
- package/dist/wu-checkpoint.d.ts +124 -0
- package/dist/wu-checkpoint.js +233 -0
- package/dist/wu-claim-helpers.d.ts +26 -0
- package/dist/wu-claim-helpers.js +63 -0
- package/dist/wu-claim-resume.d.ts +106 -0
- package/dist/wu-claim-resume.js +276 -0
- package/dist/wu-consistency-checker.d.ts +95 -0
- package/dist/wu-consistency-checker.js +567 -0
- package/dist/wu-constants.d.ts +1275 -0
- package/dist/wu-constants.js +1382 -0
- package/dist/wu-create-validators.d.ts +42 -0
- package/dist/wu-create-validators.js +93 -0
- package/dist/wu-done-branch-only.d.ts +63 -0
- package/dist/wu-done-branch-only.js +191 -0
- package/dist/wu-done-messages.d.ts +119 -0
- package/dist/wu-done-messages.js +185 -0
- package/dist/wu-done-pr.d.ts +72 -0
- package/dist/wu-done-pr.js +174 -0
- package/dist/wu-done-retry-helpers.d.ts +85 -0
- package/dist/wu-done-retry-helpers.js +172 -0
- package/dist/wu-done-ui.d.ts +37 -0
- package/dist/wu-done-ui.js +69 -0
- package/dist/wu-done-validators.d.ts +411 -0
- package/dist/wu-done-validators.js +1229 -0
- package/dist/wu-done-worktree.d.ts +182 -0
- package/dist/wu-done-worktree.js +1097 -0
- package/dist/wu-helpers.d.ts +128 -0
- package/dist/wu-helpers.js +248 -0
- package/dist/wu-lint.d.ts +70 -0
- package/dist/wu-lint.js +234 -0
- package/dist/wu-paths.d.ts +171 -0
- package/dist/wu-paths.js +178 -0
- package/dist/wu-preflight-validators.d.ts +86 -0
- package/dist/wu-preflight-validators.js +251 -0
- package/dist/wu-recovery.d.ts +138 -0
- package/dist/wu-recovery.js +341 -0
- package/dist/wu-repair-core.d.ts +131 -0
- package/dist/wu-repair-core.js +669 -0
- package/dist/wu-schema-normalization.d.ts +17 -0
- package/dist/wu-schema-normalization.js +82 -0
- package/dist/wu-schema.d.ts +793 -0
- package/dist/wu-schema.js +881 -0
- package/dist/wu-spawn-helpers.d.ts +121 -0
- package/dist/wu-spawn-helpers.js +271 -0
- package/dist/wu-spawn.d.ts +158 -0
- package/dist/wu-spawn.js +1306 -0
- package/dist/wu-state-schema.d.ts +213 -0
- package/dist/wu-state-schema.js +156 -0
- package/dist/wu-state-store.d.ts +264 -0
- package/dist/wu-state-store.js +691 -0
- package/dist/wu-status-transition.d.ts +63 -0
- package/dist/wu-status-transition.js +382 -0
- package/dist/wu-status-updater.d.ts +25 -0
- package/dist/wu-status-updater.js +116 -0
- package/dist/wu-transaction-collectors.d.ts +116 -0
- package/dist/wu-transaction-collectors.js +272 -0
- package/dist/wu-transaction.d.ts +170 -0
- package/dist/wu-transaction.js +273 -0
- package/dist/wu-validation-constants.d.ts +60 -0
- package/dist/wu-validation-constants.js +66 -0
- package/dist/wu-validation.d.ts +118 -0
- package/dist/wu-validation.js +243 -0
- package/dist/wu-validator.d.ts +62 -0
- package/dist/wu-validator.js +325 -0
- package/dist/wu-yaml-fixer.d.ts +97 -0
- package/dist/wu-yaml-fixer.js +264 -0
- package/dist/wu-yaml.d.ts +86 -0
- package/dist/wu-yaml.js +222 -0
- 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>;
|