@ricsam/r5d-worker 0.0.74 → 0.0.76

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 (32) hide show
  1. package/README.md +6 -4
  2. package/dist/cjs/main.cjs +1031 -976
  3. package/dist/cjs/package.json +1 -1
  4. package/dist/cjs/project-workspace-state.cjs +777 -0
  5. package/dist/cjs/project-worktrees.cjs +559 -0
  6. package/dist/cjs/working-tree-mirror.cjs +225 -0
  7. package/dist/cjs/workspace-git-sync.cjs +565 -0
  8. package/dist/cjs/workspace-incident-state.cjs +2 -38
  9. package/dist/mjs/main.mjs +1048 -987
  10. package/dist/mjs/package.json +1 -1
  11. package/dist/mjs/project-workspace-state.mjs +745 -0
  12. package/dist/mjs/project-worktrees.mjs +513 -0
  13. package/dist/mjs/working-tree-mirror.mjs +190 -0
  14. package/dist/mjs/workspace-git-sync.mjs +524 -0
  15. package/dist/mjs/workspace-incident-state.mjs +1 -35
  16. package/dist/types/main.d.ts +35 -55
  17. package/dist/types/project-workspace-state.d.ts +144 -0
  18. package/dist/types/project-worktrees.d.ts +112 -0
  19. package/dist/types/working-tree-mirror.d.ts +24 -0
  20. package/dist/types/workspace-git-sync.d.ts +97 -0
  21. package/dist/types/workspace-incident-state.d.ts +0 -17
  22. package/dist/types/workspace-mutation-gate.d.ts +3 -3
  23. package/package.json +1 -1
  24. package/dist/cjs/workspace-convergence.cjs +0 -280
  25. package/dist/cjs/workspace-manifest-admission.cjs +0 -60
  26. package/dist/cjs/workspace-sync.cjs +0 -2469
  27. package/dist/mjs/workspace-convergence.mjs +0 -236
  28. package/dist/mjs/workspace-manifest-admission.mjs +0 -26
  29. package/dist/mjs/workspace-sync.mjs +0 -2417
  30. package/dist/types/workspace-convergence.d.ts +0 -93
  31. package/dist/types/workspace-manifest-admission.d.ts +0 -22
  32. package/dist/types/workspace-sync.d.ts +0 -167
@@ -0,0 +1,559 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var project_worktrees_exports = {};
30
+ __export(project_worktrees_exports, {
31
+ PROJECT_WORKTREE_SNAPSHOT_PREFIX: () => PROJECT_WORKTREE_SNAPSHOT_PREFIX,
32
+ cleanupStaleProjectWorktreeSnapshots: () => cleanupStaleProjectWorktreeSnapshots,
33
+ createLinkedProjectBranch: () => createLinkedProjectBranch,
34
+ deleteLinkedProjectBranch: () => deleteLinkedProjectBranch,
35
+ deleteProjectMirrorBranch: () => deleteProjectMirrorBranch,
36
+ ensureProjectWorktrees: () => ensureProjectWorktrees,
37
+ fastForwardProjectHeadsFromMirror: () => fastForwardProjectHeadsFromMirror,
38
+ hasLinkedProjectWorktreeLayout: () => hasLinkedProjectWorktreeLayout,
39
+ projectWorktreeConfigurationFingerprint: () => projectWorktreeConfigurationFingerprint,
40
+ projectWorktreeOperationInProgress: () => projectWorktreeOperationInProgress,
41
+ projectWorktreesTestHarness: () => projectWorktreesTestHarness,
42
+ pushProjectMirrorHeads: () => pushProjectMirrorHeads,
43
+ removeProjectWorktrees: () => removeProjectWorktrees
44
+ });
45
+ module.exports = __toCommonJS(project_worktrees_exports);
46
+ var import_node_crypto = require("node:crypto");
47
+ var import_node_fs = __toESM(require("node:fs"), 1);
48
+ var import_node_os = __toESM(require("node:os"), 1);
49
+ var import_node_path = __toESM(require("node:path"), 1);
50
+ var import_managed_paths = require("./managed-paths.cjs");
51
+ var import_working_tree_mirror = require("./working-tree-mirror.cjs");
52
+ const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
53
+ const PROJECT_WORKTREE_SNAPSHOT_NAME = new RegExp(
54
+ `^${PROJECT_WORKTREE_SNAPSHOT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([1-9]\\d*)-([A-Za-z0-9]{6})$`
55
+ );
56
+ function projectWorktreeConfigurationFingerprint(input) {
57
+ return (0, import_node_crypto.createHash)("sha256").update(
58
+ JSON.stringify({
59
+ checkoutPathSegments: input.checkoutPathSegments,
60
+ projectPath: input.projectPath,
61
+ repoHttpUrl: input.repoHttpUrl,
62
+ repoAuthHeader: input.repoAuthHeader,
63
+ defaultBranch: input.defaultBranch,
64
+ branches: [...input.branches].sort((left, right) => left.branchName.localeCompare(right.branchName)).map(({ branchName, sourceBranchName, baseCommitHash }) => ({
65
+ branchName,
66
+ sourceBranchName: sourceBranchName ?? null,
67
+ baseCommitHash
68
+ })),
69
+ gitIdentity: input.gitIdentity
70
+ })
71
+ ).digest("hex");
72
+ }
73
+ const NON_RECURSIVE_GIT_CONFIG = [
74
+ "-c",
75
+ "submodule.recurse=false",
76
+ "-c",
77
+ "fetch.recurseSubmodules=false",
78
+ "-c",
79
+ "push.recurseSubmodules=false"
80
+ ];
81
+ function normalizedHttpOrigin(value) {
82
+ try {
83
+ const url = new URL(value);
84
+ return `${url.protocol}//${url.host}`;
85
+ } catch {
86
+ return value.replace(/\/+$/, "");
87
+ }
88
+ }
89
+ function authArgs(auth) {
90
+ if (!auth) return [];
91
+ const key = `http.${normalizedHttpOrigin(auth.extraHeaderUrl)}/.extraHeader`;
92
+ return ["-c", "http.extraHeader=", "-c", `${key}=`, "-c", `${key}=${auth.header}`];
93
+ }
94
+ function gitResult(cwd, args, auth) {
95
+ const result = Bun.spawnSync(["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args], {
96
+ cwd,
97
+ stdout: "pipe",
98
+ stderr: "pipe",
99
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
100
+ });
101
+ return {
102
+ exitCode: result.exitCode,
103
+ stdout: result.stdout.toString().trim(),
104
+ stderr: result.stderr.toString().trim()
105
+ };
106
+ }
107
+ function git(cwd, args, action, auth) {
108
+ const result = gitResult(cwd, args, auth);
109
+ if (result.exitCode !== 0) throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
110
+ return result.stdout;
111
+ }
112
+ function tryGit(cwd, args, auth) {
113
+ return gitResult(cwd, args, auth).exitCode === 0;
114
+ }
115
+ function branchPath(projectRoot, branchName) {
116
+ (0, import_managed_paths.validateManagedBranchName)(branchName);
117
+ const result = import_node_path.default.join(projectRoot, ...branchName.split("/"));
118
+ const relative = import_node_path.default.relative(import_node_path.default.resolve(projectRoot), import_node_path.default.resolve(result));
119
+ if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
120
+ throw new Error(`Branch path escapes its project root: ${branchName}`);
121
+ }
122
+ return result;
123
+ }
124
+ function assertNonOverlappingBranches(branches) {
125
+ const names = branches.map(({ branchName }) => branchName).sort();
126
+ if (new Set(names).size !== names.length) throw new Error("Project worktree branch names must be unique");
127
+ for (let index = 0; index < names.length; index += 1) {
128
+ const name = names[index];
129
+ (0, import_managed_paths.validateManagedBranchName)(name);
130
+ const baseCommitHash = branches.find(({ branchName }) => branchName === name)?.baseCommitHash;
131
+ if (!baseCommitHash || !/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(baseCommitHash)) {
132
+ throw new Error(`Invalid base commit hash for project branch ${name}`);
133
+ }
134
+ const overlapping = names.slice(index + 1).find((candidate) => candidate.startsWith(`${name}/`));
135
+ if (overlapping) throw new Error(`Branch folders overlap: ${name} and ${overlapping}`);
136
+ }
137
+ }
138
+ function gitDirectoryKind(checkoutPath) {
139
+ const gitPath = import_node_path.default.join(checkoutPath, ".git");
140
+ try {
141
+ const stat = import_node_fs.default.lstatSync(gitPath);
142
+ if (stat.isDirectory()) return "directory";
143
+ if (stat.isFile()) return "file";
144
+ return null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+ function commonGitDirectory(checkoutPath) {
150
+ const result = gitResult(checkoutPath, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
151
+ return result.exitCode === 0 ? import_node_path.default.resolve(result.stdout) : null;
152
+ }
153
+ function hasLinkedProjectWorktreeLayout(input) {
154
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
155
+ if (gitDirectoryKind(primaryPath) !== "directory") return false;
156
+ const primaryCommonDir = commonGitDirectory(primaryPath);
157
+ if (!primaryCommonDir) return false;
158
+ return input.branches.every(({ branchName }) => {
159
+ const checkoutPath = branchPath(input.projectRoot, branchName);
160
+ if (branchName === input.primaryBranchName) return commonGitDirectory(checkoutPath) === primaryCommonDir;
161
+ return gitDirectoryKind(checkoutPath) === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
162
+ });
163
+ }
164
+ function hasCompatibleLinkedProjectWorktreeLayout(input) {
165
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
166
+ if (gitDirectoryKind(primaryPath) !== "directory") return false;
167
+ const primaryCommonDir = commonGitDirectory(primaryPath);
168
+ if (!primaryCommonDir) return false;
169
+ return input.branches.every(({ branchName }) => {
170
+ if (branchName === input.primaryBranchName) return true;
171
+ const checkoutPath = branchPath(input.projectRoot, branchName);
172
+ const kind = gitDirectoryKind(checkoutPath);
173
+ return kind === null || kind === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
174
+ });
175
+ }
176
+ function createProjectWorktreeSnapshotRoot() {
177
+ return import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), `${PROJECT_WORKTREE_SNAPSHOT_PREFIX}${process.pid}-`));
178
+ }
179
+ function snapshotBranchTrees(projectRoot, branches) {
180
+ const root = createProjectWorktreeSnapshotRoot();
181
+ const paths = /* @__PURE__ */ new Map();
182
+ try {
183
+ for (const { branchName } of branches) {
184
+ const checkoutPath = branchPath(projectRoot, branchName);
185
+ if (!import_node_fs.default.existsSync(checkoutPath)) continue;
186
+ const snapshotPath = import_node_path.default.join(root, ...branchName.split("/"));
187
+ (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
188
+ paths.set(branchName, snapshotPath);
189
+ }
190
+ return { root, paths };
191
+ } catch (error) {
192
+ import_node_fs.default.rmSync(root, { recursive: true, force: true });
193
+ throw error;
194
+ }
195
+ }
196
+ function isProcessAlive(processId) {
197
+ try {
198
+ process.kill(processId, 0);
199
+ return true;
200
+ } catch (error) {
201
+ return error.code !== "ESRCH";
202
+ }
203
+ }
204
+ function cleanupStaleProjectWorktreeSnapshots(input = {}) {
205
+ const temporaryRoot = import_node_path.default.resolve(input.temporaryRoot ?? import_node_os.default.tmpdir());
206
+ const processAlive = input.processAlive ?? isProcessAlive;
207
+ const removed = [];
208
+ const failed = [];
209
+ let entries;
210
+ try {
211
+ entries = import_node_fs.default.readdirSync(temporaryRoot, { withFileTypes: true });
212
+ } catch (error) {
213
+ if (error.code === "ENOENT") return { removed, failed };
214
+ return { removed, failed: [{ path: temporaryRoot, error: error instanceof Error ? error.message : String(error) }] };
215
+ }
216
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
217
+ const match = PROJECT_WORKTREE_SNAPSHOT_NAME.exec(entry.name);
218
+ if (!match) continue;
219
+ const ownerProcessId = Number(match[1]);
220
+ if (!Number.isSafeInteger(ownerProcessId) || processAlive(ownerProcessId)) continue;
221
+ const candidate = import_node_path.default.join(temporaryRoot, entry.name);
222
+ try {
223
+ const stat = import_node_fs.default.lstatSync(candidate);
224
+ if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
225
+ import_node_fs.default.rmSync(candidate, { recursive: true, force: true });
226
+ removed.push(candidate);
227
+ } catch (error) {
228
+ if (error.code === "ENOENT") continue;
229
+ failed.push({ path: candidate, error: error instanceof Error ? error.message : String(error) });
230
+ }
231
+ }
232
+ return { removed, failed };
233
+ }
234
+ function configureRepository(input) {
235
+ if (tryGit(input.primaryPath, ["remote", "get-url", "origin"])) {
236
+ git(input.primaryPath, ["remote", "set-url", "origin", input.originUrl], "configure project origin");
237
+ } else {
238
+ git(input.primaryPath, ["remote", "add", "origin", input.originUrl], "configure project origin");
239
+ }
240
+ git(input.primaryPath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset project credential helpers");
241
+ if (input.credentialHelper) {
242
+ git(
243
+ input.primaryPath,
244
+ ["config", "--local", "--add", "credential.helper", input.credentialHelper],
245
+ "configure project credential helper"
246
+ );
247
+ }
248
+ if (input.gitIdentity) {
249
+ const name = input.gitIdentity.name.trim();
250
+ const email = input.gitIdentity.email.trim();
251
+ if (!name || !email) throw new Error("Git identity must include name and email");
252
+ git(input.primaryPath, ["config", "--local", "user.name", name], "configure project Git user name");
253
+ git(input.primaryPath, ["config", "--local", "user.email", email], "configure project Git user email");
254
+ }
255
+ }
256
+ function fetchProjectHeads(input) {
257
+ const originFetch = gitResult(
258
+ input.primaryPath,
259
+ ["fetch", "--no-recurse-submodules", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"],
260
+ input.originAuth
261
+ );
262
+ if (originFetch.exitCode !== 0) {
263
+ throw new Error(`Fetch project origin: ${originFetch.stderr || originFetch.stdout || `git exited ${originFetch.exitCode}`}`);
264
+ }
265
+ const mirrorFetch = gitResult(
266
+ input.primaryPath,
267
+ ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
268
+ input.mirrorAuth
269
+ );
270
+ if (mirrorFetch.exitCode !== 0 && !/(couldn't find remote ref|does not have any commits|remote repository is empty|no such ref)/i.test(
271
+ `${mirrorFetch.stderr}
272
+ ${mirrorFetch.stdout}`
273
+ )) {
274
+ throw new Error(`Fetch project head mirror: ${mirrorFetch.stderr || mirrorFetch.stdout || `git exited ${mirrorFetch.exitCode}`}`);
275
+ }
276
+ }
277
+ function ensureCommitAvailable(input) {
278
+ if (tryGit(input.primaryPath, ["cat-file", "-e", `${input.commitHash}^{commit}`])) return;
279
+ for (const [url, auth] of [
280
+ [input.mirrorUrl, input.mirrorAuth],
281
+ [input.originUrl, input.originAuth]
282
+ ]) {
283
+ if (tryGit(input.primaryPath, ["fetch", "--no-recurse-submodules", "--no-tags", url, input.commitHash], auth)) return;
284
+ }
285
+ throw new Error(`Project base commit ${input.commitHash} is unavailable from origin and the canonical mirror`);
286
+ }
287
+ function seedForBranch(input) {
288
+ const mirrorRef = `refs/r5d/mirror/${input.branch.branchName}`;
289
+ const mirrorHead = tryGit(input.primaryPath, ["show-ref", "--verify", "--quiet", mirrorRef]) ? git(input.primaryPath, ["rev-parse", mirrorRef], "resolve project mirror head") : null;
290
+ const seed = mirrorHead ?? input.branch.baseCommitHash;
291
+ ensureCommitAvailable({ ...input, commitHash: seed });
292
+ return { seed, mirrorHead };
293
+ }
294
+ function preserveHead(checkoutPath) {
295
+ if (!tryGit(checkoutPath, ["rev-parse", "--verify", "HEAD"])) return;
296
+ const head = git(checkoutPath, ["rev-parse", "HEAD"], "read pre-reconciliation project head");
297
+ git(checkoutPath, ["update-ref", `refs/r5d/pre-connect/${head}`, head], "preserve pre-reconciliation project head");
298
+ }
299
+ function initializeLinkedLayout(input) {
300
+ for (const { branchName } of [...input.branches].sort((left, right) => right.branchName.length - left.branchName.length)) {
301
+ import_node_fs.default.rmSync(branchPath(input.projectRoot, branchName), { recursive: true, force: true });
302
+ }
303
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
304
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(primaryPath), { recursive: true });
305
+ git(void 0, ["init", `--initial-branch=${input.primaryBranchName}`, primaryPath], "initialize primary project clone");
306
+ configureRepository({ ...input, primaryPath });
307
+ fetchProjectHeads({ ...input, primaryPath });
308
+ const seeds = /* @__PURE__ */ new Map();
309
+ const mirrorHeads = /* @__PURE__ */ new Map();
310
+ for (const branch of input.branches) {
311
+ const resolved = seedForBranch({ ...input, primaryPath, branch });
312
+ seeds.set(branch.branchName, resolved.seed);
313
+ mirrorHeads.set(branch.branchName, resolved.mirrorHead);
314
+ }
315
+ git(
316
+ primaryPath,
317
+ ["checkout", "--no-recurse-submodules", "-B", input.primaryBranchName, seeds.get(input.primaryBranchName)],
318
+ "anchor primary project worktree"
319
+ );
320
+ for (const branch of input.branches) {
321
+ if (branch.branchName === input.primaryBranchName) continue;
322
+ git(primaryPath, ["branch", "-f", branch.branchName, seeds.get(branch.branchName)], `anchor project branch ${branch.branchName}`);
323
+ const checkoutPath = branchPath(input.projectRoot, branch.branchName);
324
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
325
+ git(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
326
+ }
327
+ return mirrorHeads;
328
+ }
329
+ function reconcileLinkedLayout(input) {
330
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
331
+ configureRepository({ ...input, primaryPath });
332
+ tryGit(primaryPath, ["worktree", "prune"]);
333
+ fetchProjectHeads({ ...input, primaryPath });
334
+ const mirrorHeads = /* @__PURE__ */ new Map();
335
+ for (const branch of input.branches) {
336
+ const checkoutPath = branchPath(input.projectRoot, branch.branchName);
337
+ const resolved = seedForBranch({ ...input, primaryPath, branch });
338
+ mirrorHeads.set(branch.branchName, resolved.mirrorHead);
339
+ if (branch.branchName === input.primaryBranchName || commonGitDirectory(checkoutPath) === commonGitDirectory(primaryPath)) {
340
+ preserveHead(checkoutPath);
341
+ git(checkoutPath, ["reset", "--hard", resolved.seed], `anchor linked worktree ${branch.branchName}`);
342
+ continue;
343
+ }
344
+ import_node_fs.default.rmSync(checkoutPath, { recursive: true, force: true });
345
+ git(primaryPath, ["branch", "-f", branch.branchName, resolved.seed], `anchor project branch ${branch.branchName}`);
346
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(checkoutPath), { recursive: true });
347
+ git(primaryPath, ["worktree", "add", "--force", checkoutPath, branch.branchName], `create linked worktree ${branch.branchName}`);
348
+ }
349
+ return mirrorHeads;
350
+ }
351
+ function aheadBehind(checkoutPath, branchName) {
352
+ const originRef = `refs/remotes/origin/${branchName}`;
353
+ if (!tryGit(checkoutPath, ["show-ref", "--verify", "--quiet", originRef])) return { ahead: null, behind: null };
354
+ const output = git(checkoutPath, ["rev-list", "--left-right", "--count", `${originRef}...HEAD`], "measure project origin divergence");
355
+ const [behind, ahead] = output.split(/\s+/).map(Number);
356
+ return Number.isFinite(ahead) && Number.isFinite(behind) ? { ahead, behind } : { ahead: null, behind: null };
357
+ }
358
+ function ensureProjectWorktrees(input) {
359
+ const projectRoot = import_node_path.default.resolve(input.projectRoot);
360
+ assertNonOverlappingBranches(input.branches);
361
+ if (!input.branches.some(({ branchName }) => branchName === input.primaryBranchName)) {
362
+ throw new Error(`Primary project branch ${input.primaryBranchName} is missing from the workspace configuration`);
363
+ }
364
+ const snapshot = snapshotBranchTrees(projectRoot, input.branches);
365
+ try {
366
+ import_node_fs.default.mkdirSync(projectRoot, { recursive: true });
367
+ const linked = hasCompatibleLinkedProjectWorktreeLayout({
368
+ projectRoot,
369
+ primaryBranchName: input.primaryBranchName,
370
+ branches: input.branches
371
+ });
372
+ const mirrorHeads = linked ? reconcileLinkedLayout({ ...input, projectRoot }) : initializeLinkedLayout({ ...input, projectRoot });
373
+ for (const branch of input.branches) {
374
+ const snapshotPath = snapshot.paths.get(branch.branchName);
375
+ if (!snapshotPath) continue;
376
+ (0, import_working_tree_mirror.mirrorWorkingTree)({
377
+ sourceRoot: snapshotPath,
378
+ targetRoot: branchPath(projectRoot, branch.branchName),
379
+ sourceMode: "all",
380
+ deletionMode: "git"
381
+ });
382
+ }
383
+ return input.branches.map((branch) => {
384
+ const checkoutPath = branchPath(projectRoot, branch.branchName);
385
+ const divergence = aheadBehind(checkoutPath, branch.branchName);
386
+ return {
387
+ branchName: branch.branchName,
388
+ branchPath: checkoutPath,
389
+ head: git(checkoutPath, ["rev-parse", "HEAD"], `resolve project head ${branch.branchName}`),
390
+ mirrorHead: mirrorHeads.get(branch.branchName) ?? null,
391
+ ...divergence
392
+ };
393
+ }).sort((left, right) => left.branchName.localeCompare(right.branchName));
394
+ } finally {
395
+ import_node_fs.default.rmSync(snapshot.root, { recursive: true, force: true });
396
+ }
397
+ }
398
+ function projectWorktreeOperationInProgress(checkoutPath) {
399
+ const gitPaths = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer"];
400
+ return gitPaths.some((gitPath) => {
401
+ const result = gitResult(checkoutPath, ["rev-parse", "--git-path", gitPath]);
402
+ if (result.exitCode !== 0) return false;
403
+ const resolved = import_node_path.default.isAbsolute(result.stdout) ? result.stdout : import_node_path.default.resolve(checkoutPath, result.stdout);
404
+ return import_node_fs.default.existsSync(resolved);
405
+ });
406
+ }
407
+ function createLinkedProjectBranch(input) {
408
+ (0, import_managed_paths.validateManagedBranchName)(input.sourceBranchName);
409
+ (0, import_managed_paths.validateManagedBranchName)(input.branchName);
410
+ if (input.sourceBranchName === input.branchName) throw new Error("Source and target branch names must differ");
411
+ const sourcePath = branchPath(input.projectRoot, input.sourceBranchName);
412
+ const targetPath = branchPath(input.projectRoot, input.branchName);
413
+ if (!commonGitDirectory(sourcePath)) throw new Error(`Source worktree ${input.sourceBranchName} is unavailable`);
414
+ if (projectWorktreeOperationInProgress(sourcePath)) {
415
+ throw new Error(`Source worktree ${input.sourceBranchName} has an in-progress Git operation`);
416
+ }
417
+ if (import_node_fs.default.existsSync(targetPath) || tryGit(sourcePath, ["show-ref", "--verify", "--quiet", `refs/heads/${input.branchName}`])) {
418
+ throw new Error(`Project branch ${input.branchName} already exists`);
419
+ }
420
+ const registeredWorktrees = git(sourcePath, ["worktree", "list", "--porcelain"], "inspect linked project worktrees").split(/\r?\n/).flatMap((line) => line.startsWith("worktree ") ? [import_node_path.default.resolve(line.slice("worktree ".length))] : []);
421
+ const overlappingWorktree = registeredWorktrees.find((worktreePath) => {
422
+ const targetRelative = import_node_path.default.relative(worktreePath, targetPath);
423
+ const worktreeRelative = import_node_path.default.relative(targetPath, worktreePath);
424
+ return targetRelative === "" || targetRelative !== ".." && !targetRelative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(targetRelative) || worktreeRelative !== ".." && !worktreeRelative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(worktreeRelative);
425
+ });
426
+ if (overlappingWorktree) throw new Error(`Project branch folder overlaps linked worktree ${overlappingWorktree}`);
427
+ const sourceHead = git(sourcePath, ["rev-parse", "HEAD"], "resolve source branch head");
428
+ const snapshotRoot = createProjectWorktreeSnapshotRoot();
429
+ try {
430
+ (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: sourcePath, targetRoot: snapshotRoot, sourceMode: "git", deletionMode: "all" });
431
+ git(sourcePath, ["branch", input.branchName, sourceHead], `create project branch ${input.branchName}`);
432
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(targetPath), { recursive: true });
433
+ try {
434
+ git(sourcePath, ["worktree", "add", "--force", targetPath, input.branchName], `create linked worktree ${input.branchName}`);
435
+ (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: snapshotRoot, targetRoot: targetPath, sourceMode: "all", deletionMode: "git" });
436
+ } catch (error) {
437
+ import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
438
+ tryGit(sourcePath, ["worktree", "prune"]);
439
+ tryGit(sourcePath, ["branch", "-D", input.branchName]);
440
+ throw error;
441
+ }
442
+ return { branchPath: targetPath, baseCommitHash: sourceHead };
443
+ } finally {
444
+ import_node_fs.default.rmSync(snapshotRoot, { recursive: true, force: true });
445
+ }
446
+ }
447
+ function deleteLinkedProjectBranch(input) {
448
+ (0, import_managed_paths.validateManagedBranchName)(input.primaryBranchName);
449
+ (0, import_managed_paths.validateManagedBranchName)(input.branchName);
450
+ if (input.branchName === input.primaryBranchName) throw new Error(`Cannot delete the primary project branch ${input.primaryBranchName}`);
451
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
452
+ const checkoutPath = branchPath(input.projectRoot, input.branchName);
453
+ const primaryCommonDir = commonGitDirectory(primaryPath);
454
+ if (!primaryCommonDir) throw new Error("Primary project checkout is unavailable");
455
+ if (import_node_fs.default.existsSync(checkoutPath)) {
456
+ const checkoutCommonDir = commonGitDirectory(checkoutPath);
457
+ if (checkoutCommonDir !== primaryCommonDir) {
458
+ throw new Error(`Project branch path ${input.branchName} is not a linked worktree of the configured project`);
459
+ }
460
+ if (projectWorktreeOperationInProgress(checkoutPath)) {
461
+ throw new Error(`Project branch ${input.branchName} has an in-progress Git operation`);
462
+ }
463
+ git(primaryPath, ["worktree", "remove", "--force", checkoutPath], `remove linked worktree ${input.branchName}`);
464
+ } else {
465
+ tryGit(primaryPath, ["worktree", "prune"]);
466
+ }
467
+ const branchRef = `refs/heads/${input.branchName}`;
468
+ if (tryGit(primaryPath, ["show-ref", "--verify", "--quiet", branchRef])) {
469
+ git(primaryPath, ["branch", "-D", input.branchName], `delete project branch ${input.branchName}`);
470
+ }
471
+ return { branchName: input.branchName };
472
+ }
473
+ function removeProjectWorktrees(input) {
474
+ const projectRoot = import_node_path.default.resolve(input.projectRoot);
475
+ if (input.branchNames.length === 0) throw new Error("Cannot remove a project with no configured branches");
476
+ for (const branchName of input.branchNames) {
477
+ (0, import_managed_paths.validateManagedBranchName)(branchName);
478
+ const checkoutPath = branchPath(projectRoot, branchName);
479
+ if (import_node_fs.default.existsSync(checkoutPath) && projectWorktreeOperationInProgress(checkoutPath)) {
480
+ throw new Error(`Project branch ${branchName} has an in-progress Git operation`);
481
+ }
482
+ }
483
+ import_node_fs.default.rmSync(projectRoot, { recursive: true, force: true });
484
+ }
485
+ function deleteProjectMirrorBranch(input) {
486
+ (0, import_managed_paths.validateManagedBranchName)(input.branchName);
487
+ const deleted = gitResult(
488
+ input.gitDirectory,
489
+ ["push", "--no-recurse-submodules", input.mirrorUrl, `:refs/heads/${input.branchName}`],
490
+ input.mirrorAuth
491
+ );
492
+ if (deleted.exitCode !== 0 && !/(remote ref does not exist|unable to delete|no such ref)/i.test(`${deleted.stderr}
493
+ ${deleted.stdout}`)) {
494
+ throw new Error(
495
+ `Delete hidden project mirror for ${input.branchName}: ${deleted.stderr || deleted.stdout || `git exited ${deleted.exitCode}`}`
496
+ );
497
+ }
498
+ }
499
+ function pushProjectMirrorHeads(input) {
500
+ const results = [];
501
+ for (const branchName of [...input.branchNames].sort()) {
502
+ if (input.onlyBranches && !input.onlyBranches.has(branchName)) continue;
503
+ const checkoutPath = branchPath(input.projectRoot, branchName);
504
+ const head = git(checkoutPath, ["rev-parse", "HEAD"], `resolve mirror head for ${branchName}`);
505
+ if (projectWorktreeOperationInProgress(checkoutPath)) {
506
+ results.push({ branchName, head, pushed: false, reason: "git_operation_in_progress" });
507
+ continue;
508
+ }
509
+ git(
510
+ checkoutPath,
511
+ ["push", "--no-recurse-submodules", input.mirrorUrl, `+HEAD:refs/heads/${branchName}`],
512
+ `push hidden project mirror for ${branchName}`,
513
+ input.mirrorAuth
514
+ );
515
+ results.push({ branchName, head, pushed: true });
516
+ }
517
+ return results;
518
+ }
519
+ function fastForwardProjectHeadsFromMirror(input) {
520
+ const primaryPath = branchPath(input.projectRoot, input.primaryBranchName);
521
+ const fetch = gitResult(
522
+ primaryPath,
523
+ ["fetch", "--no-recurse-submodules", "--prune", input.mirrorUrl, "+refs/heads/*:refs/r5d/mirror/*"],
524
+ input.mirrorAuth
525
+ );
526
+ if (fetch.exitCode !== 0)
527
+ throw new Error(`Refresh project head mirror: ${fetch.stderr || fetch.stdout || `git exited ${fetch.exitCode}`}`);
528
+ return [...input.branchNames].sort().map((branchName) => {
529
+ const checkoutPath = branchPath(input.projectRoot, branchName);
530
+ const previousHead = git(checkoutPath, ["rev-parse", "HEAD"], `resolve local project head ${branchName}`);
531
+ const mirrorRef = `refs/r5d/mirror/${branchName}`;
532
+ const mirrorHead = tryGit(primaryPath, ["show-ref", "--verify", "--quiet", mirrorRef]) ? git(primaryPath, ["rev-parse", mirrorRef], `resolve refreshed mirror head ${branchName}`) : null;
533
+ if (!mirrorHead || mirrorHead === previousHead || projectWorktreeOperationInProgress(checkoutPath) || !tryGit(checkoutPath, ["merge-base", "--is-ancestor", previousHead, mirrorHead])) {
534
+ return { branchName, previousHead, mirrorHead, fastForwarded: false };
535
+ }
536
+ git(checkoutPath, ["update-ref", `refs/heads/${branchName}`, mirrorHead, previousHead], `fast-forward project branch ${branchName}`);
537
+ git(checkoutPath, ["reset", "--mixed", mirrorHead], `refresh project index ${branchName}`);
538
+ return { branchName, previousHead, mirrorHead, fastForwarded: true };
539
+ });
540
+ }
541
+ const projectWorktreesTestHarness = {
542
+ commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args]
543
+ };
544
+ // Annotate the CommonJS export names for ESM import in node:
545
+ 0 && (module.exports = {
546
+ PROJECT_WORKTREE_SNAPSHOT_PREFIX,
547
+ cleanupStaleProjectWorktreeSnapshots,
548
+ createLinkedProjectBranch,
549
+ deleteLinkedProjectBranch,
550
+ deleteProjectMirrorBranch,
551
+ ensureProjectWorktrees,
552
+ fastForwardProjectHeadsFromMirror,
553
+ hasLinkedProjectWorktreeLayout,
554
+ projectWorktreeConfigurationFingerprint,
555
+ projectWorktreeOperationInProgress,
556
+ projectWorktreesTestHarness,
557
+ pushProjectMirrorHeads,
558
+ removeProjectWorktrees
559
+ });