@remnic/core 9.7.8 → 9.7.10

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 (406) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +32 -30
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-boundary.d.ts +6 -6
  5. package/dist/access-boundary.js +33 -31
  6. package/dist/access-cli.js +92 -90
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-http.d.ts +6 -6
  9. package/dist/access-http.js +40 -38
  10. package/dist/access-identity-continuity-surface.d.ts +5 -5
  11. package/dist/access-identity-continuity-surface.js +32 -30
  12. package/dist/access-lcm-surface.d.ts +6 -6
  13. package/dist/access-lcm-surface.js +32 -30
  14. package/dist/access-mcp.d.ts +6 -6
  15. package/dist/access-mcp.js +37 -35
  16. package/dist/access-observe-write-surface.d.ts +6 -6
  17. package/dist/access-observe-write-surface.js +32 -30
  18. package/dist/access-operations-batch.js +35 -33
  19. package/dist/access-operations.d.ts +9 -9
  20. package/dist/access-operations.js +36 -34
  21. package/dist/access-recall-surface.d.ts +6 -6
  22. package/dist/access-recall-surface.js +32 -30
  23. package/dist/access-schema.d.ts +64 -64
  24. package/dist/{access-service-CxDZXTKt.d.ts → access-service-DSuzikHQ.d.ts} +4 -4
  25. package/dist/access-service.d.ts +6 -6
  26. package/dist/access-service.js +32 -30
  27. package/dist/access-surface-catalog.d.ts +6 -6
  28. package/dist/action-confidence.d.ts +1 -1
  29. package/dist/active-memory-bridge.d.ts +1 -1
  30. package/dist/active-recall.d.ts +1 -1
  31. package/dist/active-recall.js +1 -1
  32. package/dist/adapters/index.js +4 -4
  33. package/dist/adapters/registry.js +2 -2
  34. package/dist/{auto-sync-676N6N6Y.js → auto-sync-AAMTVZMK.js} +5 -5
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +5 -5
  38. package/dist/briefing.d.ts +2 -2
  39. package/dist/briefing.js +15 -13
  40. package/dist/buffer-surprise-report.d.ts +1 -1
  41. package/dist/buffer.d.ts +2 -2
  42. package/dist/bulk-import/index.d.ts +3 -3
  43. package/dist/calibration.d.ts +1 -1
  44. package/dist/calibration.js +2 -2
  45. package/dist/capabilities.d.ts +1 -1
  46. package/dist/{capsule-merge-GK5E647P.js → capsule-merge-Z2HUO6IK.js} +10 -2
  47. package/dist/capsule-merge-Z2HUO6IK.js.map +1 -0
  48. package/dist/{catalog-DzU9HowE.d.ts → catalog-BNwjACa5.d.ts} +1 -1
  49. package/dist/causal-behavior.d.ts +1 -1
  50. package/dist/causal-consolidation.d.ts +1 -1
  51. package/dist/causal-consolidation.js +16 -14
  52. package/dist/causal-consolidation.js.map +1 -1
  53. package/dist/causal-trajectory-graph.d.ts +1 -1
  54. package/dist/{chunk-HHAA35A3.js → chunk-2VN4JAX7.js} +548 -161
  55. package/dist/chunk-2VN4JAX7.js.map +1 -0
  56. package/dist/{chunk-CNWC274T.js → chunk-3OQ4IZJN.js} +28 -28
  57. package/dist/{chunk-JO5AT6QT.js → chunk-3ZVFLMFQ.js} +1 -1
  58. package/dist/chunk-3ZVFLMFQ.js.map +1 -0
  59. package/dist/{chunk-7XH7VJN4.js → chunk-5S7Z5SHA.js} +2 -2
  60. package/dist/{chunk-WKE4VXSZ.js → chunk-673NPXE3.js} +2 -2
  61. package/dist/{chunk-Q6JPMCPO.js → chunk-7CKS4AXY.js} +63 -58
  62. package/dist/chunk-7CKS4AXY.js.map +1 -0
  63. package/dist/{chunk-UHGHTOR5.js → chunk-7EX4YOKH.js} +7 -7
  64. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  65. package/dist/{chunk-EJCVMPIM.js → chunk-7LMEY5KZ.js} +15 -1
  66. package/dist/chunk-7LMEY5KZ.js.map +1 -0
  67. package/dist/{chunk-3F4N4WDH.js → chunk-AHLPZ2SF.js} +2 -2
  68. package/dist/{chunk-6C35R7P4.js → chunk-DJCKXI4M.js} +2 -2
  69. package/dist/{chunk-AD2KK5MT.js → chunk-DLWYAN6A.js} +9 -3
  70. package/dist/chunk-DLWYAN6A.js.map +1 -0
  71. package/dist/{chunk-TVRPRFUB.js → chunk-EQF5763Z.js} +9 -7
  72. package/dist/chunk-EQF5763Z.js.map +1 -0
  73. package/dist/{chunk-FYKIEOG6.js → chunk-FNJX2O6G.js} +3 -3
  74. package/dist/{chunk-DQPPRXFO.js → chunk-HQ7QZJM5.js} +2 -2
  75. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  76. package/dist/{chunk-KIPLG7S7.js → chunk-JKLJ6A7W.js} +2 -2
  77. package/dist/{chunk-O3BZ6PFY.js → chunk-JZM5QUTE.js} +2 -2
  78. package/dist/{chunk-KTOH7EJE.js → chunk-KXLRMM35.js} +312 -48
  79. package/dist/chunk-KXLRMM35.js.map +1 -0
  80. package/dist/{chunk-ZUFJ7ZTR.js → chunk-L4H6MQ7I.js} +6 -2
  81. package/dist/chunk-L4H6MQ7I.js.map +1 -0
  82. package/dist/{chunk-SYNYI4ZM.js → chunk-L5HJQC36.js} +2 -2
  83. package/dist/{chunk-VXMWDXIJ.js → chunk-LC7OODWZ.js} +2 -2
  84. package/dist/{chunk-ZW6OKHVX.js → chunk-LQKITFGB.js} +2 -2
  85. package/dist/{chunk-TZJVTH3F.js → chunk-MXRMNKSY.js} +14 -14
  86. package/dist/{chunk-GL3SMLMZ.js → chunk-N3LWQJ6Y.js} +150 -119
  87. package/dist/chunk-N3LWQJ6Y.js.map +1 -0
  88. package/dist/{chunk-M5QKGHCR.js → chunk-OC35KQIN.js} +6 -2
  89. package/dist/chunk-OC35KQIN.js.map +1 -0
  90. package/dist/chunk-OKUEZOGW.js +27 -0
  91. package/dist/chunk-OKUEZOGW.js.map +1 -0
  92. package/dist/{chunk-PKGWJFNR.js → chunk-OVMS3DCB.js} +2 -2
  93. package/dist/chunk-PDJTHRAW.js +36 -0
  94. package/dist/chunk-PDJTHRAW.js.map +1 -0
  95. package/dist/{chunk-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
  96. package/dist/{chunk-NMMKRVUF.js → chunk-QVALQE4P.js} +6 -6
  97. package/dist/{chunk-FXGC5UUH.js → chunk-RT7IP3D3.js} +2 -2
  98. package/dist/{chunk-AH3M6F4X.js → chunk-SBFDL2EQ.js} +2 -2
  99. package/dist/{chunk-UYZUJ4EB.js → chunk-SECMZACR.js} +2 -2
  100. package/dist/{chunk-HXDKC3BI.js → chunk-TGBJ2OMK.js} +2 -2
  101. package/dist/{chunk-DE6ENYLG.js → chunk-TXLGHDHS.js} +2 -2
  102. package/dist/{chunk-74MJH7I3.js → chunk-U4KHQTJ6.js} +12 -3
  103. package/dist/chunk-U4KHQTJ6.js.map +1 -0
  104. package/dist/{chunk-CRFTLUWT.js → chunk-UTDA43TB.js} +2 -2
  105. package/dist/{chunk-GKCCHHZK.js → chunk-UXKMMNJD.js} +4 -4
  106. package/dist/{chunk-57INMZ6F.js → chunk-UZRIKUFS.js} +2 -2
  107. package/dist/{chunk-2FSHROYK.js → chunk-V53GJUSL.js} +8 -8
  108. package/dist/{chunk-LKT755LP.js → chunk-VA4WSW54.js} +2 -2
  109. package/dist/{chunk-ZHKD4EZM.js → chunk-VCEH5UYS.js} +3 -3
  110. package/dist/{chunk-CHVU4RE5.js → chunk-VDLMAJXS.js} +3 -3
  111. package/dist/{chunk-XFTPUXCE.js → chunk-VRAD2FGO.js} +10 -10
  112. package/dist/{chunk-VF4XKTX3.js → chunk-VZ5OJE4X.js} +10 -2
  113. package/dist/chunk-VZ5OJE4X.js.map +1 -0
  114. package/dist/{chunk-UVIQHSS7.js → chunk-WUDBMLC6.js} +9 -3
  115. package/dist/chunk-WUDBMLC6.js.map +1 -0
  116. package/dist/{chunk-AWJ2FHCF.js → chunk-X47LWMKA.js} +49 -21
  117. package/dist/chunk-X47LWMKA.js.map +1 -0
  118. package/dist/{chunk-VMGMY2AI.js → chunk-XF4KOVDK.js} +8 -8
  119. package/dist/{chunk-VMGMY2AI.js.map → chunk-XF4KOVDK.js.map} +1 -1
  120. package/dist/{chunk-T7VNN3ZM.js → chunk-XVLEGMJI.js} +2 -2
  121. package/dist/{chunk-R2SJDIPO.js → chunk-YLTG6BO4.js} +9 -7
  122. package/dist/chunk-YLTG6BO4.js.map +1 -0
  123. package/dist/{chunk-QDCJNRKK.js → chunk-YXOAQAH2.js} +3 -3
  124. package/dist/{cli-BqyGVyOe.d.ts → cli-DACyuK8-.d.ts} +5 -5
  125. package/dist/cli.d.ts +8 -8
  126. package/dist/cli.js +66 -64
  127. package/dist/compounding/engine.d.ts +2 -2
  128. package/dist/compounding/engine.js +15 -13
  129. package/dist/compounding/preference-consolidator.d.ts +1 -1
  130. package/dist/compression-optimizer.d.ts +1 -1
  131. package/dist/config.d.ts +1 -1
  132. package/dist/config.js +1 -1
  133. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  134. package/dist/connectors/codex-materialize-runner.js +15 -13
  135. package/dist/connectors/codex-materialize.d.ts +1 -1
  136. package/dist/connectors/index.d.ts +1 -1
  137. package/dist/connectors/index.js +15 -13
  138. package/dist/consolidation-provenance-check.d.ts +2 -2
  139. package/dist/consolidation-provenance-check.js +6 -2
  140. package/dist/consolidation-undo.d.ts +2 -2
  141. package/dist/consolidation-undo.js +8 -1
  142. package/dist/consolidation-undo.js.map +1 -1
  143. package/dist/contradiction/index.d.ts +2 -2
  144. package/dist/conversation-index/backend.d.ts +1 -1
  145. package/dist/conversation-index/chunker.d.ts +1 -1
  146. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  147. package/dist/conversation-index/indexer.d.ts +1 -1
  148. package/dist/conversation-index/search.d.ts +1 -1
  149. package/dist/day-summary.d.ts +1 -1
  150. package/dist/delinearize.d.ts +1 -1
  151. package/dist/direct-answer-wiring.d.ts +1 -1
  152. package/dist/direct-answer.d.ts +1 -1
  153. package/dist/embedding-fallback.d.ts +1 -1
  154. package/dist/enrichment/index.d.ts +1 -1
  155. package/dist/entity-retrieval.d.ts +2 -2
  156. package/dist/entity-retrieval.js +15 -13
  157. package/dist/entity-schema.d.ts +1 -1
  158. package/dist/explicit-capture.d.ts +5 -5
  159. package/dist/extraction-faithfulness.d.ts +1 -1
  160. package/dist/extraction-faithfulness.js +3 -3
  161. package/dist/extraction-judge-telemetry.d.ts +1 -1
  162. package/dist/extraction-judge-training.d.ts +1 -1
  163. package/dist/extraction-judge.d.ts +1 -1
  164. package/dist/extraction-judge.js +3 -3
  165. package/dist/extraction.d.ts +1 -1
  166. package/dist/extraction.js +3 -3
  167. package/dist/fallback-llm.d.ts +1 -1
  168. package/dist/fallback-llm.js +2 -2
  169. package/dist/{forget-KHF7A44H.js → forget-CQCLB5LB.js} +2 -2
  170. package/dist/graph-dashboard-diff.d.ts +1 -1
  171. package/dist/graph-dashboard-key.d.ts +1 -1
  172. package/dist/graph-dashboard-parser.d.ts +1 -1
  173. package/dist/graph-edge-reinforcement.d.ts +1 -1
  174. package/dist/graph-snapshot.d.ts +1 -1
  175. package/dist/graph.d.ts +1 -1
  176. package/dist/identity-continuity.d.ts +1 -1
  177. package/dist/importance.d.ts +1 -1
  178. package/dist/importers/index.d.ts +1 -1
  179. package/dist/in-flight-reads.d.ts +19 -0
  180. package/dist/in-flight-reads.js +16 -0
  181. package/dist/in-flight-reads.js.map +1 -0
  182. package/dist/index.d.ts +13 -13
  183. package/dist/index.js +116 -114
  184. package/dist/intent.d.ts +1 -1
  185. package/dist/lcm/engine.d.ts +1 -1
  186. package/dist/lcm/engine.js +4 -4
  187. package/dist/lcm/index.d.ts +1 -1
  188. package/dist/lcm/index.js +13 -13
  189. package/dist/lcm/tools.d.ts +1 -1
  190. package/dist/lifecycle.d.ts +1 -1
  191. package/dist/live-connectors-runner.d.ts +1 -1
  192. package/dist/local-llm.d.ts +1 -1
  193. package/dist/local-model-endpoint.d.ts +1 -1
  194. package/dist/maintenance/memory-governance.d.ts +1 -1
  195. package/dist/maintenance/memory-governance.js +15 -13
  196. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -13
  197. package/dist/maintenance/rebuild-memory-projection.js +16 -14
  198. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  199. package/dist/memory-action-policy.d.ts +1 -1
  200. package/dist/memory-cache.d.ts +24 -13
  201. package/dist/memory-cache.js +3 -1
  202. package/dist/memory-corpus-version.d.ts +19 -0
  203. package/dist/memory-corpus-version.js +13 -0
  204. package/dist/memory-corpus-version.js.map +1 -0
  205. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  206. package/dist/memory-projection-store.d.ts +1 -1
  207. package/dist/memory-provenance.d.ts +1 -1
  208. package/dist/memory-worth-outcomes.d.ts +2 -2
  209. package/dist/models-json.d.ts +1 -1
  210. package/dist/namespaces/migrate.d.ts +3 -3
  211. package/dist/namespaces/migrate.js +22 -20
  212. package/dist/namespaces/principal.d.ts +1 -1
  213. package/dist/namespaces/search.d.ts +1 -1
  214. package/dist/namespaces/search.js +7 -7
  215. package/dist/namespaces/storage.d.ts +3 -3
  216. package/dist/namespaces/storage.js +15 -13
  217. package/dist/native-knowledge.d.ts +1 -1
  218. package/dist/operator-toolkit.d.ts +2 -2
  219. package/dist/operator-toolkit.js +27 -25
  220. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  221. package/dist/orchestration/maintenance.d.ts +2 -2
  222. package/dist/orchestration/maintenance.js +17 -15
  223. package/dist/{orchestrator-37Cx7yyw.d.ts → orchestrator-D83AvrPf.d.ts} +4 -4
  224. package/dist/orchestrator.d.ts +5 -5
  225. package/dist/orchestrator.js +92 -90
  226. package/dist/page-versioning.js +5 -1
  227. package/dist/patterns-cli.d.ts +1 -1
  228. package/dist/{pipeline-C6onljHc.d.ts → pipeline-BSqGpYl1.d.ts} +1 -1
  229. package/dist/policy-runtime.d.ts +1 -1
  230. package/dist/provenance.d.ts +1 -1
  231. package/dist/{qmd-J08G7MEm.d.ts → qmd-CutnpRVe.d.ts} +1 -1
  232. package/dist/qmd-preflight.d.ts +2 -2
  233. package/dist/qmd-recall-cache.d.ts +1 -1
  234. package/dist/qmd.d.ts +2 -2
  235. package/dist/qmd.js +2 -2
  236. package/dist/recall-disclosure-escalation.d.ts +1 -1
  237. package/dist/recall-explain-renderer.d.ts +1 -1
  238. package/dist/recall-explain-renderer.js +3 -3
  239. package/dist/recall-planner-llm.d.ts +1 -1
  240. package/dist/recall-planner-llm.js +2 -2
  241. package/dist/recall-state.d.ts +1 -1
  242. package/dist/recall-tag-filter.d.ts +1 -1
  243. package/dist/recall-timings.d.ts +1 -1
  244. package/dist/recall-xray-cli.d.ts +1 -1
  245. package/dist/recall-xray-cli.js +4 -4
  246. package/dist/recall-xray-renderer.d.ts +1 -1
  247. package/dist/recall-xray-renderer.js +3 -3
  248. package/dist/recall-xray.d.ts +1 -1
  249. package/dist/recall-xray.js +2 -2
  250. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  251. package/dist/replay/normalizers/claude.d.ts +1 -1
  252. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  253. package/dist/replay/normalizers/shared.d.ts +1 -1
  254. package/dist/replay/runner.d.ts +1 -1
  255. package/dist/replay/types.d.ts +1 -1
  256. package/dist/resolve-auth-token.d.ts +1 -1
  257. package/dist/resume-bundles.js +2 -2
  258. package/dist/retrieval-agents.d.ts +2 -2
  259. package/dist/retrieval-agents.js +2 -2
  260. package/dist/retrieval-tiers.d.ts +1 -1
  261. package/dist/routing/engine.d.ts +1 -1
  262. package/dist/routing/store.d.ts +1 -1
  263. package/dist/schemas.d.ts +52 -52
  264. package/dist/search/embed-helper.d.ts +1 -1
  265. package/dist/search/factory.d.ts +1 -1
  266. package/dist/search/factory.js +6 -6
  267. package/dist/search/index.d.ts +1 -1
  268. package/dist/search/index.js +12 -12
  269. package/dist/search/lancedb-backend.d.ts +1 -1
  270. package/dist/search/meilisearch-backend.d.ts +1 -1
  271. package/dist/search/noop-backend.d.ts +1 -1
  272. package/dist/search/orama-backend.d.ts +1 -1
  273. package/dist/search/port.d.ts +1 -1
  274. package/dist/search/remote-backend.d.ts +1 -1
  275. package/dist/{semantic-consolidation-BFtoVosl.d.ts → semantic-consolidation-btIVRC4l.d.ts} +1 -1
  276. package/dist/semantic-consolidation.d.ts +2 -2
  277. package/dist/semantic-consolidation.js +16 -14
  278. package/dist/semantic-rule-promotion.js +15 -13
  279. package/dist/semantic-rule-verifier.d.ts +5 -1
  280. package/dist/semantic-rule-verifier.js +15 -13
  281. package/dist/session-observer-bands.d.ts +1 -1
  282. package/dist/session-observer-state.d.ts +1 -1
  283. package/dist/shared-context/manager.d.ts +9 -9
  284. package/dist/signal.d.ts +1 -1
  285. package/dist/{storage-CXjwBr69.d.ts → storage-BVv_Mqa0.d.ts} +133 -3
  286. package/dist/storage.d.ts +2 -2
  287. package/dist/storage.js +14 -12
  288. package/dist/summarizer.d.ts +1 -1
  289. package/dist/summarizer.js +3 -3
  290. package/dist/summary-snapshot.d.ts +1 -1
  291. package/dist/temporal-index-compat.d.ts +1 -0
  292. package/dist/temporal-index-compat.js +16 -0
  293. package/dist/temporal-index-compat.js.map +1 -0
  294. package/dist/temporal-index.d.ts +58 -7
  295. package/dist/temporal-index.js +23 -3
  296. package/dist/temporal-supersession.d.ts +2 -2
  297. package/dist/temporal-timeline-recall.d.ts +1 -1
  298. package/dist/temporal-validity.d.ts +1 -1
  299. package/dist/threading.d.ts +1 -1
  300. package/dist/tier-migration.d.ts +2 -2
  301. package/dist/tier-routing.d.ts +1 -1
  302. package/dist/topics.d.ts +1 -1
  303. package/dist/transcript.d.ts +1 -1
  304. package/dist/transfer/capsule-import.js +6 -2
  305. package/dist/transfer/import-sqlite.js +2 -2
  306. package/dist/transfer/types.d.ts +66 -66
  307. package/dist/trust-score-stage.d.ts +1 -1
  308. package/dist/trust-score.d.ts +1 -1
  309. package/dist/{types-DpxqC8dI.d.ts → types-Cmkz7csa.d.ts} +27 -0
  310. package/dist/types.d.ts +1 -1
  311. package/dist/types.js +1 -1
  312. package/dist/utility-runtime.d.ts +1 -1
  313. package/dist/verified-recall.d.ts +4 -0
  314. package/dist/verified-recall.js +15 -13
  315. package/package.json +12 -2
  316. package/src/binary-lifecycle/pipeline.ts +22 -0
  317. package/src/cli.ts +8 -0
  318. package/src/compounding/engine.ts +9 -1
  319. package/src/config.ts +14 -0
  320. package/src/consolidation-undo.ts +8 -0
  321. package/src/curation/index.ts +9 -0
  322. package/src/in-flight-reads.ts +63 -0
  323. package/src/maintenance/memory-governance.ts +6 -0
  324. package/src/memory-cache-hot.test.ts +131 -0
  325. package/src/memory-cache.ts +146 -22
  326. package/src/memory-corpus-version.ts +45 -0
  327. package/src/namespaces/storage.ts +20 -1
  328. package/src/operator-toolkit.ts +7 -1
  329. package/src/orchestration/consolidation-run.ts +5 -5
  330. package/src/orchestration/contradiction-linking-coordinator.ts +2 -2
  331. package/src/orchestration/lifecycle-policy-coordinator.ts +2 -2
  332. package/src/orchestration/persistence-index.ts +6 -6
  333. package/src/orchestration/recall-internal.ts +6 -3
  334. package/src/orchestration/recall-search-pipeline.ts +36 -21
  335. package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
  336. package/src/orchestrator.ts +25 -5
  337. package/src/page-versioning.ts +14 -1
  338. package/src/provenance-frontmatter.test.ts +6 -0
  339. package/src/review/index.ts +16 -8
  340. package/src/semantic-rule-promotion.ts +9 -1
  341. package/src/semantic-rule-verifier.ts +19 -6
  342. package/src/spaces/index.ts +10 -0
  343. package/src/storage/memory-read-store.ts +9 -2
  344. package/src/storage-contract/cache-coherence.test.ts +242 -0
  345. package/src/storage-contract/namespace-routing.test.ts +26 -1
  346. package/src/storage.ts +513 -31
  347. package/src/temporal-index-compat.ts +311 -0
  348. package/src/temporal-index.test.ts +273 -22
  349. package/src/temporal-index.ts +427 -178
  350. package/src/transfer/capsule-cache-coherence.test.ts +107 -0
  351. package/src/transfer/capsule-import.ts +8 -0
  352. package/src/transfer/capsule-merge.ts +7 -0
  353. package/src/types.ts +27 -0
  354. package/src/verified-recall.ts +20 -6
  355. package/dist/capsule-merge-GK5E647P.js.map +0 -1
  356. package/dist/chunk-74MJH7I3.js.map +0 -1
  357. package/dist/chunk-AD2KK5MT.js.map +0 -1
  358. package/dist/chunk-AWJ2FHCF.js.map +0 -1
  359. package/dist/chunk-EJCVMPIM.js.map +0 -1
  360. package/dist/chunk-GL3SMLMZ.js.map +0 -1
  361. package/dist/chunk-HHAA35A3.js.map +0 -1
  362. package/dist/chunk-JO5AT6QT.js.map +0 -1
  363. package/dist/chunk-KTOH7EJE.js.map +0 -1
  364. package/dist/chunk-M5QKGHCR.js.map +0 -1
  365. package/dist/chunk-Q6JPMCPO.js.map +0 -1
  366. package/dist/chunk-R2SJDIPO.js.map +0 -1
  367. package/dist/chunk-TVRPRFUB.js.map +0 -1
  368. package/dist/chunk-UVIQHSS7.js.map +0 -1
  369. package/dist/chunk-VF4XKTX3.js.map +0 -1
  370. package/dist/chunk-ZUFJ7ZTR.js.map +0 -1
  371. /package/dist/{auto-sync-676N6N6Y.js.map → auto-sync-AAMTVZMK.js.map} +0 -0
  372. /package/dist/{chunk-CNWC274T.js.map → chunk-3OQ4IZJN.js.map} +0 -0
  373. /package/dist/{chunk-7XH7VJN4.js.map → chunk-5S7Z5SHA.js.map} +0 -0
  374. /package/dist/{chunk-WKE4VXSZ.js.map → chunk-673NPXE3.js.map} +0 -0
  375. /package/dist/{chunk-UHGHTOR5.js.map → chunk-7EX4YOKH.js.map} +0 -0
  376. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  377. /package/dist/{chunk-3F4N4WDH.js.map → chunk-AHLPZ2SF.js.map} +0 -0
  378. /package/dist/{chunk-6C35R7P4.js.map → chunk-DJCKXI4M.js.map} +0 -0
  379. /package/dist/{chunk-FYKIEOG6.js.map → chunk-FNJX2O6G.js.map} +0 -0
  380. /package/dist/{chunk-DQPPRXFO.js.map → chunk-HQ7QZJM5.js.map} +0 -0
  381. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  382. /package/dist/{chunk-KIPLG7S7.js.map → chunk-JKLJ6A7W.js.map} +0 -0
  383. /package/dist/{chunk-O3BZ6PFY.js.map → chunk-JZM5QUTE.js.map} +0 -0
  384. /package/dist/{chunk-SYNYI4ZM.js.map → chunk-L5HJQC36.js.map} +0 -0
  385. /package/dist/{chunk-VXMWDXIJ.js.map → chunk-LC7OODWZ.js.map} +0 -0
  386. /package/dist/{chunk-ZW6OKHVX.js.map → chunk-LQKITFGB.js.map} +0 -0
  387. /package/dist/{chunk-TZJVTH3F.js.map → chunk-MXRMNKSY.js.map} +0 -0
  388. /package/dist/{chunk-PKGWJFNR.js.map → chunk-OVMS3DCB.js.map} +0 -0
  389. /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
  390. /package/dist/{chunk-NMMKRVUF.js.map → chunk-QVALQE4P.js.map} +0 -0
  391. /package/dist/{chunk-FXGC5UUH.js.map → chunk-RT7IP3D3.js.map} +0 -0
  392. /package/dist/{chunk-AH3M6F4X.js.map → chunk-SBFDL2EQ.js.map} +0 -0
  393. /package/dist/{chunk-UYZUJ4EB.js.map → chunk-SECMZACR.js.map} +0 -0
  394. /package/dist/{chunk-HXDKC3BI.js.map → chunk-TGBJ2OMK.js.map} +0 -0
  395. /package/dist/{chunk-DE6ENYLG.js.map → chunk-TXLGHDHS.js.map} +0 -0
  396. /package/dist/{chunk-CRFTLUWT.js.map → chunk-UTDA43TB.js.map} +0 -0
  397. /package/dist/{chunk-GKCCHHZK.js.map → chunk-UXKMMNJD.js.map} +0 -0
  398. /package/dist/{chunk-57INMZ6F.js.map → chunk-UZRIKUFS.js.map} +0 -0
  399. /package/dist/{chunk-2FSHROYK.js.map → chunk-V53GJUSL.js.map} +0 -0
  400. /package/dist/{chunk-LKT755LP.js.map → chunk-VA4WSW54.js.map} +0 -0
  401. /package/dist/{chunk-ZHKD4EZM.js.map → chunk-VCEH5UYS.js.map} +0 -0
  402. /package/dist/{chunk-CHVU4RE5.js.map → chunk-VDLMAJXS.js.map} +0 -0
  403. /package/dist/{chunk-XFTPUXCE.js.map → chunk-VRAD2FGO.js.map} +0 -0
  404. /package/dist/{chunk-T7VNN3ZM.js.map → chunk-XVLEGMJI.js.map} +0 -0
  405. /package/dist/{chunk-QDCJNRKK.js.map → chunk-YXOAQAH2.js.map} +0 -0
  406. /package/dist/{forget-KHF7A44H.js.map → forget-CQCLB5LB.js.map} +0 -0
@@ -15,10 +15,22 @@
15
15
  * - Both indexes are plain JSON; no external dependencies
16
16
  */
17
17
 
18
- import { execFileSync } from "node:child_process";
18
+ import { execFile } from "node:child_process";
19
19
  import * as crypto from "node:crypto";
20
20
  import * as fs from "node:fs";
21
21
  import * as path from "node:path";
22
+ import { setTimeout as delay } from "node:timers/promises";
23
+ import { promisify } from "node:util";
24
+
25
+ const execFileAsync = promisify(execFile);
26
+
27
+ export {
28
+ clearIndexes,
29
+ deindexMemory,
30
+ indexMemoriesBatch,
31
+ indexMemory,
32
+ indexesExist,
33
+ } from "./temporal-index-compat.js";
22
34
 
23
35
  export interface TemporalIndex {
24
36
  /** version bumped when schema changes */
@@ -82,9 +94,42 @@ const TAG_INDEX_VERSION = 2;
82
94
  const INDEX_LOCK_STALE_MS = 60_000;
83
95
  const INDEX_LOCK_POLL_MS = 10;
84
96
  const INDEX_PROCESS_START_TOLERANCE_MS = 2_000;
85
- const INDEX_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));
86
97
  const INDEX_PROCESS_STARTED_AT_MS = Date.now() - process.uptime() * 1000;
87
98
 
99
+ interface IndexFileIdentity {
100
+ size: number;
101
+ mtimeMs: number;
102
+ ctimeMs: number;
103
+ ino: number;
104
+ }
105
+
106
+ interface CachedIndex<T> {
107
+ identity: IndexFileIdentity;
108
+ index: T;
109
+ currentSchema: boolean;
110
+ }
111
+
112
+ type IndexLoadResult<T> =
113
+ | { kind: "ok"; index: T; currentSchema: boolean }
114
+ | { kind: "missing" }
115
+ | { kind: "invalid" };
116
+
117
+ const parsedIndexCache = new Map<string, CachedIndex<unknown>>();
118
+ const writeChains = new Map<string, Promise<void>>();
119
+ // Operation-level serialization keyed by memoryDir (issue #1911, Codex Medium):
120
+ // a single logical index mutation touches TWO files (temporal + tag), each with
121
+ // its own per-file writeChain. Without an outer lock, two overlapping
122
+ // indexMemoryAsync calls could interleave so the temporal half of one commits
123
+ // with the tag half of the other. This chain makes each public mutator's paired
124
+ // writes atomic relative to other mutators on the same memoryDir.
125
+ const opChains = new Map<string, Promise<void>>();
126
+ let indexReadObserverForTest: (() => void) | undefined;
127
+
128
+ /** Test-only observer for asserting cache read/parse behavior. */
129
+ export function setIndexReadObserverForTest(observer?: () => void): void {
130
+ indexReadObserverForTest = observer;
131
+ }
132
+
88
133
  interface IndexLockOwner {
89
134
  pid: number;
90
135
  createdAt?: string;
@@ -105,30 +150,104 @@ function tagIndexPath(memoryDir: string): string {
105
150
  return path.join(stateDir(memoryDir), TAG_INDEX_FILE);
106
151
  }
107
152
 
108
- function ensureStateDir(memoryDir: string): void {
109
- const dir = stateDir(memoryDir);
110
- fs.mkdirSync(dir, { recursive: true });
153
+ async function ensureStateDir(memoryDir: string): Promise<void> {
154
+ await fs.promises.mkdir(stateDir(memoryDir), { recursive: true });
155
+ }
156
+
157
+ function identityFromStat(stat: fs.Stats): IndexFileIdentity {
158
+ return {
159
+ size: stat.size,
160
+ mtimeMs: stat.mtimeMs,
161
+ ctimeMs: stat.ctimeMs,
162
+ ino: stat.ino,
163
+ };
111
164
  }
112
165
 
113
- function readJsonSafe<T>(filePath: string, fallback: T): T {
166
+ function identityEquals(left: IndexFileIdentity, right: IndexFileIdentity): boolean {
167
+ return left.size === right.size &&
168
+ left.mtimeMs === right.mtimeMs &&
169
+ left.ctimeMs === right.ctimeMs &&
170
+ left.ino === right.ino;
171
+ }
172
+
173
+ async function loadIndexCached<T>(
174
+ filePath: string,
175
+ normalize: (raw: unknown) => T,
176
+ hasCurrentSchema: (raw: unknown) => boolean,
177
+ ): Promise<IndexLoadResult<T>> {
178
+ let handle: fs.promises.FileHandle;
114
179
  try {
115
- const raw = fs.readFileSync(filePath, "utf8");
116
- return JSON.parse(raw) as T;
117
- } catch {
118
- return fallback;
180
+ handle = await fs.promises.open(filePath, "r");
181
+ } catch (error) {
182
+ return (error as NodeJS.ErrnoException)?.code === "ENOENT"
183
+ ? { kind: "missing" }
184
+ : { kind: "invalid" };
119
185
  }
120
- }
121
186
 
122
- function writeJsonSafe(filePath: string, data: unknown): void {
123
187
  try {
124
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
188
+ const identity = identityFromStat(await handle.stat());
189
+ const cached = parsedIndexCache.get(filePath) as CachedIndex<T> | undefined;
190
+ if (cached && identityEquals(cached.identity, identity)) {
191
+ return { kind: "ok", index: cached.index, currentSchema: cached.currentSchema };
192
+ }
193
+ indexReadObserverForTest?.();
194
+ const parsed = JSON.parse(await handle.readFile("utf8")) as unknown;
195
+ const index = normalize(parsed);
196
+ const currentSchema = hasCurrentSchema(parsed);
197
+ parsedIndexCache.set(filePath, { identity, index, currentSchema });
198
+ return { kind: "ok", index, currentSchema };
125
199
  } catch {
126
- // Fail silently — indexes are advisory only
200
+ parsedIndexCache.delete(filePath);
201
+ return { kind: "invalid" };
202
+ } finally {
203
+ await handle.close().catch(() => undefined);
127
204
  }
128
205
  }
129
206
 
130
- function sleepSync(ms: number): void {
131
- Atomics.wait(INDEX_LOCK_SLEEP, 0, 0, ms);
207
+ function withPathMutex<T>(filePath: string, update: () => Promise<T>): Promise<T> {
208
+ const previous = writeChains.get(filePath) ?? Promise.resolve();
209
+ const run = previous.then(update, update);
210
+ const tail = run.then(() => undefined, () => undefined);
211
+ writeChains.set(filePath, tail);
212
+ void tail.then(() => {
213
+ if (writeChains.get(filePath) === tail) writeChains.delete(filePath);
214
+ });
215
+ return run;
216
+ }
217
+
218
+ /**
219
+ * Operation-level mutex keyed by memoryDir (issue #1911, Codex Medium). Wraps a
220
+ * whole logical index mutation (which writes BOTH the temporal and tag index
221
+ * files) so overlapping mutators on the same memoryDir cannot interleave their
222
+ * per-file writes and commit mismatched halves. The inner per-file writeChains
223
+ * still protect each file; this outer chain just orders complete operations.
224
+ */
225
+ function withMemoryDirMutex<T>(memoryDir: string, op: () => Promise<T>): Promise<T> {
226
+ const previous = opChains.get(memoryDir) ?? Promise.resolve();
227
+ const run = previous.then(op, op);
228
+ const tail = run.then(() => undefined, () => undefined);
229
+ opChains.set(memoryDir, tail);
230
+ void tail.then(() => {
231
+ if (opChains.get(memoryDir) === tail) opChains.delete(memoryDir);
232
+ });
233
+ return run;
234
+ }
235
+ async function primeCacheAfterWrite(
236
+ filePath: string,
237
+ index: unknown,
238
+ isCurrentSchema: () => boolean,
239
+ ): Promise<void> {
240
+ try {
241
+ const identity = identityFromStat(await fs.promises.stat(filePath));
242
+ // Derive currentSchema from the ACTUAL written index (issue #1911, Codex):
243
+ // hardcoding true let deindexMemoryAsync (which does not bump version) cache
244
+ // an old-schema index as current, so indexesExistAsync then skipped a
245
+ // needed rebuild. The tag index is always current (no version gate); the
246
+ // temporal index is current only when hasCurrentTemporalIndexSchema holds.
247
+ parsedIndexCache.set(filePath, { identity, index, currentSchema: isCurrentSchema() });
248
+ } catch {
249
+ parsedIndexCache.delete(filePath);
250
+ }
132
251
  }
133
252
 
134
253
  function uniqueTempPath(filePath: string): string {
@@ -142,9 +261,9 @@ function lockOwnerPath(lockDir: string): string {
142
261
  return path.join(lockDir, "owner.json");
143
262
  }
144
263
 
145
- function writeIndexLockOwner(lockDir: string): void {
264
+ async function writeIndexLockOwner(lockDir: string): Promise<void> {
146
265
  try {
147
- fs.writeFileSync(
266
+ await fs.promises.writeFile(
148
267
  lockOwnerPath(lockDir),
149
268
  JSON.stringify({
150
269
  pid: process.pid,
@@ -161,9 +280,9 @@ function writeIndexLockOwner(lockDir: string): void {
161
280
  }
162
281
  }
163
282
 
164
- function readIndexLockOwner(lockDir: string): IndexLockOwner | null {
283
+ async function readIndexLockOwner(lockDir: string): Promise<IndexLockOwner | null> {
165
284
  try {
166
- const parsed = JSON.parse(fs.readFileSync(lockOwnerPath(lockDir), "utf8")) as { pid?: unknown };
285
+ const parsed = JSON.parse(await fs.promises.readFile(lockOwnerPath(lockDir), "utf8")) as { pid?: unknown };
167
286
  if (!(typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0)) return null;
168
287
  const owner: IndexLockOwner = { pid: parsed.pid };
169
288
  if (
@@ -193,13 +312,13 @@ function processIsAlive(pid: number): boolean {
193
312
  }
194
313
  }
195
314
 
196
- function readProcessStartedAtMs(pid: number): number | null {
315
+ async function readProcessStartedAtMs(pid: number): Promise<number | null> {
197
316
  try {
198
- const output = execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
317
+ const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], {
199
318
  encoding: "utf8",
200
- stdio: ["ignore", "pipe", "ignore"],
201
319
  timeout: 1_000,
202
- }).trim();
320
+ });
321
+ const output = stdout.trim();
203
322
  if (!output) return null;
204
323
  const startedAtMs = Date.parse(output);
205
324
  return Number.isFinite(startedAtMs) ? startedAtMs : null;
@@ -208,10 +327,10 @@ function readProcessStartedAtMs(pid: number): number | null {
208
327
  }
209
328
  }
210
329
 
211
- function lockOwnerIsRunning(owner: IndexLockOwner): boolean {
330
+ async function lockOwnerIsRunning(owner: IndexLockOwner): Promise<boolean> {
212
331
  if (!processIsAlive(owner.pid)) return false;
213
332
  if (owner.processStartedAtMs === undefined) return true;
214
- const runningStartedAtMs = readProcessStartedAtMs(owner.pid);
333
+ const runningStartedAtMs = await readProcessStartedAtMs(owner.pid);
215
334
  if (runningStartedAtMs === null) return true;
216
335
  return runningStartedAtMs <= owner.processStartedAtMs + INDEX_PROCESS_START_TOLERANCE_MS;
217
336
  }
@@ -223,20 +342,20 @@ function lockIsFresh(lockInfo: fs.Stats, owner: IndexLockOwner | null): boolean
223
342
  return Date.now() - referenceMs < INDEX_LOCK_STALE_MS;
224
343
  }
225
344
 
226
- function removeAbandonedIndexLock(lockDir: string): IndexLockCleanupResult {
345
+ async function removeAbandonedIndexLock(lockDir: string): Promise<IndexLockCleanupResult> {
227
346
  try {
228
- const info = fs.lstatSync(lockDir);
347
+ const info = await fs.promises.lstat(lockDir);
229
348
  if (info.isSymbolicLink()) return "blocked";
230
349
  if (!info.isDirectory()) {
231
- fs.rmSync(lockDir, { force: true });
350
+ await fs.promises.rm(lockDir, { force: true });
232
351
  return "removed";
233
352
  }
234
- const owner = readIndexLockOwner(lockDir);
353
+ const owner = await readIndexLockOwner(lockDir);
235
354
  if (owner !== null) {
236
- if (lockOwnerIsRunning(owner)) return "wait";
355
+ if (await lockOwnerIsRunning(owner)) return "wait";
237
356
  }
238
357
  if (owner === null && lockIsFresh(info, null)) return "wait";
239
- fs.rmSync(lockDir, { recursive: true, force: true });
358
+ await fs.promises.rm(lockDir, { recursive: true, force: true });
240
359
  return "removed";
241
360
  } catch (error) {
242
361
  if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return "removed";
@@ -245,65 +364,68 @@ function removeAbandonedIndexLock(lockDir: string): IndexLockCleanupResult {
245
364
  }
246
365
  }
247
366
 
248
- function withIndexFileLock(filePath: string, update: () => void): void {
367
+ async function withIndexFileLock<T>(filePath: string, update: () => Promise<T>): Promise<T | undefined> {
249
368
  const lockDir = `${filePath}.lock.d`;
250
369
  let acquired = false;
251
370
 
252
371
  while (!acquired) {
253
372
  try {
254
- fs.mkdirSync(lockDir);
255
- writeIndexLockOwner(lockDir);
373
+ await fs.promises.mkdir(lockDir);
374
+ await writeIndexLockOwner(lockDir);
256
375
  acquired = true;
257
376
  } catch (error) {
258
377
  const code = (error as NodeJS.ErrnoException)?.code;
259
378
  if (code === "ENOENT") {
260
379
  try {
261
- fs.mkdirSync(path.dirname(lockDir), { recursive: true });
380
+ await fs.promises.mkdir(path.dirname(lockDir), { recursive: true });
262
381
  } catch {
263
- return;
382
+ return undefined;
264
383
  }
265
- sleepSync(INDEX_LOCK_POLL_MS);
384
+ await delay(INDEX_LOCK_POLL_MS);
266
385
  continue;
267
386
  }
268
- if (code !== "EEXIST") return;
269
- const cleanupResult = removeAbandonedIndexLock(lockDir);
270
- if (cleanupResult === "blocked") return;
271
- sleepSync(INDEX_LOCK_POLL_MS);
387
+ if (code !== "EEXIST") return undefined;
388
+ const cleanupResult = await removeAbandonedIndexLock(lockDir);
389
+ if (cleanupResult === "blocked") return undefined;
390
+ await delay(INDEX_LOCK_POLL_MS);
272
391
  }
273
392
  }
274
393
 
394
+ let result: T;
275
395
  try {
276
- update();
396
+ result = await update();
277
397
  } finally {
278
398
  try {
279
- fs.rmSync(lockDir, { recursive: true, force: true });
399
+ await fs.promises.rm(lockDir, { recursive: true, force: true });
280
400
  } catch {
281
401
  // Fail silently — indexes are advisory only
282
402
  }
283
403
  }
404
+ return result;
284
405
  }
285
406
 
286
407
  /**
287
408
  * Atomic write: write to a unique `.tmp` sibling then rename so readers never
288
409
  * observe a partially-written file.
289
410
  */
290
- function writeJsonAtomic(filePath: string, data: unknown): void {
291
- const payload = JSON.stringify(data, null, 2);
411
+ async function writeJsonAtomic(filePath: string, data: unknown): Promise<boolean> {
412
+ const payload = JSON.stringify(data);
292
413
  for (let attempt = 0; attempt < 3; attempt += 1) {
293
414
  const tmp = uniqueTempPath(filePath);
294
415
  try {
295
- fs.writeFileSync(tmp, payload, "utf8");
296
- fs.renameSync(tmp, filePath);
297
- return;
416
+ await fs.promises.writeFile(tmp, payload, "utf8");
417
+ await fs.promises.rename(tmp, filePath);
418
+ return true;
298
419
  } catch {
299
420
  try {
300
- fs.unlinkSync(tmp);
421
+ await fs.promises.unlink(tmp);
301
422
  } catch {
302
423
  // Fail silently — indexes are advisory only
303
424
  }
304
- sleepSync(INDEX_LOCK_POLL_MS);
425
+ await delay(INDEX_LOCK_POLL_MS);
305
426
  }
306
427
  }
428
+ return false;
307
429
  }
308
430
 
309
431
  function emptyTemporalIndex(): TemporalIndex {
@@ -324,23 +446,56 @@ function normalizeTemporalIndex(raw: TemporalIndex | null | undefined): Temporal
324
446
  };
325
447
  }
326
448
 
327
- function updateTemporalIndex(memoryDir: string, update: (index: TemporalIndex) => void): void {
449
+ function hasCurrentTemporalIndexSchema(raw: unknown): boolean {
450
+ return isRecord(raw) &&
451
+ raw.version === INDEX_VERSION &&
452
+ isRecord(raw.dates) &&
453
+ isRecord(raw.events);
454
+ }
455
+
456
+ async function updateTemporalIndex(memoryDir: string, update: (index: TemporalIndex) => void): Promise<boolean> {
328
457
  const indexPath = temporalIndexPath(memoryDir);
329
- withIndexFileLock(indexPath, () => {
330
- const index = normalizeTemporalIndex(readJsonSafe<TemporalIndex>(indexPath, emptyTemporalIndex()));
331
- update(index);
332
- writeJsonAtomic(indexPath, index);
458
+ return withPathMutex(indexPath, async () => {
459
+ const ok = await withIndexFileLock(indexPath, async () => {
460
+ const loaded = await loadIndexCached(
461
+ indexPath,
462
+ (raw) => normalizeTemporalIndex(raw as TemporalIndex),
463
+ hasCurrentTemporalIndexSchema,
464
+ );
465
+ const index = structuredClone(loaded.kind === "ok" ? loaded.index : emptyTemporalIndex());
466
+ update(index);
467
+ if (await writeJsonAtomic(indexPath, index)) {
468
+ await primeCacheAfterWrite(indexPath, index, () => hasCurrentTemporalIndexSchema(index));
469
+ return true;
470
+ }
471
+ parsedIndexCache.delete(indexPath);
472
+ return false;
473
+ });
474
+ return ok === true;
333
475
  });
334
476
  }
335
477
 
336
- function updateTagIndex(memoryDir: string, update: (index: TagIndex) => void): void {
478
+ async function updateTagIndex(memoryDir: string, update: (index: TagIndex) => void): Promise<void> {
337
479
  const indexPath = tagIndexPath(memoryDir);
338
- withIndexFileLock(indexPath, () => {
339
- const index = normalizeTagIndex(
340
- readJsonSafe<TagIndex>(indexPath, { version: TAG_INDEX_VERSION, tags: {}, aliases: {} })
341
- );
342
- update(index);
343
- writeJsonAtomic(indexPath, index);
480
+ await withPathMutex(indexPath, async () => {
481
+ await withIndexFileLock(indexPath, async () => {
482
+ const loaded = await loadIndexCached(
483
+ indexPath,
484
+ (raw) => normalizeTagIndex(raw as TagIndex),
485
+ () => true,
486
+ );
487
+ const index = structuredClone(
488
+ loaded.kind === "ok"
489
+ ? loaded.index
490
+ : { version: TAG_INDEX_VERSION, tags: {}, aliases: {} },
491
+ );
492
+ update(index);
493
+ if (await writeJsonAtomic(indexPath, index)) {
494
+ await primeCacheAfterWrite(indexPath, index, () => true);
495
+ } else {
496
+ parsedIndexCache.delete(indexPath);
497
+ }
498
+ });
344
499
  });
345
500
  }
346
501
 
@@ -678,11 +833,62 @@ function aliasPhrase(alias: string): string {
678
833
  return alias.replace(/\//g, " ").replace(/-/g, " ").replace(/\s+/g, " ").trim();
679
834
  }
680
835
 
681
- function promptContainsAlias(prompt: string, alias: string): boolean {
682
- const phrase = aliasPhrase(alias);
683
- if (!phrase) return false;
684
- const normalizedPrompt = ` ${normalizeAliasKey(prompt)} `;
685
- return normalizedPrompt.includes(` ${phrase} `);
836
+ export type TemporalIndexCompatibilityMutation =
837
+ | { kind: "index"; entries: TemporalIndexEntry[] }
838
+ | { kind: "deindex"; entries: TemporalIndexEntry[] }
839
+ | { kind: "clear" };
840
+
841
+ /** @internal Pure model shared by the deprecated synchronous compatibility surface. */
842
+ export function applyTemporalIndexCompatibilityMutation(
843
+ raw: unknown,
844
+ mutation: TemporalIndexCompatibilityMutation,
845
+ ): TemporalIndex {
846
+ const index = structuredClone(normalizeTemporalIndex(raw as TemporalIndex));
847
+ if (mutation.kind === "clear") {
848
+ return emptyTemporalIndex();
849
+ }
850
+ if (mutation.kind === "index") index.version = INDEX_VERSION;
851
+ for (const entry of mutation.entries) {
852
+ const dateKey = isoDateFromTimestamp(entry.createdAt);
853
+ if (mutation.kind === "index") {
854
+ removePathFromAllSets(index.dates, entry.path);
855
+ addPathToSet(index.dates, dateKey, entry.path);
856
+ index.events[entry.path] = temporalEventFromEntry(entry);
857
+ } else {
858
+ removePathFromSet(index.dates, dateKey, entry.path);
859
+ delete index.events[entry.path];
860
+ }
861
+ }
862
+ return index;
863
+ }
864
+
865
+ /** @internal Pure model shared by the deprecated synchronous compatibility surface. */
866
+ export function applyTagIndexCompatibilityMutation(
867
+ raw: unknown,
868
+ mutation: TemporalIndexCompatibilityMutation,
869
+ ): TagIndex {
870
+ const index = structuredClone(normalizeTagIndex(raw as TagIndex));
871
+ if (mutation.kind === "clear") {
872
+ return { version: TAG_INDEX_VERSION, tags: {}, aliases: {} };
873
+ }
874
+ for (const entry of mutation.entries) {
875
+ if (mutation.kind === "index") {
876
+ removePathFromAllTagEntries(index, entry.path);
877
+ for (const tag of entry.tags) {
878
+ if (tag && typeof tag === "string") addTagGraphEntry(index, tag, entry.path);
879
+ }
880
+ } else {
881
+ for (const tag of entry.tags) {
882
+ if (tag && typeof tag === "string") removeTagGraphEntry(index, tag, entry.path);
883
+ }
884
+ }
885
+ }
886
+ return index;
887
+ }
888
+
889
+ /** @internal Schema check shared by the deprecated synchronous compatibility surface. */
890
+ export function hasCurrentTemporalIndexSchemaForCompatibility(raw: unknown): boolean {
891
+ return hasCurrentTemporalIndexSchema(raw);
686
892
  }
687
893
 
688
894
  // ─── Public API ────────────────────────────────────────────────────────────────
@@ -695,35 +901,41 @@ function promptContainsAlias(prompt: string, alias: string): boolean {
695
901
  * @param createdAt ISO timestamp of the memory's creation date
696
902
  * @param tags Array of tag strings from the memory's frontmatter
697
903
  */
698
- export function indexMemory(
904
+ export async function indexMemoryAsync(
699
905
  memoryDir: string,
700
906
  memoryPath: string,
701
907
  createdAt: string,
702
908
  tags: string[],
703
909
  temporal: Omit<Partial<TemporalIndexEntry>, "path" | "createdAt" | "tags"> = {},
704
- ): void {
910
+ ): Promise<void> {
705
911
  try {
706
- ensureStateDir(memoryDir);
912
+ await ensureStateDir(memoryDir);
707
913
 
708
914
  const dateKey = isoDateFromTimestamp(createdAt);
709
- updateTemporalIndex(memoryDir, (index) => {
710
- index.version = INDEX_VERSION;
711
- removePathFromAllSets(index.dates, memoryPath);
712
- addPathToSet(index.dates, dateKey, memoryPath);
713
- index.events[memoryPath] = temporalEventFromEntry({
714
- path: memoryPath,
715
- createdAt,
716
- tags,
717
- ...temporal,
915
+ await withMemoryDirMutex(memoryDir, async () => {
916
+ // Write the temporal half first; if its atomic write FAILS, do NOT commit
917
+ // the tag half (issue #1911, Cursor Medium) — committing tags while
918
+ // temporal stays unchanged leaves a durable mismatch until a later index.
919
+ const temporalOk = await updateTemporalIndex(memoryDir, (index) => {
920
+ index.version = INDEX_VERSION;
921
+ removePathFromAllSets(index.dates, memoryPath);
922
+ addPathToSet(index.dates, dateKey, memoryPath);
923
+ index.events[memoryPath] = temporalEventFromEntry({
924
+ path: memoryPath,
925
+ createdAt,
926
+ tags,
927
+ ...temporal,
928
+ });
718
929
  });
719
- });
720
-
721
- updateTagIndex(memoryDir, (index) => {
722
- removePathFromAllTagEntries(index, memoryPath);
723
- for (const tag of tags) {
724
- if (tag && typeof tag === "string") {
725
- addTagGraphEntry(index, tag, memoryPath);
726
- }
930
+ if (temporalOk) {
931
+ await updateTagIndex(memoryDir, (index) => {
932
+ removePathFromAllTagEntries(index, memoryPath);
933
+ for (const tag of tags) {
934
+ if (tag && typeof tag === "string") {
935
+ addTagGraphEntry(index, tag, memoryPath);
936
+ }
937
+ }
938
+ });
727
939
  }
728
940
  });
729
941
  } catch {
@@ -734,21 +946,29 @@ export function indexMemory(
734
946
  /**
735
947
  * Remove a memory file from both indexes (called on deletion/archival).
736
948
  */
737
- export function deindexMemory(memoryDir: string, memoryPath: string, createdAt: string, tags: string[]): void {
949
+ export async function deindexMemoryAsync(
950
+ memoryDir: string,
951
+ memoryPath: string,
952
+ createdAt: string,
953
+ tags: string[],
954
+ ): Promise<void> {
738
955
  try {
739
- ensureStateDir(memoryDir);
956
+ await ensureStateDir(memoryDir);
740
957
 
741
958
  const dateKey = isoDateFromTimestamp(createdAt);
742
- updateTemporalIndex(memoryDir, (index) => {
743
- removePathFromSet(index.dates, dateKey, memoryPath);
744
- delete index.events[memoryPath];
745
- });
746
-
747
- updateTagIndex(memoryDir, (index) => {
748
- for (const tag of tags) {
749
- if (tag && typeof tag === "string") {
750
- removeTagGraphEntry(index, tag, memoryPath);
751
- }
959
+ await withMemoryDirMutex(memoryDir, async () => {
960
+ const temporalOk = await updateTemporalIndex(memoryDir, (index) => {
961
+ removePathFromSet(index.dates, dateKey, memoryPath);
962
+ delete index.events[memoryPath];
963
+ });
964
+ if (temporalOk) {
965
+ await updateTagIndex(memoryDir, (index) => {
966
+ for (const tag of tags) {
967
+ if (tag && typeof tag === "string") {
968
+ removeTagGraphEntry(index, tag, memoryPath);
969
+ }
970
+ }
971
+ });
752
972
  }
753
973
  });
754
974
  } catch {
@@ -761,20 +981,24 @@ export function deindexMemory(memoryDir: string, memoryPath: string, createdAt:
761
981
  * Called before a full-corpus rebuild so stale paths in any surviving index
762
982
  * file do not persist after the rebuild completes.
763
983
  */
764
- export function clearIndexes(memoryDir: string): void {
984
+ export async function clearIndexesAsync(memoryDir: string): Promise<void> {
765
985
  try {
766
- ensureStateDir(memoryDir);
767
- updateTemporalIndex(memoryDir, (index) => {
768
- index.version = INDEX_VERSION;
769
- index.lastRebuildAt = undefined;
770
- index.dates = {};
771
- index.events = {};
772
- });
773
- updateTagIndex(memoryDir, (index) => {
774
- index.version = TAG_INDEX_VERSION;
775
- index.lastRebuildAt = undefined;
776
- index.tags = {};
777
- index.aliases = {};
986
+ await ensureStateDir(memoryDir);
987
+ await withMemoryDirMutex(memoryDir, async () => {
988
+ const temporalOk = await updateTemporalIndex(memoryDir, (index) => {
989
+ index.version = INDEX_VERSION;
990
+ index.lastRebuildAt = undefined;
991
+ index.dates = {};
992
+ index.events = {};
993
+ });
994
+ if (temporalOk) {
995
+ await updateTagIndex(memoryDir, (index) => {
996
+ index.version = TAG_INDEX_VERSION;
997
+ index.lastRebuildAt = undefined;
998
+ index.tags = {};
999
+ index.aliases = {};
1000
+ });
1001
+ }
778
1002
  });
779
1003
  } catch {
780
1004
  // Fail silently — indexes are advisory only
@@ -785,11 +1009,15 @@ export function clearIndexes(memoryDir: string): void {
785
1009
  * Returns true when both index files exist on disk.
786
1010
  * Used to detect first-time enablement so callers can trigger a full rebuild.
787
1011
  */
788
- export function indexesExist(memoryDir: string): boolean {
1012
+ export async function indexesExistAsync(memoryDir: string): Promise<boolean> {
789
1013
  try {
790
- if (!fs.existsSync(tagIndexPath(memoryDir))) return false;
791
- const raw = JSON.parse(fs.readFileSync(temporalIndexPath(memoryDir), "utf8")) as unknown;
792
- return isRecord(raw) && raw.version === INDEX_VERSION && isRecord(raw.dates) && isRecord(raw.events);
1014
+ await fs.promises.access(tagIndexPath(memoryDir));
1015
+ const loaded = await loadIndexCached(
1016
+ temporalIndexPath(memoryDir),
1017
+ (raw) => normalizeTemporalIndex(raw as TemporalIndex),
1018
+ hasCurrentTemporalIndexSchema,
1019
+ );
1020
+ return loaded.kind === "ok" && loaded.currentSchema;
793
1021
  } catch {
794
1022
  return false;
795
1023
  }
@@ -797,34 +1025,37 @@ export function indexesExist(memoryDir: string): boolean {
797
1025
 
798
1026
  /**
799
1027
  * Batch-add multiple memories to both indexes in a single read-modify-write cycle.
800
- * More efficient than calling indexMemory() per file when adding many at once.
1028
+ * More efficient than calling indexMemoryAsync() per file when adding many at once.
801
1029
  */
802
- export function indexMemoriesBatch(
1030
+ export async function indexMemoriesBatchAsync(
803
1031
  memoryDir: string,
804
1032
  entries: TemporalIndexEntry[]
805
- ): void {
1033
+ ): Promise<void> {
806
1034
  if (entries.length === 0) return;
807
1035
  try {
808
- ensureStateDir(memoryDir);
809
-
810
- updateTemporalIndex(memoryDir, (index) => {
811
- index.version = INDEX_VERSION;
812
- for (const entry of entries) {
813
- const dateKey = isoDateFromTimestamp(entry.createdAt);
814
- removePathFromAllSets(index.dates, entry.path);
815
- addPathToSet(index.dates, dateKey, entry.path);
816
- index.events[entry.path] = temporalEventFromEntry(entry);
817
- }
818
- });
819
-
820
- updateTagIndex(memoryDir, (index) => {
821
- for (const entry of entries) {
822
- removePathFromAllTagEntries(index, entry.path);
823
- for (const tag of entry.tags) {
824
- if (tag && typeof tag === "string") {
825
- addTagGraphEntry(index, tag, entry.path);
826
- }
1036
+ await ensureStateDir(memoryDir);
1037
+
1038
+ await withMemoryDirMutex(memoryDir, async () => {
1039
+ const temporalOk = await updateTemporalIndex(memoryDir, (index) => {
1040
+ index.version = INDEX_VERSION;
1041
+ for (const entry of entries) {
1042
+ const dateKey = isoDateFromTimestamp(entry.createdAt);
1043
+ removePathFromAllSets(index.dates, entry.path);
1044
+ addPathToSet(index.dates, dateKey, entry.path);
1045
+ index.events[entry.path] = temporalEventFromEntry(entry);
827
1046
  }
1047
+ });
1048
+ if (temporalOk) {
1049
+ await updateTagIndex(memoryDir, (index) => {
1050
+ for (const entry of entries) {
1051
+ removePathFromAllTagEntries(index, entry.path);
1052
+ for (const tag of entry.tags) {
1053
+ if (tag && typeof tag === "string") {
1054
+ addTagGraphEntry(index, tag, entry.path);
1055
+ }
1056
+ }
1057
+ }
1058
+ });
828
1059
  }
829
1060
  });
830
1061
  } catch {
@@ -854,18 +1085,13 @@ export async function queryByDateRangeAsync(
854
1085
  ): Promise<Set<string> | null> {
855
1086
  try {
856
1087
  const tPath = temporalIndexPath(memoryDir);
857
- let raw: string;
858
- try {
859
- raw = await fs.promises.readFile(tPath, "utf8");
860
- } catch {
861
- return null; // File missing or unreadable
862
- }
863
- let tIndex: TemporalIndex;
864
- try {
865
- tIndex = normalizeTemporalIndex(JSON.parse(raw) as TemporalIndex);
866
- } catch {
867
- tIndex = emptyTemporalIndex();
868
- }
1088
+ const loaded = await loadIndexCached(
1089
+ tPath,
1090
+ (raw) => normalizeTemporalIndex(raw as TemporalIndex),
1091
+ hasCurrentTemporalIndexSchema,
1092
+ );
1093
+ if (loaded.kind === "missing") return null;
1094
+ const tIndex = loaded.kind === "ok" ? loaded.index : emptyTemporalIndex();
869
1095
  // toDate is exclusive (first day NOT included). Default: tomorrow so "all of today" is included.
870
1096
  const end = toDate ?? new Date(Date.now() + 86_400_000).toISOString().slice(0, 10);
871
1097
 
@@ -894,15 +1120,13 @@ export async function queryTemporalTimelineAsync(
894
1120
  options: { query?: string; limit?: number } = {},
895
1121
  ): Promise<TemporalIndexEvent[] | null> {
896
1122
  try {
897
- const raw = await fs.promises.readFile(temporalIndexPath(memoryDir), "utf8");
898
- const parsed = JSON.parse(raw) as unknown;
899
- if (
900
- !isRecord(parsed) ||
901
- parsed.version !== INDEX_VERSION ||
902
- !isRecord(parsed.dates) ||
903
- !isRecord(parsed.events)
904
- ) return null;
905
- const index = normalizeTemporalIndex(parsed as unknown as TemporalIndex);
1123
+ const loaded = await loadIndexCached(
1124
+ temporalIndexPath(memoryDir),
1125
+ (raw) => normalizeTemporalIndex(raw as TemporalIndex),
1126
+ hasCurrentTemporalIndexSchema,
1127
+ );
1128
+ if (loaded.kind !== "ok" || !loaded.currentSchema) return null;
1129
+ const index = loaded.index;
906
1130
  const chronology = Object.values(index.events)
907
1131
  .filter((event) =>
908
1132
  typeof event?.path === "string" &&
@@ -971,20 +1195,13 @@ export async function queryByTagsAsync(memoryDir: string, tags: string[]): Promi
971
1195
  if (tags.length === 0) return null;
972
1196
  try {
973
1197
  const gPath = tagIndexPath(memoryDir);
974
- let raw: string;
975
- try {
976
- raw = await fs.promises.readFile(gPath, "utf8");
977
- } catch {
978
- return null; // File missing or unreadable
979
- }
980
- let gIndex: TagIndex;
981
- try {
982
- gIndex = normalizeTagIndex(JSON.parse(raw) as TagIndex);
983
- } catch {
984
- return null;
985
- }
986
-
987
- return queryByTagsFromIndex(gIndex, tags);
1198
+ const loaded = await loadIndexCached(
1199
+ gPath,
1200
+ (raw) => normalizeTagIndex(raw as TagIndex),
1201
+ () => true,
1202
+ );
1203
+ if (loaded.kind !== "ok") return null;
1204
+ return queryByTagsFromIndex(loaded.index, tags);
988
1205
  } catch {
989
1206
  return null;
990
1207
  }
@@ -1031,17 +1248,29 @@ export async function resolvePromptTagPrefilterAsync(
1031
1248
  }> {
1032
1249
  const explicitTags = extractTagsFromPrompt(prompt);
1033
1250
  try {
1034
- const raw = await fs.promises.readFile(tagIndexPath(memoryDir), "utf8");
1035
- const tagIndex = normalizeTagIndex(JSON.parse(raw) as TagIndex);
1251
+ const loaded = await loadIndexCached(
1252
+ tagIndexPath(memoryDir),
1253
+ (raw) => normalizeTagIndex(raw as TagIndex),
1254
+ () => true,
1255
+ );
1256
+ if (loaded.kind !== "ok") {
1257
+ return { matchedTags: explicitTags, expandedTags: explicitTags, paths: null };
1258
+ }
1259
+ const tagIndex = loaded.index;
1036
1260
  const matched = new Set<string>(explicitTags);
1261
+ const normalizedPrompt = ` ${normalizeAliasKey(prompt)} `;
1262
+ const containsAlias = (alias: string): boolean => {
1263
+ const phrase = aliasPhrase(alias);
1264
+ return phrase.length > 0 && normalizedPrompt.includes(` ${phrase} `);
1265
+ };
1037
1266
 
1038
1267
  for (const canonical of Object.keys(tagIndex.tags)) {
1039
- if (promptContainsAlias(prompt, canonical)) {
1268
+ if (containsAlias(canonical)) {
1040
1269
  matched.add(canonical);
1041
1270
  }
1042
1271
  }
1043
1272
  for (const [alias, canonicals] of Object.entries(tagIndex.aliases ?? {})) {
1044
- if (!promptContainsAlias(prompt, alias)) continue;
1273
+ if (!containsAlias(alias)) continue;
1045
1274
  for (const canonical of canonicals) {
1046
1275
  matched.add(canonical);
1047
1276
  }
@@ -1066,6 +1295,26 @@ export async function resolvePromptTagPrefilterAsync(
1066
1295
  }
1067
1296
  }
1068
1297
 
1298
+ /**
1299
+ * Run a group of index reads as ONE consistent snapshot (issue #1911, Codex
1300
+ * Medium). The `read` callback executes inside a single withMemoryDirMutex
1301
+ * acquisition — the same in-memory op-lock every async mutator holds across its
1302
+ * paired temporal+tag writes — so no mutation can land between the reads inside
1303
+ * it. A recall that reads the temporal and tag indexes together therefore never
1304
+ * observes a half-updated state (e.g. a new temporal row with a not-yet-written
1305
+ * tag membership) that would let prefiltering drop the current memory. The
1306
+ * genuine-tag-miss contract is preserved: a truly absent tag still yields an
1307
+ * empty set, only now never as a transient artifact of an in-flight write.
1308
+ * Index reads are cache-backed (microseconds), so ordering them on the op-chain
1309
+ * is negligible beside the multi-second QMD phase.
1310
+ */
1311
+ export function readIndexSnapshotAsync<T>(
1312
+ memoryDir: string,
1313
+ read: () => Promise<T>,
1314
+ ): Promise<T> {
1315
+ return withMemoryDirMutex(memoryDir, read);
1316
+ }
1317
+
1069
1318
  /**
1070
1319
  * Detect if a prompt is time-sensitive (mentions specific time references).
1071
1320
  * Used to decide whether to activate the temporal prefilter.