@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.
Files changed (194) hide show
  1. package/README.md +40 -15
  2. package/dist/chunks/agent-candidate-3JN6DJKT.ts +26 -0
  3. package/dist/chunks/agent-plan-QCUJWBP4.ts +99 -0
  4. package/dist/chunks/agent-plan-QCUJWBP4.ts.map +7 -0
  5. package/dist/chunks/agent-runner-E2JU2VZ6.ts +182 -0
  6. package/dist/chunks/agent-runner-E2JU2VZ6.ts.map +7 -0
  7. package/dist/chunks/agent-store-A3F6IYWZ.ts +152 -0
  8. package/dist/chunks/agent-store-A3F6IYWZ.ts.map +7 -0
  9. package/dist/chunks/chunk-5Q5SSKNG.ts +128 -0
  10. package/dist/chunks/chunk-5Q5SSKNG.ts.map +7 -0
  11. package/dist/chunks/chunk-5XZOC7FD.ts +215 -0
  12. package/dist/chunks/chunk-5XZOC7FD.ts.map +7 -0
  13. package/dist/chunks/{chunk-IJ6VV6FN.ts → chunk-AAEEHR7H.ts} +31 -87
  14. package/dist/chunks/chunk-AAEEHR7H.ts.map +7 -0
  15. package/dist/chunks/{chunk-CHSIAC4N.ts → chunk-B4QZBDBT.ts} +2 -2
  16. package/dist/chunks/{chunk-CL7BZRRQ.ts → chunk-CRKTAPSL.ts} +8 -8
  17. package/dist/chunks/chunk-DT7M4VQS.ts +69 -0
  18. package/dist/chunks/chunk-DT7M4VQS.ts.map +7 -0
  19. package/dist/chunks/chunk-ED3TYIVW.ts +229 -0
  20. package/dist/chunks/chunk-ED3TYIVW.ts.map +7 -0
  21. package/dist/chunks/{chunk-VHXBCEVF.ts → chunk-EUMINX7L.ts} +6 -6
  22. package/dist/chunks/chunk-FQOSHGJ7.ts +215 -0
  23. package/dist/chunks/chunk-FQOSHGJ7.ts.map +7 -0
  24. package/dist/chunks/chunk-GNH73BJ5.ts +153 -0
  25. package/dist/chunks/chunk-GNH73BJ5.ts.map +7 -0
  26. package/dist/chunks/{chunk-UQ3ALBIB.ts → chunk-GP5DU62N.ts} +8 -9
  27. package/dist/chunks/chunk-GP5DU62N.ts.map +7 -0
  28. package/dist/chunks/{chunk-5RZPINUQ.ts → chunk-GSNPASKB.ts} +2 -2
  29. package/dist/chunks/{chunk-QE7NZZWT.ts → chunk-IWZH5KYS.ts} +23 -7
  30. package/dist/chunks/chunk-IWZH5KYS.ts.map +7 -0
  31. package/dist/chunks/{chunk-5ZWQ33XL.ts → chunk-JJTARUSO.ts} +15 -14
  32. package/dist/chunks/chunk-JJTARUSO.ts.map +7 -0
  33. package/dist/chunks/{chunk-2DI6PDZV.ts → chunk-NLWS55B6.ts} +15 -2
  34. package/dist/chunks/chunk-NLWS55B6.ts.map +7 -0
  35. package/dist/chunks/{chunk-SDZCTCYQ.ts → chunk-OEWQVWLC.ts} +4 -4
  36. package/dist/chunks/{chunk-MYPSMJHJ.ts → chunk-OTZRCKYP.ts} +380 -510
  37. package/dist/chunks/chunk-OTZRCKYP.ts.map +7 -0
  38. package/dist/chunks/{chunk-WFKUKVCP.ts → chunk-OWBAZA6J.ts} +24 -12
  39. package/dist/chunks/chunk-OWBAZA6J.ts.map +7 -0
  40. package/dist/chunks/chunk-PRUEIIK7.ts +24 -0
  41. package/dist/chunks/chunk-PRUEIIK7.ts.map +7 -0
  42. package/dist/chunks/chunk-QGK445K7.ts +21 -0
  43. package/dist/chunks/chunk-QGK445K7.ts.map +7 -0
  44. package/dist/chunks/{chunk-3B4Z2SKV.ts → chunk-QO333NM4.ts} +49 -21
  45. package/dist/chunks/chunk-QO333NM4.ts.map +7 -0
  46. package/dist/chunks/{chunk-4SBTAVFL.ts → chunk-QSHMOL3I.ts} +22 -24
  47. package/dist/chunks/chunk-QSHMOL3I.ts.map +7 -0
  48. package/dist/chunks/chunk-S2FMH5FT.ts +97 -0
  49. package/dist/chunks/chunk-S2FMH5FT.ts.map +7 -0
  50. package/dist/chunks/{chunk-BVVP33U5.ts → chunk-SWRVXVFD.ts} +12 -9
  51. package/dist/chunks/chunk-SWRVXVFD.ts.map +7 -0
  52. package/dist/chunks/{chunk-ZX2Q6THB.ts → chunk-UBV24CG4.ts} +4 -5
  53. package/dist/chunks/chunk-UBV24CG4.ts.map +7 -0
  54. package/dist/chunks/chunk-UOHR7LRO.ts +70 -0
  55. package/dist/chunks/chunk-UOHR7LRO.ts.map +7 -0
  56. package/dist/chunks/{chunk-4QVPTIZB.ts → chunk-UPNVIUBN.ts} +3 -3
  57. package/dist/chunks/chunk-UZPMDLTL.ts +54 -0
  58. package/dist/chunks/chunk-UZPMDLTL.ts.map +7 -0
  59. package/dist/chunks/chunk-VJN7KU4J.ts +350 -0
  60. package/dist/chunks/chunk-VJN7KU4J.ts.map +7 -0
  61. package/dist/chunks/chunk-YHT4YTYG.ts +31 -0
  62. package/dist/chunks/chunk-YHT4YTYG.ts.map +7 -0
  63. package/dist/chunks/{chunk-V6VZ5TLG.ts → chunk-YK52VDXG.ts} +6 -5
  64. package/dist/chunks/chunk-YK52VDXG.ts.map +7 -0
  65. package/dist/chunks/config-KGIYTTXQ.ts +37 -0
  66. package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts → conflict-artifacts-FXFIIBCB.ts} +8 -7
  67. package/dist/chunks/{conflict-review-4PLVLMRO.ts → conflict-review-6WIEQLXW.ts} +31 -24
  68. package/dist/chunks/{conflict-review-4PLVLMRO.ts.map → conflict-review-6WIEQLXW.ts.map} +1 -1
  69. package/dist/chunks/continuous-record-LFRVXQBD.ts +30 -0
  70. package/dist/chunks/continuous-sync-AWAAOXK2.ts +545 -0
  71. package/dist/chunks/continuous-sync-AWAAOXK2.ts.map +7 -0
  72. package/dist/chunks/{file-selection-WZX7HAY2.ts → file-selection-3JJYOVNR.ts} +5 -5
  73. package/dist/chunks/{git-backend-FD7AVSK6.ts → git-backend-KYDZ673X.ts} +28 -32
  74. package/dist/chunks/git-backend-KYDZ673X.ts.map +7 -0
  75. package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts → manager-result-dispatcher-AGBBVDDS.ts} +10 -9
  76. package/dist/chunks/{manager-ui-IURS4WXY.ts → manager-ui-KRAW3NE2.ts} +464 -164
  77. package/dist/chunks/manager-ui-KRAW3NE2.ts.map +7 -0
  78. package/dist/chunks/{merge-baseline-store-EVQAAML6.ts → merge-baseline-store-RFCAH7KL.ts} +7 -5
  79. package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts → remote-selection-ui-5725JDNC.ts} +9 -8
  80. package/dist/chunks/remote-selection-ui-5725JDNC.ts.map +7 -0
  81. package/dist/chunks/{s3-backend-AQGV6LFT.ts → s3-backend-Y7WSCLJI.ts} +38 -38
  82. package/dist/chunks/s3-backend-Y7WSCLJI.ts.map +7 -0
  83. package/dist/chunks/{setup-switch-KTUWL6DE.ts → setup-switch-E7PHKI6J.ts} +7 -6
  84. package/dist/chunks/setup-switch-E7PHKI6J.ts.map +7 -0
  85. package/dist/chunks/{setup-wizard-GSDPDFC4.ts → setup-wizard-65DROCIJ.ts} +14 -13
  86. package/dist/chunks/setup-wizard-65DROCIJ.ts.map +7 -0
  87. package/dist/chunks/{sync-inspection-K45QVLHB.ts → sync-inspection-XFJOAMJD.ts} +9 -8
  88. package/dist/chunks/sync-inspection-XFJOAMJD.ts.map +7 -0
  89. package/dist/chunks/{sync-operations-47OUX76C.ts → sync-operations-HDUDT5PD.ts} +188 -73
  90. package/dist/chunks/sync-operations-HDUDT5PD.ts.map +7 -0
  91. package/dist/chunks/{webdav-backend-TBILVEQK.ts → webdav-backend-6K26F6LT.ts} +39 -48
  92. package/dist/chunks/webdav-backend-6K26F6LT.ts.map +7 -0
  93. package/dist/index.ts +246 -131
  94. package/dist/index.ts.map +3 -3
  95. package/docs/agent-mode-verification.md +99 -0
  96. package/docs/continuous-sync-verification.md +87 -0
  97. package/docs/settings.md +131 -11
  98. package/docs/tui-contract.md +61 -0
  99. package/package.json +3 -2
  100. package/src/backends/git/git-backend.ts +1 -4
  101. package/src/backends/git/git-runner.ts +1 -5
  102. package/src/backends/s3/s3-backend.ts +3 -25
  103. package/src/backends/s3/s3-client.ts +14 -2
  104. package/src/backends/snapshot-pointer.ts +27 -0
  105. package/src/backends/webdav/webdav-backend.ts +3 -29
  106. package/src/backends/webdav/webdav-client.ts +11 -7
  107. package/src/commands/command-execution.ts +7 -3
  108. package/src/commands/command-handler.ts +15 -3
  109. package/src/commands/command-types.ts +10 -0
  110. package/src/commands/command.ts +4 -1
  111. package/src/settings/config.ts +20 -10
  112. package/src/settings/settings-store.ts +1 -34
  113. package/src/settings/settings-types.ts +10 -0
  114. package/src/settings/settings-validation.ts +4 -1
  115. package/src/snapshot/snapshot-codec.ts +1 -5
  116. package/src/state/lock.ts +13 -10
  117. package/src/state/merge-baseline-store.ts +19 -1
  118. package/src/state/state-directory.ts +8 -1
  119. package/src/sync/agent-candidate.ts +251 -0
  120. package/src/sync/agent-plan.ts +87 -0
  121. package/src/sync/agent-policy.ts +13 -0
  122. package/src/sync/agent-request.ts +17 -0
  123. package/src/sync/agent-runner.ts +188 -0
  124. package/src/sync/agent-store.ts +151 -0
  125. package/src/sync/agent-sync.ts +91 -0
  126. package/src/sync/apply-review.ts +63 -0
  127. package/src/sync/automatic-sync.ts +46 -33
  128. package/src/sync/continuous-errors.ts +85 -0
  129. package/src/sync/continuous-lease.ts +221 -0
  130. package/src/sync/continuous-record.ts +179 -0
  131. package/src/sync/continuous-storage.ts +55 -0
  132. package/src/sync/continuous-sync.ts +516 -0
  133. package/src/sync/continuous-watch.ts +59 -0
  134. package/src/sync/merged-sync.ts +268 -112
  135. package/src/sync/remote-snapshot.ts +2 -3
  136. package/src/sync/settings-merge.ts +19 -3
  137. package/src/sync/setup-switch.ts +1 -5
  138. package/src/sync/startup-check.ts +51 -29
  139. package/src/sync/startup-timings.ts +90 -0
  140. package/src/sync/sync-inspection.ts +16 -5
  141. package/src/sync/sync-mutations.ts +55 -28
  142. package/src/sync/sync-queries.ts +19 -3
  143. package/src/sync/text-merge.ts +3 -3
  144. package/src/sync-extension.ts +75 -19
  145. package/src/ui/agent-mode-setting.ts +91 -0
  146. package/src/ui/manager-state.ts +12 -6
  147. package/src/ui/manager-ui.ts +57 -7
  148. package/src/ui/merge-review.ts +3 -3
  149. package/src/ui/merged-sync-summary.ts +2 -18
  150. package/src/ui/remote-selection-ui.ts +5 -5
  151. package/src/ui/settings-frame.ts +55 -0
  152. package/src/ui/settings-ui.ts +223 -43
  153. package/src/ui/setup/s3-credentials-ui.ts +1 -5
  154. package/src/ui/setup/setup-name-ui.ts +2 -5
  155. package/src/ui/setup/setup-prompts.ts +4 -3
  156. package/src/ui/setup/setup-switcher.ts +2 -0
  157. package/src/ui/sync-behavior.ts +15 -0
  158. package/src/ui/sync-format.ts +69 -38
  159. package/src/ui/sync-setups-ui.ts +3 -5
  160. package/src/ui/transfer-changes.ts +33 -0
  161. package/src/ui/transfer-review.ts +27 -0
  162. package/dist/chunks/chunk-2DI6PDZV.ts.map +0 -7
  163. package/dist/chunks/chunk-3B4Z2SKV.ts.map +0 -7
  164. package/dist/chunks/chunk-4SBTAVFL.ts.map +0 -7
  165. package/dist/chunks/chunk-5ZWQ33XL.ts.map +0 -7
  166. package/dist/chunks/chunk-BVVP33U5.ts.map +0 -7
  167. package/dist/chunks/chunk-IJ6VV6FN.ts.map +0 -7
  168. package/dist/chunks/chunk-MYPSMJHJ.ts.map +0 -7
  169. package/dist/chunks/chunk-ORQRHEEO.ts +0 -362
  170. package/dist/chunks/chunk-ORQRHEEO.ts.map +0 -7
  171. package/dist/chunks/chunk-QE7NZZWT.ts.map +0 -7
  172. package/dist/chunks/chunk-UQ3ALBIB.ts.map +0 -7
  173. package/dist/chunks/chunk-V6VZ5TLG.ts.map +0 -7
  174. package/dist/chunks/chunk-WFKUKVCP.ts.map +0 -7
  175. package/dist/chunks/chunk-ZX2Q6THB.ts.map +0 -7
  176. package/dist/chunks/git-backend-FD7AVSK6.ts.map +0 -7
  177. package/dist/chunks/manager-ui-IURS4WXY.ts.map +0 -7
  178. package/dist/chunks/s3-backend-AQGV6LFT.ts.map +0 -7
  179. package/dist/chunks/setup-wizard-GSDPDFC4.ts.map +0 -7
  180. package/dist/chunks/sync-operations-47OUX76C.ts.map +0 -7
  181. package/dist/chunks/webdav-backend-TBILVEQK.ts.map +0 -7
  182. /package/dist/chunks/{conflict-artifacts-BB2FZMIE.ts.map → agent-candidate-3JN6DJKT.ts.map} +0 -0
  183. /package/dist/chunks/{chunk-CHSIAC4N.ts.map → chunk-B4QZBDBT.ts.map} +0 -0
  184. /package/dist/chunks/{chunk-CL7BZRRQ.ts.map → chunk-CRKTAPSL.ts.map} +0 -0
  185. /package/dist/chunks/{chunk-VHXBCEVF.ts.map → chunk-EUMINX7L.ts.map} +0 -0
  186. /package/dist/chunks/{chunk-5RZPINUQ.ts.map → chunk-GSNPASKB.ts.map} +0 -0
  187. /package/dist/chunks/{chunk-SDZCTCYQ.ts.map → chunk-OEWQVWLC.ts.map} +0 -0
  188. /package/dist/chunks/{chunk-4QVPTIZB.ts.map → chunk-UPNVIUBN.ts.map} +0 -0
  189. /package/dist/chunks/{manager-result-dispatcher-6MQKFCWB.ts.map → config-KGIYTTXQ.ts.map} +0 -0
  190. /package/dist/chunks/{merge-baseline-store-EVQAAML6.ts.map → conflict-artifacts-FXFIIBCB.ts.map} +0 -0
  191. /package/dist/chunks/{remote-selection-ui-6LJJ4IMQ.ts.map → continuous-record-LFRVXQBD.ts.map} +0 -0
  192. /package/dist/chunks/{file-selection-WZX7HAY2.ts.map → file-selection-3JJYOVNR.ts.map} +0 -0
  193. /package/dist/chunks/{setup-switch-KTUWL6DE.ts.map → manager-result-dispatcher-AGBBVDDS.ts.map} +0 -0
  194. /package/dist/chunks/{sync-inspection-K45QVLHB.ts.map → merge-baseline-store-RFCAH7KL.ts.map} +0 -0
@@ -1,362 +0,0 @@
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
- fileHashMap,
5
- sameHashes
6
- } from "./chunk-BDB4IDM3.ts";
7
- import {
8
- mergePathIdentity,
9
- planFileMerge
10
- } from "./chunk-76NB4R7U.ts";
11
- import {
12
- statePathForConfig,
13
- syncStateFingerprint
14
- } from "./chunk-4QVPTIZB.ts";
15
- import {
16
- portableSnapshot,
17
- sameLocalFields,
18
- validatePortableSnapshot,
19
- validateSnapshotFieldPolicy
20
- } from "./chunk-A6HU4ERM.ts";
21
- import {
22
- readJsonIfExists,
23
- syncDirectory,
24
- writeJson
25
- } from "./chunk-HUZ5CR4W.ts";
26
-
27
- // src/sync/conflict-artifacts.ts
28
- import { createHash, randomUUID as randomUUID2 } from "node:crypto";
29
- import fs2 from "node:fs/promises";
30
- import path2 from "node:path";
31
-
32
- // src/sync/conflict-groups.ts
33
- function conflictGroups(plan, config) {
34
- const paths = plan.decisions.map((item) => item.path);
35
- const parents = paths.map((_, index) => index);
36
- const ranks = paths.map(() => 0);
37
- const find = (index) => {
38
- while (parents[index] !== index) {
39
- parents[index] = parents[parents[index] ?? index] ?? index;
40
- index = parents[index] ?? index;
41
- }
42
- return index;
43
- };
44
- const join = (left, right) => {
45
- let a = find(left);
46
- let b = find(right);
47
- if (a === b) return;
48
- if ((ranks[a] ?? 0) < (ranks[b] ?? 0)) [a, b] = [b, a];
49
- parents[b] = a;
50
- if (ranks[a] === ranks[b]) ranks[a] = (ranks[a] ?? 0) + 1;
51
- };
52
- const identities = paths.map(mergePathIdentity);
53
- const byIdentity = /* @__PURE__ */ new Map();
54
- const byPath = new Map(paths.map((value, index) => [value, index]));
55
- identities.forEach((value, index) => {
56
- const previous = byIdentity.get(value);
57
- if (previous !== void 0) join(index, previous);
58
- else byIdentity.set(value, index);
59
- });
60
- const includes = new Set(
61
- config.include.filter((value) => !["settings.json", "AGENTS.md", "sessions"].includes(value)).map(mergePathIdentity)
62
- );
63
- const buckets = /* @__PURE__ */ new Map();
64
- const bucket = (key, index) => {
65
- const previous = buckets.get(key);
66
- if (previous === void 0) buckets.set(key, index);
67
- else join(index, previous);
68
- };
69
- paths.forEach((value, index) => {
70
- const root = value.split("/")[0] ?? "";
71
- if (["extensions", "themes", "skills", "prompts"].includes(root)) bucket(`resource:${root}`, index);
72
- const segments = (identities[index] ?? "").split("/");
73
- let ancestor = "";
74
- for (let length = 0; length < segments.length - 1; length++) {
75
- ancestor = ancestor ? `${ancestor}/${segments[length]}` : segments[length] ?? "";
76
- const parent = byIdentity.get(ancestor);
77
- if (parent !== void 0) join(index, parent);
78
- if (includes.has(ancestor)) bucket(`include:${ancestor}`, index);
79
- }
80
- });
81
- const groups = /* @__PURE__ */ new Map();
82
- for (const conflict of plan.conflicts) {
83
- const index = byPath.get(conflict.path);
84
- if (index === void 0) throw new Error("Conflict is missing from managed decisions.");
85
- const key = find(index);
86
- const group = groups.get(key) ?? { paths: [], reasons: [] };
87
- if (!group.reasons.includes(conflict.reason)) group.reasons.push(conflict.reason);
88
- groups.set(key, group);
89
- }
90
- paths.forEach((value, index) => {
91
- groups.get(find(index))?.paths.push(value);
92
- });
93
- return [...groups.values()].map((group) => ({ ...group, paths: group.paths.sort() }));
94
- }
95
-
96
- // src/sync/merge-journal.ts
97
- import { randomUUID } from "node:crypto";
98
- import fs from "node:fs/promises";
99
- import path from "node:path";
100
- function mergeJournalIdentity(config, backendIdentity) {
101
- return JSON.stringify([config.setupName, backendIdentity, [...config.include].sort(), config.localFields ?? null]);
102
- }
103
- function mergeJournalPath(config) {
104
- return `${statePathForConfig(config)}.merge-journal.json`;
105
- }
106
- async function readMergeJournal(config) {
107
- let journal;
108
- try {
109
- const stat = await fs.lstat(mergeJournalPath(config));
110
- if (!stat.isFile() || stat.size > 384 * 1024 * 1024) throw new Error("Unsafe or oversized journal.");
111
- journal = await readJsonIfExists(mergeJournalPath(config));
112
- } catch (error) {
113
- if (error.code === "ENOENT") return;
114
- throw new Error("Cannot read the private merge journal; preserve it and review recovery.");
115
- }
116
- if (!journal) return;
117
- if (journal.version !== 1 || typeof journal.identity !== "string" || typeof journal.backup !== "string" || typeof journal.stateIdentity !== "string" || journal.applyOnly !== void 0 && (journal.applyOnly !== true || !journal.committedHead || journal.committedHead.revision !== journal.expectedHead?.revision || journal.committedHead.snapshotId !== journal.expectedHead?.snapshotId || journal.upload?.id !== journal.expectedHead?.snapshotId) || journal.sessionRoot !== void 0 && (typeof journal.sessionRoot !== "string" || !path.isAbsolute(journal.sessionRoot) || path.resolve(journal.sessionRoot) !== journal.sessionRoot) || journal.warnings !== void 0 && (!Array.isArray(journal.warnings) || journal.warnings.length > 64 || journal.warnings.some((item) => typeof item !== "string")) || !journal.expectedHead || typeof journal.expectedHead.revision !== "string" || !journal.before || !journal.after || !journal.upload || !Array.isArray(journal.before.files) || !Array.isArray(journal.after.files) || !Array.isArray(journal.upload.files))
118
- throw new Error("Unsupported or damaged merge journal; preserve it and review recovery before syncing.");
119
- try {
120
- const pinnedPaths = new Set(journal.progress?.groups.flatMap((group) => group.paths) ?? []);
121
- for (const snapshot of [
122
- journal.before,
123
- journal.after,
124
- journal.upload,
125
- ...journal.accepted ? [journal.accepted] : []
126
- ]) {
127
- validateSnapshotFieldPolicy(snapshot);
128
- if (snapshot.version !== 1 && snapshot.version !== 2 && snapshot.version !== 3 || typeof snapshot.id !== "string" || typeof snapshot.profile !== "string" || snapshot.files.length > 16384 || snapshot.files.reduce(
129
- (bytes, file) => bytes + (typeof file?.contentBase64 === "string" ? file.contentBase64.length * 0.75 : Infinity),
130
- 0
131
- ) > 64 * 1024 * 1024)
132
- throw new Error("Invalid journal input.");
133
- const plan = planFileMerge({ baseline: {}, local: snapshot.files, remote: [], selectionCompatible: true });
134
- if (plan.kind !== "planned" || plan.conflicts.some((conflict) => conflict.reason !== "path-collision" || !pinnedPaths.has(conflict.path)))
135
- throw new Error("Invalid journal collision group.");
136
- }
137
- validatePortableSnapshot(journal.upload);
138
- if ((journal.upload.version === 2 || journal.upload.version === 3) && !journal.accepted)
139
- throw new Error("Portable merge journal requires an explicit accepted projection.");
140
- if (journal.accepted) validatePortableSnapshot(journal.accepted);
141
- } catch {
142
- throw new Error("Invalid merge journal paths, metadata, or bytes; preserve evidence for review.");
143
- }
144
- try {
145
- if (journal.accepted && (!sameLocalFields(journal.accepted.localFields, journal.upload.localFields) || !sameHashes(
146
- fileHashMap(portableSnapshot(journal.after, journal.upload.localFields)),
147
- fileHashMap(portableSnapshot(journal.accepted, journal.upload.localFields))
148
- )))
149
- throw new Error("Projection mismatch.");
150
- } catch {
151
- throw new Error("Invalid accepted merge projection; preserve journal evidence.");
152
- }
153
- if (journal.progress) {
154
- const known = /* @__PURE__ */ new Set([
155
- ...Object.keys(journal.progress.previous?.lastFileHashes ?? {}),
156
- ...[...journal.before.files, ...journal.after.files, ...journal.upload.files].map((file) => file.path)
157
- ]);
158
- const progress = journal.progress;
159
- if (!config.partialSync || !progress.previous || syncStateFingerprint(progress.previous) !== journal.stateIdentity || !Array.isArray(progress.groups) || progress.groups.length > 16384 || progress.groups.some(
160
- (group) => !Array.isArray(group.paths) || !group.paths.length || group.paths.length > 16384 || group.paths.some((filePath) => !known.has(filePath)) || !/^[a-f0-9-]{36}$/u.test(group.artifact)
161
- ))
162
- throw new Error("Invalid partial acceptance metadata; preserve journal evidence.");
163
- }
164
- if (journal.progress) {
165
- const before = fileHashMap(journal.before);
166
- const after = fileHashMap(journal.after);
167
- const { readConflictArtifact: readConflictArtifact2 } = await import("./conflict-artifacts-BB2FZMIE.ts");
168
- const backendIdentity = JSON.parse(journal.identity)[1];
169
- if (typeof backendIdentity !== "string") throw new Error("Invalid partial backend identity.");
170
- const byArtifact = /* @__PURE__ */ new Map();
171
- for (const group of journal.progress.groups) {
172
- const groups = byArtifact.get(group.artifact) ?? [];
173
- groups.push(group);
174
- byArtifact.set(group.artifact, groups);
175
- }
176
- for (const [token, groups] of byArtifact) {
177
- const artifact = await readConflictArtifact2(config, backendIdentity, token);
178
- const retainedGroups = new Set(artifact.groups.map((group) => JSON.stringify(group.paths)));
179
- const remote = fileHashMap(artifact.remote);
180
- const upload = fileHashMap(journal.upload);
181
- for (const group of groups) {
182
- if (!retainedGroups.has(JSON.stringify(group.paths)))
183
- throw new Error("Partial group does not match retained artifact evidence.");
184
- for (const filePath of group.paths)
185
- if (before[filePath] !== after[filePath] || remote[filePath] !== upload[filePath] || journal.progress.previous.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath])
186
- throw new Error("Withheld version changed in journal; preserve evidence.");
187
- }
188
- }
189
- }
190
- return journal;
191
- }
192
- async function writeMergeJournal(config, journal) {
193
- await writeJson(mergeJournalPath(config), journal, { maxBytes: 384 * 1024 * 1024 });
194
- }
195
- async function clearMergeJournal(config) {
196
- await fs.rm(mergeJournalPath(config), { force: true });
197
- await syncDirectory(path.dirname(mergeJournalPath(config)));
198
- }
199
- async function retireMergeJournal(config) {
200
- if (!await readMergeJournal(config)) return;
201
- const file = mergeJournalPath(config);
202
- await fs.rename(file, `${file}.${randomUUID()}.resolved`);
203
- await syncDirectory(path.dirname(file));
204
- }
205
- async function requireNoMergeJournal(config, options = {}) {
206
- const journal = await readMergeJournal(config);
207
- if (options.force || !journal) return;
208
- if (journal) {
209
- throw new Error(
210
- "A merged transfer needs recovery. Run /sync sync to reconcile it. If newer local edits or remote revisions prevent recovery, review /sync diff and explicitly choose push --force or pull --force; the selected directional result will archive the old journal, not restore old bytes. Keep its backup; do not downgrade while recovery is pending."
211
- );
212
- }
213
- }
214
-
215
- // src/sync/conflict-artifacts.ts
216
- var LIMIT = 192 * 1024 * 1024;
217
- function conflictArtifactFingerprint(artifact) {
218
- return createHash("sha256").update(JSON.stringify(artifact)).digest("hex");
219
- }
220
- function conflictDirectory(config) {
221
- return `${statePathForConfig(config)}.conflicts`;
222
- }
223
- async function directory(config, create = false) {
224
- const target = conflictDirectory(config);
225
- if (create) await fs2.mkdir(target, { recursive: true, mode: 448 });
226
- const stat = await fs2.lstat(target);
227
- if (!stat.isDirectory() || stat.isSymbolicLink() || process.platform !== "win32" && (stat.mode & 63) !== 0)
228
- throw new Error("Unsafe private conflict directory.");
229
- return target;
230
- }
231
- function tokenPath(config, token) {
232
- if (!/^[a-f0-9-]{36}$/u.test(token)) throw new Error("Invalid conflict token.");
233
- return path2.join(conflictDirectory(config), `${token}.json`);
234
- }
235
- async function saveConflictArtifact(config, backend, artifact, validate, onCreated) {
236
- validate();
237
- const identity = mergeJournalIdentity(config, backend);
238
- const local = Object.fromEntries(artifact.local.files.map((file) => [file.path, file.sha256]));
239
- const remote = Object.fromEntries(artifact.remote.files.map((file) => [file.path, file.sha256]));
240
- const fingerprint = createHash("sha256").update(
241
- JSON.stringify([
242
- identity,
243
- artifact.groups.map(
244
- (group) => group.paths.map((filePath) => [
245
- filePath,
246
- artifact.state.lastFileHashes[filePath] ?? null,
247
- local[filePath] ?? null,
248
- remote[filePath] ?? null
249
- ])
250
- )
251
- ])
252
- ).digest("hex").slice(0, 32);
253
- const token = `${fingerprint.slice(0, 8)}-${fingerprint.slice(8, 12)}-${fingerprint.slice(12, 16)}-${fingerprint.slice(16, 20)}-${fingerprint.slice(20)}`;
254
- await directory(config, true);
255
- validate();
256
- const reuse = async () => {
257
- const existing = await readConflictArtifact(config, backend, token);
258
- validate();
259
- const oldLocal = Object.fromEntries(existing.local.files.map((file) => [file.path, file.sha256]));
260
- const oldRemote = Object.fromEntries(existing.remote.files.map((file) => [file.path, file.sha256]));
261
- if (JSON.stringify(existing.groups.map((group) => group.paths)) !== JSON.stringify(artifact.groups.map((group) => group.paths)) || artifact.groups.some(
262
- (group) => group.paths.some(
263
- (filePath) => existing.state.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath] || oldLocal[filePath] !== local[filePath] || oldRemote[filePath] !== remote[filePath]
264
- )
265
- ))
266
- throw new Error("Conflict identity does not match retained immutable evidence.");
267
- return token;
268
- };
269
- const target = tokenPath(config, token);
270
- try {
271
- await fs2.lstat(target);
272
- validate();
273
- return await reuse();
274
- } catch (error) {
275
- if (error.code !== "ENOENT") throw error;
276
- }
277
- const temporary = `${target}.${randomUUID2()}.pending`;
278
- const candidate = { version: 1, identity, ...artifact };
279
- try {
280
- await writeJson(temporary, candidate, { maxBytes: LIMIT });
281
- validate();
282
- const stat = await fs2.lstat(temporary);
283
- validate();
284
- try {
285
- await fs2.link(temporary, target);
286
- } catch (error) {
287
- if (error.code !== "EEXIST") throw error;
288
- validate();
289
- return await reuse();
290
- }
291
- onCreated?.({
292
- token,
293
- target,
294
- dev: stat.dev,
295
- ino: stat.ino,
296
- size: stat.size,
297
- fingerprint: conflictArtifactFingerprint(candidate)
298
- });
299
- validate();
300
- await syncDirectory(path2.dirname(target));
301
- validate();
302
- await syncDirectory(path2.dirname(conflictDirectory(config)));
303
- validate();
304
- return token;
305
- } finally {
306
- await fs2.rm(temporary, { force: true });
307
- }
308
- }
309
- async function readConflictArtifact(config, backend, token) {
310
- try {
311
- await directory(config);
312
- const target = tokenPath(config, token);
313
- const stat = await fs2.lstat(target);
314
- if (!stat.isFile() || stat.size > LIMIT || process.platform !== "win32" && (stat.mode & 63) !== 0)
315
- throw new Error("Unsafe conflict artifact.");
316
- const artifact = JSON.parse(await fs2.readFile(target, "utf8"));
317
- if (artifact.version !== 1 || artifact.identity !== mergeJournalIdentity(config, backend) || !Array.isArray(artifact.groups) || artifact.groups.length > 16384 || artifact.groups.some(
318
- (group) => !Array.isArray(group.paths) || !group.paths.length || group.paths.length > 16384 || !Array.isArray(group.reasons)
319
- ))
320
- throw new Error("Invalid conflict artifact.");
321
- const plan = planFileMerge({
322
- baseline: artifact.state.lastFileHashes,
323
- local: artifact.local.files,
324
- remote: artifact.remote.files,
325
- selectionCompatible: true
326
- });
327
- if (plan.kind !== "planned") throw new Error("Invalid conflict versions.");
328
- const known = new Set(plan.decisions.map((item) => item.path));
329
- if (artifact.groups.some((group) => group.paths.some((value) => !known.has(value))))
330
- throw new Error("Invalid conflict paths.");
331
- if (artifact.ancestors) {
332
- if (!Array.isArray(artifact.ancestors) || artifact.ancestors.length > 16384 || artifact.ancestors.some((file) => artifact.state.lastFileHashes[file.path] !== file.sha256))
333
- throw new Error("Invalid artifact ancestor.");
334
- const basePlan = planFileMerge({
335
- baseline: {},
336
- local: artifact.ancestors,
337
- remote: [],
338
- selectionCompatible: true
339
- });
340
- if (basePlan.kind !== "planned" || basePlan.conflicts.length) throw new Error("Invalid ancestor bytes.");
341
- }
342
- syncStateFingerprint(artifact.state);
343
- return artifact;
344
- } catch {
345
- throw new Error("Cannot verify private conflict artifact; preserve evidence and review a fresh sync.");
346
- }
347
- }
348
-
349
- export {
350
- conflictGroups,
351
- mergeJournalIdentity,
352
- readMergeJournal,
353
- writeMergeJournal,
354
- clearMergeJournal,
355
- retireMergeJournal,
356
- requireNoMergeJournal,
357
- conflictArtifactFingerprint,
358
- conflictDirectory,
359
- saveConflictArtifact,
360
- readConflictArtifact
361
- };
362
- //# sourceMappingURL=chunk-ORQRHEEO.ts.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/conflict-artifacts.ts", "../../src/sync/conflict-groups.ts", "../../src/sync/merge-journal.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", "import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { RemoteHead } from \"../backends/sync-backend.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { Snapshot } from \"../snapshot/snapshot-types.js\";\nimport { readJsonIfExists, syncDirectory, writeJson } from \"../state/json-file.js\";\nimport { statePathForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { planFileMerge } from \"./file-merge-planner.js\";\nimport {\n portableSnapshot,\n sameLocalFields,\n validatePortableSnapshot,\n validateSnapshotFieldPolicy,\n} from \"./local-fields.js\";\nimport type { PartialProgress } from \"./partial-progress.js\";\nimport { fileHashMap, sameHashes } from \"./sync-state.js\";\n\nexport interface MergeJournal {\n version: 1;\n identity: string;\n before: Snapshot;\n after: Snapshot;\n accepted?: Snapshot;\n progress?: PartialProgress;\n upload: Snapshot;\n expectedHead: RemoteHead;\n committedHead?: RemoteHead;\n /** No remote publication was attempted; a stale candidate can retire if local preimages remain intact. */\n applyOnly?: boolean;\n backup: string;\n stateIdentity: string;\n /** Effective collection/apply root; absent legacy evidence cannot authorize session recovery. */\n sessionRoot?: string;\n warnings?: string[];\n}\n\nexport function mergeJournalIdentity(config: AnySyncConfig, backendIdentity: string) {\n return JSON.stringify([config.setupName, backendIdentity, [...config.include].sort(), config.localFields ?? null]);\n}\n\nexport function mergeJournalPath(config: AnySyncConfig) {\n return `${statePathForConfig(config)}.merge-journal.json`;\n}\n\nexport async function readMergeJournal(config: AnySyncConfig): Promise<MergeJournal | undefined> {\n let journal: MergeJournal | undefined;\n try {\n const stat = await fs.lstat(mergeJournalPath(config));\n if (!stat.isFile() || stat.size > 384 * 1024 * 1024) throw new Error(\"Unsafe or oversized journal.\");\n journal = await readJsonIfExists<MergeJournal>(mergeJournalPath(config));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n // A JSON parser error may quote sensitive journal contents; do not retain it in a public error.\n throw new Error(\"Cannot read the private merge journal; preserve it and review recovery.\");\n }\n if (!journal) return;\n if (\n journal.version !== 1 ||\n typeof journal.identity !== \"string\" ||\n typeof journal.backup !== \"string\" ||\n typeof journal.stateIdentity !== \"string\" ||\n (journal.applyOnly !== undefined &&\n (journal.applyOnly !== true ||\n !journal.committedHead ||\n journal.committedHead.revision !== journal.expectedHead?.revision ||\n journal.committedHead.snapshotId !== journal.expectedHead?.snapshotId ||\n journal.upload?.id !== journal.expectedHead?.snapshotId)) ||\n (journal.sessionRoot !== undefined &&\n (typeof journal.sessionRoot !== \"string\" ||\n !path.isAbsolute(journal.sessionRoot) ||\n path.resolve(journal.sessionRoot) !== journal.sessionRoot)) ||\n (journal.warnings !== undefined &&\n (!Array.isArray(journal.warnings) ||\n journal.warnings.length > 64 ||\n journal.warnings.some((item) => typeof item !== \"string\"))) ||\n !journal.expectedHead ||\n typeof journal.expectedHead.revision !== \"string\" ||\n !journal.before ||\n !journal.after ||\n !journal.upload ||\n !Array.isArray(journal.before.files) ||\n !Array.isArray(journal.after.files) ||\n !Array.isArray(journal.upload.files)\n )\n throw new Error(\"Unsupported or damaged merge journal; preserve it and review recovery before syncing.\");\n // Verify every path and byte, including the unmanaged remote files retained for publication.\n try {\n const pinnedPaths = new Set(journal.progress?.groups.flatMap((group) => group.paths) ?? []);\n for (const snapshot of [\n journal.before,\n journal.after,\n journal.upload,\n ...(journal.accepted ? [journal.accepted] : []),\n ]) {\n validateSnapshotFieldPolicy(snapshot);\n if (\n (snapshot.version !== 1 && snapshot.version !== 2 && snapshot.version !== 3) ||\n typeof snapshot.id !== \"string\" ||\n typeof snapshot.profile !== \"string\" ||\n snapshot.files.length > 16_384 ||\n snapshot.files.reduce(\n (bytes, file) =>\n bytes + (typeof file?.contentBase64 === \"string\" ? file.contentBase64.length * 0.75 : Infinity),\n 0,\n ) >\n 64 * 1024 * 1024\n )\n throw new Error(\"Invalid journal input.\");\n const plan = planFileMerge({ baseline: {}, local: snapshot.files, remote: [], selectionCompatible: true });\n if (\n plan.kind !== \"planned\" ||\n plan.conflicts.some((conflict) => conflict.reason !== \"path-collision\" || !pinnedPaths.has(conflict.path))\n )\n throw new Error(\"Invalid journal collision group.\");\n }\n validatePortableSnapshot(journal.upload);\n if ((journal.upload.version === 2 || journal.upload.version === 3) && !journal.accepted)\n throw new Error(\"Portable merge journal requires an explicit accepted projection.\");\n if (journal.accepted) validatePortableSnapshot(journal.accepted);\n } catch {\n throw new Error(\"Invalid merge journal paths, metadata, or bytes; preserve evidence for review.\");\n }\n // Integrity is intrinsic to the recorded transaction, not the user's current recovery policy.\n // completeJournal separately checks that the recorded policy matches the current setup.\n try {\n if (\n journal.accepted &&\n (!sameLocalFields(journal.accepted.localFields, journal.upload.localFields) ||\n !sameHashes(\n fileHashMap(portableSnapshot(journal.after, journal.upload.localFields)),\n fileHashMap(portableSnapshot(journal.accepted, journal.upload.localFields)),\n ))\n )\n throw new Error(\"Projection mismatch.\");\n } catch {\n throw new Error(\"Invalid accepted merge projection; preserve journal evidence.\");\n }\n if (journal.progress) {\n const known = new Set([\n ...Object.keys(journal.progress.previous?.lastFileHashes ?? {}),\n ...[...journal.before.files, ...journal.after.files, ...journal.upload.files].map((file) => file.path),\n ]);\n const progress = journal.progress;\n if (\n !config.partialSync ||\n !progress.previous ||\n syncStateFingerprint(progress.previous) !== journal.stateIdentity ||\n !Array.isArray(progress.groups) ||\n progress.groups.length > 16_384 ||\n progress.groups.some(\n (group) =>\n !Array.isArray(group.paths) ||\n !group.paths.length ||\n group.paths.length > 16_384 ||\n group.paths.some((filePath) => !known.has(filePath)) ||\n !/^[a-f0-9-]{36}$/u.test(group.artifact),\n )\n )\n throw new Error(\"Invalid partial acceptance metadata; preserve journal evidence.\");\n }\n if (journal.progress) {\n const before = fileHashMap(journal.before);\n const after = fileHashMap(journal.after);\n const { readConflictArtifact } = await import(\"./conflict-artifacts.js\");\n const backendIdentity = (JSON.parse(journal.identity) as unknown[])[1];\n if (typeof backendIdentity !== \"string\") throw new Error(\"Invalid partial backend identity.\");\n const byArtifact = new Map<string, typeof journal.progress.groups>();\n for (const group of journal.progress.groups) {\n const groups = byArtifact.get(group.artifact) ?? [];\n groups.push(group);\n byArtifact.set(group.artifact, groups);\n }\n for (const [token, groups] of byArtifact) {\n const artifact = await readConflictArtifact(config, backendIdentity, token);\n const retainedGroups = new Set(artifact.groups.map((group) => JSON.stringify(group.paths)));\n const remote = fileHashMap(artifact.remote);\n const upload = fileHashMap(journal.upload);\n for (const group of groups) {\n if (!retainedGroups.has(JSON.stringify(group.paths)))\n throw new Error(\"Partial group does not match retained artifact evidence.\");\n for (const filePath of group.paths)\n if (\n before[filePath] !== after[filePath] ||\n remote[filePath] !== upload[filePath] ||\n journal.progress.previous.lastFileHashes[filePath] !== artifact.state.lastFileHashes[filePath]\n )\n throw new Error(\"Withheld version changed in journal; preserve evidence.\");\n }\n }\n }\n return journal;\n}\n\nexport async function writeMergeJournal(config: AnySyncConfig, journal: MergeJournal) {\n await writeJson(mergeJournalPath(config), journal, { maxBytes: 384 * 1024 * 1024 });\n}\n\nexport async function clearMergeJournal(config: AnySyncConfig) {\n await fs.rm(mergeJournalPath(config), { force: true });\n await syncDirectory(path.dirname(mergeJournalPath(config)));\n}\n\nexport async function retireMergeJournal(config: AnySyncConfig) {\n if (!(await readMergeJournal(config))) return;\n const file = mergeJournalPath(config);\n await fs.rename(file, `${file}.${randomUUID()}.resolved`);\n await syncDirectory(path.dirname(file));\n}\n\nexport async function requireNoMergeJournal(config: AnySyncConfig, options: { force?: boolean } = {}) {\n const journal = await readMergeJournal(config);\n if (options.force || !journal) return;\n if (journal) {\n throw new Error(\n \"A merged transfer needs recovery. Run /sync sync to reconcile it. If newer local edits or remote revisions prevent recovery, review /sync diff and explicitly choose push --force or pull --force; the selected directional result will archive the old journal, not restore old bytes. Keep its backup; do not downgrade while recovery is pending.\",\n );\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["randomUUID", "fs", "path", "readConflictArtifact", "fs", "path", "randomUUID"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/state/merge-baseline-store.ts", "../../src/sync/session-merge.ts", "../../src/sync/text-merge.ts"],
4
- "sourcesContent": ["import { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { backendIdentityCoordinates } from \"../backends/backend-identity.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport type { Snapshot, SnapshotFile } from \"../snapshot/snapshot-types.js\";\nimport { collidingPaths, planFileMerge } from \"../sync/file-merge-planner.js\";\nimport { parseSettingsDocument } from \"../sync/json-document.js\";\nimport { validateSession } from \"../sync/session-merge.js\";\nimport { isMergeTextPath, text } from \"../sync/text-merge.js\";\nimport { syncDirectory, writeJson } from \"./json-file.js\";\nimport type { SyncState } from \"./state-types.js\";\nimport { statePathForConfig, syncStateFingerprint } from \"./sync-state-store.js\";\n\ninterface MergeBaseline {\n version: 1;\n identity: string;\n acceptedState: string;\n files: SnapshotFile[];\n}\nconst MAX_BYTES = 192 * 1024 * 1024;\nfunction eligible(file: SnapshotFile) {\n try {\n const bytes = Buffer.from(file.contentBase64, \"base64\");\n if (file.path === \"settings.json\") parseSettingsDocument(bytes);\n else if (isMergeTextPath(file.path)) text(bytes);\n else if (file.path.startsWith(\"sessions/\") && file.path.endsWith(\".jsonl\")) validateSession(bytes);\n else return false;\n return true;\n } catch {\n return false;\n }\n}\n\nfunction identity(config: AnySyncConfig) {\n return JSON.stringify([\n config.setupName,\n backendIdentityCoordinates(config),\n config.include,\n config.localFields ?? [],\n ]);\n}\nfunction directory(config: AnySyncConfig) {\n return `${statePathForConfig(config)}.ancestors`;\n}\nasync function checkDirectory(config: AnySyncConfig, create = false) {\n const target = directory(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 ancestor directory; preserve evidence.\");\n}\nfunction baselinePath(config: AnySyncConfig, state: SyncState) {\n return path.join(directory(config), `${syncStateFingerprint(state)}.json`);\n}\n\n/** Stage before accepted state, retain its previous ancestor until journal retirement. Never bootstrap from current edits. */\nexport async function stageMergeBaseline(\n config: AnySyncConfig,\n snapshot: Snapshot,\n accepted: SyncState,\n previous?: SyncState,\n) {\n const collisions = collidingPaths(snapshot.files.map((file) => file.path));\n const files = snapshot.files.filter((file) => {\n return (\n !collisions.has(file.path) &&\n accepted.lastFileHashes[file.path] === file.sha256 &&\n (file.path === \"settings.json\" || config.mergeContent) &&\n eligible(file)\n );\n });\n if (previous) {\n const stagedPaths = new Set(files.map((file) => file.path));\n let ancestors: SnapshotFile[] = [];\n try {\n ancestors = (await readMergeAncestors(config, previous)) ?? [];\n } catch {\n /* Keep unknown evidence; missing bytes are never invented. */\n }\n for (const ancestor of ancestors)\n if (\n !collisions.has(ancestor.path) &&\n accepted.lastFileHashes[ancestor.path] === ancestor.sha256 &&\n !stagedPaths.has(ancestor.path)\n ) {\n files.push(ancestor);\n stagedPaths.add(ancestor.path);\n }\n }\n const plan = planFileMerge({ baseline: {}, local: files, remote: [], selectionCompatible: true });\n if (plan.kind !== \"planned\" || plan.conflicts.length)\n throw new Error(\"Cannot stage unverified merge ancestor bytes.\");\n const record: MergeBaseline = {\n version: 1,\n identity: identity(config),\n acceptedState: syncStateFingerprint(accepted),\n files,\n };\n await checkDirectory(config, true);\n await writeJson(baselinePath(config, accepted), record, { maxBytes: MAX_BYTES });\n}\n\nasync function readBaselineRecord(\n config: AnySyncConfig,\n target: string,\n acceptedState: string,\n validate: () => void = () => {},\n): Promise<MergeBaseline> {\n const stat = await fs.lstat(target);\n validate();\n if (!stat.isFile() || stat.size > MAX_BYTES || (process.platform !== \"win32\" && (stat.mode & 0o077) !== 0))\n throw new Error(\"Unsafe cache.\");\n const record = JSON.parse(await fs.readFile(target, \"utf8\")) as MergeBaseline;\n validate();\n if (\n record.version !== 1 ||\n record.identity !== identity(config) ||\n record.acceptedState !== acceptedState ||\n !Array.isArray(record.files) ||\n record.files.length > 16_384 ||\n record.files.some((file) => !eligible(file))\n )\n throw new Error(\"Invalid cache identity.\");\n const plan = planFileMerge({ baseline: {}, local: record.files, remote: [], selectionCompatible: true });\n if (plan.kind !== \"planned\" || plan.conflicts.length) throw new Error(\"Invalid cache bytes.\");\n return record;\n}\n\nexport async function readMergeAncestors(config: AnySyncConfig, state: SyncState): Promise<SnapshotFile[] | undefined> {\n let record: MergeBaseline;\n try {\n await checkDirectory(config);\n record = await readBaselineRecord(config, baselinePath(config, state), syncStateFingerprint(state));\n for (const file of record.files) {\n if (\n state.lastFileHashes[file.path] !== file.sha256 ||\n createHash(\"sha256\").update(Buffer.from(file.contentBase64, \"base64\")).digest(\"hex\") !== file.sha256\n )\n throw new Error(\"Cache hash mismatch.\");\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw new Error(\"Merge ancestor is invalid; preserve its private cache and review the conflict.\");\n }\n return record.files;\n}\nexport async function readMergeAncestor(config: AnySyncConfig, state: SyncState, filePath: string) {\n const file = (await readMergeAncestors(config, state))?.find((file) => file.path === filePath);\n return file ? Buffer.from(file.contentBase64, \"base64\") : undefined;\n}\n\n/** Caller must hold the sync operation lock and retire the commit journal before pruning. */\nexport async function pruneMergeBaselines(config: AnySyncConfig, state: SyncState, validate: () => void) {\n validate();\n const keep = path.basename(baselinePath(config, state));\n let names: string[];\n try {\n await checkDirectory(config);\n validate();\n names = await fs.readdir(directory(config));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw error;\n }\n for (const name of names) {\n validate();\n if (name === keep || !/^[a-f0-9]{64}\\.json$/u.test(name)) continue;\n const target = path.join(directory(config), name);\n try {\n await readBaselineRecord(config, target, name.slice(0, -5), validate);\n } catch {\n validate();\n continue; // Unknown/corrupted evidence is not an owned pruning candidate.\n }\n validate();\n await fs.rm(target);\n validate();\n }\n await syncDirectory(directory(config));\n validate();\n}\n", "import { CURRENT_SESSION_VERSION } from \"@earendil-works/pi-coding-agent\";\nimport { parseJsonObjectDocument } from \"./json-document.js\";\nimport { text } from \"./text-merge.js\";\n\n// Public header IDs use Pi assertValidSessionId; generated entry IDs have a separate grammar.\nconst sessionId = (value: unknown): value is string =>\n typeof value === \"string\" && /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/u.test(value);\nconst id = (value: unknown): value is string => typeof value === \"string\" && /^[a-zA-Z0-9_-]{1,128}$/u.test(value);\nconst content = (value: unknown, types = [\"text\", \"image\", \"thinking\", \"toolCall\"]): boolean =>\n typeof value === \"string\" ||\n (Array.isArray(value) &&\n value.every(\n (block) =>\n object(block) &&\n types.includes(String(block.type)) &&\n ((block.type === \"text\" && typeof block.text === \"string\") ||\n (block.type === \"image\" && typeof block.data === \"string\" && typeof block.mimeType === \"string\") ||\n (block.type === \"thinking\" && typeof block.thinking === \"string\") ||\n (block.type === \"toolCall\" &&\n typeof block.id === \"string\" &&\n typeof block.name === \"string\" &&\n object(block.arguments))),\n ));\nconst message = (value: unknown) => {\n if (!object(value) || typeof value.timestamp !== \"number\" || !Number.isFinite(value.timestamp)) return false;\n switch (value.role) {\n case \"system\":\n return (\n content(value.content, [\"text\"]) &&\n (value.sections === undefined ||\n (object(value.sections) &&\n Object.values(value.sections).every((section) => section === null || typeof section === \"string\"))) &&\n (value.toolsAdded === undefined ||\n (Array.isArray(value.toolsAdded) &&\n value.toolsAdded.every(\n (tool) =>\n object(tool) &&\n typeof tool.name === \"string\" &&\n typeof tool.description === \"string\" &&\n object(tool.parameters),\n ))) &&\n (value.toolsRemoved === undefined ||\n (Array.isArray(value.toolsRemoved) &&\n value.toolsRemoved.every((tool) => object(tool) && typeof tool.name === \"string\")))\n );\n case \"user\":\n return content(value.content, [\"text\", \"image\"]);\n case \"assistant\":\n return (\n Array.isArray(value.content) &&\n content(value.content, [\"text\", \"thinking\", \"toolCall\"]) &&\n typeof value.api === \"string\" &&\n typeof value.provider === \"string\" &&\n typeof value.model === \"string\" &&\n object(value.usage) &&\n [\"pending\", \"stop\", \"length\", \"toolUse\", \"error\", \"aborted\", \"deferred\"].includes(String(value.stopReason))\n );\n case \"toolResult\":\n return (\n typeof value.toolCallId === \"string\" &&\n typeof value.toolName === \"string\" &&\n Array.isArray(value.content) &&\n content(value.content, [\"text\", \"image\"]) &&\n typeof value.isError === \"boolean\"\n );\n case \"custom\":\n return (\n typeof value.customType === \"string\" &&\n content(value.content, [\"text\", \"image\"]) &&\n typeof value.display === \"boolean\"\n );\n case \"bashExecution\":\n return (\n typeof value.command === \"string\" &&\n typeof value.output === \"string\" &&\n typeof value.cancelled === \"boolean\" &&\n typeof value.truncated === \"boolean\"\n );\n case \"branchSummary\":\n return typeof value.summary === \"string\" && (value.fromId === null || typeof value.fromId === \"string\");\n case \"compactionSummary\":\n return (\n typeof value.summary === \"string\" &&\n typeof value.tokensBefore === \"number\" &&\n Number.isFinite(value.tokensBefore) &&\n value.tokensBefore >= 0\n );\n default:\n return false;\n }\n};\nconst object = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n/** Stricter than Pi's repair-tolerant reader: never skip malformed lines or migrate a log. */\nexport function validateSession(bytes: Buffer) {\n if (CURRENT_SESSION_VERSION !== 3) throw new Error(\"Unaudited Pi session format; require review.\");\n const raw = text(bytes);\n if (!raw.endsWith(\"\\n\")) throw new Error(\"Incomplete session tail.\");\n const rows: unknown[] = raw\n .slice(0, -1)\n .split(\"\\n\")\n .map((line) => {\n if (line.startsWith(\"\\uFEFF\")) throw new Error(\"Unexpected session-record BOM; require review.\");\n return parseJsonObjectDocument(Buffer.from(line)).root.value;\n });\n if (rows.length > 16_384) throw new Error(\"Session entry bound exceeded.\");\n const header = rows[0];\n if (\n !object(header) ||\n header.type !== \"session\" ||\n header.version !== CURRENT_SESSION_VERSION ||\n !sessionId(header.id) ||\n typeof header.cwd !== \"string\" ||\n typeof header.timestamp !== \"string\" ||\n !Number.isFinite(Date.parse(header.timestamp)) ||\n (header.parentSession !== undefined && typeof header.parentSession !== \"string\")\n )\n throw new Error(\"Unsupported session header.\");\n const ids = new Set<string>();\n const known = (value: unknown) => id(value) && ids.has(value);\n for (const row of rows.slice(1)) {\n if (\n !object(row) ||\n !id(row.id) ||\n ids.has(row.id) ||\n typeof row.timestamp !== \"string\" ||\n !Number.isFinite(Date.parse(row.timestamp)) ||\n (row.parentId !== null && !known(row.parentId))\n )\n throw new Error(\"Invalid session graph.\");\n const strings = (...keys: string[]) => keys.every((key) => typeof row[key] === \"string\");\n let valid = false;\n switch (row.type) {\n case \"message\":\n valid = message(row.message);\n break;\n case \"thinking_level_change\":\n valid = strings(\"thinkingLevel\");\n break;\n case \"model_change\":\n valid = strings(\"provider\", \"modelId\");\n break;\n case \"usage\":\n valid = strings(\"kind\", \"provider\", \"model\") && object(row.usage);\n break;\n case \"compaction\":\n valid =\n strings(\"summary\") &&\n (known(row.firstKeptEntryId) || row.firstKeptEntryId === row.id) &&\n (row.systemMessage === undefined ||\n (object(row.systemMessage) && row.systemMessage.role === \"system\" && message(row.systemMessage))) &&\n typeof row.tokensBefore === \"number\" &&\n Number.isFinite(row.tokensBefore) &&\n row.tokensBefore >= 0;\n break;\n case \"branch_summary\":\n valid = strings(\"summary\") && (known(row.fromId) || row.fromId === \"root\");\n break;\n case \"custom\":\n valid = strings(\"customType\");\n break;\n case \"custom_message\":\n valid = strings(\"customType\") && typeof row.display === \"boolean\" && content(row.content, [\"text\", \"image\"]);\n break;\n case \"context_edit\":\n valid =\n known(row.targetId) &&\n (row.replacement === null || (object(row.replacement) && content(row.replacement.content)));\n break;\n case \"label\":\n valid = known(row.targetId) && (row.label === undefined || typeof row.label === \"string\");\n break;\n case \"session_info\":\n valid = row.name === undefined || typeof row.name === \"string\";\n break;\n }\n if (!valid) throw new Error(\"Unsupported session entry.\");\n ids.add(row.id);\n }\n return header.id;\n}\nexport function mergeSession(base: Buffer, local: Buffer, remote: Buffer): Buffer | undefined {\n try {\n const identity = validateSession(base);\n if (validateSession(local) !== identity || validateSession(remote) !== identity) return;\n const prefix = (left: Buffer, right: Buffer) =>\n right.length >= left.length && right.subarray(0, left.length).equals(left);\n if (!prefix(base, local) || !prefix(base, remote)) return;\n if (prefix(local, remote)) return remote;\n if (prefix(remote, local)) return local;\n } catch {\n return;\n }\n}\n", "import path from \"node:path\";\nexport const TEXT_LIMIT = 1024 * 1024;\nexport const TEXT_MERGE_WORK_LIMIT = 8_000_000;\nexport interface TextMergeBudget {\n remaining: number;\n}\nexport interface TextMergeOptions {\n budget?: TextMergeBudget;\n signal?: AbortSignal;\n}\nfunction charge(budget: TextMergeBudget, work: number) {\n if (work > budget.remaining) throw new Error(\"Text merge operation complexity bound exceeded.\");\n budget.remaining -= work;\n}\nexport function isMergeTextPath(filePath: string) {\n return (\n filePath === \"AGENTS.md\" ||\n (/\\.(?:md|txt)$/u.test(filePath) &&\n [\"prompts\", \"skills\"].includes(filePath.split(\"/\")[0] ?? \"\") &&\n path.posix.basename(filePath) !== \"keybindings.json\")\n );\n}\nexport function text(bytes: Buffer) {\n if (bytes.length > TEXT_LIMIT) throw new Error(\"Text merge bound exceeded.\");\n const decoded = bytes.toString(\"utf8\");\n if (\n !Buffer.from(decoded).equals(bytes) ||\n bytes.some((byte) => (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) || byte === 127)\n )\n throw new Error(\"Unsupported text encoding.\");\n return decoded;\n}\ntype Hunk = { start: number; end: number; lines: string[] };\nfunction lines(value: string) {\n return value.match(/[^\\n]*\\n|[^\\n]+$/gu) ?? [];\n}\nfunction edits(base: string[], next: string[], signal?: AbortSignal): Hunk[] {\n const width = next.length + 1;\n if ((base.length + 1) * width > 4_000_000) throw new Error(\"Text merge complexity bound exceeded.\");\n // Intern once so each charged DP cell compares fixed-size IDs, not long line strings.\n const ids = new Map<string, number>();\n const intern = (line: string) => {\n let id = ids.get(line);\n if (id === undefined) {\n id = ids.size;\n ids.set(line, id);\n }\n return id;\n };\n const baseIds = base.map(intern);\n const nextIds = next.map(intern);\n const grid = new Uint32Array((base.length + 1) * width);\n for (let i = base.length - 1; i >= 0; i--) {\n signal?.throwIfAborted();\n for (let j = next.length - 1; j >= 0; j--)\n grid[i * width + j] =\n baseIds[i] === nextIds[j]\n ? 1 + (grid[(i + 1) * width + j + 1] ?? 0)\n : Math.max(grid[(i + 1) * width + j] ?? 0, grid[i * width + j + 1] ?? 0);\n }\n const result: Hunk[] = [];\n let i = 0;\n let j = 0;\n let active: Hunk | undefined;\n while (i < base.length || j < next.length) {\n if (i < base.length && j < next.length && baseIds[i] === nextIds[j]) {\n if (active) {\n result.push(active);\n active = undefined;\n }\n i++;\n j++;\n continue;\n }\n active ??= { start: i, end: i, lines: [] };\n if (j < next.length && (i === base.length || (grid[i * width + j + 1] ?? 0) >= (grid[(i + 1) * width + j] ?? 0)))\n active.lines.push(next[j++] ?? \"\");\n else active.end = ++i;\n }\n if (active) result.push(active);\n return result;\n}\nexport function mergeText(\n baseBytes: Buffer,\n localBytes: Buffer,\n remoteBytes: Buffer,\n options: TextMergeOptions = {},\n): Buffer | undefined {\n const budget = options.budget ?? { remaining: TEXT_MERGE_WORK_LIMIT };\n try {\n options.signal?.throwIfAborted();\n const base = lines(text(baseBytes));\n const ours = lines(text(localBytes));\n const theirs = lines(text(remoteBytes));\n const cells = [ours, theirs].map((next) => (base.length + 1) * (next.length + 1));\n if (cells.some((count) => count > 4_000_000)) return;\n charge(budget, (cells[0] ?? 0) + (cells[1] ?? 0));\n const local = edits(base, ours, options.signal);\n const remote = edits(base, theirs, options.signal);\n const combined = [...local];\n for (const right of remote) {\n let same = false;\n for (const left of local) {\n options.signal?.throwIfAborted();\n charge(budget, 1);\n if (left.start === right.start && left.end === right.end && left.lines.join(\"\") === right.lines.join(\"\")) {\n same = true;\n continue;\n }\n const overlap =\n left.start === left.end || right.start === right.end\n ? left.start <= right.end && right.start <= left.end\n : left.start < right.end && right.start < left.end;\n if (overlap) return;\n }\n if (!same) combined.push(right);\n }\n combined.sort((left, right) => left.start - right.start || left.end - right.end);\n let cursor = 0;\n const output: string[] = [];\n for (const edit of combined) {\n output.push(...base.slice(cursor, edit.start), ...edit.lines);\n cursor = edit.end;\n }\n output.push(...base.slice(cursor));\n const result = Buffer.from(output.join(\"\"));\n text(result);\n return result;\n } catch {\n options.signal?.throwIfAborted();\n return;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["path", "id", "i", "j", "identity", "path", "file"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/setup-switch.ts"],
4
- "sourcesContent": ["import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { setSyncSetupCompletions } from \"../commands/command.js\";\nimport { loadConfig, syncConfigReviewIdentity, syncSetupReviewIdentity } from \"../settings/config.js\";\nimport { configuredSyncSetupNames, updateLocalConfig } from \"../settings/settings-store.js\";\nimport type { OnSwitchAction } from \"../settings/settings-types.js\";\nimport { normalizeOnSwitch } from \"../settings/settings-validation.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { SetupPullRequiresUiError } from \"./sync-errors.js\";\n\nexport { SetupPullRequiresUiError } from \"./sync-errors.js\";\n\nexport const SETUP_SWITCH_ACTION_OPTIONS: ReadonlyArray<{\n label: string;\n value: OnSwitchAction;\n}> = [\n { label: \"Ask before pull\", value: \"ask-before-pull\" },\n { label: \"Start pull\", value: \"pull-after-switch\" },\n { label: \"Switch only\", value: \"switch-only\" },\n];\n\nexport function setupSwitchActionLabel(action: OnSwitchAction) {\n return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.value === action)?.label ?? action;\n}\n\nexport function setupSwitchActionFromLabel(label: string): OnSwitchAction | undefined {\n return SETUP_SWITCH_ACTION_OPTIONS.find((option) => option.label === label)?.value;\n}\n\nexport async function saveOnSwitch(action: OnSwitchAction, signal?: AbortSignal) {\n await updateLocalConfig((settings) => ({ ...settings, onSwitch: action }), signal);\n}\n\nexport type SetupPullOutcome = \"applied\" | \"cancelled\";\n\nexport interface SetupSwitchResult {\n pullApplied: boolean;\n}\n\nexport async function useSyncSetup(\n ctx: ExtensionCommandContext,\n name: string,\n pullCurrentSetup?: (setup: string) => Promise<SetupPullOutcome | undefined>,\n expectedAction?: OnSwitchAction,\n signal?: AbortSignal,\n expectedSetupIdentity?: string,\n): Promise<SetupSwitchResult> {\n const normalized = name.trim();\n if (!normalized) throw new Error(\"Usage: /sync use <setup>\");\n const loadedConfig = await loadConfig(normalized);\n const reviewedSetupIdentity = expectedSetupIdentity ?? syncConfigReviewIdentity(loadedConfig);\n throwIfAborted(signal);\n const switchResult: { action: OnSwitchAction; switched: boolean } = {\n action: \"ask-before-pull\",\n switched: false,\n };\n await updateLocalConfig((current) => {\n throwIfAborted(signal);\n const setup = current.syncSetups[normalized];\n if (!setup) {\n throw new Error(`Sync setup \u201C${safeTerminalText(normalized)}\u201D no longer exists.`);\n }\n const connectionName = setup.storage.connection;\n const connection = current.storageConnections[connectionName];\n if (\n !connection ||\n syncSetupReviewIdentity(normalized, setup, connectionName, connection) !== reviewedSetupIdentity\n ) {\n throw new Error(\n `Sync setup \u201C${safeTerminalText(normalized)}\u201D changed while the switch preview was open; reopen it and review the current destination.`,\n );\n }\n switchResult.action = normalizeOnSwitch(current.onSwitch);\n if (expectedAction !== undefined && switchResult.action !== expectedAction) {\n throw new Error(\"Setup-switch behavior changed while the preview was open; reopen it and retry.\");\n }\n if (switchResult.action === \"pull-after-switch\" && !ctx.hasUI) {\n throw new SetupPullRequiresUiError(\n `Automatic setup pulls require interactive confirmation; sync setup \u201C${safeTerminalText(normalized)}\u201D was not switched. Use TUI or RPC mode.`,\n );\n }\n if (current.activeSyncSetup === normalized) return current;\n switchResult.switched = true;\n return { ...current, activeSyncSetup: normalized };\n }, signal);\n throwIfAborted(signal);\n if (!switchResult.switched) {\n ctx.ui.notify(`Sync setup \u201C${safeTerminalText(normalized)}\u201D is already current.`, \"info\");\n return { pullApplied: false };\n }\n setSyncSetupCompletions(await configuredSyncSetupNames());\n throwIfAborted(signal);\n\n if (switchResult.action === \"switch-only\") {\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D. No files were pulled.`, \"info\");\n return { pullApplied: false };\n }\n if (switchResult.action === \"ask-before-pull\") {\n if (ctx.mode !== \"tui\") {\n ctx.ui.notify(\n `Switched to \u201C${safeTerminalText(normalized)}\u201D. No files were pulled because confirmation requires TUI mode; run /sync pull to apply this setup.`,\n \"info\",\n );\n return { pullApplied: false };\n }\n const confirmed = await ctx.ui.confirm(\n `Review a pull for sync setup \u201C${safeTerminalText(normalized)}\u201D now?`,\n \"pi-sync will check the remote snapshot and show the exact local writes and deletions before applying anything.\",\n { signal },\n );\n throwIfAborted(signal);\n if (!confirmed) {\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D; files were not pulled.`, \"info\");\n return { pullApplied: false };\n }\n }\n\n ctx.ui.notify(`Switched to \u201C${safeTerminalText(normalized)}\u201D. Checking remote files for a reviewed pull\u2026`, \"info\");\n if (!pullCurrentSetup) {\n throw new Error(`Switched to \u201C${safeTerminalText(normalized)}\u201D, but pull is unavailable.`);\n }\n const pullOutcome = await pullCurrentSetup(normalized);\n throwIfAborted(signal);\n if (pullOutcome === \"cancelled\") {\n ctx.ui.notify(\n `Pull cancelled; sync setup \u201C${safeTerminalText(normalized)}\u201D remains current and synced files were not changed.`,\n \"info\",\n );\n }\n return { pullApplied: pullOutcome === \"applied\" };\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n if (!signal?.aborted) return;\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/remote-snapshot.ts"],
4
- "sourcesContent": ["import type { RemoteHead, SyncBackend } from \"../backends/sync-backend.js\";\nimport { syncConfigReviewFingerprint } from \"../settings/config.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { filterSnapshotForConfigPolicy } from \"../snapshot/snapshot.js\";\nimport type { Snapshot } from \"../snapshot/snapshot-types.js\";\nimport { safeTerminalText } from \"../ui/terminal-text.js\";\nimport { sameLocalFields, validatePortableSnapshot } from \"./local-fields.js\";\nimport {\n inspectRemoteSelection,\n type RemoteSelectionState,\n remoteSelectionMismatch,\n sameSyncInclude,\n snapshotSelectionInclude,\n} from \"./sync-policy.js\";\n\nexport async function readSnapshotForHead(backend: SyncBackend, head: RemoteHead, signal?: AbortSignal) {\n const snapshot = await backend.readSnapshot(head.snapshotRef, signal);\n if (signal?.aborted) {\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n }\n validatePortableSnapshot(snapshot);\n if (snapshot.id !== head.snapshotId) {\n throw new Error(`Remote head ${head.snapshotId} resolved to unexpected snapshot ${snapshot.id}.`);\n }\n if (head.selection) {\n const snapshotInclude = snapshotSelectionInclude(snapshot);\n if (!snapshotInclude || !sameSyncInclude(head.selection.include, snapshotInclude)) {\n throw new Error(\"Remote head selection does not match its immutable snapshot.\");\n }\n }\n return snapshot;\n}\n\nexport function requireCompatibleRemoteSelection(config: AnySyncConfig, snapshot: Snapshot) {\n if (!sameLocalFields(config.localFields, snapshot.localFields))\n throw new Error(\n \"Remote local-field policy differs; confirm a reviewed directional migration. Old history is not erased.\",\n );\n const state = inspectRemoteSelection(config.include, snapshot);\n if (state.kind === \"different\") {\n throw remoteSelectionMismatch(config, state.include, syncConfigReviewFingerprint(config));\n }\n}\n\nexport function formatRemoteSelectionStatus(state: RemoteSelectionState | undefined) {\n if (!state) return \"remote included content: unavailable (remote is empty)\";\n if (state.kind === \"same\") return \"remote included content: matches this setup\";\n if (state.kind === \"legacy\") {\n return `remote included content: unavailable in legacy snapshot (${state.discovered.length} path${state.discovered.length === 1 ? \"\" : \"s\"} discovered, partial)`;\n }\n return [\n \"remote included content: differs from this setup\",\n `remote-only selection: ${safeList(state.remoteOnly)}`,\n `local-only selection: ${safeList(state.localOnly)}`,\n ].join(\"\\n\");\n}\n\nfunction safeList(values: readonly string[]) {\n return values.length > 0 ? values.map(safeTerminalText).join(\", \") : \"none\";\n}\n\nexport async function readRemoteSnapshot(\n backend: SyncBackend,\n config: AnySyncConfig,\n signal?: AbortSignal,\n options: { allowSelectionDifference?: boolean } = {},\n) {\n const head = await backend.readHead(signal);\n if (!head) return { head: undefined, snapshot: undefined, selectionState: undefined };\n const snapshot = await readSnapshotForHead(backend, head, signal);\n const selectionState = inspectRemoteSelection(config.include, snapshot);\n if (!options.allowSelectionDifference && selectionState.kind === \"different\") {\n const configIdentity = syncConfigReviewFingerprint(config);\n throw remoteSelectionMismatch(config, selectionState.include, configIdentity);\n }\n return {\n head,\n snapshot: filterSnapshotForConfigPolicy(snapshot, config),\n selectionState,\n };\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;AAeA,eAAsB,oBAAoB,SAAsB,MAAkB,QAAsB;AACtG,QAAM,WAAW,MAAM,QAAQ,aAAa,KAAK,aAAa,MAAM;AACpE,MAAI,QAAQ,SAAS;AACnB,UAAM,OAAO,kBAAkB,QAAQ,OAAO,SAAS,IAAI,aAAa,6BAA6B,YAAY;AAAA,EACnH;AACA,2BAAyB,QAAQ;AACjC,MAAI,SAAS,OAAO,KAAK,YAAY;AACnC,UAAM,IAAI,MAAM,eAAe,KAAK,UAAU,oCAAoC,SAAS,EAAE,GAAG;AAAA,EAClG;AACA,MAAI,KAAK,WAAW;AAClB,UAAM,kBAAkB,yBAAyB,QAAQ;AACzD,QAAI,CAAC,mBAAmB,CAAC,gBAAgB,KAAK,UAAU,SAAS,eAAe,GAAG;AACjF,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAChF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iCAAiC,QAAuB,UAAoB;AAC1F,MAAI,CAAC,gBAAgB,OAAO,aAAa,SAAS,WAAW;AAC3D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AACF,QAAM,QAAQ,uBAAuB,OAAO,SAAS,QAAQ;AAC7D,MAAI,MAAM,SAAS,aAAa;AAC9B,UAAM,wBAAwB,QAAQ,MAAM,SAAS,4BAA4B,MAAM,CAAC;AAAA,EAC1F;AACF;AAEO,SAAS,4BAA4B,OAAyC;AACnF,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,SAAS,OAAQ,QAAO;AAClC,MAAI,MAAM,SAAS,UAAU;AAC3B,WAAO,4DAA4D,MAAM,WAAW,MAAM,QAAQ,MAAM,WAAW,WAAW,IAAI,KAAK,GAAG;AAAA,EAC5I;AACA,SAAO;AAAA,IACL;AAAA,IACA,0BAA0B,SAAS,MAAM,UAAU,CAAC;AAAA,IACpD,yBAAyB,SAAS,MAAM,SAAS,CAAC;AAAA,EACpD,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,SAAS,QAA2B;AAC3C,SAAO,OAAO,SAAS,IAAI,OAAO,IAAI,gBAAgB,EAAE,KAAK,IAAI,IAAI;AACvE;AAEA,eAAsB,mBACpB,SACA,QACA,QACA,UAAkD,CAAC,GACnD;AACA,QAAM,OAAO,MAAM,QAAQ,SAAS,MAAM;AAC1C,MAAI,CAAC,KAAM,QAAO,EAAE,MAAM,QAAW,UAAU,QAAW,gBAAgB,OAAU;AACpF,QAAM,WAAW,MAAM,oBAAoB,SAAS,MAAM,MAAM;AAChE,QAAM,iBAAiB,uBAAuB,OAAO,SAAS,QAAQ;AACtE,MAAI,CAAC,QAAQ,4BAA4B,eAAe,SAAS,aAAa;AAC5E,UAAM,iBAAiB,4BAA4B,MAAM;AACzD,UAAM,wBAAwB,QAAQ,eAAe,SAAS,cAAc;AAAA,EAC9E;AACA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,8BAA8B,UAAU,MAAM;AAAA,IACxD;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/sync/sync-inspection.ts"],
4
- "sourcesContent": ["import { createSyncBackend, type SyncBackendFactory } from \"../backends/backend-factory.js\";\nimport type { AnySyncConfig } from \"../settings/settings-types.js\";\nimport { createSnapshot } from \"../snapshot/snapshot.js\";\nimport type { SnapshotOptions } from \"../snapshot/snapshot-types.js\";\nimport { readStateForConfig, syncStateFingerprint } from \"../state/sync-state-store.js\";\nimport { throwIfAborted } from \"./signals.js\";\nimport { inspectRemoteSelection } from \"./sync-policy.js\";\nimport { hasLocalChanges, remoteChangedSinceState } from \"./sync-state.js\";\n\n/** Advisory baseline-relative observation, never an authorization to transfer files. */\nexport async function inspectSync(\n config: AnySyncConfig,\n options: SnapshotOptions,\n signal?: AbortSignal,\n factory: SyncBackendFactory = createSyncBackend,\n) {\n throwIfAborted(signal);\n const backend = await factory(config);\n throwIfAborted(signal);\n const local = await createSnapshot(config.snapshotIdentity, { ...options, signal });\n throwIfAborted(signal);\n const state = await readStateForConfig(config);\n throwIfAborted(signal);\n const head = await backend.readHead(signal);\n throwIfAborted(signal);\n return {\n head,\n selectionState: head ? inspectRemoteSelection(config.include, { selection: head.selection, files: [] }) : undefined,\n localFiles: local.files.length,\n localChanged: hasLocalChanges(local, state, config),\n remoteChanged: remoteChangedSinceState(head, state, config, (left, right) => backend.sameRevision(left, right)),\n firstSync: !state.lastAppliedSnapshot,\n emptyInclude: config.include.length === 0,\n stateIdentity: syncStateFingerprint(state),\n destination: backend.destination,\n capability: backend.capability,\n };\n}\n\nexport type SyncInspection = Awaited<ReturnType<typeof inspectSync>>;\n\nexport interface StartupObservation {\n setupName: string;\n configIdentity: string;\n checkedAt: string;\n automaticTransfer?: boolean;\n inspection: SyncInspection;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;AAUA,eAAsB,YACpB,QACA,SACA,QACA,UAA8B,mBAC9B;AACA,iBAAe,MAAM;AACrB,QAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,iBAAe,MAAM;AACrB,QAAM,QAAQ,MAAM,eAAe,OAAO,kBAAkB,EAAE,GAAG,SAAS,OAAO,CAAC;AAClF,iBAAe,MAAM;AACrB,QAAM,QAAQ,MAAM,mBAAmB,MAAM;AAC7C,iBAAe,MAAM;AACrB,QAAM,OAAO,MAAM,QAAQ,SAAS,MAAM;AAC1C,iBAAe,MAAM;AACrB,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,OAAO,uBAAuB,OAAO,SAAS,EAAE,WAAW,KAAK,WAAW,OAAO,CAAC,EAAE,CAAC,IAAI;AAAA,IAC1G,YAAY,MAAM,MAAM;AAAA,IACxB,cAAc,gBAAgB,OAAO,OAAO,MAAM;AAAA,IAClD,eAAe,wBAAwB,MAAM,OAAO,QAAQ,CAAC,MAAM,UAAU,QAAQ,aAAa,MAAM,KAAK,CAAC;AAAA,IAC9G,WAAW,CAAC,MAAM;AAAA,IAClB,cAAc,OAAO,QAAQ,WAAW;AAAA,IACxC,eAAe,qBAAqB,KAAK;AAAA,IACzC,aAAa,QAAQ;AAAA,IACrB,YAAY,QAAQ;AAAA,EACtB;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/snapshot/snapshot-codec.ts"],
4
- "sourcesContent": ["import { Readable, Writable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { promisify } from \"node:util\";\nimport { createGunzip, gzip } from \"node:zlib\";\nimport { snapshotSelectionInclude } from \"../sync/sync-policy.js\";\nimport type { Snapshot } from \"./snapshot-types.js\";\n\nconst VERSION = 1;\nconst MAX_DECOMPRESSED_SNAPSHOT_BYTES = 512 * 1024 * 1024;\nconst gzipAsync = promisify(gzip);\n\nexport async function encodeSnapshot(snapshot: Snapshot) {\n snapshotSelectionInclude(snapshot);\n return gzipAsync(Buffer.from(JSON.stringify(snapshot), \"utf8\"));\n}\n\nexport async function decodeSnapshot(\n buffer: Buffer,\n options: { signal?: AbortSignal; maxOutputLength?: number } = {},\n): Promise<Snapshot> {\n throwIfAborted(options.signal);\n const limit = options.maxOutputLength ?? MAX_DECOMPRESSED_SNAPSHOT_BYTES;\n const chunks: Buffer[] = [];\n let total = 0;\n const sink = new Writable({\n write(chunk: Buffer, _encoding, callback) {\n total += chunk.byteLength;\n if (total > limit) {\n callback(new Error(`Decompressed snapshot exceeds the ${limit}-byte limit.`));\n return;\n }\n chunks.push(Buffer.from(chunk));\n callback();\n },\n });\n await pipeline(Readable.from([buffer]), createGunzip(), sink, { signal: options.signal });\n throwIfAborted(options.signal);\n const parsed = JSON.parse(Buffer.concat(chunks, total).toString(\"utf8\")) as Snapshot;\n if ((parsed.version !== VERSION && parsed.version !== 2 && parsed.version !== 3) || !Array.isArray(parsed.files)) {\n throw new Error(\"Unsupported snapshot format.\");\n }\n if (parsed.version === 2 || parsed.version === 3) {\n const { normalizeLocalFields, validatePortableSnapshot } = await import(\"../sync/local-fields.js\");\n validatePortableSnapshot(parsed);\n if (parsed.localFields !== undefined) parsed.localFields = normalizeLocalFields(parsed.localFields);\n } else if (parsed.localFields !== undefined) throw new Error(\"Portable field policy requires snapshot version 2.\");\n snapshotSelectionInclude(parsed);\n return parsed;\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n if (!signal?.aborted) return;\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n"],
5
- "mappings": ";;;;;;;AAAA,SAAS,UAAU,gBAAgB;AACnC,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,cAAc,YAAY;AAInC,IAAM,UAAU;AAChB,IAAM,kCAAkC,MAAM,OAAO;AACrD,IAAM,YAAY,UAAU,IAAI;AAEhC,eAAsB,eAAe,UAAoB;AACvD,2BAAyB,QAAQ;AACjC,SAAO,UAAU,OAAO,KAAK,KAAK,UAAU,QAAQ,GAAG,MAAM,CAAC;AAChE;AAEA,eAAsB,eACpB,QACA,UAA8D,CAAC,GAC5C;AACnB,iBAAe,QAAQ,MAAM;AAC7B,QAAM,QAAQ,QAAQ,mBAAmB;AACzC,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ;AACZ,QAAM,OAAO,IAAI,SAAS;AAAA,IACxB,MAAM,OAAe,WAAW,UAAU;AACxC,eAAS,MAAM;AACf,UAAI,QAAQ,OAAO;AACjB,iBAAS,IAAI,MAAM,qCAAqC,KAAK,cAAc,CAAC;AAC5E;AAAA,MACF;AACA,aAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAC9B,eAAS;AAAA,IACX;AAAA,EACF,CAAC;AACD,QAAM,SAAS,SAAS,KAAK,CAAC,MAAM,CAAC,GAAG,aAAa,GAAG,MAAM,EAAE,QAAQ,QAAQ,OAAO,CAAC;AACxF,iBAAe,QAAQ,MAAM;AAC7B,QAAM,SAAS,KAAK,MAAM,OAAO,OAAO,QAAQ,KAAK,EAAE,SAAS,MAAM,CAAC;AACvE,MAAK,OAAO,YAAY,WAAW,OAAO,YAAY,KAAK,OAAO,YAAY,KAAM,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AAChH,UAAM,IAAI,MAAM,8BAA8B;AAAA,EAChD;AACA,MAAI,OAAO,YAAY,KAAK,OAAO,YAAY,GAAG;AAChD,UAAM,EAAE,sBAAsB,yBAAyB,IAAI,MAAM,OAAO,4BAAyB;AACjG,6BAAyB,MAAM;AAC/B,QAAI,OAAO,gBAAgB,OAAW,QAAO,cAAc,qBAAqB,OAAO,WAAW;AAAA,EACpG,WAAW,OAAO,gBAAgB,OAAW,OAAM,IAAI,MAAM,oDAAoD;AACjH,2BAAyB,MAAM;AAC/B,SAAO;AACT;AAEA,SAAS,eAAe,QAAsB;AAC5C,MAAI,CAAC,QAAQ,QAAS;AACtB,QAAM,OAAO,kBAAkB,QAAQ,OAAO,SAAS,IAAI,aAAa,6BAA6B,YAAY;AACnH;",
6
- "names": []
7
- }