@narumitw/pi-sync 0.54.2 → 0.56.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/README.md +40 -15
- package/dist/chunks/agent-candidate-3JN6DJKT.ts +26 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts +99 -0
- package/dist/chunks/agent-plan-QCUJWBP4.ts.map +7 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts +182 -0
- package/dist/chunks/agent-runner-E2JU2VZ6.ts.map +7 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts +152 -0
- package/dist/chunks/agent-store-A3F6IYWZ.ts.map +7 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts +128 -0
- package/dist/chunks/chunk-5Q5SSKNG.ts.map +7 -0
- package/dist/chunks/chunk-5XZOC7FD.ts +215 -0
- package/dist/chunks/chunk-5XZOC7FD.ts.map +7 -0
- package/dist/chunks/{chunk-IJ6VV6FN.ts → chunk-AAEEHR7H.ts} +31 -87
- package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
- package/dist/chunks/{chunk-CHSIAC4N.ts → chunk-B4QZBDBT.ts} +2 -2
- package/dist/chunks/{chunk-CL7BZRRQ.ts → chunk-CRKTAPSL.ts} +8 -8
- package/dist/chunks/chunk-DT7M4VQS.ts +69 -0
- package/dist/chunks/chunk-DT7M4VQS.ts.map +7 -0
- package/dist/chunks/chunk-ED3TYIVW.ts +229 -0
- package/dist/chunks/chunk-ED3TYIVW.ts.map +7 -0
- package/dist/chunks/{chunk-VHXBCEVF.ts → chunk-EUMINX7L.ts} +6 -6
- package/dist/chunks/chunk-FQOSHGJ7.ts +215 -0
- package/dist/chunks/chunk-FQOSHGJ7.ts.map +7 -0
- package/dist/chunks/chunk-GNH73BJ5.ts +153 -0
- package/dist/chunks/chunk-GNH73BJ5.ts.map +7 -0
- package/dist/chunks/{chunk-UQ3ALBIB.ts → chunk-GP5DU62N.ts} +8 -9
- package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
- package/dist/chunks/{chunk-5RZPINUQ.ts → chunk-GSNPASKB.ts} +2 -2
- package/dist/chunks/{chunk-QE7NZZWT.ts → chunk-IWZH5KYS.ts} +23 -7
- package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
- package/dist/chunks/{chunk-5ZWQ33XL.ts → chunk-JJTARUSO.ts} +15 -14
- package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
- package/dist/chunks/{chunk-2DI6PDZV.ts → chunk-NLWS55B6.ts} +15 -2
- package/dist/chunks/chunk-NLWS55B6.ts.map +7 -0
- package/dist/chunks/{chunk-SDZCTCYQ.ts → chunk-OEWQVWLC.ts} +4 -4
- package/dist/chunks/{chunk-MYPSMJHJ.ts → chunk-OTZRCKYP.ts} +380 -510
- package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
- package/dist/chunks/{chunk-WFKUKVCP.ts → chunk-OWBAZA6J.ts} +24 -12
- package/dist/chunks/chunk-OWBAZA6J.ts.map +7 -0
- package/dist/chunks/chunk-PRUEIIK7.ts +24 -0
- package/dist/chunks/chunk-PRUEIIK7.ts.map +7 -0
- package/dist/chunks/chunk-QGK445K7.ts +21 -0
- package/dist/chunks/chunk-QGK445K7.ts.map +7 -0
- package/dist/chunks/{chunk-3B4Z2SKV.ts → chunk-QO333NM4.ts} +49 -21
- package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
- package/dist/chunks/{chunk-4SBTAVFL.ts → chunk-QSHMOL3I.ts} +22 -24
- package/dist/chunks/chunk-QSHMOL3I.ts.map +7 -0
- package/dist/chunks/chunk-S2FMH5FT.ts +97 -0
- package/dist/chunks/chunk-S2FMH5FT.ts.map +7 -0
- package/dist/chunks/{chunk-BVVP33U5.ts → chunk-SWRVXVFD.ts} +12 -9
- package/dist/chunks/chunk-SWRVXVFD.ts.map +7 -0
- package/dist/chunks/{chunk-ZX2Q6THB.ts → chunk-UBV24CG4.ts} +4 -5
- package/dist/chunks/chunk-UBV24CG4.ts.map +7 -0
- package/dist/chunks/chunk-UOHR7LRO.ts +70 -0
- package/dist/chunks/chunk-UOHR7LRO.ts.map +7 -0
- package/dist/chunks/{chunk-4QVPTIZB.ts → chunk-UPNVIUBN.ts} +3 -3
- package/dist/chunks/chunk-UZPMDLTL.ts +54 -0
- package/dist/chunks/chunk-UZPMDLTL.ts.map +7 -0
- package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
- package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
- package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
- package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
- package/dist/chunks/{chunk-V6VZ5TLG.ts → chunk-YK52VDXG.ts} +6 -5
- package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
- package/dist/chunks/config-KGIYTTXQ.ts +37 -0
- package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
- package/dist/chunks/{conflict-review-4PLVLMRO.ts → conflict-review-6WIEQLXW.ts} +31 -24
- package/dist/chunks/{conflict-review-4PLVLMRO.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
- package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
- package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
- package/dist/chunks/{file-selection-WZX7HAY2.ts → file-selection-3JJYOVNR.ts} +5 -5
- package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
- package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
- package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
- package/dist/chunks/{manager-ui-IURS4WXY.ts → manager-ui-KRAW3NE2.ts} +464 -164
- package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
- package/dist/chunks/{merge-baseline-store-EVQAAML6.ts → merge-baseline-store-RFCAH7KL.ts} +7 -5
- package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts → remote-selection-ui-5725JDNC.ts} +9 -8
- package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
- package/dist/chunks/{s3-backend-AQGV6LFT.ts → s3-backend-Y7WSCLJI.ts} +38 -38
- package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
- package/dist/chunks/{setup-switch-KTUWL6DE.ts → setup-switch-E7PHKI6J.ts} +7 -6
- package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
- package/dist/chunks/{setup-wizard-GSDPDFC4.ts → setup-wizard-65DROCIJ.ts} +14 -13
- package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
- package/dist/chunks/{sync-inspection-K45QVLHB.ts → sync-inspection-XFJOAMJD.ts} +9 -8
- package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
- package/dist/chunks/{sync-operations-47OUX76C.ts → sync-operations-HDUDT5PD.ts} +188 -73
- package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
- package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
- package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
- package/dist/index.ts +246 -131
- package/dist/index.ts.map +3 -3
- package/docs/agent-mode-verification.md +99 -0
- package/docs/continuous-sync-verification.md +87 -0
- package/docs/settings.md +131 -11
- package/docs/tui-contract.md +61 -0
- package/package.json +3 -2
- package/src/backends/git/git-backend.ts +1 -4
- package/src/backends/git/git-runner.ts +1 -5
- package/src/backends/s3/s3-backend.ts +3 -25
- package/src/backends/s3/s3-client.ts +14 -2
- package/src/backends/snapshot-pointer.ts +27 -0
- package/src/backends/webdav/webdav-backend.ts +3 -29
- package/src/backends/webdav/webdav-client.ts +11 -7
- package/src/commands/command-execution.ts +7 -3
- package/src/commands/command-handler.ts +15 -3
- package/src/commands/command-types.ts +10 -0
- package/src/commands/command.ts +4 -1
- package/src/settings/config.ts +20 -10
- package/src/settings/settings-store.ts +1 -34
- package/src/settings/settings-types.ts +10 -0
- package/src/settings/settings-validation.ts +4 -1
- package/src/snapshot/snapshot-codec.ts +1 -5
- package/src/state/lock.ts +13 -10
- package/src/state/merge-baseline-store.ts +19 -1
- package/src/state/state-directory.ts +8 -1
- package/src/sync/agent-candidate.ts +251 -0
- package/src/sync/agent-plan.ts +87 -0
- package/src/sync/agent-policy.ts +13 -0
- package/src/sync/agent-request.ts +17 -0
- package/src/sync/agent-runner.ts +188 -0
- package/src/sync/agent-store.ts +151 -0
- package/src/sync/agent-sync.ts +91 -0
- package/src/sync/apply-review.ts +63 -0
- package/src/sync/automatic-sync.ts +46 -33
- package/src/sync/continuous-errors.ts +85 -0
- package/src/sync/continuous-lease.ts +221 -0
- package/src/sync/continuous-record.ts +179 -0
- package/src/sync/continuous-storage.ts +55 -0
- package/src/sync/continuous-sync.ts +516 -0
- package/src/sync/continuous-watch.ts +59 -0
- package/src/sync/merged-sync.ts +268 -112
- package/src/sync/remote-snapshot.ts +2 -3
- package/src/sync/settings-merge.ts +19 -3
- package/src/sync/setup-switch.ts +1 -5
- package/src/sync/startup-check.ts +51 -29
- package/src/sync/startup-timings.ts +90 -0
- package/src/sync/sync-inspection.ts +16 -5
- package/src/sync/sync-mutations.ts +55 -28
- package/src/sync/sync-queries.ts +19 -3
- package/src/sync/text-merge.ts +3 -3
- package/src/sync-extension.ts +75 -19
- package/src/ui/agent-mode-setting.ts +91 -0
- package/src/ui/manager-state.ts +12 -6
- package/src/ui/manager-ui.ts +57 -7
- package/src/ui/merge-review.ts +3 -3
- package/src/ui/merged-sync-summary.ts +2 -18
- package/src/ui/remote-selection-ui.ts +5 -5
- package/src/ui/settings-frame.ts +55 -0
- package/src/ui/settings-ui.ts +223 -43
- package/src/ui/setup/s3-credentials-ui.ts +1 -5
- package/src/ui/setup/setup-name-ui.ts +2 -5
- package/src/ui/setup/setup-prompts.ts +4 -3
- package/src/ui/setup/setup-switcher.ts +2 -0
- package/src/ui/sync-behavior.ts +15 -0
- package/src/ui/sync-format.ts +69 -38
- package/src/ui/sync-setups-ui.ts +3 -5
- package/src/ui/transfer-changes.ts +33 -0
- package/src/ui/transfer-review.ts +27 -0
- package/dist/chunks/chunk-2DI6PDZV.ts.map +0 -7
- package/dist/chunks/chunk-3B4Z2SKV.ts.map +0 -7
- package/dist/chunks/chunk-4SBTAVFL.ts.map +0 -7
- package/dist/chunks/chunk-5ZWQ33XL.ts.map +0 -7
- package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
- package/dist/chunks/chunk-IJ6VV6FN.ts.map +0 -7
- package/dist/chunks/chunk-MYPSMJHJ.ts.map +0 -7
- package/dist/chunks/chunk-ORQRHEEO.ts +0 -362
- package/dist/chunks/chunk-ORQRHEEO.ts.map +0 -7
- package/dist/chunks/chunk-QE7NZZWT.ts.map +0 -7
- package/dist/chunks/chunk-UQ3ALBIB.ts.map +0 -7
- package/dist/chunks/chunk-V6VZ5TLG.ts.map +0 -7
- package/dist/chunks/chunk-WFKUKVCP.ts.map +0 -7
- package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
- package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
- package/dist/chunks/manager-ui-IURS4WXY.ts.map +0 -7
- package/dist/chunks/s3-backend-AQGV6LFT.ts.map +0 -7
- package/dist/chunks/setup-wizard-GSDPDFC4.ts.map +0 -7
- package/dist/chunks/sync-operations-47OUX76C.ts.map +0 -7
- package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
- /package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CHSIAC4N.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
- /package/dist/chunks/{chunk-CL7BZRRQ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
- /package/dist/chunks/{chunk-VHXBCEVF.ts.map → chunk-EUMINX7L.ts.map} +0 -0
- /package/dist/chunks/{chunk-5RZPINUQ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
- /package/dist/chunks/{chunk-SDZCTCYQ.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
- /package/dist/chunks/{chunk-4QVPTIZB.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts.map → config-KGIYTTXQ.ts.map} +0 -0
- /package/dist/chunks/{merge-baseline-store-EVQAAML6.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
- /package/dist/chunks/{file-selection-WZX7HAY2.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
- /package/dist/chunks/{setup-switch-KTUWL6DE.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-K45QVLHB.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
|
@@ -6,72 +6,16 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
ensureStateDir,
|
|
8
8
|
lockPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UPNVIUBN.ts";
|
|
10
10
|
import {
|
|
11
11
|
LOCKFILE_FS_ADAPTER,
|
|
12
12
|
LOCK_GUARD_STALE_MS,
|
|
13
|
-
LOCK_GUARD_UPDATE_MS
|
|
14
|
-
|
|
15
|
-
} from "./chunk-BVVP33U5.ts";
|
|
16
|
-
import {
|
|
17
|
-
encodeSnapshot
|
|
18
|
-
} from "./chunk-ZX2Q6THB.ts";
|
|
19
|
-
import {
|
|
20
|
-
agentDir,
|
|
21
|
-
createSnapshot,
|
|
22
|
-
sessionDirFromContext,
|
|
23
|
-
sessionSnapshotPathFromAbsolute
|
|
24
|
-
} from "./chunk-QOFMXPM5.ts";
|
|
25
|
-
import {
|
|
26
|
-
syncDirectory
|
|
27
|
-
} from "./chunk-HUZ5CR4W.ts";
|
|
28
|
-
|
|
29
|
-
// src/sync/sync-local.ts
|
|
30
|
-
import fs from "node:fs/promises";
|
|
31
|
-
import path from "node:path";
|
|
32
|
-
function protectedSessionPaths(ctx, sessionRoot = sessionDirFromContext(ctx)) {
|
|
33
|
-
const getSessionFile = ctx.sessionManager.getSessionFile;
|
|
34
|
-
if (typeof getSessionFile !== "function") return /* @__PURE__ */ new Set();
|
|
35
|
-
const sessionFile = getSessionFile.call(ctx.sessionManager);
|
|
36
|
-
const snapshotPath = sessionFile ? sessionSnapshotPathFromAbsolute(sessionFile, sessionRoot) : void 0;
|
|
37
|
-
return snapshotPath ? /* @__PURE__ */ new Set([snapshotPath]) : /* @__PURE__ */ new Set();
|
|
38
|
-
}
|
|
39
|
-
function captureMutationOwner(ctx, signal) {
|
|
40
|
-
const manager = ctx.sessionManager;
|
|
41
|
-
const file = manager.getSessionFile?.();
|
|
42
|
-
const id = manager.getSessionId?.();
|
|
43
|
-
const cwd = manager.getCwd?.();
|
|
44
|
-
const root = agentDir();
|
|
45
|
-
return () => {
|
|
46
|
-
throwIfAborted(signal);
|
|
47
|
-
if (ctx.sessionManager !== manager || manager.getSessionFile?.() !== file || manager.getSessionId?.() !== id || manager.getCwd?.() !== cwd || agentDir() !== root)
|
|
48
|
-
throw new Error("Session changed during file mutation; evidence retained for review.");
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
async function backupLocal(profile, options = {}, signal) {
|
|
52
|
-
throwIfAborted(signal);
|
|
53
|
-
const snapshot = await createSnapshot(profile, { ...options, signal });
|
|
54
|
-
throwIfAborted(signal);
|
|
55
|
-
const backupDirectory = path.join(stateDir(), "backups");
|
|
56
|
-
await fs.mkdir(backupDirectory, { recursive: true, mode: 448 });
|
|
57
|
-
throwIfAborted(signal);
|
|
58
|
-
const backupPath = path.join(backupDirectory, `${snapshot.id}.json.gz`);
|
|
59
|
-
const encoded = await encodeSnapshot(snapshot);
|
|
60
|
-
throwIfAborted(signal);
|
|
61
|
-
const handle = await fs.open(backupPath, "wx", 384);
|
|
62
|
-
try {
|
|
63
|
-
await handle.writeFile(encoded);
|
|
64
|
-
await handle.sync();
|
|
65
|
-
} finally {
|
|
66
|
-
await handle.close();
|
|
67
|
-
}
|
|
68
|
-
await syncDirectory(backupDirectory);
|
|
69
|
-
return backupPath;
|
|
70
|
-
}
|
|
13
|
+
LOCK_GUARD_UPDATE_MS
|
|
14
|
+
} from "./chunk-SWRVXVFD.ts";
|
|
71
15
|
|
|
72
16
|
// src/state/lock.ts
|
|
73
17
|
import { randomUUID } from "node:crypto";
|
|
74
|
-
import
|
|
18
|
+
import fs from "node:fs/promises";
|
|
75
19
|
import lockfile from "proper-lockfile";
|
|
76
20
|
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
77
21
|
var MAX_PROCESS_ID = 2147483647;
|
|
@@ -104,12 +48,15 @@ async function withLock(command, fn, options = {}) {
|
|
|
104
48
|
if (rechecked.status !== "valid" || rechecked.lock.id !== inspection.lock.id || !isStaleLock(rechecked.lock)) {
|
|
105
49
|
throw new Error("pi-sync lock changed while preparing transaction recovery; retry.");
|
|
106
50
|
}
|
|
107
|
-
await
|
|
51
|
+
await fs.rm(lockPath(), { force: true });
|
|
108
52
|
inspection = { status: "missing" };
|
|
109
53
|
}
|
|
110
54
|
if (inspection.status === "valid") {
|
|
111
|
-
throw
|
|
112
|
-
|
|
55
|
+
throw Object.assign(
|
|
56
|
+
new Error(
|
|
57
|
+
`pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`
|
|
58
|
+
),
|
|
59
|
+
{ code: "ELOCKED" }
|
|
113
60
|
);
|
|
114
61
|
}
|
|
115
62
|
if (inspection.status === "unreadable") {
|
|
@@ -117,7 +64,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
117
64
|
"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running."
|
|
118
65
|
);
|
|
119
66
|
}
|
|
120
|
-
await
|
|
67
|
+
await fs.writeFile(lockPath(), JSON.stringify(lock, null, " "), { flag: "wx" });
|
|
121
68
|
guard.throwIfCompromised();
|
|
122
69
|
result = await fn();
|
|
123
70
|
guard.throwIfCompromised();
|
|
@@ -127,7 +74,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
127
74
|
}
|
|
128
75
|
try {
|
|
129
76
|
const current = await readLock();
|
|
130
|
-
if (current?.id === lock.id) await
|
|
77
|
+
if (current?.id === lock.id) await fs.rm(lockPath(), { force: true });
|
|
131
78
|
} catch (error) {
|
|
132
79
|
if (!failed) {
|
|
133
80
|
failed = true;
|
|
@@ -146,7 +93,7 @@ async function withLock(command, fn, options = {}) {
|
|
|
146
93
|
}
|
|
147
94
|
async function inspectLock() {
|
|
148
95
|
try {
|
|
149
|
-
const text = await
|
|
96
|
+
const text = await fs.readFile(lockPath(), "utf8");
|
|
150
97
|
if (text.trim().length === 0) return { status: "unreadable" };
|
|
151
98
|
const parsed = JSON.parse(text);
|
|
152
99
|
return isLockFile(parsed) ? { status: "valid", lock: parsed } : { status: "unreadable" };
|
|
@@ -178,9 +125,9 @@ function isStaleLock(lock) {
|
|
|
178
125
|
}
|
|
179
126
|
}
|
|
180
127
|
async function unlock(ctx, options) {
|
|
181
|
-
|
|
128
|
+
throwIfAborted(options.signal);
|
|
182
129
|
await ensureStateDir();
|
|
183
|
-
|
|
130
|
+
throwIfAborted(options.signal);
|
|
184
131
|
let guard;
|
|
185
132
|
try {
|
|
186
133
|
guard = await acquireGuard();
|
|
@@ -205,9 +152,9 @@ async function unlock(ctx, options) {
|
|
|
205
152
|
if (failed) throw failure;
|
|
206
153
|
}
|
|
207
154
|
async function unlockGuarded(ctx, options) {
|
|
208
|
-
|
|
155
|
+
throwIfAborted(options.signal);
|
|
209
156
|
let inspection = await inspectLock();
|
|
210
|
-
|
|
157
|
+
throwIfAborted(options.signal);
|
|
211
158
|
if (inspection.status === "missing") {
|
|
212
159
|
ctx.ui.notify("No pi-sync lock is present.", "info");
|
|
213
160
|
return;
|
|
@@ -221,10 +168,10 @@ async function unlockGuarded(ctx, options) {
|
|
|
221
168
|
return;
|
|
222
169
|
}
|
|
223
170
|
inspection = await inspectLock();
|
|
224
|
-
|
|
171
|
+
throwIfAborted(options.signal);
|
|
225
172
|
if (inspection.status === "unreadable") {
|
|
226
|
-
|
|
227
|
-
await
|
|
173
|
+
throwIfAborted(options.signal);
|
|
174
|
+
await fs.rm(lockPath(), { force: true });
|
|
228
175
|
if (!options.signal?.aborted) {
|
|
229
176
|
ctx.ui.notify(
|
|
230
177
|
"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.",
|
|
@@ -242,16 +189,12 @@ async function unlockGuarded(ctx, options) {
|
|
|
242
189
|
ctx.ui.notify("Lock owner is still live; refusing to remove it.", "warning");
|
|
243
190
|
return;
|
|
244
191
|
}
|
|
245
|
-
|
|
246
|
-
await
|
|
192
|
+
throwIfAborted(options.signal);
|
|
193
|
+
await fs.rm(lockPath(), { force: true });
|
|
247
194
|
if (!options.signal?.aborted) {
|
|
248
195
|
ctx.ui.notify("Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.", "info");
|
|
249
196
|
}
|
|
250
197
|
}
|
|
251
|
-
function throwIfAborted2(signal) {
|
|
252
|
-
if (!signal?.aborted) return;
|
|
253
|
-
throw signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted", "AbortError");
|
|
254
|
-
}
|
|
255
198
|
async function releaseGuard(guard) {
|
|
256
199
|
try {
|
|
257
200
|
await guard.release();
|
|
@@ -286,11 +229,15 @@ async function describeHeldLock() {
|
|
|
286
229
|
return new Error("pi-sync lock owner exited; retry shortly while the lock guard expires.");
|
|
287
230
|
}
|
|
288
231
|
if (current) {
|
|
289
|
-
return
|
|
290
|
-
`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`
|
|
232
|
+
return Object.assign(
|
|
233
|
+
new Error(`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`),
|
|
234
|
+
{ code: "ELOCKED" }
|
|
291
235
|
);
|
|
292
236
|
}
|
|
293
|
-
return
|
|
237
|
+
return Object.assign(
|
|
238
|
+
new Error("Pi-sync is currently running (lock metadata is unreadable or still being written)."),
|
|
239
|
+
{ code: "ELOCKED" }
|
|
240
|
+
);
|
|
294
241
|
}
|
|
295
242
|
function isLockHeldError(error) {
|
|
296
243
|
return error.code === "ELOCKED";
|
|
@@ -306,9 +253,6 @@ export {
|
|
|
306
253
|
inspectLock,
|
|
307
254
|
isLockGuardHeld,
|
|
308
255
|
isStaleLock,
|
|
309
|
-
unlock
|
|
310
|
-
protectedSessionPaths,
|
|
311
|
-
captureMutationOwner,
|
|
312
|
-
backupLocal
|
|
256
|
+
unlock
|
|
313
257
|
};
|
|
314
|
-
//# sourceMappingURL=chunk-
|
|
258
|
+
//# sourceMappingURL=chunk-AAEEHR7H.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/state/lock.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport lockfile from \"proper-lockfile\";\nimport type { CommandOptions } from \"../commands/command-types.js\";\nimport { throwIfAborted } from \"../sync/signals.js\";\nimport { LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS } from \"./lock-policy.js\";\nimport { LOCKFILE_FS_ADAPTER } from \"./lockfile-fs.js\";\nimport type { LockFile } from \"./state-types.js\";\nimport { ensureStateDir, lockPath } from \"./sync-state-store.js\";\n\nconst LOCK_STALE_MS = 30 * 60 * 1000;\nconst MAX_PROCESS_ID = 2_147_483_647;\n\ninterface Guard {\n release: () => Promise<void>;\n throwIfCompromised: () => void;\n isCompromised: () => boolean;\n}\n\nexport type LockInspection = { status: \"missing\" } | { status: \"unreadable\" } | { status: \"valid\"; lock: LockFile };\n\nexport async function withLock<T>(\n command: string,\n fn: () => Promise<T>,\n options: { reclaimStale?: boolean } = {},\n): Promise<T> {\n await ensureStateDir();\n const lock: LockFile = {\n id: randomUUID(),\n pid: process.pid,\n command,\n startedAt: new Date().toISOString(),\n };\n let guard: Guard | undefined;\n let result: T | undefined;\n let failed = false;\n let failure: unknown;\n try {\n try {\n guard = await acquireGuard();\n } catch (error) {\n if (!isLockHeldError(error)) throw error;\n throw await describeHeldLock();\n }\n\n let inspection = await inspectLock();\n if (inspection.status === \"valid\" && isStaleLock(inspection.lock)) {\n if (!options.reclaimStale) {\n throw new Error(`pi-sync lock is stale (pid ${inspection.lock.pid}). Run /sync unlock --stale, then retry.`);\n }\n guard.throwIfCompromised();\n const rechecked = await inspectLock();\n if (rechecked.status !== \"valid\" || rechecked.lock.id !== inspection.lock.id || !isStaleLock(rechecked.lock)) {\n throw new Error(\"pi-sync lock changed while preparing transaction recovery; retry.\");\n }\n await fs.rm(lockPath(), { force: true });\n inspection = { status: \"missing\" };\n }\n if (inspection.status === \"valid\") {\n throw Object.assign(\n new Error(\n `pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`,\n ),\n { code: \"ELOCKED\" },\n );\n }\n if (inspection.status === \"unreadable\") {\n throw new Error(\n \"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running.\",\n );\n }\n\n await fs.writeFile(lockPath(), JSON.stringify(lock, null, \"\\t\"), { flag: \"wx\" });\n guard.throwIfCompromised();\n result = await fn();\n guard.throwIfCompromised();\n } catch (error) {\n failed = true;\n failure = error;\n }\n\n try {\n const current = await readLock();\n if (current?.id === lock.id) await fs.rm(lockPath(), { force: true });\n } catch (error) {\n if (!failed) {\n failed = true;\n failure = error;\n }\n }\n if (guard) {\n const releaseError = await releaseGuard(guard);\n if (releaseError && !failed) {\n failed = true;\n failure = releaseError;\n }\n }\n if (failed) throw failure;\n return result as T;\n}\n\nexport async function inspectLock(): Promise<LockInspection> {\n try {\n const text = await fs.readFile(lockPath(), \"utf8\");\n if (text.trim().length === 0) return { status: \"unreadable\" };\n const parsed = JSON.parse(text) as unknown;\n return isLockFile(parsed) ? { status: \"valid\", lock: parsed } : { status: \"unreadable\" };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { status: \"missing\" };\n if (error instanceof SyntaxError) return { status: \"unreadable\" };\n throw error;\n }\n}\n\nexport async function readLock(): Promise<LockFile | undefined> {\n const inspection = await inspectLock();\n return inspection.status === \"valid\" ? inspection.lock : undefined;\n}\n\nexport async function lockFileExists(): Promise<boolean> {\n try {\n await fs.stat(lockPath());\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw error;\n }\n}\n\nexport function isLockGuardHeld() {\n return lockfile.check(lockPath(), {\n fs: LOCKFILE_FS_ADAPTER,\n lockfilePath: `${lockPath()}.guard`,\n realpath: false,\n stale: LOCK_GUARD_STALE_MS,\n });\n}\n\nexport function isStaleLock(lock: LockFile) {\n try {\n process.kill(lock.pid, 0);\n return false;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ESRCH\") return true;\n return Date.now() - Date.parse(lock.startedAt) > LOCK_STALE_MS;\n }\n}\n\nexport async function unlock(ctx: ExtensionCommandContext, options: CommandOptions) {\n throwIfAborted(options.signal);\n await ensureStateDir();\n throwIfAborted(options.signal);\n let guard: Guard;\n try {\n guard = await acquireGuard();\n } catch (error) {\n if (!isLockHeldError(error)) throw error;\n ctx.ui.notify((await describeHeldLock()).message, \"warning\");\n return;\n }\n\n let failed = false;\n let failure: unknown;\n try {\n await unlockGuarded(ctx, options);\n } catch (error) {\n failed = true;\n failure = error;\n }\n const releaseError = await releaseGuard(guard);\n if (releaseError && !failed) {\n failed = true;\n failure = releaseError;\n }\n if (failed) throw failure;\n}\n\nasync function unlockGuarded(ctx: ExtensionCommandContext, options: CommandOptions) {\n throwIfAborted(options.signal);\n let inspection = await inspectLock();\n throwIfAborted(options.signal);\n if (inspection.status === \"missing\") {\n ctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n return;\n }\n if (inspection.status === \"unreadable\") {\n if (!options.stale) {\n ctx.ui.notify(\n \"Pi-sync lock metadata is unreadable. Use /sync unlock --stale only after verifying no sync is running.\",\n \"warning\",\n );\n return;\n }\n inspection = await inspectLock();\n throwIfAborted(options.signal);\n if (inspection.status === \"unreadable\") {\n // Legacy writers expose an empty file before writing owner metadata, so no\n // automatic test can prove this file is abandoned. The explicit --stale\n // flag is the user's confirmation that no legacy sync is still running.\n throwIfAborted(options.signal);\n await fs.rm(lockPath(), { force: true });\n if (!options.signal?.aborted) {\n ctx.ui.notify(\n \"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n \"info\",\n );\n }\n return;\n }\n if (inspection.status === \"missing\") {\n ctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n return;\n }\n }\n if (!isStaleLock(inspection.lock)) {\n ctx.ui.notify(\"Lock owner is still live; refusing to remove it.\", \"warning\");\n return;\n }\n throwIfAborted(options.signal);\n await fs.rm(lockPath(), { force: true });\n if (!options.signal?.aborted) {\n ctx.ui.notify(\"Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.\", \"info\");\n }\n}\n\nasync function releaseGuard(guard: Guard) {\n try {\n await guard.release();\n return undefined;\n } catch (error) {\n return guard.isCompromised() ? undefined : error;\n }\n}\n\nasync function acquireGuard(): Promise<Guard> {\n let compromisedError: Error | undefined;\n const release = await lockfile.lock(lockPath(), {\n fs: LOCKFILE_FS_ADAPTER,\n lockfilePath: `${lockPath()}.guard`,\n realpath: false,\n stale: LOCK_GUARD_STALE_MS,\n update: LOCK_GUARD_UPDATE_MS,\n onCompromised: (error) => {\n compromisedError = error;\n },\n });\n return {\n release,\n throwIfCompromised: () => {\n if (compromisedError) throw compromisedError;\n },\n isCompromised: () => compromisedError !== undefined,\n };\n}\n\nasync function describeHeldLock() {\n const current = await readLock();\n if (current && isStaleLock(current)) {\n return new Error(\"pi-sync lock owner exited; retry shortly while the lock guard expires.\");\n }\n if (current) {\n return Object.assign(\n new Error(`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`),\n { code: \"ELOCKED\" },\n );\n }\n return Object.assign(\n new Error(\"Pi-sync is currently running (lock metadata is unreadable or still being written).\"),\n { code: \"ELOCKED\" },\n );\n}\n\nfunction isLockHeldError(error: unknown) {\n return (error as NodeJS.ErrnoException).code === \"ELOCKED\";\n}\n\nfunction isLockFile(value: unknown): value is LockFile {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const lock = value as Partial<LockFile>;\n return (\n typeof lock.id === \"string\" &&\n lock.id.length > 0 &&\n Number.isInteger(lock.pid) &&\n (lock.pid ?? 0) > 0 &&\n (lock.pid ?? 0) <= MAX_PROCESS_ID &&\n typeof lock.command === \"string\" &&\n lock.command.length > 0 &&\n typeof lock.startedAt === \"string\" &&\n Number.isFinite(Date.parse(lock.startedAt))\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
syncSetupStorageReview,
|
|
7
7
|
updateLocalConfig,
|
|
8
8
|
validateConfigName
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QO333NM4.ts";
|
|
10
10
|
import {
|
|
11
11
|
normalizeSyncInclude
|
|
12
12
|
} from "./chunk-QOFMXPM5.ts";
|
|
@@ -195,4 +195,4 @@ export {
|
|
|
195
195
|
removeSyncSetup,
|
|
196
196
|
removeStorageConnection
|
|
197
197
|
};
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
198
|
+
//# sourceMappingURL=chunk-B4QZBDBT.ts.map
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
4
|
withLock
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
mergePathIdentity
|
|
8
|
-
} from "./chunk-76NB4R7U.ts";
|
|
9
|
-
import {
|
|
10
|
-
stateDir
|
|
11
|
-
} from "./chunk-BVVP33U5.ts";
|
|
5
|
+
} from "./chunk-AAEEHR7H.ts";
|
|
12
6
|
import {
|
|
13
7
|
agentDir,
|
|
14
8
|
configuredSessionDir,
|
|
15
9
|
expandSessionDir,
|
|
16
10
|
sessionStorageRoot
|
|
17
11
|
} from "./chunk-QOFMXPM5.ts";
|
|
12
|
+
import {
|
|
13
|
+
mergePathIdentity
|
|
14
|
+
} from "./chunk-76NB4R7U.ts";
|
|
15
|
+
import {
|
|
16
|
+
stateDir
|
|
17
|
+
} from "./chunk-SWRVXVFD.ts";
|
|
18
18
|
import {
|
|
19
19
|
assertWithinRoot,
|
|
20
20
|
isDeniedPath,
|
|
@@ -1010,4 +1010,4 @@ export {
|
|
|
1010
1010
|
recoverSnapshotTransactionsOnStartup,
|
|
1011
1011
|
recoverPendingSnapshotTransactions
|
|
1012
1012
|
};
|
|
1013
|
-
//# sourceMappingURL=chunk-
|
|
1013
|
+
//# sourceMappingURL=chunk-CRKTAPSL.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
SyncBackendConflictError
|
|
5
|
+
} from "./chunk-YDVQYUHE.ts";
|
|
6
|
+
|
|
7
|
+
// src/sync/continuous-errors.ts
|
|
8
|
+
var ContinuousError = class extends Error {
|
|
9
|
+
constructor(kind, message, retryAfterMs) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.kind = kind;
|
|
12
|
+
this.retryAfterMs = retryAfterMs;
|
|
13
|
+
this.name = "ContinuousError";
|
|
14
|
+
}
|
|
15
|
+
kind;
|
|
16
|
+
retryAfterMs;
|
|
17
|
+
};
|
|
18
|
+
function continuousFailure(error) {
|
|
19
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20
|
+
for (let current = error; current && !seen.has(current) && seen.size < 8; ) {
|
|
21
|
+
seen.add(current);
|
|
22
|
+
if (current instanceof ContinuousError)
|
|
23
|
+
return {
|
|
24
|
+
kind: current.kind,
|
|
25
|
+
retry: ["transient", "contention", "stale", "budget"].includes(current.kind),
|
|
26
|
+
delay: current.retryAfterMs
|
|
27
|
+
};
|
|
28
|
+
if (current instanceof SyncBackendConflictError) return { kind: "stale", retry: true };
|
|
29
|
+
if (typeof current !== "object") break;
|
|
30
|
+
const value = current;
|
|
31
|
+
if (value.status === 401 || value.status === 403) return { kind: "authentication", retry: false };
|
|
32
|
+
if (value.status === 408 || value.status === 429 || typeof value.status === "number" && Number.isInteger(value.status) && value.status >= 500 && value.status <= 599)
|
|
33
|
+
return { kind: "transient", retry: true, delay: boundedRetryDelay(value.retryAfterMs) };
|
|
34
|
+
if (value.code === "ELOCKED") return { kind: "contention", retry: true };
|
|
35
|
+
if (value.code === "ECOMPROMISED") return { kind: "stale", retry: true };
|
|
36
|
+
if (value.name === "TimeoutError" || [
|
|
37
|
+
"ECONNRESET",
|
|
38
|
+
"ECONNREFUSED",
|
|
39
|
+
"ETIMEDOUT",
|
|
40
|
+
"ENETUNREACH",
|
|
41
|
+
"EHOSTUNREACH",
|
|
42
|
+
"ENOTFOUND",
|
|
43
|
+
"EAI_AGAIN",
|
|
44
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
45
|
+
"UND_ERR_SOCKET"
|
|
46
|
+
].includes(String(value.code)))
|
|
47
|
+
return { kind: "transient", retry: true };
|
|
48
|
+
if (["EACCES", "EPERM", "ENOSPC", "EROFS", "EIO"].includes(String(value.code)))
|
|
49
|
+
return { kind: "storage", retry: false };
|
|
50
|
+
current = value.cause;
|
|
51
|
+
}
|
|
52
|
+
return { kind: "unknown", retry: false };
|
|
53
|
+
}
|
|
54
|
+
function boundedRetryDelay(value) {
|
|
55
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.min(value, 3e5) : void 0;
|
|
56
|
+
}
|
|
57
|
+
function retryAfter(response) {
|
|
58
|
+
const value = response.headers.get("retry-after");
|
|
59
|
+
if (!value) return;
|
|
60
|
+
const seconds = Number(value);
|
|
61
|
+
return boundedRetryDelay(Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(value) - Date.now());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
ContinuousError,
|
|
66
|
+
continuousFailure,
|
|
67
|
+
retryAfter
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=chunk-DT7M4VQS.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/continuous-errors.ts"],
|
|
4
|
+
"sourcesContent": ["import { SyncBackendConflictError } from \"../backends/sync-backend.js\";\n\nexport type ContinuousErrorKind =\n | \"transient\"\n | \"contention\"\n | \"stale\"\n | \"authentication\"\n | \"storage\"\n | \"unsafe\"\n | \"model\"\n | \"budget\"\n | \"unknown\";\n\n/** Categories are safe to persist; provider messages and causes are not. */\nexport class ContinuousError extends Error {\n constructor(\n readonly kind: ContinuousErrorKind,\n message: string,\n readonly retryAfterMs?: number,\n ) {\n super(message);\n this.name = \"ContinuousError\";\n }\n}\n\nexport function continuousFailure(error: unknown): { kind: ContinuousErrorKind; retry: boolean; delay?: number } {\n const seen = new Set<unknown>();\n for (let current = error; current && !seen.has(current) && seen.size < 8; ) {\n seen.add(current);\n if (current instanceof ContinuousError)\n return {\n kind: current.kind,\n retry: [\"transient\", \"contention\", \"stale\", \"budget\"].includes(current.kind),\n delay: current.retryAfterMs,\n };\n if (current instanceof SyncBackendConflictError) return { kind: \"stale\", retry: true };\n if (typeof current !== \"object\") break;\n const value = current as {\n status?: unknown;\n code?: unknown;\n name?: unknown;\n cause?: unknown;\n retryAfterMs?: unknown;\n };\n if (value.status === 401 || value.status === 403) return { kind: \"authentication\", retry: false };\n if (\n value.status === 408 ||\n value.status === 429 ||\n (typeof value.status === \"number\" && Number.isInteger(value.status) && value.status >= 500 && value.status <= 599)\n )\n return { kind: \"transient\", retry: true, delay: boundedRetryDelay(value.retryAfterMs) };\n if (value.code === \"ELOCKED\") return { kind: \"contention\", retry: true };\n if (value.code === \"ECOMPROMISED\") return { kind: \"stale\", retry: true };\n if (\n value.name === \"TimeoutError\" ||\n [\n \"ECONNRESET\",\n \"ECONNREFUSED\",\n \"ETIMEDOUT\",\n \"ENETUNREACH\",\n \"EHOSTUNREACH\",\n \"ENOTFOUND\",\n \"EAI_AGAIN\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n \"UND_ERR_SOCKET\",\n ].includes(String(value.code))\n )\n return { kind: \"transient\", retry: true };\n if ([\"EACCES\", \"EPERM\", \"ENOSPC\", \"EROFS\", \"EIO\"].includes(String(value.code)))\n return { kind: \"storage\", retry: false };\n current = value.cause;\n }\n return { kind: \"unknown\", retry: false };\n}\n\nexport function boundedRetryDelay(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? Math.min(value, 300_000) : undefined;\n}\n\nexport function retryAfter(response: Response): number | undefined {\n const value = response.headers.get(\"retry-after\");\n if (!value) return;\n const seconds = Number(value);\n return boundedRetryDelay(Number.isFinite(seconds) ? seconds * 1000 : Date.parse(value) - Date.now());\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;AAcO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YACW,MACT,SACS,cACT;AACA,UAAM,OAAO;AAJJ;AAEA;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAAA,EAEA;AAKb;AAEO,SAAS,kBAAkB,OAA+E;AAC/G,QAAM,OAAO,oBAAI,IAAa;AAC9B,WAAS,UAAU,OAAO,WAAW,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,OAAO,KAAK;AAC1E,SAAK,IAAI,OAAO;AAChB,QAAI,mBAAmB;AACrB,aAAO;AAAA,QACL,MAAM,QAAQ;AAAA,QACd,OAAO,CAAC,aAAa,cAAc,SAAS,QAAQ,EAAE,SAAS,QAAQ,IAAI;AAAA,QAC3E,OAAO,QAAQ;AAAA,MACjB;AACF,QAAI,mBAAmB,yBAA0B,QAAO,EAAE,MAAM,SAAS,OAAO,KAAK;AACrF,QAAI,OAAO,YAAY,SAAU;AACjC,UAAM,QAAQ;AAOd,QAAI,MAAM,WAAW,OAAO,MAAM,WAAW,IAAK,QAAO,EAAE,MAAM,kBAAkB,OAAO,MAAM;AAChG,QACE,MAAM,WAAW,OACjB,MAAM,WAAW,OAChB,OAAO,MAAM,WAAW,YAAY,OAAO,UAAU,MAAM,MAAM,KAAK,MAAM,UAAU,OAAO,MAAM,UAAU;AAE9G,aAAO,EAAE,MAAM,aAAa,OAAO,MAAM,OAAO,kBAAkB,MAAM,YAAY,EAAE;AACxF,QAAI,MAAM,SAAS,UAAW,QAAO,EAAE,MAAM,cAAc,OAAO,KAAK;AACvE,QAAI,MAAM,SAAS,eAAgB,QAAO,EAAE,MAAM,SAAS,OAAO,KAAK;AACvE,QACE,MAAM,SAAS,kBACf;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,SAAS,OAAO,MAAM,IAAI,CAAC;AAE7B,aAAO,EAAE,MAAM,aAAa,OAAO,KAAK;AAC1C,QAAI,CAAC,UAAU,SAAS,UAAU,SAAS,KAAK,EAAE,SAAS,OAAO,MAAM,IAAI,CAAC;AAC3E,aAAO,EAAE,MAAM,WAAW,OAAO,MAAM;AACzC,cAAU,MAAM;AAAA,EAClB;AACA,SAAO,EAAE,MAAM,WAAW,OAAO,MAAM;AACzC;AAEO,SAAS,kBAAkB,OAAoC;AACpE,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,OAAO,GAAO,IAAI;AACxG;AAEO,SAAS,WAAW,UAAwC;AACjE,QAAM,QAAQ,SAAS,QAAQ,IAAI,aAAa;AAChD,MAAI,CAAC,MAAO;AACZ,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,kBAAkB,OAAO,SAAS,OAAO,IAAI,UAAU,MAAO,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;AACrG;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
mergeJournalIdentity
|
|
5
|
+
} from "./chunk-GNH73BJ5.ts";
|
|
6
|
+
import {
|
|
7
|
+
mergePathIdentity,
|
|
8
|
+
planFileMerge
|
|
9
|
+
} from "./chunk-76NB4R7U.ts";
|
|
10
|
+
import {
|
|
11
|
+
statePathForConfig,
|
|
12
|
+
syncStateFingerprint
|
|
13
|
+
} from "./chunk-UPNVIUBN.ts";
|
|
14
|
+
import {
|
|
15
|
+
syncDirectory,
|
|
16
|
+
writeJson
|
|
17
|
+
} from "./chunk-HUZ5CR4W.ts";
|
|
18
|
+
|
|
19
|
+
// src/sync/conflict-artifacts.ts
|
|
20
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
21
|
+
import fs from "node:fs/promises";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
|
|
24
|
+
// src/sync/conflict-groups.ts
|
|
25
|
+
function conflictGroups(plan, config) {
|
|
26
|
+
const paths = plan.decisions.map((item) => item.path);
|
|
27
|
+
const parents = paths.map((_, index) => index);
|
|
28
|
+
const ranks = paths.map(() => 0);
|
|
29
|
+
const find = (index) => {
|
|
30
|
+
while (parents[index] !== index) {
|
|
31
|
+
parents[index] = parents[parents[index] ?? index] ?? index;
|
|
32
|
+
index = parents[index] ?? index;
|
|
33
|
+
}
|
|
34
|
+
return index;
|
|
35
|
+
};
|
|
36
|
+
const join = (left, right) => {
|
|
37
|
+
let a = find(left);
|
|
38
|
+
let b = find(right);
|
|
39
|
+
if (a === b) return;
|
|
40
|
+
if ((ranks[a] ?? 0) < (ranks[b] ?? 0)) [a, b] = [b, a];
|
|
41
|
+
parents[b] = a;
|
|
42
|
+
if (ranks[a] === ranks[b]) ranks[a] = (ranks[a] ?? 0) + 1;
|
|
43
|
+
};
|
|
44
|
+
const identities = paths.map(mergePathIdentity);
|
|
45
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
46
|
+
const byPath = new Map(paths.map((value, index) => [value, index]));
|
|
47
|
+
identities.forEach((value, index) => {
|
|
48
|
+
const previous = byIdentity.get(value);
|
|
49
|
+
if (previous !== void 0) join(index, previous);
|
|
50
|
+
else byIdentity.set(value, index);
|
|
51
|
+
});
|
|
52
|
+
const includes = new Set(
|
|
53
|
+
config.include.filter((value) => !["settings.json", "AGENTS.md", "sessions"].includes(value)).map(mergePathIdentity)
|
|
54
|
+
);
|
|
55
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
56
|
+
const bucket = (key, index) => {
|
|
57
|
+
const previous = buckets.get(key);
|
|
58
|
+
if (previous === void 0) buckets.set(key, index);
|
|
59
|
+
else join(index, previous);
|
|
60
|
+
};
|
|
61
|
+
paths.forEach((value, index) => {
|
|
62
|
+
const root = value.split("/")[0] ?? "";
|
|
63
|
+
if (["extensions", "themes", "skills", "prompts"].includes(root)) bucket(`resource:${root}`, index);
|
|
64
|
+
const segments = (identities[index] ?? "").split("/");
|
|
65
|
+
let ancestor = "";
|
|
66
|
+
for (let length = 0; length < segments.length - 1; length++) {
|
|
67
|
+
ancestor = ancestor ? `${ancestor}/${segments[length]}` : segments[length] ?? "";
|
|
68
|
+
const parent = byIdentity.get(ancestor);
|
|
69
|
+
if (parent !== void 0) join(index, parent);
|
|
70
|
+
if (includes.has(ancestor)) bucket(`include:${ancestor}`, index);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const groups = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const conflict of plan.conflicts) {
|
|
75
|
+
const index = byPath.get(conflict.path);
|
|
76
|
+
if (index === void 0) throw new Error("Conflict is missing from managed decisions.");
|
|
77
|
+
const key = find(index);
|
|
78
|
+
const group = groups.get(key) ?? { paths: [], reasons: [] };
|
|
79
|
+
if (!group.reasons.includes(conflict.reason)) group.reasons.push(conflict.reason);
|
|
80
|
+
groups.set(key, group);
|
|
81
|
+
}
|
|
82
|
+
paths.forEach((value, index) => {
|
|
83
|
+
groups.get(find(index))?.paths.push(value);
|
|
84
|
+
});
|
|
85
|
+
return [...groups.values()].map((group) => ({ ...group, paths: group.paths.sort() }));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/sync/conflict-artifacts.ts
|
|
89
|
+
var LIMIT = 192 * 1024 * 1024;
|
|
90
|
+
function conflictArtifactFingerprint(artifact) {
|
|
91
|
+
return createHash("sha256").update(JSON.stringify(artifact)).digest("hex");
|
|
92
|
+
}
|
|
93
|
+
function conflictDirectory(config) {
|
|
94
|
+
return `${statePathForConfig(config)}.conflicts`;
|
|
95
|
+
}
|
|
96
|
+
async function directory(config, create = false) {
|
|
97
|
+
const target = conflictDirectory(config);
|
|
98
|
+
if (create) await fs.mkdir(target, { recursive: true, mode: 448 });
|
|
99
|
+
const stat = await fs.lstat(target);
|
|
100
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || process.platform !== "win32" && (stat.mode & 63) !== 0)
|
|
101
|
+
throw new Error("Unsafe private conflict directory.");
|
|
102
|
+
return target;
|
|
103
|
+
}
|
|
104
|
+
function tokenPath(config, token) {
|
|
105
|
+
if (!/^[a-f0-9-]{36}$/u.test(token)) throw new Error("Invalid conflict token.");
|
|
106
|
+
return path.join(conflictDirectory(config), `${token}.json`);
|
|
107
|
+
}
|
|
108
|
+
async function saveConflictArtifact(config, backend, artifact, validate, onCreated) {
|
|
109
|
+
validate();
|
|
110
|
+
const identity = mergeJournalIdentity(config, backend);
|
|
111
|
+
const local = Object.fromEntries(artifact.local.files.map((file) => [file.path, file.sha256]));
|
|
112
|
+
const remote = Object.fromEntries(artifact.remote.files.map((file) => [file.path, file.sha256]));
|
|
113
|
+
const fingerprint = createHash("sha256").update(
|
|
114
|
+
JSON.stringify([
|
|
115
|
+
identity,
|
|
116
|
+
artifact.groups.map(
|
|
117
|
+
(group) => group.paths.map((filePath) => [
|
|
118
|
+
filePath,
|
|
119
|
+
artifact.state.lastFileHashes[filePath] ?? null,
|
|
120
|
+
local[filePath] ?? null,
|
|
121
|
+
remote[filePath] ?? null
|
|
122
|
+
])
|
|
123
|
+
)
|
|
124
|
+
])
|
|
125
|
+
).digest("hex").slice(0, 32);
|
|
126
|
+
const token = `${fingerprint.slice(0, 8)}-${fingerprint.slice(8, 12)}-${fingerprint.slice(12, 16)}-${fingerprint.slice(16, 20)}-${fingerprint.slice(20)}`;
|
|
127
|
+
await directory(config, true);
|
|
128
|
+
validate();
|
|
129
|
+
const reuse = async () => {
|
|
130
|
+
const existing = await readConflictArtifact(config, backend, token);
|
|
131
|
+
validate();
|
|
132
|
+
const oldLocal = Object.fromEntries(existing.local.files.map((file) => [file.path, file.sha256]));
|
|
133
|
+
const oldRemote = Object.fromEntries(existing.remote.files.map((file) => [file.path, file.sha256]));
|
|
134
|
+
if (JSON.stringify(existing.groups.map((group) => group.paths)) !== JSON.stringify(artifact.groups.map((group) => group.paths)) || artifact.groups.some(
|
|
135
|
+
(group) => group.paths.some(
|
|
136
|
+
(filePath) => existing.state.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath] || oldLocal[filePath] !== local[filePath] || oldRemote[filePath] !== remote[filePath]
|
|
137
|
+
)
|
|
138
|
+
))
|
|
139
|
+
throw new Error("Conflict identity does not match retained immutable evidence.");
|
|
140
|
+
return token;
|
|
141
|
+
};
|
|
142
|
+
const target = tokenPath(config, token);
|
|
143
|
+
try {
|
|
144
|
+
await fs.lstat(target);
|
|
145
|
+
validate();
|
|
146
|
+
return await reuse();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error.code !== "ENOENT") throw error;
|
|
149
|
+
}
|
|
150
|
+
const temporary = `${target}.${randomUUID()}.pending`;
|
|
151
|
+
const candidate = { version: 1, identity, ...artifact };
|
|
152
|
+
try {
|
|
153
|
+
await writeJson(temporary, candidate, { maxBytes: LIMIT });
|
|
154
|
+
validate();
|
|
155
|
+
const stat = await fs.lstat(temporary);
|
|
156
|
+
validate();
|
|
157
|
+
try {
|
|
158
|
+
await fs.link(temporary, target);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.code !== "EEXIST") throw error;
|
|
161
|
+
validate();
|
|
162
|
+
return await reuse();
|
|
163
|
+
}
|
|
164
|
+
onCreated?.({
|
|
165
|
+
token,
|
|
166
|
+
target,
|
|
167
|
+
dev: stat.dev,
|
|
168
|
+
ino: stat.ino,
|
|
169
|
+
size: stat.size,
|
|
170
|
+
fingerprint: conflictArtifactFingerprint(candidate)
|
|
171
|
+
});
|
|
172
|
+
validate();
|
|
173
|
+
await syncDirectory(path.dirname(target));
|
|
174
|
+
validate();
|
|
175
|
+
await syncDirectory(path.dirname(conflictDirectory(config)));
|
|
176
|
+
validate();
|
|
177
|
+
return token;
|
|
178
|
+
} finally {
|
|
179
|
+
await fs.rm(temporary, { force: true });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function readConflictArtifact(config, backend, token) {
|
|
183
|
+
try {
|
|
184
|
+
await directory(config);
|
|
185
|
+
const target = tokenPath(config, token);
|
|
186
|
+
const stat = await fs.lstat(target);
|
|
187
|
+
if (!stat.isFile() || stat.size > LIMIT || process.platform !== "win32" && (stat.mode & 63) !== 0)
|
|
188
|
+
throw new Error("Unsafe conflict artifact.");
|
|
189
|
+
const artifact = JSON.parse(await fs.readFile(target, "utf8"));
|
|
190
|
+
if (artifact.version !== 1 || artifact.identity !== mergeJournalIdentity(config, backend) || !Array.isArray(artifact.groups) || artifact.groups.length > 16384 || artifact.groups.some(
|
|
191
|
+
(group) => !Array.isArray(group.paths) || !group.paths.length || group.paths.length > 16384 || !Array.isArray(group.reasons)
|
|
192
|
+
))
|
|
193
|
+
throw new Error("Invalid conflict artifact.");
|
|
194
|
+
const plan = planFileMerge({
|
|
195
|
+
baseline: artifact.state.lastFileHashes,
|
|
196
|
+
local: artifact.local.files,
|
|
197
|
+
remote: artifact.remote.files,
|
|
198
|
+
selectionCompatible: true
|
|
199
|
+
});
|
|
200
|
+
if (plan.kind !== "planned") throw new Error("Invalid conflict versions.");
|
|
201
|
+
const known = new Set(plan.decisions.map((item) => item.path));
|
|
202
|
+
if (artifact.groups.some((group) => group.paths.some((value) => !known.has(value))))
|
|
203
|
+
throw new Error("Invalid conflict paths.");
|
|
204
|
+
if (artifact.ancestors) {
|
|
205
|
+
if (!Array.isArray(artifact.ancestors) || artifact.ancestors.length > 16384 || artifact.ancestors.some((file) => artifact.state.lastFileHashes[file.path] !== file.sha256))
|
|
206
|
+
throw new Error("Invalid artifact ancestor.");
|
|
207
|
+
const basePlan = planFileMerge({
|
|
208
|
+
baseline: {},
|
|
209
|
+
local: artifact.ancestors,
|
|
210
|
+
remote: [],
|
|
211
|
+
selectionCompatible: true
|
|
212
|
+
});
|
|
213
|
+
if (basePlan.kind !== "planned" || basePlan.conflicts.length) throw new Error("Invalid ancestor bytes.");
|
|
214
|
+
}
|
|
215
|
+
syncStateFingerprint(artifact.state);
|
|
216
|
+
return artifact;
|
|
217
|
+
} catch {
|
|
218
|
+
throw new Error("Cannot verify private conflict artifact; preserve evidence and review a fresh sync.");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export {
|
|
223
|
+
conflictGroups,
|
|
224
|
+
conflictArtifactFingerprint,
|
|
225
|
+
conflictDirectory,
|
|
226
|
+
saveConflictArtifact,
|
|
227
|
+
readConflictArtifact
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=chunk-ED3TYIVW.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/sync/conflict-artifacts.ts", "../../src/sync/conflict-groups.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash, randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { Snapshot, SnapshotFile } from \"../snapshot/snapshot-types.js\";\nimport { syncDirectory, writeJson } from \"../state/json-file.js\";\nimport type { SyncState } from \"../state/state-types.js\";\nimport { statePathForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { planFileMerge } from \"./file-merge-planner.js\";\n\nexport { conflictGroups } from \"./conflict-groups.js\";\n\nimport { mergeJournalIdentity } from \"./merge-journal.js\";\nexport interface ConflictArtifact {\n version: 1;\n identity: string;\n ancestors?: SnapshotFile[];\n state: SyncState;\n local: Snapshot;\n remote: Snapshot;\n groups: { paths: string[]; reasons: string[] }[];\n observed: { snapshotId: string; revision: string };\n}\nconst LIMIT = 192 * 1024 * 1024;\nexport function conflictArtifactFingerprint(artifact: ConflictArtifact) {\n return createHash(\"sha256\").update(JSON.stringify(artifact)).digest(\"hex\");\n}\nexport function conflictDirectory(config: AnySyncConfig) {\n return `${statePathForConfig(config)}.conflicts`;\n}\nasync function directory(config: AnySyncConfig, create = false) {\n const target = conflictDirectory(config);\n if (create) await fs.mkdir(target, { recursive: true, mode: 0o700 });\n const stat = await fs.lstat(target);\n if (!stat.isDirectory() || stat.isSymbolicLink() || (process.platform !== \"win32\" && (stat.mode & 0o077) !== 0))\n throw new Error(\"Unsafe private conflict directory.\");\n return target;\n}\nfunction tokenPath(config: AnySyncConfig, token: string) {\n if (!/^[a-f0-9-]{36}$/u.test(token)) throw new Error(\"Invalid conflict token.\");\n return path.join(conflictDirectory(config), `${token}.json`);\n}\nexport interface CreatedConflictArtifact {\n token: string;\n target: string;\n dev: number;\n ino: number;\n size: number;\n fingerprint: string;\n}\nexport async function saveConflictArtifact(\n config: AnySyncConfig,\n backend: string,\n artifact: Omit<ConflictArtifact, \"version\" | \"identity\">,\n validate: () => void,\n onCreated?: (created: CreatedConflictArtifact) => void,\n) {\n validate();\n const identity = mergeJournalIdentity(config, backend);\n const local = Object.fromEntries(artifact.local.files.map((file) => [file.path, file.sha256]));\n const remote = Object.fromEntries(artifact.remote.files.map((file) => [file.path, file.sha256]));\n const fingerprint = createHash(\"sha256\")\n .update(\n JSON.stringify([\n identity,\n artifact.groups.map((group) =>\n group.paths.map((filePath) => [\n filePath,\n artifact.state.lastFileHashes[filePath] ?? null,\n local[filePath] ?? null,\n remote[filePath] ?? null,\n ]),\n ),\n ]),\n )\n .digest(\"hex\")\n .slice(0, 32);\n const token = `${fingerprint.slice(0, 8)}-${fingerprint.slice(8, 12)}-${fingerprint.slice(12, 16)}-${fingerprint.slice(16, 20)}-${fingerprint.slice(20)}`;\n await directory(config, true);\n validate();\n const reuse = async () => {\n const existing = await readConflictArtifact(config, backend, token);\n validate();\n const oldLocal = Object.fromEntries(existing.local.files.map((file) => [file.path, file.sha256]));\n const oldRemote = Object.fromEntries(existing.remote.files.map((file) => [file.path, file.sha256]));\n if (\n JSON.stringify(existing.groups.map((group) => group.paths)) !==\n JSON.stringify(artifact.groups.map((group) => group.paths)) ||\n artifact.groups.some((group) =>\n group.paths.some(\n (filePath) =>\n existing.state.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath] ||\n oldLocal[filePath] !== local[filePath] ||\n oldRemote[filePath] !== remote[filePath],\n ),\n )\n )\n throw new Error(\"Conflict identity does not match retained immutable evidence.\");\n return token;\n };\n const target = tokenPath(config, token);\n try {\n await fs.lstat(target);\n validate();\n return await reuse();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n const temporary = `${target}.${randomUUID()}.pending`;\n const candidate: ConflictArtifact = { version: 1, identity, ...artifact };\n try {\n await writeJson(temporary, candidate, { maxBytes: LIMIT });\n validate();\n const stat = await fs.lstat(temporary);\n validate();\n try {\n // Publish immutable evidence without replacing a concurrent/pre-existing owner.\n await fs.link(temporary, target);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n validate();\n return await reuse();\n }\n // Register ownership before any cancellation or durability await can fail.\n onCreated?.({\n token,\n target,\n dev: stat.dev,\n ino: stat.ino,\n size: stat.size,\n fingerprint: conflictArtifactFingerprint(candidate),\n });\n validate();\n await syncDirectory(path.dirname(target));\n validate();\n await syncDirectory(path.dirname(conflictDirectory(config)));\n validate();\n return token;\n } finally {\n await fs.rm(temporary, { force: true });\n }\n}\nexport async function readConflictArtifact(config: AnySyncConfig, backend: string, token: string) {\n try {\n await directory(config);\n const target = tokenPath(config, token);\n const stat = await fs.lstat(target);\n if (!stat.isFile() || stat.size > LIMIT || (process.platform !== \"win32\" && (stat.mode & 0o077) !== 0))\n throw new Error(\"Unsafe conflict artifact.\");\n const artifact = JSON.parse(await fs.readFile(target, \"utf8\")) as ConflictArtifact;\n if (\n artifact.version !== 1 ||\n artifact.identity !== mergeJournalIdentity(config, backend) ||\n !Array.isArray(artifact.groups) ||\n artifact.groups.length > 16_384 ||\n artifact.groups.some(\n (group) =>\n !Array.isArray(group.paths) ||\n !group.paths.length ||\n group.paths.length > 16_384 ||\n !Array.isArray(group.reasons),\n )\n )\n throw new Error(\"Invalid conflict artifact.\");\n const plan = planFileMerge({\n baseline: artifact.state.lastFileHashes,\n local: artifact.local.files,\n remote: artifact.remote.files,\n selectionCompatible: true,\n });\n if (plan.kind !== \"planned\") throw new Error(\"Invalid conflict versions.\");\n const known = new Set(plan.decisions.map((item) => item.path));\n if (artifact.groups.some((group) => group.paths.some((value) => !known.has(value))))\n throw new Error(\"Invalid conflict paths.\");\n if (artifact.ancestors) {\n if (\n !Array.isArray(artifact.ancestors) ||\n artifact.ancestors.length > 16_384 ||\n artifact.ancestors.some((file) => artifact.state.lastFileHashes[file.path] !== file.sha256)\n )\n throw new Error(\"Invalid artifact ancestor.\");\n const basePlan = planFileMerge({\n baseline: {},\n local: artifact.ancestors,\n remote: [],\n selectionCompatible: true,\n });\n if (basePlan.kind !== \"planned\" || basePlan.conflicts.length) throw new Error(\"Invalid ancestor bytes.\");\n }\n syncStateFingerprint(artifact.state);\n return artifact;\n } catch {\n throw new Error(\"Cannot verify private conflict artifact; preserve evidence and review a fresh sync.\");\n }\n}\n", "import type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { type FileMergePlan, mergePathIdentity } from \"./file-merge-planner.js\";\n\n/** Indexed connected closure; path relationships are identical to the conservative merge policy. */\nexport function conflictGroups(plan: Extract<FileMergePlan, { kind: \"planned\" }>, config: AnySyncConfig) {\n const paths = plan.decisions.map((item) => item.path);\n const parents = paths.map((_, index) => index);\n const ranks = paths.map(() => 0);\n const find = (index: number): number => {\n while (parents[index] !== index) {\n parents[index] = parents[parents[index] ?? index] ?? index;\n index = parents[index] ?? index;\n }\n return index;\n };\n const join = (left: number, right: number) => {\n let a = find(left);\n let b = find(right);\n if (a === b) return;\n if ((ranks[a] ?? 0) < (ranks[b] ?? 0)) [a, b] = [b, a];\n parents[b] = a;\n if (ranks[a] === ranks[b]) ranks[a] = (ranks[a] ?? 0) + 1;\n };\n const identities = paths.map(mergePathIdentity);\n const byIdentity = new Map<string, number>();\n const byPath = new Map(paths.map((value, index) => [value, index]));\n identities.forEach((value, index) => {\n const previous = byIdentity.get(value);\n if (previous !== undefined) join(index, previous);\n else byIdentity.set(value, index);\n });\n const includes = new Set(\n config.include\n .filter((value) => ![\"settings.json\", \"AGENTS.md\", \"sessions\"].includes(value))\n .map(mergePathIdentity),\n );\n const buckets = new Map<string, number>();\n const bucket = (key: string, index: number) => {\n const previous = buckets.get(key);\n if (previous === undefined) buckets.set(key, index);\n else join(index, previous);\n };\n paths.forEach((value, index) => {\n const root = value.split(\"/\")[0] ?? \"\";\n if ([\"extensions\", \"themes\", \"skills\", \"prompts\"].includes(root)) bucket(`resource:${root}`, index);\n const segments = (identities[index] ?? \"\").split(\"/\");\n let ancestor = \"\";\n for (let length = 0; length < segments.length - 1; length++) {\n ancestor = ancestor ? `${ancestor}/${segments[length]}` : (segments[length] ?? \"\");\n const parent = byIdentity.get(ancestor);\n if (parent !== undefined) join(index, parent);\n if (includes.has(ancestor)) bucket(`include:${ancestor}`, index);\n }\n });\n const groups = new Map<number, { paths: string[]; reasons: string[] }>();\n for (const conflict of plan.conflicts) {\n const index = byPath.get(conflict.path);\n if (index === undefined) throw new Error(\"Conflict is missing from managed decisions.\");\n const key = find(index);\n const group = groups.get(key) ?? { paths: [], reasons: [] };\n if (!group.reasons.includes(conflict.reason)) group.reasons.push(conflict.reason);\n groups.set(key, group);\n }\n paths.forEach((value, index) => {\n groups.get(find(index))?.paths.push(value);\n });\n return [...groups.values()].map((group) => ({ ...group, paths: group.paths.sort() }));\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|