@ricsam/r5d-worker 0.0.36 → 0.0.37

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.36",
3
+ "version": "0.0.37",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,652 @@
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 workspace_sync_exports = {};
30
+ __export(workspace_sync_exports, {
31
+ MAX_WORKSPACE_SYNC_DIFF_BYTES: () => MAX_WORKSPACE_SYNC_DIFF_BYTES,
32
+ WORKSPACE_BRANCH: () => WORKSPACE_BRANCH,
33
+ WORKSPACE_PERIODIC_SCAN_INTERVAL_MS: () => WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
34
+ WorkspaceSyncSingleFlight: () => WorkspaceSyncSingleFlight,
35
+ calculateWorkspaceDiffFingerprint: () => calculateWorkspaceDiffFingerprint,
36
+ encodeWorkspaceBranch: () => encodeWorkspaceBranch,
37
+ mirrorShadowWorkspaceToVisible: () => mirrorShadowWorkspaceToVisible,
38
+ mirrorVisibleWorkspaceToShadow: () => mirrorVisibleWorkspaceToShadow,
39
+ synchronizeWorkspace: () => synchronizeWorkspace,
40
+ visibleProjectBranchPath: () => visibleProjectBranchPath,
41
+ workspacePlansRelativePath: () => workspacePlansRelativePath,
42
+ workspaceProjectBranchRelativePath: () => workspaceProjectBranchRelativePath
43
+ });
44
+ module.exports = __toCommonJS(workspace_sync_exports);
45
+ var import_node_crypto = require("node:crypto");
46
+ var import_node_fs = __toESM(require("node:fs"), 1);
47
+ var import_node_path = __toESM(require("node:path"), 1);
48
+ const WORKSPACE_BRANCH = "main";
49
+ const MAX_WORKSPACE_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
50
+ const WORKSPACE_PERIODIC_SCAN_INTERVAL_MS = 5e3;
51
+ const MAX_REPORTED_WORKSPACE_PATHS = 2e3;
52
+ const MAX_REPORTED_WORKSPACE_STATUS_BYTES = 256 * 1024;
53
+ const MAX_MIRROR_COMPARISON_CACHE_ENTRIES = 2e5;
54
+ const MIRROR_COMPARE_BUFFER_BYTES = 64 * 1024;
55
+ const mirrorComparisonCache = /* @__PURE__ */ new Map();
56
+ function gitArgs(input, args) {
57
+ return input.authHeader ? ["git", "-c", `http.extraHeader=${input.authHeader}`, ...args] : ["git", ...args];
58
+ }
59
+ function runGitResult(input, cwd, args) {
60
+ const result = Bun.spawnSync(gitArgs(input, args), {
61
+ cwd,
62
+ stdout: "pipe",
63
+ stderr: "pipe",
64
+ env: {
65
+ ...process.env,
66
+ GIT_TERMINAL_PROMPT: "0"
67
+ }
68
+ });
69
+ return {
70
+ exitCode: result.exitCode,
71
+ stdout: result.stdout.toString().trim(),
72
+ stderr: result.stderr.toString().trim()
73
+ };
74
+ }
75
+ function runGit(input, cwd, args, action) {
76
+ const result = runGitResult(input, cwd, args);
77
+ if (result.exitCode !== 0) {
78
+ throw new Error(`${action}: ${result.stderr || result.stdout || `git exited ${result.exitCode}`}`);
79
+ }
80
+ return result.stdout;
81
+ }
82
+ function tryGit(input, cwd, args) {
83
+ return runGitResult(input, cwd, args).exitCode === 0;
84
+ }
85
+ function encodeWorkspaceBranch(branchName) {
86
+ return encodeURIComponent(branchName);
87
+ }
88
+ function workspaceProjectBranchRelativePath(projectId, branchName) {
89
+ return import_node_path.default.posix.join("projects", projectId, "branches", encodeWorkspaceBranch(branchName));
90
+ }
91
+ function workspacePlansRelativePath(projectId, branchName) {
92
+ return import_node_path.default.posix.join("plans", projectId, encodeWorkspaceBranch(branchName));
93
+ }
94
+ function visibleProjectBranchPath(projectsRoot, manifest, branchName) {
95
+ return import_node_path.default.join(projectsRoot, manifest.repoSlug || manifest.projectId, branchName);
96
+ }
97
+ function localPlansBranchPath(plansRoot, projectId, branchName) {
98
+ return import_node_path.default.join(plansRoot, projectId, branchName);
99
+ }
100
+ function assertInside(root, candidate, label) {
101
+ const resolvedRoot = import_node_path.default.resolve(root);
102
+ const resolvedCandidate = import_node_path.default.resolve(candidate);
103
+ const relative = import_node_path.default.relative(resolvedRoot, resolvedCandidate);
104
+ if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
105
+ throw new Error(`${label} escapes its managed root: ${candidate}`);
106
+ }
107
+ }
108
+ function listGitEligibleFiles(checkoutPath) {
109
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(checkoutPath, ".git"))) {
110
+ return [];
111
+ }
112
+ const result = Bun.spawnSync(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."], {
113
+ cwd: checkoutPath,
114
+ stdout: "pipe",
115
+ stderr: "pipe",
116
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
117
+ });
118
+ if (result.exitCode !== 0) {
119
+ throw new Error(`inspect eligible project files: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
120
+ }
121
+ return result.stdout.toString().split("\0").filter((entry) => entry.length > 0 && entry !== ".git" && !entry.startsWith(".git/")).filter((entry) => {
122
+ try {
123
+ const stat = import_node_fs.default.lstatSync(import_node_path.default.join(checkoutPath, ...entry.split("/")));
124
+ return stat.isFile() || stat.isSymbolicLink();
125
+ } catch {
126
+ return false;
127
+ }
128
+ }).sort();
129
+ }
130
+ function listShadowTrackedFiles(input, relativeRoot) {
131
+ const normalizedRoot = relativeRoot.split(import_node_path.default.sep).join("/").replace(/^\/+|\/+$/g, "");
132
+ const output = runGit(input, input.shadowRoot, ["ls-files", "-z", "--cached", "--", normalizedRoot], "list canonical workspace files");
133
+ const prefix = `${normalizedRoot}/`;
134
+ return output.split("\0").filter((entry) => entry.startsWith(prefix)).map((entry) => entry.slice(prefix.length)).filter(Boolean).sort();
135
+ }
136
+ function listFilesRecursively(root, filter) {
137
+ if (!import_node_fs.default.existsSync(root)) return [];
138
+ const files = [];
139
+ const visit = (current, relativeDir) => {
140
+ for (const entry of import_node_fs.default.readdirSync(current, { withFileTypes: true })) {
141
+ if (entry.name === ".git") continue;
142
+ const relativePath = relativeDir ? import_node_path.default.posix.join(relativeDir, entry.name) : entry.name;
143
+ const absolutePath = import_node_path.default.join(current, entry.name);
144
+ if (entry.isDirectory()) {
145
+ visit(absolutePath, relativePath);
146
+ } else if (!filter || filter(relativePath)) {
147
+ files.push(relativePath);
148
+ }
149
+ }
150
+ };
151
+ visit(root, "");
152
+ return files.sort();
153
+ }
154
+ function removeEmptyDirectories(root) {
155
+ if (!import_node_fs.default.existsSync(root)) return;
156
+ const visit = (current) => {
157
+ let empty = true;
158
+ for (const entry of import_node_fs.default.readdirSync(current, { withFileTypes: true })) {
159
+ const absolutePath = import_node_path.default.join(current, entry.name);
160
+ if (entry.isDirectory()) {
161
+ if (!visit(absolutePath)) empty = false;
162
+ } else {
163
+ empty = false;
164
+ }
165
+ }
166
+ if (empty && current !== root) import_node_fs.default.rmdirSync(current);
167
+ return empty;
168
+ };
169
+ visit(root);
170
+ }
171
+ function copyEntry(sourceRoot, targetRoot, relativePath) {
172
+ const sourcePath = import_node_path.default.resolve(sourceRoot, ...relativePath.split("/"));
173
+ const targetPath = import_node_path.default.resolve(targetRoot, ...relativePath.split("/"));
174
+ assertInside(sourceRoot, sourcePath, "Workspace source path");
175
+ assertInside(targetRoot, targetPath, "Workspace target path");
176
+ const stat = import_node_fs.default.lstatSync(sourcePath);
177
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(targetPath), { recursive: true });
178
+ import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
179
+ if (stat.isSymbolicLink()) {
180
+ import_node_fs.default.symlinkSync(import_node_fs.default.readlinkSync(sourcePath), targetPath);
181
+ return;
182
+ }
183
+ if (stat.isFile()) {
184
+ import_node_fs.default.copyFileSync(sourcePath, targetPath);
185
+ import_node_fs.default.chmodSync(targetPath, stat.mode);
186
+ }
187
+ }
188
+ function entrySignature(stat) {
189
+ return [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
190
+ }
191
+ function regularFilesEqual(sourcePath, targetPath, size) {
192
+ const sourceFd = import_node_fs.default.openSync(sourcePath, "r");
193
+ const targetFd = import_node_fs.default.openSync(targetPath, "r");
194
+ const sourceBuffer = Buffer.allocUnsafe(Math.min(MIRROR_COMPARE_BUFFER_BYTES, Math.max(1, size)));
195
+ const targetBuffer = Buffer.allocUnsafe(sourceBuffer.length);
196
+ try {
197
+ let offset = 0;
198
+ while (offset < size) {
199
+ const length = Math.min(sourceBuffer.length, size - offset);
200
+ const sourceBytes = import_node_fs.default.readSync(sourceFd, sourceBuffer, 0, length, offset);
201
+ const targetBytes = import_node_fs.default.readSync(targetFd, targetBuffer, 0, length, offset);
202
+ if (sourceBytes === 0 || targetBytes === 0 || sourceBytes !== targetBytes || !sourceBuffer.subarray(0, sourceBytes).equals(targetBuffer.subarray(0, targetBytes))) {
203
+ return false;
204
+ }
205
+ offset += sourceBytes;
206
+ }
207
+ return true;
208
+ } finally {
209
+ import_node_fs.default.closeSync(sourceFd);
210
+ import_node_fs.default.closeSync(targetFd);
211
+ }
212
+ }
213
+ function entriesEqual(sourcePath, targetPath) {
214
+ let sourceStat;
215
+ let targetStat;
216
+ try {
217
+ sourceStat = import_node_fs.default.lstatSync(sourcePath);
218
+ targetStat = import_node_fs.default.lstatSync(targetPath);
219
+ } catch {
220
+ return false;
221
+ }
222
+ const cacheKey = `${sourcePath}\0${targetPath}`;
223
+ const signature = `${entrySignature(sourceStat)}\0${entrySignature(targetStat)}`;
224
+ if (mirrorComparisonCache.get(cacheKey) === signature) return true;
225
+ let equal = false;
226
+ if (sourceStat.isSymbolicLink() && targetStat.isSymbolicLink()) {
227
+ equal = import_node_fs.default.readlinkSync(sourcePath) === import_node_fs.default.readlinkSync(targetPath);
228
+ } else if (sourceStat.isFile() && targetStat.isFile()) {
229
+ equal = sourceStat.size === targetStat.size && (sourceStat.mode & 73) === (targetStat.mode & 73) && regularFilesEqual(sourcePath, targetPath, sourceStat.size);
230
+ }
231
+ if (equal) {
232
+ if (mirrorComparisonCache.size >= MAX_MIRROR_COMPARISON_CACHE_ENTRIES) mirrorComparisonCache.clear();
233
+ mirrorComparisonCache.set(cacheKey, signature);
234
+ } else {
235
+ mirrorComparisonCache.delete(cacheKey);
236
+ }
237
+ return equal;
238
+ }
239
+ function mirrorFileSet(sourceRoot, targetRoot, sourceFiles, targetFiles) {
240
+ import_node_fs.default.mkdirSync(targetRoot, { recursive: true });
241
+ const sourceSet = new Set(sourceFiles);
242
+ for (const relativePath of targetFiles) {
243
+ if (sourceSet.has(relativePath)) continue;
244
+ const targetPath = import_node_path.default.resolve(targetRoot, ...relativePath.split("/"));
245
+ assertInside(targetRoot, targetPath, "Workspace deletion path");
246
+ import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
247
+ }
248
+ for (const relativePath of sourceFiles) {
249
+ const sourcePath = import_node_path.default.resolve(sourceRoot, ...relativePath.split("/"));
250
+ if (!import_node_fs.default.existsSync(sourcePath)) continue;
251
+ const stat = import_node_fs.default.lstatSync(sourcePath);
252
+ if (!stat.isFile() && !stat.isSymbolicLink()) continue;
253
+ const targetPath = import_node_path.default.resolve(targetRoot, ...relativePath.split("/"));
254
+ if (entriesEqual(sourcePath, targetPath)) continue;
255
+ copyEntry(sourceRoot, targetRoot, relativePath);
256
+ }
257
+ removeEmptyDirectories(targetRoot);
258
+ }
259
+ function mirrorVisibleProjectToShadow(input, manifest, branchName) {
260
+ const visibleRoot = visibleProjectBranchPath(input.projectsRoot, manifest, branchName);
261
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(visibleRoot, ".git"))) return;
262
+ const shadowRoot = import_node_path.default.join(input.shadowRoot, ...workspaceProjectBranchRelativePath(manifest.projectId, branchName).split("/"));
263
+ mirrorFileSet(visibleRoot, shadowRoot, listGitEligibleFiles(visibleRoot), listFilesRecursively(shadowRoot));
264
+ }
265
+ function mirrorShadowProjectToVisible(input, manifest, branchName) {
266
+ const visibleRoot = visibleProjectBranchPath(input.projectsRoot, manifest, branchName);
267
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(visibleRoot, ".git"))) return;
268
+ const relativeRoot = workspaceProjectBranchRelativePath(manifest.projectId, branchName);
269
+ const shadowRoot = import_node_path.default.join(input.shadowRoot, ...relativeRoot.split("/"));
270
+ const shadowFiles = listShadowTrackedFiles(input, relativeRoot);
271
+ mirrorFileSet(shadowRoot, visibleRoot, shadowFiles, listGitEligibleFiles(visibleRoot));
272
+ }
273
+ function mirrorLocalPlansToShadow(input, manifest, branchName) {
274
+ const sourceRoot = localPlansBranchPath(input.plansRoot, manifest.projectId, branchName);
275
+ if (!import_node_fs.default.existsSync(sourceRoot)) return;
276
+ const targetRoot = import_node_path.default.join(input.shadowRoot, ...workspacePlansRelativePath(manifest.projectId, branchName).split("/"));
277
+ const planFilter = (relativePath) => relativePath.endsWith(".plan.md");
278
+ mirrorFileSet(sourceRoot, targetRoot, listFilesRecursively(sourceRoot, planFilter), listFilesRecursively(targetRoot));
279
+ }
280
+ function mirrorShadowPlansToLocal(input, manifest, branchName) {
281
+ const relativeRoot = workspacePlansRelativePath(manifest.projectId, branchName);
282
+ const sourceRoot = import_node_path.default.join(input.shadowRoot, ...relativeRoot.split("/"));
283
+ const targetRoot = localPlansBranchPath(input.plansRoot, manifest.projectId, branchName);
284
+ const planFilter = (relativePath) => relativePath.endsWith(".plan.md");
285
+ mirrorFileSet(
286
+ sourceRoot,
287
+ targetRoot,
288
+ listShadowTrackedFiles(input, relativeRoot).filter(planFilter),
289
+ listFilesRecursively(targetRoot, planFilter)
290
+ );
291
+ }
292
+ function pruneDirectoryChildren(root, allowedNames) {
293
+ if (!import_node_fs.default.existsSync(root)) return;
294
+ for (const entry of import_node_fs.default.readdirSync(root)) {
295
+ if (allowedNames.has(entry)) continue;
296
+ const target = import_node_path.default.resolve(root, entry);
297
+ assertInside(root, target, "Workspace manifest prune path");
298
+ import_node_fs.default.rmSync(target, { recursive: true, force: true });
299
+ }
300
+ }
301
+ function pruneShadowToManifest(input) {
302
+ const projectIds = new Set(input.projects.map((project) => project.projectId));
303
+ const projectsRoot = import_node_path.default.join(input.shadowRoot, "projects");
304
+ const plansRoot = import_node_path.default.join(input.shadowRoot, "plans");
305
+ pruneDirectoryChildren(projectsRoot, projectIds);
306
+ pruneDirectoryChildren(plansRoot, projectIds);
307
+ for (const manifest of input.projects) {
308
+ const encodedBranches = new Set(manifest.branches.map(encodeWorkspaceBranch));
309
+ pruneDirectoryChildren(import_node_path.default.join(projectsRoot, manifest.projectId, "branches"), encodedBranches);
310
+ pruneDirectoryChildren(import_node_path.default.join(plansRoot, manifest.projectId), encodedBranches);
311
+ }
312
+ }
313
+ function mirrorVisibleWorkspaceToShadow(input, excludedCheckouts = /* @__PURE__ */ new Set()) {
314
+ pruneShadowToManifest(input);
315
+ for (const manifest of [...input.projects].sort((left, right) => left.projectId.localeCompare(right.projectId))) {
316
+ for (const branchName of [...new Set(manifest.branches)].sort()) {
317
+ if (excludedCheckouts.has(`${manifest.projectId}\0${branchName}`)) continue;
318
+ mirrorVisibleProjectToShadow(input, manifest, branchName);
319
+ mirrorLocalPlansToShadow(input, manifest, branchName);
320
+ }
321
+ }
322
+ }
323
+ function reconcileNewVisibleCheckouts(input) {
324
+ for (const target of input.newVisibleCheckouts ?? []) {
325
+ const manifest = input.projects.find((project) => project.projectId === target.projectId);
326
+ if (!manifest || !manifest.branches.includes(target.branchName)) continue;
327
+ const projectRoot = workspaceProjectBranchRelativePath(target.projectId, target.branchName);
328
+ if (listShadowTrackedFiles(input, projectRoot).length > 0) {
329
+ mirrorShadowProjectToVisible(input, manifest, target.branchName);
330
+ } else {
331
+ mirrorVisibleProjectToShadow(input, manifest, target.branchName);
332
+ }
333
+ const plansRoot = workspacePlansRelativePath(target.projectId, target.branchName);
334
+ if (listShadowTrackedFiles(input, plansRoot).length > 0) {
335
+ mirrorShadowPlansToLocal(input, manifest, target.branchName);
336
+ } else {
337
+ mirrorLocalPlansToShadow(input, manifest, target.branchName);
338
+ }
339
+ }
340
+ }
341
+ function mirrorShadowWorkspaceToVisible(input) {
342
+ for (const manifest of [...input.projects].sort((left, right) => left.projectId.localeCompare(right.projectId))) {
343
+ for (const branchName of [...new Set(manifest.branches)].sort()) {
344
+ mirrorShadowProjectToVisible(input, manifest, branchName);
345
+ mirrorShadowPlansToLocal(input, manifest, branchName);
346
+ }
347
+ }
348
+ }
349
+ function ensureShadowWorkspace(input) {
350
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(input.shadowRoot), { recursive: true });
351
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(input.shadowRoot, ".git"))) {
352
+ import_node_fs.default.rmSync(input.shadowRoot, { recursive: true, force: true });
353
+ const clone = runGitResult(input, import_node_path.default.dirname(input.shadowRoot), ["clone", "--origin", "origin", input.remoteUrl, input.shadowRoot]);
354
+ if (clone.exitCode !== 0) {
355
+ throw new Error(`clone canonical workspace: ${clone.stderr || clone.stdout || `git exited ${clone.exitCode}`}`);
356
+ }
357
+ }
358
+ if (tryGit(input, input.shadowRoot, ["remote", "get-url", "origin"])) {
359
+ runGit(input, input.shadowRoot, ["remote", "set-url", "origin", input.remoteUrl], "update canonical workspace remote");
360
+ } else {
361
+ runGit(input, input.shadowRoot, ["remote", "add", "origin", input.remoteUrl], "add canonical workspace remote");
362
+ }
363
+ runGit(input, input.shadowRoot, ["config", "user.name", "r5d.dev Worker"], "configure workspace Git name");
364
+ runGit(input, input.shadowRoot, ["config", "user.email", "worker@r5d.dev"], "configure workspace Git email");
365
+ runGit(input, input.shadowRoot, ["fetch", "origin", "--prune"], "fetch canonical workspace");
366
+ const hasHead = tryGit(input, input.shadowRoot, ["rev-parse", "--verify", "HEAD"]);
367
+ const hasRemoteMain = tryGit(input, input.shadowRoot, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${WORKSPACE_BRANCH}`]);
368
+ if (!hasHead && hasRemoteMain) {
369
+ runGit(input, input.shadowRoot, ["checkout", "-B", WORKSPACE_BRANCH, `origin/${WORKSPACE_BRANCH}`], "checkout canonical workspace");
370
+ } else if (!hasHead) {
371
+ runGit(input, input.shadowRoot, ["checkout", "--orphan", WORKSPACE_BRANCH], "create canonical workspace branch");
372
+ }
373
+ }
374
+ function revParse(input, revision) {
375
+ const result = runGitResult(input, input.shadowRoot, ["rev-parse", "--verify", revision]);
376
+ return result.exitCode === 0 ? result.stdout : null;
377
+ }
378
+ function gitStatus(input) {
379
+ return runGit(input, input.shadowRoot, ["status", "--porcelain=v1", "--untracked-files=all"], "read workspace status");
380
+ }
381
+ function stagedPaths(input) {
382
+ return runGit(input, input.shadowRoot, ["diff", "--cached", "--name-only", "-z"], "list workspace changes").split("\0").filter(Boolean).sort();
383
+ }
384
+ async function stagedDiffSizeBytes(input) {
385
+ const subprocess = Bun.spawn(gitArgs(input, ["diff", "--cached", "--binary", "--no-ext-diff"]), {
386
+ cwd: input.shadowRoot,
387
+ stdout: "pipe",
388
+ stderr: "pipe",
389
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
390
+ });
391
+ const stderrPromise = new Response(subprocess.stderr).text();
392
+ const reader = subprocess.stdout.getReader();
393
+ let total = 0;
394
+ while (true) {
395
+ const chunk = await reader.read();
396
+ if (chunk.done) break;
397
+ total += chunk.value.byteLength;
398
+ if (total > MAX_WORKSPACE_SYNC_DIFF_BYTES) {
399
+ subprocess.kill();
400
+ await subprocess.exited;
401
+ await stderrPromise;
402
+ return MAX_WORKSPACE_SYNC_DIFF_BYTES + 1;
403
+ }
404
+ }
405
+ const exitCode = await subprocess.exited;
406
+ const stderr = (await stderrPromise).trim();
407
+ if (exitCode !== 0) throw new Error(`measure workspace diff: ${stderr || `git exited ${exitCode}`}`);
408
+ return total;
409
+ }
410
+ function reportedPaths(paths) {
411
+ if (paths.length <= MAX_REPORTED_WORKSPACE_PATHS) return paths;
412
+ return [
413
+ ...paths.slice(0, MAX_REPORTED_WORKSPACE_PATHS),
414
+ `[${paths.length - MAX_REPORTED_WORKSPACE_PATHS} additional paths omitted; inspect with git status]`
415
+ ];
416
+ }
417
+ function reportedStatus(status) {
418
+ const bytes = Buffer.from(status, "utf8");
419
+ if (bytes.length <= MAX_REPORTED_WORKSPACE_STATUS_BYTES) return status;
420
+ return `${new TextDecoder().decode(bytes.subarray(0, MAX_REPORTED_WORKSPACE_STATUS_BYTES))}
421
+ [status truncated; inspect on the origin worker]`;
422
+ }
423
+ function affectedProjects(paths) {
424
+ const ids = /* @__PURE__ */ new Set();
425
+ for (const filePath of paths) {
426
+ const match = /^(?:projects|plans)\/([^/]+)\//.exec(filePath);
427
+ if (match?.[1]) ids.add(match[1]);
428
+ }
429
+ return [...ids].sort();
430
+ }
431
+ function commitMessage(input) {
432
+ return JSON.stringify({
433
+ type: "workspace_sync",
434
+ attemptId: input.attemptId,
435
+ worker: input.workerLabel,
436
+ trigger: input.trigger,
437
+ ...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
438
+ });
439
+ }
440
+ function isNonFastForward(result) {
441
+ const output = `${result.stdout}
442
+ ${result.stderr}`.toLowerCase();
443
+ return output.includes("non-fast-forward") || output.includes("fetch first") || output.includes("[rejected]");
444
+ }
445
+ function resetShadowToRemote(input) {
446
+ runGit(input, input.shadowRoot, ["fetch", "origin", "--prune"], "fetch canonical workspace before reset");
447
+ const remoteHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
448
+ if (!remoteHead) return null;
449
+ runGitResult(input, input.shadowRoot, ["rebase", "--abort"]);
450
+ runGit(input, input.shadowRoot, ["reset", "--hard", `origin/${WORKSPACE_BRANCH}`], "reset workspace to canonical state");
451
+ runGit(input, input.shadowRoot, ["clean", "-fd"], "clean reset workspace");
452
+ mirrorShadowWorkspaceToVisible(input);
453
+ return remoteHead;
454
+ }
455
+ function baseResult(input, startingHead) {
456
+ return {
457
+ type: "workspace_sync",
458
+ attemptId: input.attemptId ?? crypto.randomUUID(),
459
+ workerLabel: input.workerLabel,
460
+ trigger: input.trigger,
461
+ startingHead,
462
+ rebaseCount: 0,
463
+ diffSizeBytes: 0,
464
+ gitStatus: "",
465
+ affectedProjects: [],
466
+ affectedPaths: [],
467
+ discardedPaths: [],
468
+ localChangesDiscarded: false
469
+ };
470
+ }
471
+ async function synchronizeWorkspace(rawInput) {
472
+ const input = { ...rawInput, attemptId: rawInput.attemptId ?? crypto.randomUUID() };
473
+ try {
474
+ ensureShadowWorkspace(input);
475
+ const remoteHeadAtStart = revParse(input, `origin/${WORKSPACE_BRANCH}`);
476
+ const result = baseResult(input, remoteHeadAtStart);
477
+ if (input.resetToCanonical) {
478
+ const publishedHead = resetShadowToRemote(input);
479
+ return {
480
+ ...result,
481
+ outcome: "reset",
482
+ publishedHead: publishedHead ?? void 0,
483
+ localChangesDiscarded: true,
484
+ gitStatus: gitStatus(input)
485
+ };
486
+ }
487
+ if (!input.skipVisibleMirror) {
488
+ const newCheckoutKeys = new Set((input.newVisibleCheckouts ?? []).map((target) => `${target.projectId}\0${target.branchName}`));
489
+ mirrorVisibleWorkspaceToShadow(input, newCheckoutKeys);
490
+ reconcileNewVisibleCheckouts(input);
491
+ }
492
+ runGit(input, input.shadowRoot, ["add", "-A"], "stage workspace changes");
493
+ const paths = stagedPaths(input);
494
+ const diffSizeBytes = await stagedDiffSizeBytes(input);
495
+ const statusBeforeCommit = reportedStatus(gitStatus(input));
496
+ const observed = {
497
+ ...result,
498
+ diffSizeBytes,
499
+ gitStatus: statusBeforeCommit,
500
+ affectedPaths: reportedPaths(paths),
501
+ affectedProjects: affectedProjects(paths)
502
+ };
503
+ if (paths.length > 0 && diffSizeBytes > MAX_WORKSPACE_SYNC_DIFF_BYTES && !input.confirmedLargeDiff) {
504
+ return { ...observed, outcome: "large_diff_blocked" };
505
+ }
506
+ if (paths.length > 0) {
507
+ runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit workspace changes");
508
+ }
509
+ const candidateHead = revParse(input, "HEAD");
510
+ const hasRemoteMain = Boolean(revParse(input, `origin/${WORKSPACE_BRANCH}`));
511
+ const hasUnpushedCommit = candidateHead ? !hasRemoteMain || !tryGit(input, input.shadowRoot, ["merge-base", "--is-ancestor", candidateHead, `origin/${WORKSPACE_BRANCH}`]) : false;
512
+ if (!hasUnpushedCommit) {
513
+ const localHead = revParse(input, "HEAD");
514
+ const currentRemoteHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
515
+ if (currentRemoteHead && localHead !== currentRemoteHead) {
516
+ runGit(input, input.shadowRoot, ["reset", "--hard", `origin/${WORKSPACE_BRANCH}`], "update workspace from canonical state");
517
+ runGit(input, input.shadowRoot, ["clean", "-fd"], "clean updated workspace");
518
+ mirrorShadowWorkspaceToVisible(input);
519
+ return {
520
+ ...observed,
521
+ outcome: "updated",
522
+ candidateHead: candidateHead ?? void 0,
523
+ publishedHead: currentRemoteHead,
524
+ gitStatus: gitStatus(input)
525
+ };
526
+ }
527
+ if (input.skipVisibleMirror) mirrorShadowWorkspaceToVisible(input);
528
+ return {
529
+ ...observed,
530
+ outcome: "no_change",
531
+ candidateHead: candidateHead ?? void 0,
532
+ publishedHead: currentRemoteHead ?? localHead ?? void 0,
533
+ gitStatus: gitStatus(input)
534
+ };
535
+ }
536
+ let rebaseCount = 0;
537
+ for (let pushAttempt = 0; pushAttempt < 3; pushAttempt += 1) {
538
+ const push = runGitResult(input, input.shadowRoot, ["push", "origin", `HEAD:refs/heads/${WORKSPACE_BRANCH}`]);
539
+ if (push.exitCode === 0) {
540
+ const publishedHead = revParse(input, "HEAD") ?? candidateHead;
541
+ mirrorShadowWorkspaceToVisible(input);
542
+ return {
543
+ ...observed,
544
+ outcome: "published",
545
+ candidateHead: candidateHead ?? void 0,
546
+ publishedHead: publishedHead ?? void 0,
547
+ rebaseCount,
548
+ gitStatus: gitStatus(input)
549
+ };
550
+ }
551
+ if (!isNonFastForward(push)) {
552
+ return {
553
+ ...observed,
554
+ outcome: "failed",
555
+ candidateHead: candidateHead ?? void 0,
556
+ rebaseCount,
557
+ error: push.stderr || push.stdout || "Workspace push failed",
558
+ gitStatus: gitStatus(input)
559
+ };
560
+ }
561
+ runGit(input, input.shadowRoot, ["fetch", "origin", "--prune"], "fetch workspace after push race");
562
+ const rebase = runGitResult(input, input.shadowRoot, ["rebase", `origin/${WORKSPACE_BRANCH}`]);
563
+ rebaseCount += 1;
564
+ if (rebase.exitCode !== 0) {
565
+ const conflicted = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]);
566
+ const discardedPaths = reportedPaths([.../* @__PURE__ */ new Set([...paths, ...conflicted.stdout.split("\0").filter(Boolean)])].sort());
567
+ const publishedHead = resetShadowToRemote(input);
568
+ return {
569
+ ...observed,
570
+ outcome: "conflict_reset",
571
+ candidateHead: candidateHead ?? void 0,
572
+ publishedHead: publishedHead ?? void 0,
573
+ rebaseCount,
574
+ discardedPaths,
575
+ localChangesDiscarded: true,
576
+ gitStatus: gitStatus(input)
577
+ };
578
+ }
579
+ }
580
+ return {
581
+ ...observed,
582
+ outcome: "failed",
583
+ candidateHead: candidateHead ?? void 0,
584
+ rebaseCount,
585
+ error: "Workspace push did not converge after three attempts",
586
+ gitStatus: gitStatus(input)
587
+ };
588
+ } catch (error) {
589
+ const result = baseResult(input, null);
590
+ return {
591
+ ...result,
592
+ outcome: "failed",
593
+ error: error instanceof Error ? error.message : String(error)
594
+ };
595
+ }
596
+ }
597
+ function calculateWorkspaceDiffFingerprint(input) {
598
+ ensureShadowWorkspace(input);
599
+ if (!input.skipVisibleMirror) mirrorVisibleWorkspaceToShadow(input);
600
+ runGit(input, input.shadowRoot, ["add", "-A"], "stage workspace fingerprint");
601
+ const stagedTree = runGit(input, input.shadowRoot, ["write-tree"], "write workspace fingerprint tree");
602
+ const headTree = revParse(input, "HEAD^{tree}");
603
+ const localHead = revParse(input, "HEAD");
604
+ const remoteHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
605
+ if (stagedTree === headTree && localHead === remoteHead) {
606
+ return (0, import_node_crypto.createHash)("sha256").update("").digest("hex");
607
+ }
608
+ return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify({ stagedTree, localHead, remoteHead })).digest("hex");
609
+ }
610
+ class WorkspaceSyncSingleFlight {
611
+ queue = Promise.resolve();
612
+ run(input) {
613
+ const queued = this.queue.then(
614
+ () => synchronizeWorkspace(input),
615
+ () => synchronizeWorkspace(input)
616
+ );
617
+ this.queue = queued.then(
618
+ () => void 0,
619
+ () => void 0
620
+ );
621
+ return queued;
622
+ }
623
+ fingerprint(input) {
624
+ const queued = this.queue.then(
625
+ () => calculateWorkspaceDiffFingerprint(input),
626
+ () => calculateWorkspaceDiffFingerprint(input)
627
+ );
628
+ this.queue = queued.then(
629
+ () => void 0,
630
+ () => void 0
631
+ );
632
+ return queued;
633
+ }
634
+ afterCurrent() {
635
+ return this.queue;
636
+ }
637
+ }
638
+ // Annotate the CommonJS export names for ESM import in node:
639
+ 0 && (module.exports = {
640
+ MAX_WORKSPACE_SYNC_DIFF_BYTES,
641
+ WORKSPACE_BRANCH,
642
+ WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
643
+ WorkspaceSyncSingleFlight,
644
+ calculateWorkspaceDiffFingerprint,
645
+ encodeWorkspaceBranch,
646
+ mirrorShadowWorkspaceToVisible,
647
+ mirrorVisibleWorkspaceToShadow,
648
+ synchronizeWorkspace,
649
+ visibleProjectBranchPath,
650
+ workspacePlansRelativePath,
651
+ workspaceProjectBranchRelativePath
652
+ });