@remnic/core 9.43.0 → 9.45.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 (258) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +42 -41
  3. package/dist/access-authorization-probe.d.ts +5 -5
  4. package/dist/access-authorization-probe.js +43 -42
  5. package/dist/access-boundary.d.ts +5 -5
  6. package/dist/access-boundary.js +42 -41
  7. package/dist/access-cli.js +100 -97
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +5 -5
  10. package/dist/access-extraction-force-flush.js +42 -41
  11. package/dist/access-http-lcm-compaction.d.ts +5 -5
  12. package/dist/access-http-lifecycle-flush.d.ts +5 -5
  13. package/dist/access-http-query.d.ts +8 -0
  14. package/dist/access-http-query.js +180 -0
  15. package/dist/access-http.d.ts +5 -5
  16. package/dist/access-http.js +51 -49
  17. package/dist/access-identity-continuity-surface.d.ts +4 -4
  18. package/dist/access-identity-continuity-surface.js +42 -41
  19. package/dist/access-lcm-surface.d.ts +5 -5
  20. package/dist/access-lcm-surface.js +42 -41
  21. package/dist/access-mcp.d.ts +5 -5
  22. package/dist/access-mcp.js +47 -46
  23. package/dist/access-namespace-preflight.js +42 -41
  24. package/dist/access-observe-write-surface.d.ts +5 -5
  25. package/dist/access-observe-write-surface.js +42 -41
  26. package/dist/access-operations-batch.js +44 -43
  27. package/dist/access-operations.d.ts +8 -8
  28. package/dist/access-operations.js +46 -45
  29. package/dist/access-recall-concurrency.d.ts +5 -5
  30. package/dist/access-recall-concurrency.js +42 -41
  31. package/dist/access-recall-response.d.ts +5 -5
  32. package/dist/access-recall-response.js +42 -41
  33. package/dist/access-recall-surface.d.ts +5 -5
  34. package/dist/access-recall-surface.js +42 -41
  35. package/dist/access-schema.d.ts +84 -84
  36. package/dist/access-schema.js +3 -3
  37. package/dist/{access-service-D1BkxwSf.d.ts → access-service-DBo88Dmm.d.ts} +14 -4
  38. package/dist/access-service-helpers.d.ts +5 -5
  39. package/dist/access-service.d.ts +5 -5
  40. package/dist/access-service.js +42 -41
  41. package/dist/access-surface-catalog.d.ts +5 -5
  42. package/dist/access-surface-catalog.js +5 -0
  43. package/dist/access-surface-catalog.js.map +1 -1
  44. package/dist/access-wearables-meetings-surface.d.ts +2 -2
  45. package/dist/{auto-sync-UPTUVE4X.js → auto-sync-5OE5QOZB.js} +3 -3
  46. package/dist/bootstrap.d.ts +4 -4
  47. package/dist/briefing.d.ts +1 -1
  48. package/dist/briefing.js +9 -8
  49. package/dist/buffer.d.ts +1 -1
  50. package/dist/buffer.js +6 -6
  51. package/dist/causal-behavior.js +2 -2
  52. package/dist/causal-chain.js +2 -2
  53. package/dist/causal-consolidation.js +13 -12
  54. package/dist/causal-consolidation.js.map +1 -1
  55. package/dist/causal-retrieval.js +2 -2
  56. package/dist/causal-trajectory-graph.js +2 -2
  57. package/dist/causal-trajectory.js +1 -1
  58. package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
  59. package/dist/{chunk-CMENJQ6I.js → chunk-2QR2RRH6.js} +2 -2
  60. package/dist/{chunk-ZERSIPFC.js → chunk-3HKQOFOV.js} +22 -20
  61. package/dist/{chunk-ZERSIPFC.js.map → chunk-3HKQOFOV.js.map} +1 -1
  62. package/dist/{chunk-4DYBLQZC.js → chunk-3HLCRYBV.js} +3 -3
  63. package/dist/{chunk-L2DPF4DF.js → chunk-3SHB4YAW.js} +4 -4
  64. package/dist/{chunk-UV72DEZD.js → chunk-4B77XSG5.js} +5 -5
  65. package/dist/{chunk-GUQYMOPU.js → chunk-4LMA77JH.js} +5 -5
  66. package/dist/{chunk-Q53RLEW7.js → chunk-5SM35XZR.js} +2 -2
  67. package/dist/{chunk-YJHU5B3M.js → chunk-5YGO5VEH.js} +2 -2
  68. package/dist/{chunk-AX2J2LC4.js → chunk-5ZE7QHTK.js} +2 -2
  69. package/dist/{chunk-5QZEVMLY.js → chunk-65V4K4MT.js} +56 -38
  70. package/dist/chunk-65V4K4MT.js.map +1 -0
  71. package/dist/{chunk-YFHMW37V.js → chunk-6CZPDBJS.js} +5 -5
  72. package/dist/{chunk-BI6RE2ZS.js → chunk-6OLQJJYE.js} +2 -2
  73. package/dist/chunk-6UWB5PTR.js +413 -0
  74. package/dist/chunk-6UWB5PTR.js.map +1 -0
  75. package/dist/chunk-6ZYQ36IB.js +30 -0
  76. package/dist/chunk-6ZYQ36IB.js.map +1 -0
  77. package/dist/{chunk-GS5UVGQK.js → chunk-7JO6RVBI.js} +6 -6
  78. package/dist/{chunk-MBTPUVHZ.js → chunk-7PVFOFPJ.js} +2 -2
  79. package/dist/{chunk-KPJQTSRJ.js → chunk-C7EI7CA6.js} +2 -2
  80. package/dist/{chunk-NRV4Y3PM.js → chunk-CAGPP5NB.js} +1 -1
  81. package/dist/{chunk-NF3U3MPG.js → chunk-ECK75GDJ.js} +2 -2
  82. package/dist/{chunk-KDXD2AM6.js → chunk-FLBPATMZ.js} +44 -44
  83. package/dist/{chunk-OEXKYYVH.js → chunk-FVGQ5LIS.js} +6 -6
  84. package/dist/{chunk-AR2FSZBP.js → chunk-G24XIK6H.js} +2 -2
  85. package/dist/{chunk-5TOV3D6U.js → chunk-GBJJUPNV.js} +6 -6
  86. package/dist/{chunk-UP67AFRN.js → chunk-GOPWZYZH.js} +29 -29
  87. package/dist/chunk-GOPWZYZH.js.map +1 -0
  88. package/dist/{chunk-EPQ76W5T.js → chunk-GQBZ7EVF.js} +2 -2
  89. package/dist/{chunk-BGXHVOBI.js → chunk-HLPD7B7P.js} +2 -2
  90. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  91. package/dist/{chunk-JDXDA5OO.js → chunk-J4OKD6QX.js} +2 -2
  92. package/dist/{chunk-LZ5JKQCS.js → chunk-JZ7SSAQ5.js} +3 -3
  93. package/dist/chunk-K6OTW3TP.js +24 -0
  94. package/dist/chunk-K6OTW3TP.js.map +1 -0
  95. package/dist/{chunk-EHKDORQN.js → chunk-NVRYW264.js} +9 -9
  96. package/dist/{chunk-MU7ZHSZE.js → chunk-PO5LSKQO.js} +2 -2
  97. package/dist/{chunk-ONHRRJZM.js → chunk-QJAQNXWI.js} +6 -6
  98. package/dist/{chunk-WGCTFGLW.js → chunk-QQ3NDJOM.js} +3 -3
  99. package/dist/{chunk-BEQOZOYZ.js → chunk-RVLJEII5.js} +30 -511
  100. package/dist/chunk-RVLJEII5.js.map +1 -0
  101. package/dist/chunk-S5SLVSVY.js +517 -0
  102. package/dist/chunk-S5SLVSVY.js.map +1 -0
  103. package/dist/{chunk-QY66HJ4X.js → chunk-SS7YWVPF.js} +5 -5
  104. package/dist/{chunk-ZVVASRUT.js → chunk-U5G66ORD.js} +90 -88
  105. package/dist/chunk-U5G66ORD.js.map +1 -0
  106. package/dist/{chunk-JYIFGGEM.js → chunk-UZGJFIIG.js} +2 -2
  107. package/dist/{chunk-NHRNYJPJ.js → chunk-VCGPO2VD.js} +7 -7
  108. package/dist/{chunk-F2KMAFEB.js → chunk-VRYJSKOM.js} +2 -2
  109. package/dist/{chunk-HNZKVLAN.js → chunk-W6ROK2KU.js} +2 -2
  110. package/dist/{chunk-SEUSFKNS.js → chunk-WC4SPYHX.js} +14 -14
  111. package/dist/{chunk-MSXWUXFE.js → chunk-WG2PI44B.js} +2 -2
  112. package/dist/{chunk-4VXGRIH6.js → chunk-WUNIWKY4.js} +5 -5
  113. package/dist/{chunk-3C2TGBDB.js → chunk-XFQRFWVS.js} +2 -2
  114. package/dist/{chunk-5N26WBPQ.js → chunk-XMZV27S5.js} +5 -5
  115. package/dist/{chunk-JGXXPDM2.js → chunk-YQQK7W7T.js} +3 -3
  116. package/dist/{chunk-3F2LKUMU.js → chunk-ZAB5PG65.js} +2 -2
  117. package/dist/{chunk-MVW234LO.js → chunk-ZPJJE7MF.js} +2 -2
  118. package/dist/{chunk-6GIMMNVK.js → chunk-ZS5UZU5M.js} +4 -4
  119. package/dist/{cli-IUhV-lmk.d.ts → cli-DQ4VhjqX.d.ts} +3 -3
  120. package/dist/cli.d.ts +6 -6
  121. package/dist/cli.js +69 -67
  122. package/dist/compounding/engine.d.ts +1 -1
  123. package/dist/compounding/engine.js +9 -8
  124. package/dist/connectors/codex-materialize-runner.js +9 -8
  125. package/dist/connectors/index.js +9 -8
  126. package/dist/consolidation-provenance-check.d.ts +1 -1
  127. package/dist/consolidation-undo.d.ts +1 -1
  128. package/dist/contradiction/index.d.ts +1 -1
  129. package/dist/convergence-refresh.d.ts +24 -0
  130. package/dist/convergence-refresh.js +75 -0
  131. package/dist/convergence-refresh.js.map +1 -0
  132. package/dist/conversation-index/backend.js +2 -2
  133. package/dist/corpus-watermark.js +20 -19
  134. package/dist/entity-retrieval.d.ts +1 -1
  135. package/dist/entity-retrieval.js +9 -8
  136. package/dist/explicit-capture.d.ts +4 -4
  137. package/dist/explicit-capture.js +4 -3
  138. package/dist/extraction.js +15 -14
  139. package/dist/{graph-edge-decay-Z6ENALU2.js → graph-edge-decay-GF3ZSGSP.js} +4 -4
  140. package/dist/graph-edge-decay-GF3ZSGSP.js.map +1 -0
  141. package/dist/graph-snapshot.js +3 -3
  142. package/dist/graph.js +2 -2
  143. package/dist/index.d.ts +398 -398
  144. package/dist/index.js +161 -158
  145. package/dist/lcm/index.js +3 -3
  146. package/dist/maintenance/memory-governance.js +10 -9
  147. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
  148. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -8
  149. package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
  150. package/dist/maintenance/rebuild-memory-projection.js +11 -10
  151. package/dist/{maintenance-DVBLXkFw.d.ts → maintenance-_RV38GlE.d.ts} +1 -1
  152. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  153. package/dist/memory-worth-outcomes.d.ts +1 -1
  154. package/dist/namespaces/migrate.d.ts +1 -1
  155. package/dist/namespaces/migrate.js +19 -18
  156. package/dist/namespaces/search.js +9 -9
  157. package/dist/namespaces/storage.d.ts +1 -1
  158. package/dist/namespaces/storage.js +9 -8
  159. package/dist/offline-sync-impression-drain.js +4 -4
  160. package/dist/operator-doctor-corpus.js +21 -20
  161. package/dist/operator-toolkit.d.ts +1 -1
  162. package/dist/operator-toolkit.js +30 -29
  163. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  164. package/dist/orchestration/maintenance.d.ts +2 -2
  165. package/dist/orchestration/maintenance.js +16 -15
  166. package/dist/{orchestrator-fYTncnLy.d.ts → orchestrator-B2ecFpRO.d.ts} +4 -4
  167. package/dist/orchestrator.d.ts +4 -4
  168. package/dist/orchestrator.js +101 -98
  169. package/dist/qmd.js +4 -4
  170. package/dist/reconcile/manifest.d.ts +33 -0
  171. package/dist/reconcile/manifest.js +248 -0
  172. package/dist/reconcile/manifest.js.map +1 -0
  173. package/dist/reconcile/plan.d.ts +1 -1
  174. package/dist/reconcile/plan.js +8 -404
  175. package/dist/reconcile/plan.js.map +1 -1
  176. package/dist/resume-bundles.js +1 -1
  177. package/dist/schemas.d.ts +76 -76
  178. package/dist/search/factory.js +8 -8
  179. package/dist/search/index.js +14 -14
  180. package/dist/semantic-consolidation.js +11 -10
  181. package/dist/semantic-rule-promotion.js +9 -8
  182. package/dist/semantic-rule-verifier.js +10 -9
  183. package/dist/{service-CLuMO-pa.d.ts → service-DW06sY3P.d.ts} +1 -1
  184. package/dist/shared-context/manager.d.ts +8 -8
  185. package/dist/source-agent-qualifier.js +14 -13
  186. package/dist/{storage-Blr71GpS.d.ts → storage-qRIQPA6F.d.ts} +12 -0
  187. package/dist/storage.d.ts +1 -1
  188. package/dist/storage.js +8 -7
  189. package/dist/temporal-supersession.d.ts +1 -1
  190. package/dist/tier-migration.d.ts +1 -1
  191. package/dist/transfer/autodetect.js +1 -1
  192. package/dist/transfer/backup.js +1 -1
  193. package/dist/transfer/capsule-export.js +2 -2
  194. package/dist/transfer/types.d.ts +66 -66
  195. package/dist/verified-recall.js +10 -9
  196. package/package.json +12 -2
  197. package/src/access-http-query.ts +25 -0
  198. package/src/access-http.test.ts +136 -0
  199. package/src/access-http.ts +26 -26
  200. package/src/access-service-offline-file-content.test.ts +40 -0
  201. package/src/access-service.ts +34 -0
  202. package/src/access-surface-catalog.ts +5 -0
  203. package/src/convergence-refresh.ts +28 -0
  204. package/src/orchestrator-convergence-refresh.test.ts +114 -0
  205. package/src/orchestrator.ts +5 -4
  206. package/src/reconcile/manifest.test.ts +261 -0
  207. package/src/reconcile/manifest.ts +278 -0
  208. package/src/reconcile/plan.ts +1 -0
  209. package/src/storage/content-hash-index.ts +26 -0
  210. package/dist/chunk-5QZEVMLY.js.map +0 -1
  211. package/dist/chunk-BEQOZOYZ.js.map +0 -1
  212. package/dist/chunk-UP67AFRN.js.map +0 -1
  213. package/dist/chunk-ZVVASRUT.js.map +0 -1
  214. /package/dist/{graph-edge-decay-Z6ENALU2.js.map → access-http-query.js.map} +0 -0
  215. /package/dist/{auto-sync-UPTUVE4X.js.map → auto-sync-5OE5QOZB.js.map} +0 -0
  216. /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
  217. /package/dist/{chunk-CMENJQ6I.js.map → chunk-2QR2RRH6.js.map} +0 -0
  218. /package/dist/{chunk-4DYBLQZC.js.map → chunk-3HLCRYBV.js.map} +0 -0
  219. /package/dist/{chunk-L2DPF4DF.js.map → chunk-3SHB4YAW.js.map} +0 -0
  220. /package/dist/{chunk-UV72DEZD.js.map → chunk-4B77XSG5.js.map} +0 -0
  221. /package/dist/{chunk-GUQYMOPU.js.map → chunk-4LMA77JH.js.map} +0 -0
  222. /package/dist/{chunk-Q53RLEW7.js.map → chunk-5SM35XZR.js.map} +0 -0
  223. /package/dist/{chunk-YJHU5B3M.js.map → chunk-5YGO5VEH.js.map} +0 -0
  224. /package/dist/{chunk-AX2J2LC4.js.map → chunk-5ZE7QHTK.js.map} +0 -0
  225. /package/dist/{chunk-YFHMW37V.js.map → chunk-6CZPDBJS.js.map} +0 -0
  226. /package/dist/{chunk-BI6RE2ZS.js.map → chunk-6OLQJJYE.js.map} +0 -0
  227. /package/dist/{chunk-GS5UVGQK.js.map → chunk-7JO6RVBI.js.map} +0 -0
  228. /package/dist/{chunk-MBTPUVHZ.js.map → chunk-7PVFOFPJ.js.map} +0 -0
  229. /package/dist/{chunk-KPJQTSRJ.js.map → chunk-C7EI7CA6.js.map} +0 -0
  230. /package/dist/{chunk-NRV4Y3PM.js.map → chunk-CAGPP5NB.js.map} +0 -0
  231. /package/dist/{chunk-NF3U3MPG.js.map → chunk-ECK75GDJ.js.map} +0 -0
  232. /package/dist/{chunk-KDXD2AM6.js.map → chunk-FLBPATMZ.js.map} +0 -0
  233. /package/dist/{chunk-OEXKYYVH.js.map → chunk-FVGQ5LIS.js.map} +0 -0
  234. /package/dist/{chunk-AR2FSZBP.js.map → chunk-G24XIK6H.js.map} +0 -0
  235. /package/dist/{chunk-5TOV3D6U.js.map → chunk-GBJJUPNV.js.map} +0 -0
  236. /package/dist/{chunk-EPQ76W5T.js.map → chunk-GQBZ7EVF.js.map} +0 -0
  237. /package/dist/{chunk-BGXHVOBI.js.map → chunk-HLPD7B7P.js.map} +0 -0
  238. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  239. /package/dist/{chunk-JDXDA5OO.js.map → chunk-J4OKD6QX.js.map} +0 -0
  240. /package/dist/{chunk-LZ5JKQCS.js.map → chunk-JZ7SSAQ5.js.map} +0 -0
  241. /package/dist/{chunk-EHKDORQN.js.map → chunk-NVRYW264.js.map} +0 -0
  242. /package/dist/{chunk-MU7ZHSZE.js.map → chunk-PO5LSKQO.js.map} +0 -0
  243. /package/dist/{chunk-ONHRRJZM.js.map → chunk-QJAQNXWI.js.map} +0 -0
  244. /package/dist/{chunk-WGCTFGLW.js.map → chunk-QQ3NDJOM.js.map} +0 -0
  245. /package/dist/{chunk-QY66HJ4X.js.map → chunk-SS7YWVPF.js.map} +0 -0
  246. /package/dist/{chunk-JYIFGGEM.js.map → chunk-UZGJFIIG.js.map} +0 -0
  247. /package/dist/{chunk-NHRNYJPJ.js.map → chunk-VCGPO2VD.js.map} +0 -0
  248. /package/dist/{chunk-F2KMAFEB.js.map → chunk-VRYJSKOM.js.map} +0 -0
  249. /package/dist/{chunk-HNZKVLAN.js.map → chunk-W6ROK2KU.js.map} +0 -0
  250. /package/dist/{chunk-SEUSFKNS.js.map → chunk-WC4SPYHX.js.map} +0 -0
  251. /package/dist/{chunk-MSXWUXFE.js.map → chunk-WG2PI44B.js.map} +0 -0
  252. /package/dist/{chunk-4VXGRIH6.js.map → chunk-WUNIWKY4.js.map} +0 -0
  253. /package/dist/{chunk-3C2TGBDB.js.map → chunk-XFQRFWVS.js.map} +0 -0
  254. /package/dist/{chunk-5N26WBPQ.js.map → chunk-XMZV27S5.js.map} +0 -0
  255. /package/dist/{chunk-JGXXPDM2.js.map → chunk-YQQK7W7T.js.map} +0 -0
  256. /package/dist/{chunk-3F2LKUMU.js.map → chunk-ZAB5PG65.js.map} +0 -0
  257. /package/dist/{chunk-MVW234LO.js.map → chunk-ZPJJE7MF.js.map} +0 -0
  258. /package/dist/{chunk-6GIMMNVK.js.map → chunk-ZS5UZU5M.js.map} +0 -0
@@ -0,0 +1,278 @@
1
+ import { createHash } from "node:crypto";
2
+ import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
3
+ import { ContentHashIndex, type ContentHashPathEntry } from "../storage/content-hash-index.js";
4
+ import type { MemoryFrontmatter, MemoryStatus } from "../types.js";
5
+ import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
6
+ import {
7
+ type ReconcileFileState,
8
+ type ReconcilePlan,
9
+ type ReconcilePlanEntry,
10
+ summarizeReconcilePlan,
11
+ } from "./plan.js";
12
+
13
+ export const RECONCILE_MANIFEST_FORMAT = "remnic-reconcile-manifest";
14
+ export const RECONCILE_MANIFEST_SCHEMA_VERSION = 1;
15
+
16
+ export interface ReconcileMemoryIdentity {
17
+ id: string;
18
+ category: string;
19
+ contentHash: string;
20
+ status: MemoryStatus;
21
+ }
22
+
23
+ export interface ReconcileManifestFile extends ReconcileFileState {
24
+ memory?: ReconcileMemoryIdentity;
25
+ }
26
+
27
+ type ActiveFactManifestFile = ReconcileManifestFile & { memory: ReconcileMemoryIdentity };
28
+
29
+ export interface ReconcileManifest {
30
+ format: typeof RECONCILE_MANIFEST_FORMAT;
31
+ schemaVersion: typeof RECONCILE_MANIFEST_SCHEMA_VERSION;
32
+ files: ReconcileManifestFile[];
33
+ }
34
+
35
+ export interface BuildReconcileManifestOptions {
36
+ files: Iterable<ReconcileFileState>;
37
+ readFile: (file: ReconcileFileState) => Promise<Buffer | string | null>;
38
+ cachedFiles?: Iterable<ReconcileManifestFile>;
39
+ }
40
+
41
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
42
+ const MEMORY_DIRS = new Set(RECALL_FALLBACK_DIRS);
43
+
44
+ function isMemoryPath(filePath: string): boolean {
45
+ if (!filePath.endsWith(".md")) return false;
46
+ const segments = filePath.split("/");
47
+ let index = 0;
48
+ if (segments[index] === "cold" || segments[index] === "archive") index += 1;
49
+ return MEMORY_DIRS.has(segments[index] ?? "");
50
+ }
51
+
52
+ function parseScalar(value: string | undefined): string | undefined {
53
+ if (value === undefined) return undefined;
54
+ const trimmed = value.trim();
55
+ if (trimmed.length === 0) return undefined;
56
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
57
+ try {
58
+ const parsed = JSON.parse(trimmed) as unknown;
59
+ return typeof parsed === "string" ? parsed : trimmed;
60
+ } catch {
61
+ return trimmed.slice(1, -1).replace(/\\"/g, '"');
62
+ }
63
+ }
64
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
65
+ return trimmed.slice(1, -1).replace(/''/g, "'");
66
+ }
67
+ return trimmed;
68
+ }
69
+
70
+ function parsedMemoryIdentity(filePath: string, raw: Buffer | string): ReconcileMemoryIdentity | undefined {
71
+ if (!isMemoryPath(filePath)) return undefined;
72
+ const match = (Buffer.isBuffer(raw) ? raw.toString("utf8") : raw).match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
73
+ if (!match) return undefined;
74
+ const fields = new Map<string, string>();
75
+ for (const line of match[1].split("\n")) {
76
+ const separator = line.indexOf(":");
77
+ if (separator > 0) fields.set(line.slice(0, separator).trim(), line.slice(separator + 1).trim());
78
+ }
79
+ const id = parseScalar(fields.get("id"));
80
+ if (!id) return undefined;
81
+ const category = parseScalar(fields.get("category")) ?? "fact";
82
+ const storedHash = parseScalar(fields.get("contentHash"));
83
+ const contentHash =
84
+ storedHash && SHA256_PATTERN.test(storedHash)
85
+ ? storedHash.toLowerCase()
86
+ : ContentHashIndex.computeHash(match[2].trim());
87
+ const status = inferMemoryStatus(
88
+ {
89
+ status: parseScalar(fields.get("status")) as MemoryStatus | undefined,
90
+ archivedAt: parseScalar(fields.get("archivedAt")),
91
+ } as MemoryFrontmatter,
92
+ filePath
93
+ );
94
+ return { id, category, contentHash, status };
95
+ }
96
+
97
+ export async function buildReconcileManifest(options: BuildReconcileManifestOptions): Promise<ReconcileManifest> {
98
+ const cachedByPath = new Map<string, ReconcileManifestFile>();
99
+ for (const cached of options.cachedFiles ?? []) {
100
+ cachedByPath.set(cached.path, cached);
101
+ }
102
+
103
+ const files: ReconcileManifestFile[] = [];
104
+ for (const file of options.files) {
105
+ const cached = cachedByPath.get(file.path);
106
+ if (cached?.sha256.toLowerCase() === file.sha256.toLowerCase()) {
107
+ files.push({ ...file, ...(cached.memory ? { memory: cached.memory } : {}) });
108
+ continue;
109
+ }
110
+
111
+ let raw: Buffer | string | null = null;
112
+ if (isMemoryPath(file.path)) {
113
+ try {
114
+ raw = await options.readFile(file);
115
+ } catch {
116
+ raw = null;
117
+ }
118
+ }
119
+ if (raw !== null && createHash("sha256").update(raw).digest("hex") !== file.sha256.toLowerCase()) {
120
+ raw = null;
121
+ }
122
+ const memory = raw === null ? undefined : parsedMemoryIdentity(file.path, raw);
123
+ files.push({ ...file, ...(memory ? { memory } : {}) });
124
+ }
125
+ files.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
126
+ return {
127
+ format: RECONCILE_MANIFEST_FORMAT,
128
+ schemaVersion: RECONCILE_MANIFEST_SCHEMA_VERSION,
129
+ files,
130
+ };
131
+ }
132
+
133
+ function activeFactByPath(manifest: ReconcileManifest | undefined): Map<string, ActiveFactManifestFile> {
134
+ const result = new Map<string, ActiveFactManifestFile>();
135
+ for (const file of manifest?.files ?? []) {
136
+ if (file.memory?.category === "fact" && file.memory.status === "active") {
137
+ result.set(file.path, file as ActiveFactManifestFile);
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+
143
+ function contentHashRows(files: Iterable<ActiveFactManifestFile>): ContentHashPathEntry[] {
144
+ const rows: ContentHashPathEntry[] = [];
145
+ for (const file of files) {
146
+ rows.push({ path: file.path, contentHash: file.memory.contentHash });
147
+ }
148
+ return rows;
149
+ }
150
+
151
+ function comparePlanEntries(left: ReconcilePlanEntry, right: ReconcilePlanEntry): number {
152
+ if (left.namespace !== right.namespace) return left.namespace < right.namespace ? -1 : 1;
153
+ return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
154
+ }
155
+
156
+ export function collapseActiveFactDuplicates(
157
+ plan: ReconcilePlan,
158
+ localManifests: ReadonlyMap<string, ReconcileManifest>,
159
+ peerManifests: ReadonlyMap<string, ReconcileManifest>
160
+ ): ReconcilePlan {
161
+ const entriesByNamespace = new Map<string, ReconcilePlanEntry[]>();
162
+ for (const entry of plan.entries) {
163
+ const entries = entriesByNamespace.get(entry.namespace) ?? [];
164
+ entries.push({ ...entry });
165
+ entriesByNamespace.set(entry.namespace, entries);
166
+ }
167
+
168
+ let changed = false;
169
+ for (const namespace of new Set([...localManifests.keys(), ...peerManifests.keys()])) {
170
+ const entries = entriesByNamespace.get(namespace) ?? [];
171
+ const localManifest = localManifests.get(namespace);
172
+ const peerManifest = peerManifests.get(namespace);
173
+ const localByPath = activeFactByPath(localManifest);
174
+ const peerByPath = activeFactByPath(peerManifest);
175
+ const localFilesByPath = new Map((localManifest?.files ?? []).map((file) => [file.path, file]));
176
+ const peerFilesByPath = new Map((peerManifest?.files ?? []).map((file) => [file.path, file]));
177
+ const localByHash = new Map<string, ActiveFactManifestFile[]>();
178
+ const peerByHash = new Map<string, ActiveFactManifestFile[]>();
179
+
180
+ for (const file of localByPath.values()) {
181
+ const hash = file.memory.contentHash;
182
+ const bucket = localByHash.get(hash) ?? [];
183
+ bucket.push(file);
184
+ localByHash.set(hash, bucket);
185
+ }
186
+ for (const file of peerByPath.values()) {
187
+ const hash = file.memory.contentHash;
188
+ const bucket = peerByHash.get(hash) ?? [];
189
+ bucket.push(file);
190
+ peerByHash.set(hash, bucket);
191
+ }
192
+
193
+ const removed = new Set<ReconcilePlanEntry>();
194
+ const replacements: ReconcilePlanEntry[] = [];
195
+ for (const hash of new Set([...localByHash.keys(), ...peerByHash.keys()])) {
196
+ const localCandidates = (localByHash.get(hash) ?? []).filter((file) => {
197
+ const opposite = peerFilesByPath.get(file.path);
198
+ const activeOpposite = peerByPath.get(file.path);
199
+ return opposite === undefined || activeOpposite?.memory.contentHash === hash;
200
+ });
201
+ const peerCandidates = (peerByHash.get(hash) ?? []).filter((file) => {
202
+ const opposite = localFilesByPath.get(file.path);
203
+ const activeOpposite = localByPath.get(file.path);
204
+ return opposite === undefined || activeOpposite?.memory.contentHash === hash;
205
+ });
206
+ const localPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(localCandidates));
207
+ const peerPath = ContentHashIndex.resolvePathByHash(hash, contentHashRows(peerCandidates));
208
+
209
+ if (localPath && peerPath && localPath !== peerPath) {
210
+ const localFile = localByPath.get(localPath);
211
+ const peerFile = peerByPath.get(peerPath);
212
+ if (!localFile || !peerFile) continue;
213
+ const duplicatePaths = new Set([
214
+ ...localCandidates.map((file) => file.path),
215
+ ...peerCandidates.map((file) => file.path),
216
+ ]);
217
+ const unsafeEntry = entries.some(
218
+ (entry) => duplicatePaths.has(entry.path) && (entry.action === "suppress" || entry.action === "conflict")
219
+ );
220
+ if (unsafeEntry) continue;
221
+ for (const entry of entries) {
222
+ if (
223
+ duplicatePaths.has(entry.path) &&
224
+ (entry.action === "pull" || entry.action === "push" || entry.action === "identical")
225
+ ) {
226
+ removed.add(entry);
227
+ }
228
+ }
229
+ replacements.push({
230
+ path: localPath < peerPath ? localPath : peerPath,
231
+ namespace,
232
+ action: "identical",
233
+ reason: "semantic_duplicate",
234
+ localSha256: localFile.sha256,
235
+ peerSha256: peerFile.sha256,
236
+ });
237
+ changed = true;
238
+ continue;
239
+ }
240
+
241
+ const sameSideEntries = entries.filter((entry) => {
242
+ if (localPath && entry.reason === "local_only") {
243
+ return localCandidates.some((file) => file.path === entry.path);
244
+ }
245
+ if (peerPath && entry.reason === "peer_only") {
246
+ return peerCandidates.some((file) => file.path === entry.path);
247
+ }
248
+ return false;
249
+ });
250
+ const canonicalPath = localPath ?? peerPath;
251
+ const hasSharedCanonical = localPath !== undefined && localPath === peerPath;
252
+ if (!canonicalPath || sameSideEntries.length === 0 || (!hasSharedCanonical && sameSideEntries.length < 2)) {
253
+ continue;
254
+ }
255
+ for (const entry of sameSideEntries) {
256
+ if (entry.path !== canonicalPath) {
257
+ removed.add(entry);
258
+ changed = true;
259
+ }
260
+ }
261
+ }
262
+
263
+ if (removed.size > 0 || replacements.length > 0) {
264
+ entriesByNamespace.set(
265
+ namespace,
266
+ [...entries.filter((entry) => !removed.has(entry)), ...replacements].sort(comparePlanEntries)
267
+ );
268
+ }
269
+ }
270
+
271
+ if (!changed) return plan;
272
+ const entries = [...entriesByNamespace.values()].flat().sort(comparePlanEntries);
273
+ return {
274
+ entries,
275
+ byNamespace: summarizeReconcilePlan(entries),
276
+ converged: entries.every((entry) => entry.action === "identical"),
277
+ };
278
+ }
@@ -79,6 +79,7 @@ export type ReconcileReason =
79
79
  | "peer_changed"
80
80
  | "local_changed"
81
81
  | "same_content"
82
+ | "semantic_duplicate"
82
83
  | "both_modified"
83
84
  | "peer_deleted"
84
85
  | "local_deleted"
@@ -84,6 +84,11 @@ type DiskFingerprint = {
84
84
  ctimeMs: number;
85
85
  birthtimeMs: number;
86
86
  };
87
+ export interface ContentHashPathEntry {
88
+ readonly path: string;
89
+ readonly contentHash: string;
90
+ }
91
+
87
92
  /**
88
93
  * Content-hash dedup index for facts.
89
94
  * Normalizes content (lowercase, strip punctuation, collapse whitespace),
@@ -669,6 +674,27 @@ export class ContentHashIndex {
669
674
  this.dirty = true;
670
675
  }
671
676
 
677
+ /**
678
+ * Resolve a pre-computed semantic hash to one deterministic corpus path.
679
+ *
680
+ * This lookup is separate from durable hash membership: `this.hashes` stays
681
+ * one hash per line and `has(content)` keeps its existing raw-content contract.
682
+ * Reconciliation supplies short-lived manifest rows where hash and path coexist.
683
+ */
684
+ static resolvePathByHash(
685
+ hash: string,
686
+ entries: Iterable<ContentHashPathEntry>,
687
+ ): string | undefined {
688
+ const canonicalHash = hash.toLowerCase();
689
+ if (!/^[a-f0-9]{64}$/.test(canonicalHash)) return undefined;
690
+ let canonicalPath: string | undefined;
691
+ for (const entry of entries) {
692
+ if (entry.contentHash.toLowerCase() !== canonicalHash || entry.path.length === 0) continue;
693
+ if (canonicalPath === undefined || entry.path < canonicalPath) canonicalPath = entry.path;
694
+ }
695
+ return canonicalPath;
696
+ }
697
+
672
698
  /** Normalize content (delegates to content-hash.ts for a single source of truth). */
673
699
  static normalizeContent(content: string): string {
674
700
  return normalizeContent(content);