@narumitw/pi-sync 0.51.0 → 0.52.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 (181) hide show
  1. package/README.md +30 -5
  2. package/dist/chunks/{chunk-7KHKWL3Q.ts → chunk-2CBF6SMT.ts} +8 -7
  3. package/dist/chunks/chunk-2CBF6SMT.ts.map +7 -0
  4. package/dist/chunks/{chunk-YFADF5ZQ.ts → chunk-2DI6PDZV.ts} +5 -1
  5. package/dist/chunks/chunk-2DI6PDZV.ts.map +7 -0
  6. package/dist/chunks/chunk-2GUXI2XT.ts +1702 -0
  7. package/dist/chunks/chunk-2GUXI2XT.ts.map +7 -0
  8. package/dist/chunks/{chunk-4MPIH46N.ts → chunk-3B4Z2SKV.ts} +40 -6
  9. package/dist/chunks/chunk-3B4Z2SKV.ts.map +7 -0
  10. package/dist/chunks/{chunk-YPXGU5OR.ts → chunk-4QVPTIZB.ts} +16 -6
  11. package/dist/chunks/{chunk-YPXGU5OR.ts.map → chunk-4QVPTIZB.ts.map} +2 -2
  12. package/dist/chunks/{chunk-UA7RXOIT.ts → chunk-4SBTAVFL.ts} +6 -6
  13. package/dist/chunks/{chunk-6546TS54.ts → chunk-5RZPINUQ.ts} +7 -4
  14. package/dist/chunks/{chunk-6546TS54.ts.map → chunk-5RZPINUQ.ts.map} +2 -2
  15. package/dist/chunks/chunk-76NB4R7U.ts +107 -0
  16. package/dist/chunks/chunk-76NB4R7U.ts.map +7 -0
  17. package/dist/chunks/{chunk-DM4X4FIA.ts → chunk-7D6RDCVC.ts} +8 -10
  18. package/dist/chunks/{chunk-DM4X4FIA.ts.map → chunk-7D6RDCVC.ts.map} +2 -2
  19. package/dist/chunks/chunk-A6HU4ERM.ts +124 -0
  20. package/dist/chunks/chunk-A6HU4ERM.ts.map +7 -0
  21. package/dist/chunks/chunk-AMJRUXFT.ts +17 -0
  22. package/dist/chunks/chunk-AMJRUXFT.ts.map +7 -0
  23. package/dist/chunks/chunk-ANWNQIR7.ts +1013 -0
  24. package/dist/chunks/chunk-ANWNQIR7.ts.map +7 -0
  25. package/dist/chunks/{chunk-T56RNRZJ.ts → chunk-BDB4IDM3.ts} +19 -10
  26. package/dist/chunks/chunk-BDB4IDM3.ts.map +7 -0
  27. package/dist/chunks/{chunk-2RZSQWBH.ts → chunk-CHSIAC4N.ts} +5 -5
  28. package/dist/chunks/{chunk-2RZSQWBH.ts.map → chunk-CHSIAC4N.ts.map} +2 -2
  29. package/dist/chunks/chunk-HUZ5CR4W.ts +110 -0
  30. package/dist/chunks/chunk-HUZ5CR4W.ts.map +7 -0
  31. package/dist/chunks/{chunk-QLSN7AGX.ts → chunk-IJ6VV6FN.ts} +81 -19
  32. package/dist/chunks/chunk-IJ6VV6FN.ts.map +7 -0
  33. package/dist/chunks/chunk-ORQRHEEO.ts +362 -0
  34. package/dist/chunks/chunk-ORQRHEEO.ts.map +7 -0
  35. package/dist/chunks/chunk-QE7NZZWT.ts +380 -0
  36. package/dist/chunks/chunk-QE7NZZWT.ts.map +7 -0
  37. package/dist/chunks/{chunk-HGJH56KR.ts → chunk-QHSQCK7L.ts} +4 -4
  38. package/dist/chunks/chunk-QOFMXPM5.ts +735 -0
  39. package/dist/chunks/chunk-QOFMXPM5.ts.map +7 -0
  40. package/dist/chunks/{chunk-DZUSTGQV.ts → chunk-UQ3ALBIB.ts} +4 -4
  41. package/dist/chunks/{chunk-Q32CQWPM.ts → chunk-V6VZ5TLG.ts} +13 -6
  42. package/dist/chunks/chunk-V6VZ5TLG.ts.map +7 -0
  43. package/dist/chunks/{chunk-E42GXD6D.ts → chunk-WKPX2SIC.ts} +4 -4
  44. package/dist/chunks/chunk-ZW5ZLGBI.ts +154 -0
  45. package/dist/chunks/chunk-ZW5ZLGBI.ts.map +7 -0
  46. package/dist/chunks/{chunk-6NT3YFGY.ts → chunk-ZX2Q6THB.ts} +8 -3
  47. package/dist/chunks/{chunk-6NT3YFGY.ts.map → chunk-ZX2Q6THB.ts.map} +2 -2
  48. package/dist/chunks/conflict-artifacts-BB2FZMIE.ts +27 -0
  49. package/dist/chunks/conflict-review-T3PB7ZSD.ts +175 -0
  50. package/dist/chunks/conflict-review-T3PB7ZSD.ts.map +7 -0
  51. package/dist/chunks/{file-selection-RAVHRYFG.ts → file-selection-WZX7HAY2.ts} +8 -7
  52. package/dist/chunks/{file-selection-RAVHRYFG.ts.map → file-selection-WZX7HAY2.ts.map} +1 -1
  53. package/dist/chunks/{git-backend-IAANDL3K.ts → git-backend-HLGKUQJT.ts} +19 -7
  54. package/dist/chunks/git-backend-HLGKUQJT.ts.map +7 -0
  55. package/dist/chunks/local-fields-6DGYOOIK.ts +22 -0
  56. package/dist/chunks/manager-result-dispatcher-VIDT3QBU.ts +23 -0
  57. package/dist/chunks/{manager-ui-763NEKJY.ts → manager-ui-HSMHS3OK.ts} +236 -57
  58. package/dist/chunks/manager-ui-HSMHS3OK.ts.map +7 -0
  59. package/dist/chunks/merge-baseline-store-EVQAAML6.ts +22 -0
  60. package/dist/chunks/remote-selection-ui-SMF5CLFO.ts +22 -0
  61. package/dist/chunks/{s3-backend-XGXUOPUN.ts → s3-backend-DJ2NT3SZ.ts} +16 -9
  62. package/dist/chunks/s3-backend-DJ2NT3SZ.ts.map +7 -0
  63. package/dist/chunks/{setup-switch-5G2VNJHL.ts → setup-switch-KTUWL6DE.ts} +9 -6
  64. package/dist/chunks/{setup-wizard-YIEWGX32.ts → setup-wizard-GSDPDFC4.ts} +10 -7
  65. package/dist/chunks/{setup-wizard-YIEWGX32.ts.map → setup-wizard-GSDPDFC4.ts.map} +1 -1
  66. package/dist/chunks/{snapshot-JL5DFSXI.ts → snapshot-THS5EY2L.ts} +6 -9
  67. package/dist/chunks/snapshot-THS5EY2L.ts.map +7 -0
  68. package/dist/chunks/{sync-inspection-P3ENJLNF.ts → sync-inspection-VBJF2FWC.ts} +10 -10
  69. package/dist/chunks/sync-inspection-VBJF2FWC.ts.map +7 -0
  70. package/dist/chunks/{sync-operations-AR76EB6C.ts → sync-operations-27VQUDH2.ts} +309 -508
  71. package/dist/chunks/sync-operations-27VQUDH2.ts.map +7 -0
  72. package/dist/chunks/{sync-state-VHJUZ4YG.ts → sync-state-CFL4RRRB.ts} +6 -6
  73. package/dist/chunks/sync-state-CFL4RRRB.ts.map +7 -0
  74. package/dist/chunks/{webdav-backend-3EGUONAT.ts → webdav-backend-ETQGSI6I.ts} +13 -7
  75. package/dist/chunks/webdav-backend-ETQGSI6I.ts.map +7 -0
  76. package/dist/index.ts +142 -43
  77. package/dist/index.ts.map +2 -2
  78. package/docs/content-merge-audit.md +156 -0
  79. package/docs/merge-implementation-audit.md +312 -0
  80. package/docs/settings-merge-audit.md +153 -0
  81. package/docs/settings.md +36 -0
  82. package/package.json +6 -6
  83. package/src/backends/git/git-backend.ts +2 -0
  84. package/src/backends/git/git-storage.ts +8 -3
  85. package/src/backends/s3/s3-backend.ts +2 -1
  86. package/src/backends/webdav/webdav-backend.ts +2 -1
  87. package/src/commands/command-execution.ts +10 -0
  88. package/src/commands/command.ts +4 -0
  89. package/src/settings/config.ts +12 -0
  90. package/src/settings/settings-management.ts +2 -2
  91. package/src/settings/settings-types.ts +14 -1
  92. package/src/settings/settings-validation.ts +24 -3
  93. package/src/snapshot/session-paths.ts +34 -7
  94. package/src/snapshot/snapshot-apply.ts +81 -32
  95. package/src/snapshot/snapshot-case-replacement.ts +80 -0
  96. package/src/snapshot/snapshot-codec.ts +6 -1
  97. package/src/snapshot/snapshot-root-transition.ts +154 -0
  98. package/src/snapshot/snapshot-transaction-plan.ts +72 -0
  99. package/src/snapshot/snapshot-transaction.ts +844 -102
  100. package/src/snapshot/snapshot-types.ts +5 -0
  101. package/src/state/json-file.ts +32 -5
  102. package/src/state/merge-baseline-store.ts +182 -0
  103. package/src/state/mutation-directory-sync.ts +34 -0
  104. package/src/state/state-types.ts +4 -0
  105. package/src/state/sync-state-store.ts +14 -1
  106. package/src/sync/automatic-sync.ts +11 -3
  107. package/src/sync/conflict-artifact-ownership.ts +63 -0
  108. package/src/sync/conflict-artifacts.ts +195 -0
  109. package/src/sync/conflict-groups.ts +68 -0
  110. package/src/sync/conflict-preview.ts +56 -0
  111. package/src/sync/conflict-resolution.ts +93 -0
  112. package/src/sync/conflict-retention.ts +72 -0
  113. package/src/sync/conflict-review.ts +95 -0
  114. package/src/sync/content-conflicts.ts +73 -0
  115. package/src/sync/field-migration.ts +39 -0
  116. package/src/sync/file-merge-planner.ts +143 -0
  117. package/src/sync/json-document.ts +184 -0
  118. package/src/sync/local-fields.ts +134 -0
  119. package/src/sync/merge-apply.ts +380 -0
  120. package/src/sync/merge-journal.ts +219 -0
  121. package/src/sync/merged-sync.ts +723 -0
  122. package/src/sync/partial-progress.ts +54 -0
  123. package/src/sync/remote-snapshot.ts +6 -0
  124. package/src/sync/session-merge.ts +194 -0
  125. package/src/sync/settings-conflicts.ts +52 -0
  126. package/src/sync/settings-merge.ts +57 -0
  127. package/src/sync/startup-check.ts +90 -5
  128. package/src/sync/sync-inspection.ts +1 -0
  129. package/src/sync/sync-local.ts +61 -0
  130. package/src/sync/sync-mutations.ts +256 -114
  131. package/src/sync/sync-queries.ts +6 -2
  132. package/src/sync/sync-state.ts +14 -5
  133. package/src/sync/text-merge.ts +133 -0
  134. package/src/sync-extension.ts +8 -0
  135. package/src/ui/manager-state.ts +12 -1
  136. package/src/ui/manager-ui.ts +10 -0
  137. package/src/ui/merge-review.ts +28 -0
  138. package/src/ui/remote-selection-ui.ts +1 -0
  139. package/src/ui/settings-ui.ts +183 -2
  140. package/src/ui/setup/setup-actions.ts +2 -2
  141. package/src/ui/setup/setup-switcher.ts +2 -1
  142. package/src/ui/storage-connections-ui.ts +11 -7
  143. package/src/ui/sync-attention.ts +3 -0
  144. package/src/ui/sync-format.ts +16 -12
  145. package/src/ui/sync-setups-ui.ts +1 -0
  146. package/src/ui/terminal-text.ts +9 -2
  147. package/dist/chunks/chunk-4AIHXRKE.ts +0 -197
  148. package/dist/chunks/chunk-4AIHXRKE.ts.map +0 -7
  149. package/dist/chunks/chunk-4MPIH46N.ts.map +0 -7
  150. package/dist/chunks/chunk-7KHKWL3Q.ts.map +0 -7
  151. package/dist/chunks/chunk-EEJPYTWE.ts +0 -348
  152. package/dist/chunks/chunk-EEJPYTWE.ts.map +0 -7
  153. package/dist/chunks/chunk-IVXEDPAD.ts +0 -370
  154. package/dist/chunks/chunk-IVXEDPAD.ts.map +0 -7
  155. package/dist/chunks/chunk-MSSW6F3R.ts +0 -12
  156. package/dist/chunks/chunk-MSSW6F3R.ts.map +0 -7
  157. package/dist/chunks/chunk-OVDTF5CH.ts +0 -90
  158. package/dist/chunks/chunk-OVDTF5CH.ts.map +0 -7
  159. package/dist/chunks/chunk-Q32CQWPM.ts.map +0 -7
  160. package/dist/chunks/chunk-QLSN7AGX.ts.map +0 -7
  161. package/dist/chunks/chunk-SWUXA3MH.ts +0 -18
  162. package/dist/chunks/chunk-SWUXA3MH.ts.map +0 -7
  163. package/dist/chunks/chunk-T56RNRZJ.ts.map +0 -7
  164. package/dist/chunks/chunk-YFADF5ZQ.ts.map +0 -7
  165. package/dist/chunks/git-backend-IAANDL3K.ts.map +0 -7
  166. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +0 -23
  167. package/dist/chunks/manager-ui-763NEKJY.ts.map +0 -7
  168. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +0 -22
  169. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +0 -7
  170. package/dist/chunks/sync-operations-AR76EB6C.ts.map +0 -7
  171. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +0 -7
  172. /package/dist/chunks/{chunk-UA7RXOIT.ts.map → chunk-4SBTAVFL.ts.map} +0 -0
  173. /package/dist/chunks/{chunk-HGJH56KR.ts.map → chunk-QHSQCK7L.ts.map} +0 -0
  174. /package/dist/chunks/{chunk-DZUSTGQV.ts.map → chunk-UQ3ALBIB.ts.map} +0 -0
  175. /package/dist/chunks/{chunk-E42GXD6D.ts.map → chunk-WKPX2SIC.ts.map} +0 -0
  176. /package/dist/chunks/{manager-result-dispatcher-OTUMQM26.ts.map → conflict-artifacts-BB2FZMIE.ts.map} +0 -0
  177. /package/dist/chunks/{remote-selection-ui-TZTJF32Y.ts.map → local-fields-6DGYOOIK.ts.map} +0 -0
  178. /package/dist/chunks/{setup-switch-5G2VNJHL.ts.map → manager-result-dispatcher-VIDT3QBU.ts.map} +0 -0
  179. /package/dist/chunks/{snapshot-JL5DFSXI.ts.map → merge-baseline-store-EVQAAML6.ts.map} +0 -0
  180. /package/dist/chunks/{sync-inspection-P3ENJLNF.ts.map → remote-selection-ui-SMF5CLFO.ts.map} +0 -0
  181. /package/dist/chunks/{sync-state-VHJUZ4YG.ts.map → setup-switch-KTUWL6DE.ts.map} +0 -0
@@ -0,0 +1,153 @@
1
+ # Settings merge audit
2
+
3
+ Phase 2 extends [Phase 1 PR #1455](https://github.com/narumiruna/pi-extensions/pull/1455) with explicit opt-in settings merging, verified accepted ancestors and portable machine-local field rules. Existing defaults remain unchanged.
4
+
5
+ ## Touched areas and MUST verification
6
+
7
+ | Area | Requirements | Verification |
8
+ | --- | --- | --- |
9
+ | Ancestors and journal | Private bounded atomic persistence, verified identity/hashes, preserve recovery evidence, revalidate ownership after awaits. | Cache/state/journal boundary Review; `merge-baseline-store.test.ts` and existing merged fault-injection tests. |
10
+ | JSON and file mutation | Preserve parser semantics and unknown fields, whole-transfer barriers, secret scanning, queues and stale-byte guards. | Format table below; `settings-merge.test.ts`, `merged-sync.test.ts`, `local-fields.test.ts`; apply-boundary Review. |
11
+ | Settings/migration | Explicit opt-in, ordered private writes, invalid-file protection, unknown-field preservation, no credential values in review. | `docs/extension-settings.md` Review; existing cross-process settings tests; new rule validation and cancellation/stale-review tests. |
12
+ | UI/lifecycle | Observable supported modes, owned cancellation, no automatic activation or prefix changes. | `docs/extension-conventions.md` Review; Settings uses existing Kit settings screen and native input/confirm; lifecycle and remapped review tests retained. |
13
+ | Packaging/docs | README warnings/sections, Changeset, both root gates, generated-loader smoke. | `docs/readme-conventions.md` Review; check/test/pack/Jiti/RPC evidence in PR handoff. |
14
+
15
+ ## Authoritative format table
16
+
17
+ Installed Pi's `dist/core/settings-manager.js` loads `JSON.parse(stripBom(content))`, then migrates queue/steering, websocket/transport, old skill objects and retry delay fields. Public `SettingsManager.inMemory(...).getGlobalSettings()` exercises that migration path without private imports or filesystem writes. Inputs changed by migration are withheld, not repaired. Parser failures are sanitized without retaining raw payload causes.
18
+
19
+ Installed `dist/core/keybindings.js` parses BOM-compatible JSON, filters invalid binding values, migrates legacy names with canonical-name precedence, orders known names then extras, and delegates matching to TUI. Those aliases and overlapping keys need their own semantic strategy: **keybindings.json remains file-level**.
20
+
21
+ | Input/unit | Decision |
22
+ | --- | --- |
23
+ | Global settings.json, UTF-8 object JSON, optional BOM | Eligible only with a verified accepted ancestor and mergeSettings enabled. |
24
+ | Comments, trailing comma, nonobject, invalid UTF-8, duplicate/prototype-sensitive keys, over 1 MiB/depth 64/16,384 nodes | Review; original bytes remain unchanged. |
25
+ | Scalars, absence, null, unknown fields | Conservative presence-aware three-way decisions; accepted deletion preserves absence. |
26
+ | Numeric literals | Safe integers and finite decimals whose mathematical value round-trips through JavaScript serialization; reject unsafe integers, overflow, underflow and rounded decimals at every nested leaf before comparison or projection. |
27
+ | Objects/arrays | Atomic, including unknown nested objects; no array union or unproven recursive invariants. |
28
+ | Provider/model, queue/steering, websocket/transport, skills/skill-command, analytics/tracking ID fields | Coupled root units; incompatible group edits conflict. |
29
+ | Unaffected local members | Raw spelling, separators, ordering, BOM, line endings and trailing bytes retained; new members use local indentation. Output is parsed again. |
30
+ | Missing/mismatched/corrupt ancestor | Review, never synthesize current data or rely on remote retention. |
31
+
32
+ ## Ancestors and crash boundaries
33
+
34
+ Cache only eligible settings.json bytes under the denied private state root. Version-1 records bind ordered selection, local-field rules, setup/backend coordinates and the complete accepted-state fingerprint. Each fingerprint has a bounded private file in a checked private directory. Staging a candidate never replaces the previous state's ancestor. Every cache read validates schema, identity, canonical bytes and SHA; unknown/corrupted evidence is retained. Reader and pruning share private-regular-file, size, schema/identity and byte validation; reading additionally checks file hashes against the supplied accepted state. Nonprivate and nonregular records are never pruning candidates.
35
+
36
+ | Durable boundary | Recovery |
37
+ | --- | --- |
38
+ | Cache staging fails before state | Existing state/ancestor survive; merge journal remains; directional accepted remote/apply is reported as persistence failure. |
39
+ | Cache exists but state not advanced | Old ancestor remains; publication journal can reconcile known images and restage idempotently. |
40
+ | State accepted but journal remains | Accepted fingerprint retires redundant journal without replaying old local bytes. |
41
+ | Journal retired, pruning interrupted | Accepted ancestor retained; extra owned historical records are safe to prune later. Unknown records are never pruning candidates. |
42
+ | Missing cache after restart/history removal | Field conflicts remain review; remote history is not an ancestor dependency. |
43
+
44
+ Directional push, pull and rollback stage accepted logical snapshots before baseline acceptance, then prune only after successful acceptance/journal retirement. All critical continuations validate session ownership. Pull and rollback recheck reviewed local bytes/settings/head; full-byte guards conservatively refuse changes even if only excluded fields changed while a plan was pending.
45
+
46
+ ## Portable policy and compatibility
47
+
48
+ mergeSettings defaults false and is independently toggleable without deleting evidence. localFields is an explicit sorted array of settings.json root names, with bounded safe names and whole coupled-group rules. Existing locked ordered settings persistence remains authoritative; saves preserve unknown fields and invalid files. UI input uses Pi's native editing/paste behavior, with signal and owner checks after every await.
49
+
50
+ Saving rules explicitly upgrades settings to version 4, including after rule removal; portable snapshots use version 2. Old settings validators/decoders reject these versions. No new schema is inferred from absent fields: unrelated setups in the shared version-4 document retain absent policy and snapshot version 1. Explicit empty rules remain a portable opt-in and require migration from absence. Snapshot version 2 requires an explicit rule array across codecs, backend validators and journal images. Portable codec/transport/raw-remote/upload/accepted images also verify that declared root exclusions are absent, including case-normalized settings paths and escaped JSON keys. Physical journal before/after images intentionally retain local-only values; a portable journal requires an explicit accepted projection. Empty initial acceptance checks policy compatibility, and advisory inspection detects policy-only baseline changes. A setup without rules cannot accept a portable snapshot without explicit configuration. Returning to an older client requires a separately reviewed full copy into a distinct version-3 setup; pending evidence must not be downgraded.
51
+
52
+ Portable JSON is deterministically canonicalized, excluding selected fields. Local overlay retains latest validated values/absence and unaffected semantic values' raw spelling. Complete settings deletion with any excluded root field present is refused, including null values; validated documents with no excluded fields may be deleted. State hashes and ancestors describe the portable common version; journal before/after describe physical local bytes, accepted describes portable bytes, and upload preserves unmanaged remote files. Journal validation checks the accepted projection against physical after-images. Journal identity preserves policy presence, and the upload policy must match before either candidate retirement or committed recovery. Older ambiguous absent-policy identities fail closed without deleting evidence; reviewed force directions remain the recovery route.
53
+
54
+ Rule additions/removals and transitions from absent to explicit empty policy block ordinary/background merge. Explicit force directions require extra TUI/RPC migration confirmation even with --yes, warn about old history and removal risks, and validate the fresh setup/local/head before mutation. Push publishes the configured policy; pull only adopts already-matching authoritative remote rules and refuses a mismatch before review or mutation. A pull cannot manufacture a portable baseline against an unchanged nonportable remote. Cancelled/stale review changes neither files nor head. Remote bytes and newly merged/projected content remain subject to secret scanning. Excluding a value now does not erase prior remote history or private backups.
55
+
56
+ ## Review of commit `d0d3a44f` (PR #1456)
57
+
58
+ | Feedback | Classification and evidence | Outcome |
59
+ | --- | --- | --- |
60
+ | [Couple analytics and tracking ID](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756573) | The installed Pi SettingsManager setter changes these fields as one operation. Independent acceptance can enable analytics with no tracking ID. | Actionable: treat both as one three-way decision unit; unilateral and divergent test cases exercise that invariant. |
61
+ | [Finish rollback after cancellation](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756576) | The rollback publication uses its own bounded completion signal, but prior state persistence still used the aborted command signal. | Actionable: after the successful remote commit use cancellation-independent ownership validation to persist state and prune; retain session identity checks. A deterministic post-publication abort test asserts that accepted revision and local result agree. |
62
+ | [Repair Phase 3 plan link](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756581) | The plan referenced a Phase 2 plan deleted by this PR. | Actionable: point `docs/plans/2026-10-05_pi-sync-content-merge-plan.md` to this package audit instead. |
63
+ | [Coupled analytics exclusions](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191769392) | An exclusion of only one member of the analytics/tracking-ID pair could produce an enabled policy without its identifier or transfer another machine's identifier. The settings-merger pairing alone did not enforce portable policy. | Actionable: require both or neither in `normalizeLocalFields()`, shared by settings validation, snapshot validation, portable projection and overlay; tests cover one-sided refusal and the two-field local overlay. |
64
+ | [Push post-commit bookkeeping](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191769400) | A backend can finish remote publication after the caller's abort, but `writeStateForConfig()` previously received the abort-bound owner guard. The same post-commit cancellation class applies after pull transaction installation; rollback was fixed in the prior follow-up. | Actionable: preserve abort-bound guards before commit and during apply; after successful publication or local installation, use cancellation-independent session ownership checks for state acceptance. Push and pull cancellation regressions verify accepted revisions and local bytes. |
65
+
66
+ The three preceding threads have evidence-backed GitHub replies and were resolved after the signed `335579bd` push; reply to and resolve the two later threads only after their follow-up is committed and verified. Earlier inline feedback is already addressed by the code and regressions described above and by the PR's previous signed review commits (`c2dfbcff`, `48b9f46d`, `d192f85c`, `d0d3a44f`); the submitted reviews and conversation summaries are wrappers for those findings with no distinct unanswered technical request. This ledger does not assert that GitHub threads were resolved.
67
+
68
+ ## Stacked-branch conflict integration
69
+
70
+ The merge from Phase 1 head `4a756001` retains Phase 2 portable settings and accepted-ancestor behavior while incorporating Phase 1 root pinning, active-file/physical-alias checks, post-journal preimage checks, additive apply-only recovery evidence, version-4 removal retirement, staged restoration, bounded indexed transaction planning and cancellation-aware preflight. Physical before/after images and applicability checks always use raw local bytes; accepted state and ancestors use the portable projection. No source-side conflict choice is treated as proof of correctness: every Phase 1-only module was compared with its upstream version, and both feature-specific test sets are retained.
71
+
72
+ `stacked-merge-safety.test.ts` exercises portable session-root rejection, apply-only remote races, excluded-field edits during journal publication and single-download no-op acceptance. The complete upstream merged-sync tests run alongside the four retained Phase 2 ancestor/settings regressions. `npm run check` passes; root `npm test` ran 6,811 tests with 6,807 passing and four unrelated `pi-worktree` Git integration failures on host Git 2.34.1 (unsupported `git worktree list -z` and sparse-checkout inventory behavior). This is not a passing root test gate. Package build/pack, actual tarball inspection, all 36 README required-heading checks and an isolated offline Pi RPC package-directory readiness/registration/EOF smoke pass. Upstream main changes and package versions are inherited through the merge, not independent scope expansion. The completed Phase 2 plan remains deleted and the Phase 3 link points here. External writers, physical power loss, Windows and compiled runtime remain unverified or outside the claimed contract.
73
+
74
+ ## Review of head `c919d7d` (PR #1456)
75
+
76
+ The complete paginated review evidence contains 29 substantive items. The 22 prior resolved items retain their published replies, code and regression evidence: initial acceptance/policy, early-exit pruning, version recovery advice, effective previews, v4 management, production backends, sibling policy absence, absent/empty distinction, refreshed force-push review, cancelled status, safe deletion, journal identity, forced pull restriction, unsafe cache pruning, policy inspection, portable content integrity, final matching acceptance, analytics merge coupling, rollback post-publication acceptance, Phase 3 link, analytics exclusions and push/pull post-commit acceptance. These remain **Already addressed**; no duplicate replies are required. Submitted-review wrappers and activity summaries add no substantive request.
77
+
78
+ All seven pending items are P2 and directly concern Phase 2's portable-policy, accepted-ancestor or recovery goal; none is deferred or uncertain. Evidence was classified before implementation:
79
+
80
+ | Thread | Evidence and relationship to goal | Disposition and verification |
81
+ | --- | --- | --- |
82
+ | [Rollback post-local commit](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870946) | Rollback alone still called the command-abort guard after successful local transaction retirement, before bounded publication. | Actionable: use cancellation-independent owner validation after local commit and before remote publication. `post-commit-cleanup.test.ts` injects abort at transaction retirement; session replacement still refuses publication. |
83
+ | [Rollback pending journal](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870951) | Current head already invokes `requireNoMergeJournal(config)` without force; the `c919d7d` integration inherited the fix and both force variants' regressions. | Already addressed: `merged-sync.test.ts` verifies no publication, unchanged local/state/head/journal for ordinary and force rollback. |
84
+ | [Unmanaged settings exclusions](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870954) | Nonempty rules with unmanaged settings preserved excluded remote values into v2 uploads rejected by all backends. | Actionable: reject nonempty rules unless normalized include selects settings.json. `local-fields.test.ts` covers absent/empty/nonempty rules, normalized casing, empty/session/custom selections and failed-save preservation. |
85
+ | [Post-commit pruning](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192016163) | All three directions returned on caller abort before pruning fingerprint-specific ancestors despite completed acceptance/retirement. | Actionable: prune with owner-only validation before abort-only notification exit. Repeated push/pull/rollback regressions retain only accepted and unknown evidence. |
86
+ | [Rollback authoritative policy](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192016170) | Historical policy alone missed an authoritative current-head rule change and allowed --yes overwrite without migration review. | Actionable: validate/read the current immutable head and authorize its policy before mutation; retain fresh-head/settings/byte guards. `rollback-field-policy.test.ts` covers accept, decline, abort and review races, plus unchanged control. |
87
+ | [Newly portable rollback values](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192539160) | Reprojection cannot restore fields omitted by historical policy; overlay under removed rules drops current local values. | Actionable: adopt the proposed conservative restriction, refusing history whose policy differs from config before confirmation, backup, apply or publication. Presence/addition/removal/replacement tests preserve bytes/state/head and recommend authoritative push migration first. |
88
+ | [Recorded journal projection](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192539164) | Structural validation used current rules, making intact old-policy after-images unreadable and blocking reviewed force recovery. | Actionable: validate projection with recorded upload rules and require accepted/upload policy agreement; current-policy enforcement remains in completeJournal. `local-field-recovery.test.ts` verifies intact readable evidence, ordinary recovery refusal and reviewed force-direction archival; corrupted images remain refused. |
89
+
90
+ Touched-area MUST checks follow the table at the start of this audit and unchanged `docs/extension-conventions.md`, `docs/extension-settings.md` and `docs/readme-conventions.md`: deterministic command/RPC tests, cancellation/owner validation after awaits, private ordered atomic settings persistence, invalid-file and unknown-field preservation, root check/test gates and Changeset. No new settings store, background task, custom key, model-visible transition or automatic reload is introduced. Rollback now fails closed when current authoritative policy cannot be read instead of publishing an unreviewed policy over unreadable evidence. Post-local-commit publication remains bounded to 30 seconds; owner replacement still retains the existing backup for manual recovery rather than operating under a replaced session.
91
+
92
+ ### Integration and validation outcome
93
+
94
+ The base advanced to `9e1c544a` during this review and GitHub reported conflicts. Signed merge `bdd5b946` integrates that base without rewriting history. The sole source conflict combines Phase 1's post-backup/root-resolution recovery freshness checks with Phase 2's physical overlay, settings fingerprint and full-byte guards. All inherited transaction completion, spelling-queue and canonical-target tests remain intact; no upstream plan is removed. The base's filesystem-sensitive test control fixes the initial case-sensitive-only failure on this native case-insensitive macOS host.
95
+
96
+ `npm run check` passes (build, Biome, package boundaries and workspace typechecks). Twelve focused suites pass **237 tests**, with one inapplicable case-sensitive control skipped; native case-insensitive recovery runs and passes. The complete `npm test` invocation finished **6,936 passed / 18 failed / one skipped across 520 files**. Every failure is a five-second timeout in six subprocess-heavy suites, not a settings/recovery assertion failure. A single-worker rerun of all six failing files passes **87 / 91 tests**, including every portable Git round trip, the entire Git backend suite, attachment cancellation and worktree integration. Four older controls still exceed five seconds: the two generic Git head/revision/history/diagnostics contracts, root fresh-cache storage and the all-routes Git test. Their test bodies are unchanged from the base; the base's retained audit independently records the Git-route timing failure. Defer decomposition/investigation of these slow legacy controls to a separate PR; the timing cause is not established and no timeout override or unrelated Git change is adopted. The full local gate is **not green**. Earlier incomplete full attempts are not passing evidence.
97
+
98
+ Package build, `npm run package:pack -- sync`, actual tarball inspection (generated entry, changed runtime sources and linked guides) and isolated package-directory RPC readiness/sync-registration/EOF shutdown pass without provider requests. Generated Jiti/lazy tests pass in the completed root run. `git diff --check` passes. Settings reads/writes continue through the existing locked/queued/private atomic protocol; the new invalid-selection save regression preserves exact bytes and unknown fields. Final semantic review covers all post-commit direction exits, recorded/current/historical policy roles, head/byte/settings races, journal validation versus recovery authorization, evidence retention/pruning and session replacement after commit. No npm publication, tag or release workflow dispatch is performed.
99
+
100
+ ## Review of remote head `9a03fffa` after base rebase
101
+
102
+ Fetched every REST page for reviews, inline and conversation comments, and all 32 GraphQL threads (no additional thread/comment page remains). PR #1456 remains open on main; remote head is `9a03fffa`, local rebased head is `20b7afa6`, and current base is `d81f7aab`. The previous 29 original findings retain published replies and resolved threads; their unchanged evidence is reused, with affected behavior covered by the latest package/full test runs. Submitted reviews and conversation summaries are wrappers, not separate technical requests.
103
+
104
+ | Feedback | Severity | Scope/evidence | Outcome |
105
+ | --- | --- | --- | --- |
106
+ | [Persist local-field policy during initial state acceptance](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736317) | P1 | Portable settings/accepted-state goal; local-fields.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
107
+ | [Prune ancestors after early successful acceptance](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736344) | P2 | Portable settings/accepted-state goal; merged-sync.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
108
+ | [Update the accepted-version recovery message](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736373) | P2 | Portable settings/accepted-state goal; local-fields.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
109
+ | [Compare effective snapshots in diff and confirmation views](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736391) | P2 | Portable settings/accepted-state goal; local-fields.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
110
+ | [Allow version 4 in every settings-management path](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736405) | P1 | Portable settings/accepted-state goal; settings-management.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
111
+ | [Teach every backend to publish snapshot version 2](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4186736430) | P1 | Portable settings/accepted-state goal; backend-contract-suite.ts (production backend tests); prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
112
+ | [Preserve absent policies on unrelated version-4 setups](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4187294448) | P1 | Portable settings/accepted-state goal; local-field-policy-presence.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
113
+ | [Distinguish absent and explicitly empty field policies](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4187294455) | P2 | Portable settings/accepted-state goal; local-field-policy-presence.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
114
+ | [Reconfirm policy after refreshing a force-push head](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4187294468) | P2 | Portable settings/accepted-state goal; local-field-migration.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
115
+ | [Clear the sync status when migration is cancelled](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4187294480) | P2 | Portable settings/accepted-state goal; local-field-migration.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
116
+ | [Allow safe deletion when no excluded value exists](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4187294490) | P2 | Portable settings/accepted-state goal; local-field-deletion.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
117
+ | [Keep absent policies distinct in recovery journals](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190365558) | P2 | Portable settings/accepted-state goal; local-field-recovery.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
118
+ | [Complete forced pull policy migrations](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190365563) | P2 | Portable settings/accepted-state goal; local-field-pull-policy.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
119
+ | [Preserve unsafe ancestor records during pruning](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190365568) | P2 | Portable settings/accepted-state goal; merge-baseline-store.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
120
+ | [Report local-field policy changes during inspection](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190585931) | P2 | Portable settings/accepted-state goal; local-field-inspection.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
121
+ | [Reject snapshots that retain excluded fields](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190585937) | P2 | Portable settings/accepted-state goal; portable-snapshot-integrity.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
122
+ | [Guard the final matching-state acceptance](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190585943) | P2 | Portable settings/accepted-state goal; empty-policy-acceptance.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
123
+ | [Couple analytics opt-in with its tracking identifier](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756573) | P2 | Portable settings/accepted-state goal; settings-merge.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
124
+ | [Finish rollback bookkeeping after the commit boundary](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756576) | P2 | Portable settings/accepted-state goal; backend-orchestration.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
125
+ | [Preserve the Phase 2 plan link target](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4190756581) | P2 | Portable settings/accepted-state goal; Phase 3 plan link → existing audit; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
126
+ | [Couple analytics fields in local exclusions](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191769392) | P2 | Portable settings/accepted-state goal; local-fields.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
127
+ | [Persist push state after the publication boundary](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191769400) | P2 | Portable settings/accepted-state goal; backend-orchestration.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
128
+ | [Continue rollback publication after local commit](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870946) | P2 | Portable settings/accepted-state goal; post-commit-cleanup.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
129
+ | [Block rollback while a merge journal is pending](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870951) | P2 | Portable settings/accepted-state goal; merged-sync.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
130
+ | [Reject local-field rules when settings are excluded](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4191870954) | P2 | Portable settings/accepted-state goal; local-fields.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
131
+ | [Prune baselines before post-commit cancellation returns](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192016163) | P2 | Portable settings/accepted-state goal; post-commit-cleanup.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
132
+ | [Confirm rollback migrations against the current head](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192016170) | P2 | Portable settings/accepted-state goal; rollback-field-policy.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
133
+ | [Preserve newly portable fields during rollback](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192539160) | P2 | Portable settings/accepted-state goal; rollback-field-policy.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
134
+ | [Validate journals with their recorded field policy](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4192539164) | P2 | Portable settings/accepted-state goal; local-field-recovery.test.ts; prior reply records branch evidence/corrections. | Already addressed; resolved, no duplicate reply. |
135
+ | [Validate journal projections without rewriting JSON bytes](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4193737421) | P2 | In scope: portable settings integrity and local-value preservation. Canonicalize both recorded projections for intrinsic integrity, canonicalize newly accepted state/ancestors, and recognize legacy accepted raw-byte fingerprints without replay. `portable-merge-projection.test.ts` covers ordered/pretty/nested JSON, interruption, repeat sync, initial equality, advanced-head retirement and semantic corruption. | Already addressed by this fix; deterministic regressions and both final gates pass; resolution follows publication. |
136
+ | [Distinguish integers that exceed JavaScript's safe range](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4193737429) | P2 | In scope: portable settings integrity and local-value preservation. Reject unsafe integers and nonfinite/underflow/rounded decimal tokens before JSON comparisons, canonicalization or overlay. `settings-number-safety.test.ts` covers signed/exponent/nested forms, the reported lost edit and supported spelling controls. | Already addressed by this fix; deterministic regressions and both final gates pass; resolution follows publication. |
137
+ | [Refresh local-only values at the apply boundary](https://github.com/narumiruna/pi-extensions/pull/1456#discussion_r4193737434) | P1 | In scope: portable settings integrity and local-value preservation. Bind pull/rollback settings preimages to reviewed raw hashes or absence under Pi queues and compare captured backups before journal publication. `local-field-apply-preimage.test.ts` covers edits/deletions/creations at backup, apply entry, before/after transaction copy and journal publication; state/head remain unchanged. | Already addressed by this fix; deterministic regressions and both final gates pass; resolution follows publication. |
138
+
139
+ The new items are independently consistent with the authoritative code: intrinsic journal integrity compares canonical projection hashes against noncanonical accepted bytes; JSON.parse and jsonEqual round numeric values before comparison and portable serialization; directional overlays are frozen before backup and transaction preparation captures a newer preimage without binding it to the reviewed settings hash. No severity upgrade or scope expansion is inferred from reviewer labels. Native filesystem, physical power loss, compiled runtime and live storage/provider claims remain outside verified evidence.
140
+
141
+ ### New failure-class audit and validation
142
+
143
+ The three new suites initially reproduced 31 failures against rebased head `20b7afa6`; existing post-copy/post-journal guards and supported-number controls already passed. After fixes and added compatibility/spelling controls, all 67 new tests pass. The initial package run passed 1,819 tests with one native-filesystem skip; the six additional spelling controls pass in the final focused and full runs. The semantic audit follows `docs/extension-conventions.md`, `docs/extension-settings.md`, `docs/readme-conventions.md`, root and package instructions: every parser/canonicalization/overlay consumer; intrinsic recorded-policy validation versus current recovery authorization; all matching and merged acceptance sites and legacy completed states; pull/rollback raw preimages, queue acquisition, captured backups, later live-image checks, cancellation/owner changes and session-root transitions. No journal/schema version, UI component, settings store, model prefix, dependency or runtime-loading boundary changes. Unexpected preparation failures retain private evidence; no new cleanup path can restore stale bytes. Existing arbitrary-writer CAS limits remain, including the unavoidable final check-to-rename interval. Both physical case aliases and separate case-sensitive spelling replacements retain source/destination preimage guards.
144
+
145
+ The rebase preserves the current base's path/terminal/root-transition and startup-safety fixes. Source changes from the previous published head are base updates plus portable-safety integration; the earlier 29 fixes remain verified by the complete package tests. The four ancestor/settings regressions moved back into `merged-sync.test.ts`, and legacy portable advanced-head cleanup is explicitly covered in the new projection suite. No duplicate review reply is needed for those already resolved items.
146
+
147
+ Final verification: `npm run check` passes; `npm test` passes all 526 files with 7,170 tests and one native case-insensitive filesystem skip. Five focused parser/projection/preimage/case/root-recovery suites pass 124 tests with the same skip. Package pack dry run and actual tarball inspection pass: generated `dist/index.ts`, changed runtime sources and linked guides are present, and package tests are excluded. Isolated built-package RPC readiness, sync registration, lazy `/sync status` and EOF shutdown pass without a provider request. `git diff --check` passes. No timeout override, release dispatch, npm publication, visibility change or tag was performed. Native case-insensitive storage, Windows/compiled runtime, physical power loss and live storage/provider transfers remain unverified.
148
+
149
+ ## Limits and evidence
150
+
151
+ Node cannot provide atomic content CAS against arbitrary external writers; pause those writers for stronger exclusion. Only this process's current session is protected. No resources, model prefix or active tools are changed by transfer. POSIX directory syncing is exercised; Windows and compiled-binary paths are not claimed verified. No live storage/provider transfer is required by deterministic tests.
152
+
153
+ Root check and 492 files / 6,346 tests passed before the final documentation and additional rollback/parser hardening; final gate counts and packaging evidence belong in the PR handoff, not an inferred completion claim here.
package/docs/settings.md CHANGED
@@ -202,6 +202,42 @@ Adding `sessions` requires a privacy acknowledgement in interactive flows.
202
202
  Session JSONL can contain prompts, tool output, file paths, images, and secrets.
203
203
  Pull protects the currently open session file; restart Pi or resume a pulled session to use newly synchronized conversations.
204
204
 
205
+ ### Automatic startup transfer
206
+
207
+ `sync.automaticTransfer` is optional and accepts only a boolean; absent means `false`. Set it through **/sync → Settings → Automatic transfer at startup**. It is separate from `sync.automatic`: the legacy setting retains observation and selected-content shutdown behavior.
208
+
209
+ When explicitly enabled, one startup transfer waits for an idle boundary in TUI/RPC. It requires an accepted baseline and a matching authoritative remote include list, retains all-or-nothing conflict review, checks secrets unless the global scan override is enabled, and never reloads resources. Git and verified conditional WebDAV support this automatic policy; R2/S3 require manual sync. Print/JSON skip automatic startup transfer. A settings save takes effect for subsequent authorization checks; enabling schedules the next session start, while foreground Settings cancels and drains any current work before saving. There is no polling.
210
+
211
+ Turning it off does not revert transfers or remove backups/journals. For interrupted operations, follow [merge recovery](merge-implementation-audit.md). Unknown fields remain preserved and malformed/invalid files block saves, just as for the existing policy.
212
+
213
+ ### Settings field merge and machine-local fields
214
+
215
+ `sync.mergeSettings` is experimental, defaults to `false`, and is available in the owning Settings screen. With a verified accepted ancestor, global `settings.json` root fields merge conservatively: absent differs from null, arrays/nested objects are atomic, and provider/model and other coupled fields form one unit. Unknown fields are preserved. Pi accepts plain UTF-8 JSON with an optional BOM, not JSONC; duplicate/prototype-sensitive keys, invalid encodings, migration-dependent syntax, and over-bound inputs require directional review. Field merging and nonempty portable projections also refuse unsafe integers, nonfinite numbers, and numeric literals that change mathematical value when serialized through JavaScript; they never round those values into an accepted edit. `keybindings.json` remains file-level because alias and matcher semantics need a separate strategy.
216
+
217
+ `sync.localFields` is an explicit sorted string array of whole global `settings.json` root field names, not a heuristic or project override. Example: `"localFields": ["shellPath"]`. Coupled fields must be excluded together. Nonempty rules require `settings.json` in `sync.include`; absent or explicitly empty rules remain valid for other selections. Saving nonempty rules in Settings confirms an upgrade to settings version 4; manual edits must declare version 4 too. Absent rules keep a setup nonportable, including unrelated setups in a version-4 document. An explicit array opts that setup into snapshot version 2; `[]` after rule removal retains this reviewed opt-in and differs from absence. Older clients must refuse portable data rather than replace local-only values.
218
+
219
+ Portable projections normalize remote JSON deterministically while local overlay preserves unaffected local member/value bytes and excluded values or absence. Accepted hashes and ancestors use that same projection; compatible noncanonical remote JSON does not create a false local change. Recovery verifies both recorded projections in canonical form while retaining the original physical bytes and exact checksums. Pull and rollback bind settings writes/deletions to the reviewed raw-file hash or absence under Pi's mutation queue, including transaction backup preparation; a newer excluded value causes refusal rather than overwrite. A portable whole-file deletion is allowed when no excluded root field is present; a present excluded value (including null) or unsupported local document blocks deletion for review. No override file is loaded and no resources are automatically activated. Portable remote snapshots must actually omit every declared excluded root field; inconsistent or unsupported settings documents fail closed before acceptance, without displaying payload values. Policy-only changes remain visible in status/startup inspection even when bytes are unchanged. Excluded data can remain in old remote versions and local backups; removing a rule can make it publishable or replaceable again.
220
+
221
+ Changing rules blocks ordinary/background merge until a reviewed directional migration. To change the authoritative remote rules, use `push --force` from the authoritative machine first. `pull --force` may adopt a changed accepted policy only when the configured rules already match the remote; it never rewrites remote policy and refuses a mismatch before review or mutation. Rollback requires its historical snapshot to use the configured rules; it refuses mismatched history instead of deleting newly portable local values. Use authoritative `push --force` to migrate from current local bytes, then choose matching history. A rollback that overwrites a different current-head policy requires the same additional observable TUI/RPC migration confirmation even with `--yes`. Cancellation, changed bytes/settings/head, and invalid files prevent acceptance. Inspect `/sync diff` first. Configure the same portable rule set on every receiving machine; other local values may differ. Rule names, not credential values, appear in review.
222
+
223
+ Accepted ancestors are private, hash/identity-bound, bounded records below the denied state directory. Capture occurs after accepted directions or journaled merge; cache staging precedes state acceptance, and previous ancestors survive failures until completed journal retirement permits pruning. Missing/corrupt cache never invents a baseline from current data or assumes remote retention. Preserve unknown/corrupted or nonprivate records and pending journals for manual review; pruning applies the same storage/schema/byte validation as reading. Journal identity distinguishes absent and explicit empty policies. Integrity validation uses the journal's recorded rules, so intact old-policy journals remain readable after a settings change; ordinary replay still rejects mismatched current rules, while reviewed force push/pull may archive that evidence. Older absence-conflating journal identities fail closed and remain available for an explicitly reviewed force direction; do not downgrade portable setups. To return to an older client, first use this version to review rule removal and publish a complete nonportable copy to a separate version-3 setup, with Pi closed during the change. Never reinterpret portable snapshots with an old binary.
224
+
225
+ ### Content merge and partial progress
226
+
227
+ `sync.mergeContent` and `sync.partialSync` are independent experimental booleans, both default `false`. The owning **Content / partial sync** setting offers Off, Content only, Partial only, and Content & partial and confirms an explicit settings-version-5 upgrade. Manual configuration must also declare version 5. Settings version 5 retains the portable-policy behavior of version 4; moving from a legacy setup can require the existing separately confirmed directional policy migration. Disabling options retains the schema and recovery evidence.
228
+
229
+ Content merge initially supports global `AGENTS.md` and `.md`/`.txt` prompt/skill resources. Strict UTF-8, 1 MiB/file and a four-million-cell complexity bound apply. BOM, CRLF/LF and final-newline bytes are not silently normalized; unsupported/binary/oversized input remains a file conflict. Output never includes conflict markers. JSON/keybindings/executable resources are not treated as generic text; settings has its separate strategy. A clean line merge is not proof of semantic correctness.
230
+
231
+ Same-session content merge accepts only complete validated Pi v3 histories with exact immutable byte prefixes and comparable local/remote extensions. It rejects malformed/partial records, migrations, identity/header changes, duplicate ids, broken parent references, compaction rewrites and divergent appends. The current loaded session and its aliases remain protected; active sessions in other processes are not discoverable through this mechanism. Nothing resumes a pulled conversation.
232
+
233
+ Partial progress stores per-path accepted hashes separately from the observed remote head. Withheld local versions remain local; the complete published snapshot retains the observed remote versions, never omission/deletion. Their previous verified ancestors survive staging and restart. Resource and collision groups defer together when independence cannot be proved; partially conflicted JSON and sessions remain atomic. Publication/apply/baseline is journaled, and a changed head replans only proven pre-commit attempts; unknown outcomes preserve evidence. Older codecs refuse version-3 partial snapshots and older settings readers refuse version 5, so do not collapse state for downgrade.
234
+
235
+ `/sync conflicts` (also **History & recovery → Review unresolved conflicts**) shows private versions only when requested, in TUI or paginated RPC. It rejects confirmation/force bypass flags, reviews an entire group, then separately asks for local or remote authority. It revalidates the reviewed artifact fingerprint, group versions, baseline, configuration, ownership and current observed head before apply; stale/cancelled review performs no transfer. The manager always reports unresolved groups and the observed head separately from Last applied. Repeated unchanged conflicts retain one stable opaque identity rather than creating more evidence or modal reviews.
236
+
237
+ Artifacts are private immutable records below denied state storage with a 192 MiB/record cap; accepted ancestor records use the same bound, and actual content strategies have smaller limits. Unresolved and unknown/corrupted evidence is never automatically removed. Completed cleanup retains the newest 32 **proven-common, unreferenced** artifacts; pinned files remain even if other groups in the same record completed. Total evidence can exceed those bounds across unresolved/unknown records: inspect it manually with Pi closed rather than deleting pending evidence. This is not remote-history erasure.
238
+
239
+ Do not run older binaries against version-5 setups or pending journals. To downgrade, drain work, resolve/review every withheld group with this version, retain evidence, and create a separately reviewed complete copy in a distinct version-3 setup. Disabling partial sync with unresolved state blocks ordinary sync until it is re-enabled or an explicit force direction accepts a complete selected version. No watcher/debounce/polling optimization is enabled: content revalidation is preferred over treating revision equality as authority. See the [implementation and recovery audit](content-merge-audit.md).
240
+
205
241
  ### Unsupported old settings and recovery
206
242
 
207
243
  Version 1, version 2, and non-empty unversioned documents are unsupported after the version 3 schema reset.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-sync",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "Pi extension that syncs Pi configuration through Git, WebDAV, Cloudflare R2, or S3-compatible storage.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,19 +40,19 @@
40
40
  "typecheck": "tsc --noEmit"
41
41
  },
42
42
  "dependencies": {
43
- "@narumitw/pi-tui-kit": "^0.59.0",
44
- "fast-xml-parser": "^5.11.1",
43
+ "@narumitw/pi-tui-kit": "^0.65.1",
44
+ "fast-xml-parser": "^5.11.2",
45
45
  "proper-lockfile": "^4.1.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@earendil-works/pi-coding-agent": "0.85.1",
49
- "@earendil-works/pi-tui": "0.85.1",
48
+ "@earendil-works/pi-coding-agent": "1.0.2",
49
+ "@earendil-works/pi-tui": "1.0.2",
50
50
  "@types/proper-lockfile": "^4.1.4",
51
51
  "esbuild": "0.28.2",
52
52
  "typescript": "7.0.2"
53
53
  },
54
54
  "peerDependencies": {
55
- "@earendil-works/pi-coding-agent": "*",
55
+ "@earendil-works/pi-coding-agent": ">=1.0.2",
56
56
  "@earendil-works/pi-tui": "*"
57
57
  },
58
58
  "repository": {
@@ -141,6 +141,7 @@ export class GitSyncBackend implements SyncBackend {
141
141
  profile: manifest.profile,
142
142
  ...(manifest.snapshotSyncSessions === undefined ? {} : { syncSessions: manifest.snapshotSyncSessions }),
143
143
  ...(manifest.selection === undefined ? {} : { selection: manifest.selection }),
144
+ ...(manifest.localFields === undefined ? {} : { localFields: manifest.localFields }),
144
145
  files,
145
146
  };
146
147
  validateGitSnapshot(snapshot, manifest, this.config.destination.namespace);
@@ -172,6 +173,7 @@ export class GitSyncBackend implements SyncBackend {
172
173
  syncSessions: snapshot.syncSessions === true || snapshot.files.some((file) => file.path.startsWith("sessions/")),
173
174
  ...(snapshot.syncSessions === undefined ? {} : { snapshotSyncSessions: snapshot.syncSessions }),
174
175
  ...(snapshot.selection === undefined ? {} : { selection: snapshot.selection }),
176
+ ...(snapshot.localFields === undefined ? {} : { localFields: snapshot.localFields }),
175
177
  files: files.map(({ path: filePath, sha256: fileSha, size }) => ({
176
178
  path: filePath,
177
179
  sha256: fileSha,
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { Snapshot, SnapshotSelection } from "../../snapshot/snapshot-types.js";
3
+ import { sameLocalFields, validatePortableSnapshot, validateSnapshotFieldPolicy } from "../../sync/local-fields.js";
3
4
  import { portableSnapshotSelection, snapshotSelectionInclude } from "../../sync/sync-policy.js";
4
5
 
5
6
  export const GIT_MANIFEST_VERSION = 2;
@@ -7,7 +8,6 @@ export const MAX_GIT_MANIFEST_BYTES = 1024 * 1024;
7
8
  export const MAX_GIT_TREE_OUTPUT_BYTES = 16 * 1024 * 1024;
8
9
  export const MAX_GIT_PAYLOAD_BYTES = 100 * 1024 * 1024;
9
10
  export const MAX_GIT_SNAPSHOT_BYTES = 512 * 1024 * 1024;
10
- const SNAPSHOT_VERSION = 1;
11
11
 
12
12
  export interface GitManifestFile {
13
13
  path: string;
@@ -25,6 +25,7 @@ export interface GitManifest {
25
25
  syncSessions: boolean;
26
26
  snapshotSyncSessions?: boolean;
27
27
  selection?: SnapshotSelection;
28
+ localFields?: string[];
28
29
  files: GitManifestFile[];
29
30
  }
30
31
 
@@ -55,7 +56,7 @@ export function requireGitManifest(value: unknown): GitManifest {
55
56
  }
56
57
  if (
57
58
  manifest.version !== GIT_MANIFEST_VERSION ||
58
- manifest.snapshotVersion !== SNAPSHOT_VERSION ||
59
+ (manifest.snapshotVersion !== 1 && manifest.snapshotVersion !== 2 && manifest.snapshotVersion !== 3) ||
59
60
  typeof manifest.snapshotId !== "string" ||
60
61
  manifest.snapshotId.length > 512 ||
61
62
  !/^[A-Za-z0-9._-]+$/u.test(manifest.snapshotId) ||
@@ -83,11 +84,13 @@ export function requireGitManifest(value: unknown): GitManifest {
83
84
  "syncSessions",
84
85
  ...(manifest.snapshotSyncSessions === undefined ? [] : ["snapshotSyncSessions"]),
85
86
  ...(manifest.selection === undefined ? [] : ["selection"]),
87
+ ...(manifest.localFields === undefined ? [] : ["localFields"]),
86
88
  "files",
87
89
  ])
88
90
  ) {
89
91
  throw new Error("Git publication manifest is malformed.");
90
92
  }
93
+ validateSnapshotFieldPolicy({ version: manifest.snapshotVersion, localFields: manifest.localFields });
91
94
  if (manifest.selection !== undefined) portableSnapshotSelection(manifest.selection);
92
95
  let total = 0;
93
96
  const paths = new Set<string>();
@@ -122,6 +125,8 @@ export function validateGitSnapshot(snapshot: Snapshot, manifest: GitManifest, n
122
125
  const syncSessions =
123
126
  snapshot.syncSessions === true || snapshot.files.some((file) => file.path.startsWith("sessions/"));
124
127
  if (
128
+ snapshot.version !== manifest.snapshotVersion ||
129
+ !sameLocalFields(snapshot.localFields, manifest.localFields) ||
125
130
  snapshot.id !== manifest.snapshotId ||
126
131
  snapshot.createdAt !== manifest.createdAt ||
127
132
  snapshot.machine !== manifest.machine ||
@@ -144,8 +149,8 @@ export function validateGitSnapshot(snapshot: Snapshot, manifest: GitManifest, n
144
149
 
145
150
  export function prepareGitSnapshot(snapshot: Snapshot, namespace: string) {
146
151
  snapshotSelectionInclude(snapshot);
152
+ validatePortableSnapshot(snapshot);
147
153
  if (
148
- snapshot.version !== SNAPSHOT_VERSION ||
149
154
  typeof snapshot.id !== "string" ||
150
155
  !snapshot.id ||
151
156
  snapshot.id.length > 512 ||
@@ -3,6 +3,7 @@ import { encodeKey, posixJoin } from "../../paths.js";
3
3
  import { sessionTokenWarnings } from "../../settings/config.js";
4
4
  import { decodeSnapshot, encodeSnapshot } from "../../snapshot/snapshot-codec.js";
5
5
  import type { Snapshot } from "../../snapshot/snapshot-types.js";
6
+ import { validatePortableSnapshot } from "../../sync/local-fields.js";
6
7
  import { syncErrorGuidance } from "../../sync/sync-error-guidance.js";
7
8
  import { portableSnapshotSelection } from "../../sync/sync-policy.js";
8
9
  import type { LatestPointer, RemoteObject, ResolvedS3Backend } from "../backend-types.js";
@@ -409,8 +410,8 @@ function requireHistory(value: { version: number; snapshots: LatestPointer[] } |
409
410
  }
410
411
 
411
412
  function assertSnapshotIdentity(snapshot: Snapshot, expectedProfile: string) {
413
+ validatePortableSnapshot(snapshot);
412
414
  if (
413
- snapshot.version !== VERSION ||
414
415
  snapshot.profile !== expectedProfile ||
415
416
  !isSafeSnapshotReference(snapshot.id) ||
416
417
  !isSafeMetadata(snapshot.createdAt, 64) ||
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { decodeSnapshot, encodeSnapshot } from "../../snapshot/snapshot-codec.js";
3
3
  import type { Snapshot } from "../../snapshot/snapshot-types.js";
4
+ import { validatePortableSnapshot } from "../../sync/local-fields.js";
4
5
  import { syncErrorGuidance } from "../../sync/sync-error-guidance.js";
5
6
  import { portableSnapshotSelection } from "../../sync/sync-policy.js";
6
7
  import type { LatestPointer, RemoteObject, ResolvedWebDavBackend } from "../backend-types.js";
@@ -500,8 +501,8 @@ function remoteHistoryEntry(pointer: LatestPointer): RemoteHistoryEntry {
500
501
  }
501
502
 
502
503
  function assertSnapshotIdentity(snapshot: Snapshot, namespace: string) {
504
+ validatePortableSnapshot(snapshot);
503
505
  if (
504
- snapshot.version !== VERSION ||
505
506
  snapshot.profile !== namespace ||
506
507
  !isSafeReference(snapshot.id) ||
507
508
  !isSafeMetadata(snapshot.createdAt, 64) ||
@@ -13,6 +13,7 @@ import { RemoteSelectionMismatchError } from "../sync/sync-policy.js";
13
13
  import { automaticSyncSummary } from "../ui/automatic-sync-summary.js";
14
14
  import type { RunRouteResult } from "../ui/cancellable-operation.js";
15
15
  import { setSyncStatus } from "../ui/sync-status.js";
16
+ import { safeTerminalText } from "../ui/terminal-text.js";
16
17
  import { parseOptions, resolveSyncCommand, splitArgs, usage, validateCommandOptions } from "./command.js";
17
18
  import type { CommandOptions } from "./command-types.js";
18
19
 
@@ -124,6 +125,12 @@ export async function executeCommand(
124
125
  await operations.diff(ctx, options);
125
126
  return { kind: "completed" };
126
127
  }
128
+ case "conflicts": {
129
+ const { showConflicts } = await import("../sync/conflict-review.js");
130
+ throwIfAborted(options.signal);
131
+ await withLock("sync", () => showConflicts(ctx, options));
132
+ return { kind: "completed" };
133
+ }
127
134
  case "doctor": {
128
135
  const operations = await loaders.operations();
129
136
  throwIfAborted(options.signal);
@@ -244,6 +251,9 @@ async function showConfig(ctx: ExtensionCommandContext, options: CommandOptions)
244
251
  ...storageLines,
245
252
  `storage path: ${config.storagePath}`,
246
253
  `automatic sync: ${automaticSyncSummary(config.automatic)}`,
254
+ `settings field merge: ${config.mergeSettings ? "On (experimental)" : "Off"}`,
255
+ `machine-local settings.json fields: ${(config.localFields ?? []).map(safeTerminalText).join(", ") || "none"}`,
256
+ `automatic transfer at startup: ${config.automaticTransfer ? "On — selected-file upload/replace/delete; no reload" : "Off"}`,
247
257
  `included content: ${config.include.join(", ") || "none"}`,
248
258
  `sessions: ${config.include.includes("sessions") ? "included" : "not included"}`,
249
259
  `settings file: ${localConfigPath()}`,
@@ -13,6 +13,7 @@ export const SYNC_COMMANDS = [
13
13
  { name: "files", description: "Choose included content" },
14
14
  { name: "status", description: "Show sync status" },
15
15
  { name: "diff", description: "Show local/remote diff" },
16
+ { name: "conflicts", description: "Review private unresolved conflict groups" },
16
17
  { name: "doctor", description: "Check config, secrets, and lock state" },
17
18
  { name: "push", description: "Upload local settings" },
18
19
  { name: "pull", description: "Apply remote settings" },
@@ -45,6 +46,7 @@ const SYNC_FLAG_COMPLETIONS: Record<string, readonly CommandArgumentCompletion[]
45
46
  files: [SETUP_FLAG_COMPLETION],
46
47
  status: [SETUP_FLAG_COMPLETION],
47
48
  diff: [SETUP_FLAG_COMPLETION],
49
+ conflicts: [SETUP_FLAG_COMPLETION],
48
50
  doctor: [SETUP_FLAG_COMPLETION],
49
51
  push: [
50
52
  ...YES_FLAG_COMPLETIONS,
@@ -110,6 +112,7 @@ export function validateCommandOptions(command: string, options: CommandOptions)
110
112
  "files",
111
113
  "status",
112
114
  "diff",
115
+ "conflicts",
113
116
  "doctor",
114
117
  "push",
115
118
  "pull",
@@ -230,6 +233,7 @@ export function usage() {
230
233
  "Usage: /sync <command>",
231
234
  `Commands: ${commands}`,
232
235
  "Automatic sync: background startup checks only; use /sync to review and transfer. At shutdown, selected content is pushed automatically only when sessions are included. Startup checks run in TUI/RPC, not print/JSON.",
236
+ "Automatic transfer at startup is a separate opt-in: one conflict-free established-baseline transfer at idle in TUI/RPC, with conditional/lease publication and no resource reload.",
233
237
  "Settings: use /sync init or edit storage connections and sync setups in ~/.pi/agent/pi-sync.json (or the configured Pi agent directory). Version 1 and version 2 settings are unsupported and are never rewritten.",
234
238
  ].join("\n");
235
239
  }
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { backendIdentityCoordinates } from "../backends/backend-identity.js";
3
3
  import { normalizeGitBranch, normalizeGitDirectory, normalizeGitRemote } from "../backends/git/git-config.js";
4
4
  import { normalizeWebDavPath, normalizeWebDavUrl } from "../backends/webdav/webdav-config.js";
5
+ import { normalizeLocalFields } from "../sync/local-fields.js";
5
6
  import { normalizeSyncInclude } from "../sync/sync-policy.js";
6
7
  import { localConfigPath, readActiveLocalConfigDocumentForRepair } from "./config-file.js";
7
8
  import { requireSettings } from "./settings-store.js";
@@ -75,6 +76,7 @@ export async function loadPartialConfig(setupName?: string): Promise<PartialConf
75
76
  ...storageReviewFromConfig(config),
76
77
  include: [...config.include],
77
78
  automatic: config.automatic,
79
+ automaticTransfer: config.automaticTransfer ?? false,
78
80
  onSwitch: config.onSwitch,
79
81
  showStatus: config.showStatus,
80
82
  };
@@ -109,6 +111,11 @@ export function syncConfigReviewIdentity(config: AnySyncConfig) {
109
111
  backendIdentityCoordinates(config),
110
112
  config.include,
111
113
  config.automatic,
114
+ config.automaticTransfer ?? false,
115
+ config.localFields,
116
+ config.mergeSettings ?? false,
117
+ config.mergeContent ?? false,
118
+ config.partialSync ?? false,
112
119
  ]);
113
120
  }
114
121
 
@@ -155,6 +162,11 @@ function resolveSyncConfig(
155
162
  snapshotIdentity: namespace,
156
163
  include,
157
164
  automatic: setup.sync.automatic,
165
+ automaticTransfer: setup.sync.automaticTransfer ?? false,
166
+ localFields: setup.sync.localFields === undefined ? undefined : normalizeLocalFields(setup.sync.localFields),
167
+ mergeSettings: setup.sync.mergeSettings ?? false,
168
+ mergeContent: setup.sync.mergeContent ?? false,
169
+ partialSync: setup.sync.partialSync ?? false,
158
170
  onSwitch,
159
171
  skipSecretScan,
160
172
  showStatus,
@@ -193,8 +193,8 @@ export async function removeStorageConnection(name: string, signal?: AbortSignal
193
193
 
194
194
  async function updateSettings(update: (settings: PiSyncSettingsV3) => PiSyncSettingsV3, signal?: AbortSignal) {
195
195
  return updateLocalConfig((settings) => {
196
- if (settings.version !== 3) {
197
- throw new Error("Storage connections and sync setups require version 3 pi-sync settings.");
196
+ if (settings.version !== 3 && settings.version !== 4 && settings.version !== 5) {
197
+ throw new Error("Storage connections and sync setups require version 3 or 4 or 5 pi-sync settings.");
198
198
  }
199
199
  return update(settings);
200
200
  }, signal);
@@ -79,6 +79,13 @@ export type SyncSetupStorageSettings =
79
79
  export interface SyncPolicySettings {
80
80
  include: string[];
81
81
  automatic: boolean;
82
+ /** Explicit opt-in, separate from the legacy observe/shutdown policy. */
83
+ automaticTransfer?: boolean;
84
+ /** Version 4: settings.json root fields never uploaded. */
85
+ localFields?: string[];
86
+ mergeSettings?: boolean;
87
+ mergeContent?: boolean;
88
+ partialSync?: boolean;
82
89
  [key: string]: unknown;
83
90
  }
84
91
 
@@ -89,7 +96,7 @@ export interface SyncSetupSettings {
89
96
  }
90
97
 
91
98
  export interface PiSyncSettingsV3 {
92
- version: 3;
99
+ version: 3 | 4 | 5;
93
100
  activeSyncSetup?: string;
94
101
  onSwitch: OnSwitchAction;
95
102
  skipSecretScan?: boolean;
@@ -107,6 +114,11 @@ export interface SyncConfig<Backend extends ResolvedSyncBackend = ResolvedS3Back
107
114
  snapshotIdentity: string;
108
115
  include: string[];
109
116
  automatic: boolean;
117
+ automaticTransfer?: boolean;
118
+ localFields?: string[];
119
+ mergeSettings?: boolean;
120
+ mergeContent?: boolean;
121
+ partialSync?: boolean;
110
122
  onSwitch: OnSwitchAction;
111
123
  skipSecretScan: boolean;
112
124
  showStatus: boolean;
@@ -124,6 +136,7 @@ export interface PartialConfig {
124
136
  storagePath: string;
125
137
  include: string[];
126
138
  automatic: boolean;
139
+ automaticTransfer?: boolean;
127
140
  onSwitch: OnSwitchAction;
128
141
  showStatus: boolean;
129
142
  bucket?: string;