@remnic/core 9.64.0 → 9.64.1

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 (398) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +13 -13
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +14 -14
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +13 -13
  7. package/dist/access-cli.js +47 -47
  8. package/dist/access-coding-context-resolution.d.ts +1 -1
  9. package/dist/access-extraction-force-flush.d.ts +11 -11
  10. package/dist/access-extraction-force-flush.js +13 -13
  11. package/dist/access-health-types.d.ts +1 -1
  12. package/dist/access-http-lcm-compaction.d.ts +11 -11
  13. package/dist/access-http-lifecycle-flush.d.ts +11 -11
  14. package/dist/access-http-offline-stream.d.ts +11 -11
  15. package/dist/access-http-query.d.ts +2 -2
  16. package/dist/access-http-query.js +15 -15
  17. package/dist/access-http.d.ts +12 -12
  18. package/dist/access-http.js +24 -24
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +13 -13
  21. package/dist/access-lcm-surface.d.ts +11 -11
  22. package/dist/access-lcm-surface.js +13 -13
  23. package/dist/access-mcp.d.ts +11 -11
  24. package/dist/access-mcp.js +18 -18
  25. package/dist/access-memory-search-fanout.d.ts +2 -2
  26. package/dist/{access-namespace-preflight-CrF0FKaz.d.ts → access-namespace-preflight-Dj9uR4Cq.d.ts} +1 -1
  27. package/dist/access-namespace-preflight.d.ts +3 -3
  28. package/dist/access-namespace-preflight.js +13 -13
  29. package/dist/access-observe-write-surface.d.ts +11 -11
  30. package/dist/access-observe-write-surface.js +13 -13
  31. package/dist/access-offline-manifest.d.ts +9 -9
  32. package/dist/access-operations-batch.js +14 -14
  33. package/dist/access-operations.d.ts +14 -14
  34. package/dist/access-operations.js +17 -17
  35. package/dist/access-recall-concurrency.d.ts +11 -11
  36. package/dist/access-recall-concurrency.js +13 -13
  37. package/dist/access-recall-response.d.ts +11 -11
  38. package/dist/access-recall-response.js +13 -13
  39. package/dist/access-recall-surface.d.ts +11 -11
  40. package/dist/access-recall-surface.js +13 -13
  41. package/dist/access-schema.d.ts +88 -88
  42. package/dist/{access-service-BqF4UK8Z.d.ts → access-service-Bd0lzaHp.d.ts} +71 -71
  43. package/dist/access-service-helpers.d.ts +11 -11
  44. package/dist/access-service.d.ts +11 -11
  45. package/dist/access-service.js +13 -13
  46. package/dist/access-surface-catalog.d.ts +11 -11
  47. package/dist/access-surface-catalog.js +15 -15
  48. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  49. package/dist/action-confidence.d.ts +1 -1
  50. package/dist/active-memory-bridge.d.ts +1 -1
  51. package/dist/active-recall.d.ts +1 -1
  52. package/dist/active-recall.js +1 -1
  53. package/dist/ambient-provenance.d.ts +1 -1
  54. package/dist/artifact-search.d.ts +1 -1
  55. package/dist/behavior-learner.d.ts +1 -1
  56. package/dist/behavior-signals.d.ts +1 -1
  57. package/dist/bootstrap.d.ts +9 -9
  58. package/dist/briefing-window.d.ts +2 -2
  59. package/dist/briefing.d.ts +2 -2
  60. package/dist/briefing.js +3 -3
  61. package/dist/buffer-surprise-report.d.ts +1 -1
  62. package/dist/buffer-turn-helpers.d.ts +1 -1
  63. package/dist/buffer.d.ts +2 -2
  64. package/dist/buffer.js +2 -2
  65. package/dist/bulk-import/index.d.ts +3 -3
  66. package/dist/calibration.d.ts +1 -1
  67. package/dist/capabilities.d.ts +1 -1
  68. package/dist/{catalog-C0KIwZqR.d.ts → catalog-rKYbN7zF.d.ts} +1 -1
  69. package/dist/causal-behavior.d.ts +1 -1
  70. package/dist/causal-consolidation.d.ts +1 -1
  71. package/dist/causal-consolidation.js +4 -4
  72. package/dist/causal-trajectory-graph.d.ts +1 -1
  73. package/dist/{chunk-5SZ6CLGJ.js → chunk-2RZCOG63.js} +3 -2
  74. package/dist/{chunk-5SZ6CLGJ.js.map → chunk-2RZCOG63.js.map} +1 -1
  75. package/dist/{chunk-7DRSWPUV.js → chunk-3CYVTO6A.js} +3 -3
  76. package/dist/{chunk-LJQGQUNS.js → chunk-4FSCGYTQ.js} +2 -2
  77. package/dist/{chunk-ML4MI2RR.js → chunk-4KKVDOCD.js} +24 -24
  78. package/dist/{chunk-Z3Q5I5F6.js → chunk-5Q5BEWJ3.js} +2 -2
  79. package/dist/{chunk-6BV6ZZZA.js → chunk-6I6IOMKS.js} +2 -2
  80. package/dist/{chunk-GIMQJ5RX.js → chunk-7D3TTGHY.js} +20 -11
  81. package/dist/chunk-7D3TTGHY.js.map +1 -0
  82. package/dist/{chunk-4IIDTPSK.js → chunk-7X35RPN3.js} +2 -2
  83. package/dist/{chunk-FEP45MYY.js → chunk-AGC3Q53R.js} +2 -2
  84. package/dist/{chunk-ZWYXSF2T.js → chunk-AL43367I.js} +96 -4
  85. package/dist/chunk-AL43367I.js.map +1 -0
  86. package/dist/{chunk-WCWIV2JH.js → chunk-B2GICNOJ.js} +2 -2
  87. package/dist/{chunk-BTBAHTQ5.js → chunk-CI6YIT5S.js} +2 -2
  88. package/dist/{chunk-CUDSA46O.js → chunk-CUIAHZPZ.js} +2 -2
  89. package/dist/{chunk-BTUIHRRR.js → chunk-D267K4V5.js} +2 -2
  90. package/dist/{chunk-QICOU4RY.js → chunk-DLZBOEJY.js} +2 -2
  91. package/dist/{chunk-AR253JBM.js → chunk-DPPTG6VJ.js} +1 -1
  92. package/dist/chunk-DPPTG6VJ.js.map +1 -0
  93. package/dist/{chunk-BPLPGOUJ.js → chunk-DRS3OX77.js} +2 -2
  94. package/dist/{chunk-3F3CVVDS.js → chunk-DTQ3OB7O.js} +2 -2
  95. package/dist/{chunk-Z4UPRZR6.js → chunk-DZ2AVS2H.js} +2 -2
  96. package/dist/{chunk-YINSSARY.js → chunk-EAWJSY2Z.js} +1 -1
  97. package/dist/{chunk-QFPX7QZG.js → chunk-FMGTFMT3.js} +9 -9
  98. package/dist/{chunk-CTD5CPD2.js → chunk-GSQDPL45.js} +3 -3
  99. package/dist/{chunk-UUYQWKHO.js → chunk-IHPYTG6F.js} +2 -2
  100. package/dist/{chunk-QATILW4B.js → chunk-IPUUMLSM.js} +21 -21
  101. package/dist/{chunk-2BRCV2IV.js → chunk-JE3AKPOO.js} +2 -2
  102. package/dist/{chunk-DZFPHARJ.js → chunk-KOE3RLNN.js} +2 -2
  103. package/dist/{chunk-WQ6TRWAE.js → chunk-NMQXUGTU.js} +3 -3
  104. package/dist/{chunk-65NQFK7D.js → chunk-OTVHRZZD.js} +2 -2
  105. package/dist/{chunk-77E7LLMV.js → chunk-OWAYQK5V.js} +8 -8
  106. package/dist/{chunk-YGSDWO3U.js → chunk-P4SD7BSA.js} +2 -2
  107. package/dist/{chunk-G5F4MXXC.js → chunk-Q3IRE6E4.js} +2 -2
  108. package/dist/{chunk-HZXO2CJ6.js → chunk-QHNO6AND.js} +2 -2
  109. package/dist/{chunk-5OOTHWGJ.js → chunk-RMB22Q3R.js} +2 -2
  110. package/dist/{chunk-BB2LH5JM.js → chunk-RNORDDD6.js} +11 -1
  111. package/dist/chunk-RNORDDD6.js.map +1 -0
  112. package/dist/{chunk-R7XE5ZUR.js → chunk-UIYYS2YI.js} +2 -2
  113. package/dist/{chunk-SYU5JOBQ.js → chunk-UWSETW6O.js} +2 -2
  114. package/dist/{chunk-CK3DZ6OE.js → chunk-VCNGVU62.js} +2 -2
  115. package/dist/{chunk-KG2CT762.js → chunk-VSKA2ATE.js} +2 -2
  116. package/dist/{chunk-PQODT53H.js → chunk-WDFXRHOZ.js} +3 -3
  117. package/dist/{chunk-6NMZXEJ7.js → chunk-WNO5W2TP.js} +2 -2
  118. package/dist/{chunk-63JMI45N.js → chunk-XBRGJJK7.js} +2 -2
  119. package/dist/{chunk-S6KMN6MJ.js → chunk-XFAWGUNX.js} +2 -2
  120. package/dist/{chunk-SRLQROQJ.js → chunk-XSPRZZEX.js} +3 -3
  121. package/dist/{chunk-EHC3FIJ4.js → chunk-Y5ZIQFWH.js} +2 -2
  122. package/dist/{chunk-6IKXAAJI.js → chunk-YY63AMGP.js} +2 -2
  123. package/dist/{chunk-7ML7STOD.js → chunk-ZGGTIBWN.js} +2 -2
  124. package/dist/{chunk-X6HNXVXS.js → chunk-ZUIVJ3BB.js} +4 -4
  125. package/dist/{cli-BX0O688c.d.ts → cli-0dDmOsJ8.d.ts} +5 -5
  126. package/dist/cli.d.ts +13 -13
  127. package/dist/cli.js +37 -37
  128. package/dist/coding/pre-action-gate.d.ts +1 -1
  129. package/dist/compounding/engine.d.ts +2 -2
  130. package/dist/compounding/engine.js +3 -3
  131. package/dist/compounding/preference-consolidator.d.ts +1 -1
  132. package/dist/compression-optimizer.d.ts +1 -1
  133. package/dist/config.d.ts +1 -1
  134. package/dist/config.js +1 -1
  135. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  136. package/dist/connectors/codex-materialize-runner.js +3 -3
  137. package/dist/connectors/codex-materialize.d.ts +1 -1
  138. package/dist/connectors/index.d.ts +1 -1
  139. package/dist/connectors/index.js +3 -3
  140. package/dist/consolidation-provenance-check.d.ts +2 -2
  141. package/dist/consolidation-undo.d.ts +2 -2
  142. package/dist/contradiction/index.d.ts +2 -2
  143. package/dist/converge-config.d.ts +1 -1
  144. package/dist/convergence-refresh.d.ts +2 -2
  145. package/dist/convergence-refresh.js +5 -5
  146. package/dist/conversation-index/backend.d.ts +1 -1
  147. package/dist/conversation-index/chunker.d.ts +1 -1
  148. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  149. package/dist/conversation-index/indexer.d.ts +1 -1
  150. package/dist/conversation-index/search.d.ts +1 -1
  151. package/dist/corpus-watermark.d.ts +1 -1
  152. package/dist/corpus-watermark.js +5 -5
  153. package/dist/day-summary.d.ts +1 -1
  154. package/dist/delinearize.d.ts +1 -1
  155. package/dist/dependency-propagation-config.d.ts +1 -1
  156. package/dist/{dependency-propagation-delivery-Cph4PnBe.d.ts → dependency-propagation-delivery-Cr8u-XwC.d.ts} +1 -1
  157. package/dist/direct-answer-wiring.d.ts +1 -1
  158. package/dist/direct-answer.d.ts +1 -1
  159. package/dist/embedding-fallback.d.ts +1 -1
  160. package/dist/enrichment/index.d.ts +1 -1
  161. package/dist/entity-origin-fields.d.ts +1 -1
  162. package/dist/entity-retrieval.d.ts +2 -2
  163. package/dist/entity-retrieval.js +3 -3
  164. package/dist/entity-schema.d.ts +1 -1
  165. package/dist/episodic-context.d.ts +1 -1
  166. package/dist/explicit-capture.d.ts +9 -9
  167. package/dist/external-wiki-access.d.ts +11 -11
  168. package/dist/external-wiki-access.js +14 -14
  169. package/dist/external-wiki-collection-registration.d.ts +1 -1
  170. package/dist/external-wiki-collection.d.ts +1 -1
  171. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  172. package/dist/extraction-error-classification.d.ts +1 -1
  173. package/dist/extraction-faithfulness.d.ts +1 -1
  174. package/dist/extraction-judge-telemetry.d.ts +1 -1
  175. package/dist/extraction-judge-training.d.ts +1 -1
  176. package/dist/extraction-judge.d.ts +1 -1
  177. package/dist/extraction-liveness.d.ts +1 -1
  178. package/dist/extraction-normalization.d.ts +1 -1
  179. package/dist/extraction-prompt.d.ts +1 -1
  180. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  181. package/dist/extraction-source-grounding.d.ts +1 -1
  182. package/dist/extraction.d.ts +1 -1
  183. package/dist/extraction.js +6 -6
  184. package/dist/fallback-llm.d.ts +1 -1
  185. package/dist/graph-dashboard-diff.d.ts +1 -1
  186. package/dist/graph-dashboard-key.d.ts +1 -1
  187. package/dist/graph-dashboard-parser.d.ts +1 -1
  188. package/dist/graph-edge-reinforcement.d.ts +1 -1
  189. package/dist/graph-path-reconstruction.d.ts +1 -1
  190. package/dist/graph-path-scoring.d.ts +1 -1
  191. package/dist/graph-snapshot.d.ts +1 -1
  192. package/dist/graph.d.ts +1 -1
  193. package/dist/harmonic-retrieval.js +1 -1
  194. package/dist/importance.d.ts +1 -1
  195. package/dist/importers/index.d.ts +1 -1
  196. package/dist/in-flight-reads.d.ts +1 -1
  197. package/dist/index.d.ts +697 -697
  198. package/dist/index.js +47 -47
  199. package/dist/intent.d.ts +1 -1
  200. package/dist/lcm/engine.d.ts +1 -1
  201. package/dist/lcm/index.d.ts +1 -1
  202. package/dist/lcm/tools.d.ts +1 -1
  203. package/dist/lifecycle.d.ts +1 -1
  204. package/dist/live-connectors-runner.d.ts +1 -1
  205. package/dist/local-llm.d.ts +1 -1
  206. package/dist/local-model-endpoint.d.ts +1 -1
  207. package/dist/maintenance/memory-governance.d.ts +1 -1
  208. package/dist/maintenance/memory-governance.js +3 -3
  209. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  210. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  211. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  212. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  213. package/dist/{maintenance-BNq31xpo.d.ts → maintenance-BpNjuZk4.d.ts} +3 -3
  214. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  215. package/dist/memory-action-policy.d.ts +1 -1
  216. package/dist/memory-cache.d.ts +1 -1
  217. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  218. package/dist/memory-projection-store.d.ts +1 -1
  219. package/dist/memory-provenance.d.ts +1 -1
  220. package/dist/memory-snapshot.d.ts +1 -1
  221. package/dist/memory-worth-outcomes.d.ts +2 -2
  222. package/dist/models-json.d.ts +1 -1
  223. package/dist/namespaces/migrate.d.ts +3 -3
  224. package/dist/namespaces/migrate.js +4 -4
  225. package/dist/namespaces/principal.d.ts +1 -1
  226. package/dist/namespaces/search.d.ts +1 -1
  227. package/dist/namespaces/storage.d.ts +3 -3
  228. package/dist/namespaces/storage.js +3 -3
  229. package/dist/native-knowledge.d.ts +1 -1
  230. package/dist/offline-sync-impression-drain.d.ts +1 -1
  231. package/dist/operator-doctor-corpus.d.ts +1 -1
  232. package/dist/operator-doctor-corpus.js +6 -6
  233. package/dist/operator-doctor-replica.d.ts +1 -1
  234. package/dist/operator-toolkit.d.ts +3 -3
  235. package/dist/operator-toolkit.js +12 -12
  236. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  237. package/dist/orchestration/maintenance.d.ts +4 -4
  238. package/dist/orchestration/maintenance.js +9 -9
  239. package/dist/{orchestrator-CBuUOP2J.d.ts → orchestrator-B3ZQ8CTJ.d.ts} +8 -8
  240. package/dist/orchestrator.d.ts +9 -9
  241. package/dist/orchestrator.js +47 -47
  242. package/dist/patterns-cli.d.ts +1 -1
  243. package/dist/{pipeline-CW_Q4WvX.d.ts → pipeline-q5emoXD6.d.ts} +1 -1
  244. package/dist/policy-runtime.d.ts +1 -1
  245. package/dist/proactive-contention.d.ts +1 -1
  246. package/dist/provenance.d.ts +1 -1
  247. package/dist/{public-http-BeJ1_JI1.d.ts → public-http-CQN-i7Ai.d.ts} +1 -1
  248. package/dist/{qmd-q-cAzYuC.d.ts → qmd-YC6YGphs.d.ts} +1 -1
  249. package/dist/qmd-preflight.d.ts +2 -2
  250. package/dist/qmd-recall-cache.d.ts +1 -1
  251. package/dist/qmd.d.ts +2 -2
  252. package/dist/recall-concurrency-config.d.ts +1 -1
  253. package/dist/recall-disclosure-escalation.d.ts +1 -1
  254. package/dist/recall-explain-renderer.d.ts +1 -1
  255. package/dist/recall-explain-renderer.js +3 -3
  256. package/dist/recall-memory-map.d.ts +1 -1
  257. package/dist/recall-planner-llm.d.ts +1 -1
  258. package/dist/recall-state.d.ts +1 -1
  259. package/dist/recall-tag-filter.d.ts +1 -1
  260. package/dist/recall-timings.d.ts +1 -1
  261. package/dist/recall-xray-cli.d.ts +1 -1
  262. package/dist/recall-xray-cli.js +4 -4
  263. package/dist/recall-xray-renderer.d.ts +1 -1
  264. package/dist/recall-xray-renderer.js +3 -3
  265. package/dist/recall-xray.d.ts +1 -1
  266. package/dist/recall-xray.js +2 -2
  267. package/dist/reconcile/cursor.d.ts +1 -1
  268. package/dist/reconcile/manifest.d.ts +1 -1
  269. package/dist/reconcile/plan.d.ts +1 -1
  270. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  271. package/dist/replay/normalizers/claude.d.ts +1 -1
  272. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  273. package/dist/replay/normalizers/shared.d.ts +1 -1
  274. package/dist/replay/runner.d.ts +1 -1
  275. package/dist/replay/types.d.ts +1 -1
  276. package/dist/replica-divergence.d.ts +1 -1
  277. package/dist/replica-peers-config.d.ts +1 -1
  278. package/dist/resolve-auth-token.d.ts +1 -1
  279. package/dist/resume-bundles.js +2 -2
  280. package/dist/retrieval-agents.d.ts +2 -2
  281. package/dist/retrieval-tiers.d.ts +1 -1
  282. package/dist/routing/engine.d.ts +1 -1
  283. package/dist/routing/store.d.ts +1 -1
  284. package/dist/salvage-envelope.d.ts +1 -1
  285. package/dist/schemas.d.ts +108 -108
  286. package/dist/{scope-profiles-CIr4mXgp.d.ts → scope-profiles-De8F7YQ6.d.ts} +1 -1
  287. package/dist/search/embed-helper.d.ts +1 -1
  288. package/dist/search/factory.d.ts +1 -1
  289. package/dist/search/index.d.ts +1 -1
  290. package/dist/search/lancedb-backend.d.ts +1 -1
  291. package/dist/search/meilisearch-backend.d.ts +1 -1
  292. package/dist/search/noop-backend.d.ts +1 -1
  293. package/dist/search/orama-backend.d.ts +1 -1
  294. package/dist/search/port.d.ts +1 -1
  295. package/dist/search/remote-backend.d.ts +1 -1
  296. package/dist/{semantic-consolidation-BZ6J46j-.d.ts → semantic-consolidation-Bf-hm2LQ.d.ts} +1 -1
  297. package/dist/semantic-consolidation.d.ts +2 -2
  298. package/dist/semantic-consolidation.js +4 -4
  299. package/dist/semantic-rule-promotion.js +3 -3
  300. package/dist/semantic-rule-verifier.d.ts +1 -1
  301. package/dist/semantic-rule-verifier.js +3 -3
  302. package/dist/{service-CCl4iyyY.d.ts → service-D83osSRn.d.ts} +2 -2
  303. package/dist/session-observer-bands.d.ts +1 -1
  304. package/dist/session-observer-state.d.ts +1 -1
  305. package/dist/shared-context/manager.d.ts +9 -9
  306. package/dist/signal.d.ts +1 -1
  307. package/dist/source-agent-qualifier.d.ts +1 -1
  308. package/dist/source-agent-qualifier.js +5 -5
  309. package/dist/{storage-BPXmJKIt.d.ts → storage-BHWbpbhj.d.ts} +1 -1
  310. package/dist/storage.d.ts +2 -2
  311. package/dist/storage.js +2 -2
  312. package/dist/summarizer.d.ts +1 -1
  313. package/dist/summary-snapshot.d.ts +1 -1
  314. package/dist/support-passport/index.d.ts +14 -14
  315. package/dist/support-passport/index.js +16 -16
  316. package/dist/temporal-supersession.d.ts +2 -2
  317. package/dist/temporal-timeline-recall.d.ts +1 -1
  318. package/dist/temporal-validity.d.ts +1 -1
  319. package/dist/threading.d.ts +1 -1
  320. package/dist/tier-migration.d.ts +2 -2
  321. package/dist/tier-routing.d.ts +1 -1
  322. package/dist/topics.d.ts +1 -1
  323. package/dist/transcript.d.ts +1 -1
  324. package/dist/transfer/types.d.ts +66 -66
  325. package/dist/trust-score-stage.d.ts +1 -1
  326. package/dist/trust-score.d.ts +1 -1
  327. package/dist/{types-CltSWcND.d.ts → types-CpgsN-Gb.d.ts} +24 -3
  328. package/dist/types.d.ts +1 -1
  329. package/dist/types.js +1 -1
  330. package/dist/utility-runtime.d.ts +1 -1
  331. package/dist/verified-recall.js +3 -3
  332. package/dist/write-envelope.d.ts +1 -1
  333. package/package.json +2 -2
  334. package/src/config.ts +5 -6
  335. package/src/location/cli.ts +163 -0
  336. package/src/location/config.test.ts +75 -0
  337. package/src/location/config.ts +105 -0
  338. package/src/location/intervals.test.ts +202 -0
  339. package/src/location/intervals.ts +221 -0
  340. package/src/location/parse-companions.ts +12 -0
  341. package/src/location/pipeline.test.ts +324 -0
  342. package/src/location/pipeline.ts +245 -0
  343. package/src/location/recall-exclusion.test.ts +36 -0
  344. package/src/location/registry.test.ts +74 -0
  345. package/src/location/registry.ts +54 -0
  346. package/src/location/store.test.ts +170 -0
  347. package/src/location/store.ts +478 -0
  348. package/src/location/types.ts +95 -0
  349. package/src/orchestration/generic-recall-paths.ts +17 -0
  350. package/src/orchestration/orchestrator-helpers.ts +22 -0
  351. package/src/orchestration/qmd-result-resolver.ts +1 -0
  352. package/src/types.ts +2 -3
  353. package/dist/chunk-AR253JBM.js.map +0 -1
  354. package/dist/chunk-BB2LH5JM.js.map +0 -1
  355. package/dist/chunk-GIMQJ5RX.js.map +0 -1
  356. package/dist/chunk-ZWYXSF2T.js.map +0 -1
  357. /package/dist/{chunk-7DRSWPUV.js.map → chunk-3CYVTO6A.js.map} +0 -0
  358. /package/dist/{chunk-LJQGQUNS.js.map → chunk-4FSCGYTQ.js.map} +0 -0
  359. /package/dist/{chunk-ML4MI2RR.js.map → chunk-4KKVDOCD.js.map} +0 -0
  360. /package/dist/{chunk-Z3Q5I5F6.js.map → chunk-5Q5BEWJ3.js.map} +0 -0
  361. /package/dist/{chunk-6BV6ZZZA.js.map → chunk-6I6IOMKS.js.map} +0 -0
  362. /package/dist/{chunk-4IIDTPSK.js.map → chunk-7X35RPN3.js.map} +0 -0
  363. /package/dist/{chunk-FEP45MYY.js.map → chunk-AGC3Q53R.js.map} +0 -0
  364. /package/dist/{chunk-WCWIV2JH.js.map → chunk-B2GICNOJ.js.map} +0 -0
  365. /package/dist/{chunk-BTBAHTQ5.js.map → chunk-CI6YIT5S.js.map} +0 -0
  366. /package/dist/{chunk-CUDSA46O.js.map → chunk-CUIAHZPZ.js.map} +0 -0
  367. /package/dist/{chunk-BTUIHRRR.js.map → chunk-D267K4V5.js.map} +0 -0
  368. /package/dist/{chunk-QICOU4RY.js.map → chunk-DLZBOEJY.js.map} +0 -0
  369. /package/dist/{chunk-BPLPGOUJ.js.map → chunk-DRS3OX77.js.map} +0 -0
  370. /package/dist/{chunk-3F3CVVDS.js.map → chunk-DTQ3OB7O.js.map} +0 -0
  371. /package/dist/{chunk-Z4UPRZR6.js.map → chunk-DZ2AVS2H.js.map} +0 -0
  372. /package/dist/{chunk-YINSSARY.js.map → chunk-EAWJSY2Z.js.map} +0 -0
  373. /package/dist/{chunk-QFPX7QZG.js.map → chunk-FMGTFMT3.js.map} +0 -0
  374. /package/dist/{chunk-CTD5CPD2.js.map → chunk-GSQDPL45.js.map} +0 -0
  375. /package/dist/{chunk-UUYQWKHO.js.map → chunk-IHPYTG6F.js.map} +0 -0
  376. /package/dist/{chunk-QATILW4B.js.map → chunk-IPUUMLSM.js.map} +0 -0
  377. /package/dist/{chunk-2BRCV2IV.js.map → chunk-JE3AKPOO.js.map} +0 -0
  378. /package/dist/{chunk-DZFPHARJ.js.map → chunk-KOE3RLNN.js.map} +0 -0
  379. /package/dist/{chunk-WQ6TRWAE.js.map → chunk-NMQXUGTU.js.map} +0 -0
  380. /package/dist/{chunk-65NQFK7D.js.map → chunk-OTVHRZZD.js.map} +0 -0
  381. /package/dist/{chunk-77E7LLMV.js.map → chunk-OWAYQK5V.js.map} +0 -0
  382. /package/dist/{chunk-YGSDWO3U.js.map → chunk-P4SD7BSA.js.map} +0 -0
  383. /package/dist/{chunk-G5F4MXXC.js.map → chunk-Q3IRE6E4.js.map} +0 -0
  384. /package/dist/{chunk-HZXO2CJ6.js.map → chunk-QHNO6AND.js.map} +0 -0
  385. /package/dist/{chunk-5OOTHWGJ.js.map → chunk-RMB22Q3R.js.map} +0 -0
  386. /package/dist/{chunk-R7XE5ZUR.js.map → chunk-UIYYS2YI.js.map} +0 -0
  387. /package/dist/{chunk-SYU5JOBQ.js.map → chunk-UWSETW6O.js.map} +0 -0
  388. /package/dist/{chunk-CK3DZ6OE.js.map → chunk-VCNGVU62.js.map} +0 -0
  389. /package/dist/{chunk-KG2CT762.js.map → chunk-VSKA2ATE.js.map} +0 -0
  390. /package/dist/{chunk-PQODT53H.js.map → chunk-WDFXRHOZ.js.map} +0 -0
  391. /package/dist/{chunk-6NMZXEJ7.js.map → chunk-WNO5W2TP.js.map} +0 -0
  392. /package/dist/{chunk-63JMI45N.js.map → chunk-XBRGJJK7.js.map} +0 -0
  393. /package/dist/{chunk-S6KMN6MJ.js.map → chunk-XFAWGUNX.js.map} +0 -0
  394. /package/dist/{chunk-SRLQROQJ.js.map → chunk-XSPRZZEX.js.map} +0 -0
  395. /package/dist/{chunk-EHC3FIJ4.js.map → chunk-Y5ZIQFWH.js.map} +0 -0
  396. /package/dist/{chunk-6IKXAAJI.js.map → chunk-YY63AMGP.js.map} +0 -0
  397. /package/dist/{chunk-7ML7STOD.js.map → chunk-ZGGTIBWN.js.map} +0 -0
  398. /package/dist/{chunk-X6HNXVXS.js.map → chunk-ZUIVJ3BB.js.map} +0 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Location day-file + sync-state store (issue #2044).
3
+ *
4
+ * Day documents live at `<memoryDir>/locations/<YYYY-MM-DD>.md` — OUTSIDE the
5
+ * memory scan roots (facts/, entities/, …) so raw location history never
6
+ * surfaces as a memory in recall or governance passes, and INSIDE the QMD
7
+ * collection root so the markdown stays full-text searchable once an index
8
+ * update runs. Per-source sync state lives at
9
+ * `<memoryDir>/state/locations/sync.json` and is written only after the day
10
+ * document is durable (see pipeline.ts).
11
+ *
12
+ * Both writes are atomic (temp file + rename) and containment-checked: a
13
+ * symlinked `locations/` root or day file is rejected even when it resolves
14
+ * inside the memory dir (AGENTS.md pattern #3), and a path that escapes the
15
+ * memory dir is refused outright.
16
+ */
17
+
18
+ import { createHash } from "node:crypto";
19
+ import { lstat, mkdir, readFile, readdir, realpath } from "node:fs/promises";
20
+ import path from "node:path";
21
+
22
+ import { writeFileAtomically } from "../maintenance/atomic-file.js";
23
+ import { pathIsInside } from "../utils/path-containment.js";
24
+ import { isValidLocationDate, placeDurations } from "./intervals.js";
25
+ import type { LocationSegment } from "./types.js";
26
+
27
+ export const LOCATIONS_DIR_NAME = "locations";
28
+ export const LOCATION_DAY_KIND = "location-day";
29
+ export const LOCATION_DAY_FORMAT_VERSION = 1;
30
+
31
+ /** Cap on remembered day payloads per source (~3 months of days). */
32
+ const MAX_TRACKED_LOCATION_DAYS = 90;
33
+
34
+ const DAY_FILE_NAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.md$/;
35
+
36
+ // Serializes state load→mutate→save sections per state file so two sources
37
+ // syncing concurrently cannot lose-update each other's entries.
38
+ const stateLocks = new Map<string, Promise<unknown>>();
39
+
40
+ function withStateLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
41
+ const prior = stateLocks.get(key) ?? Promise.resolve();
42
+ const next = prior.then(fn, fn);
43
+ const tail = next.then(
44
+ () => undefined,
45
+ () => undefined,
46
+ );
47
+ stateLocks.set(key, tail);
48
+ void tail.then(() => {
49
+ if (stateLocks.get(key) === tail) stateLocks.delete(key);
50
+ });
51
+ return next;
52
+ }
53
+
54
+ export function locationDayFilePath(memoryDir: string, date: string): string {
55
+ if (!isValidLocationDate(date)) {
56
+ // Never interpolate an unvalidated date into a filesystem path (a `../`
57
+ // would escape <memoryDir>/locations/). Reject loudly.
58
+ throw new RangeError(`Invalid location date "${date}"; expected a real YYYY-MM-DD day.`);
59
+ }
60
+ return path.join(memoryDir, LOCATIONS_DIR_NAME, `${date}.md`);
61
+ }
62
+
63
+ export function locationSyncStateFilePath(memoryDir: string): string {
64
+ return path.join(memoryDir, "state", "locations", "sync.json");
65
+ }
66
+
67
+ /** A source's derived day payload, kept in sync state for multi-source merges. */
68
+ export interface LocationSourceDayPayload {
69
+ observationCount: number;
70
+ segments: LocationSegment[];
71
+ providerDisplayName?: string;
72
+ }
73
+
74
+ export interface LocationSourceSyncState {
75
+ lastSyncedAtUtc?: string;
76
+ days?: Record<string, LocationSourceDayPayload>;
77
+ }
78
+
79
+ export interface LocationSyncStateFile {
80
+ version: 1;
81
+ sources: Record<string, LocationSourceSyncState>;
82
+ }
83
+
84
+ export function emptyLocationSyncState(): LocationSyncStateFile {
85
+ return { version: 1, sources: {} };
86
+ }
87
+
88
+ export async function loadLocationSyncState(memoryDir: string): Promise<LocationSyncStateFile> {
89
+ let raw: string;
90
+ try {
91
+ raw = await readFile(locationSyncStateFilePath(memoryDir), "utf-8");
92
+ } catch (err) {
93
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return emptyLocationSyncState();
94
+ throw err;
95
+ }
96
+ let parsed: unknown;
97
+ try {
98
+ parsed = JSON.parse(raw);
99
+ } catch {
100
+ // A corrupt state file must not brick syncing forever; treat it as a cold
101
+ // start. The worst case is re-syncing days already on disk, and the day
102
+ // file's contentHash makes that rewrite idempotent.
103
+ return emptyLocationSyncState();
104
+ }
105
+ if (
106
+ typeof parsed !== "object" ||
107
+ parsed === null ||
108
+ Array.isArray(parsed) ||
109
+ typeof (parsed as LocationSyncStateFile).sources !== "object" ||
110
+ (parsed as LocationSyncStateFile).sources === null
111
+ ) {
112
+ return emptyLocationSyncState();
113
+ }
114
+ return { version: 1, sources: (parsed as LocationSyncStateFile).sources };
115
+ }
116
+
117
+ export async function saveLocationSyncState(
118
+ memoryDir: string,
119
+ state: LocationSyncStateFile,
120
+ ): Promise<void> {
121
+ await mkdir(path.dirname(locationSyncStateFilePath(memoryDir)), { recursive: true, mode: 0o700 });
122
+ await writeFileAtomically(
123
+ locationSyncStateFilePath(memoryDir),
124
+ `${JSON.stringify(state, null, 2)}\n`,
125
+ );
126
+ }
127
+
128
+ function pruneSourceDays(source: LocationSourceSyncState): void {
129
+ const days = source.days;
130
+ if (days === undefined) return;
131
+ const keys = Object.keys(days).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
132
+ for (const key of keys.slice(0, Math.max(0, keys.length - MAX_TRACKED_LOCATION_DAYS))) {
133
+ delete days[key];
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Record one source's completed day sync. Runs the whole read-modify-write
139
+ * under the per-directory state lock so concurrent sources cannot clobber
140
+ * each other, and prunes day payloads beyond the retention cap.
141
+ */
142
+ export async function updateLocationSourceDay(
143
+ memoryDir: string,
144
+ sourceId: string,
145
+ date: string,
146
+ payload: LocationSourceDayPayload,
147
+ completedAtUtc: string,
148
+ ): Promise<LocationSyncStateFile> {
149
+ if (!isValidLocationDate(date)) {
150
+ throw new RangeError(`Invalid location date "${date}"; expected a real YYYY-MM-DD day.`);
151
+ }
152
+ return withStateLock(locationSyncStateFilePath(memoryDir), async () => {
153
+ const state = await loadLocationSyncState(memoryDir);
154
+ const source = state.sources[sourceId] ?? {};
155
+ const days = source.days ?? {};
156
+ days[date] = payload;
157
+ source.days = days;
158
+ source.lastSyncedAtUtc = completedAtUtc;
159
+ state.sources[sourceId] = source;
160
+ for (const candidate of Object.values(state.sources)) pruneSourceDays(candidate);
161
+ await saveLocationSyncState(memoryDir, state);
162
+ return state;
163
+ });
164
+ }
165
+ /** Frontmatter persisted on a rendered day document. */
166
+ export interface LocationDayMeta {
167
+ kind: typeof LOCATION_DAY_KIND;
168
+ date: string;
169
+ timezone: string;
170
+ formatVersion: number;
171
+ sources: string[];
172
+ observationCount: number;
173
+ /** SHA-256 of the rendered body (rebuild idempotency). */
174
+ contentHash: string;
175
+ }
176
+
177
+ /** A parsed day document (frontmatter + rendered body). */
178
+ export interface LocationDayDocument {
179
+ meta: LocationDayMeta;
180
+ body: string;
181
+ }
182
+
183
+ /** The per-source view a day document is composed from. */
184
+ export interface LocationDaySourceEntry extends LocationSourceDayPayload {
185
+ /** Provider display name for the timeline heading; falls back to the id. */
186
+ providerDisplayName?: string;
187
+ }
188
+
189
+ export function hashLocationBody(body: string): string {
190
+ return createHash("sha256").update(body, "utf8").digest("hex");
191
+ }
192
+
193
+ function clockHhMm(iso: string, timezone: string): string {
194
+ return new Intl.DateTimeFormat("en-GB", {
195
+ timeZone: timezone,
196
+ hour: "2-digit",
197
+ minute: "2-digit",
198
+ hour12: false,
199
+ }).format(new Date(iso));
200
+ }
201
+
202
+ function formatDuration(ms: number): string {
203
+ const totalMinutes = Math.round(ms / 60_000);
204
+ const hours = Math.floor(totalMinutes / 60);
205
+ const minutes = totalMinutes % 60;
206
+ if (hours <= 0) return `${minutes}m`;
207
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
208
+ }
209
+
210
+ /** Collapse whitespace and strip markdown emphasis/backticks from a label. */
211
+ function safeLabel(label: string): string {
212
+ return label.replace(/\s+/g, " ").replace(/[`*_]/g, "").trim();
213
+ }
214
+
215
+ function formatPlaceLabel(segment: LocationSegment): string {
216
+ const label = safeLabel(segment.place.label);
217
+ return segment.place.kind === undefined ? label : `${label} (${segment.place.kind})`;
218
+ }
219
+
220
+ function formatCoordinates(segment: LocationSegment): string {
221
+ if (segment.place.latitude === undefined || segment.place.longitude === undefined) return "";
222
+ return ` @ ${segment.place.latitude.toFixed(4)},${segment.place.longitude.toFixed(4)}`;
223
+ }
224
+
225
+ export function composeLocationDayBody(
226
+ date: string,
227
+ timezone: string,
228
+ sources: Record<string, LocationDaySourceEntry>,
229
+ ): string {
230
+ const lines: string[] = [`# Location day — ${date} (${timezone})`, ""];
231
+ const allSegments: LocationSegment[] = [];
232
+ let observationCount = 0;
233
+ for (const entry of Object.values(sources)) {
234
+ allSegments.push(...entry.segments);
235
+ observationCount += entry.observationCount;
236
+ }
237
+
238
+ lines.push("## Summary", "");
239
+ if (observationCount === 0) {
240
+ lines.push("No location observations recorded.", "");
241
+ } else {
242
+ const durations = placeDurations(allSegments).sort((a, b) => b.totalMs - a.totalMs);
243
+ lines.push(
244
+ `- Places visited: ${durations.length}`,
245
+ ...durations.map(
246
+ (duration) => `- ${safeLabel(duration.place.label)}: ${formatDuration(duration.totalMs)}`,
247
+ ),
248
+ "",
249
+ );
250
+ }
251
+
252
+ const sourceIds = Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
253
+ for (const sourceId of sourceIds) {
254
+ const entry = sources[sourceId]!;
255
+ lines.push(`## Timeline — ${safeLabel(entry.providerDisplayName ?? sourceId)}`, "");
256
+ if (entry.segments.length === 0) {
257
+ lines.push("No observations.", "");
258
+ continue;
259
+ }
260
+ for (const segment of entry.segments) {
261
+ lines.push(
262
+ `- ${clockHhMm(segment.startUtc, timezone)}–${clockHhMm(segment.endUtc, timezone)} — ${formatPlaceLabel(segment)}${formatCoordinates(segment)}`,
263
+ );
264
+ }
265
+ lines.push("");
266
+ }
267
+ return `${lines.join("\n").trimEnd()}\n`;
268
+ }
269
+
270
+ export function composeLocationDayMeta(
271
+ date: string,
272
+ timezone: string,
273
+ sources: Record<string, LocationDaySourceEntry>,
274
+ body: string,
275
+ ): LocationDayMeta {
276
+ let observationCount = 0;
277
+ for (const entry of Object.values(sources)) observationCount += entry.observationCount;
278
+ return {
279
+ kind: LOCATION_DAY_KIND,
280
+ date,
281
+ timezone,
282
+ formatVersion: LOCATION_DAY_FORMAT_VERSION,
283
+ sources: Object.keys(sources).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),
284
+ observationCount,
285
+ contentHash: hashLocationBody(body),
286
+ };
287
+ }
288
+
289
+ function frontmatterScalar(key: string, value: string | number): string {
290
+ return `${key}: ${typeof value === "number" ? value : JSON.stringify(value)}`;
291
+ }
292
+
293
+ function frontmatterList(key: string, values: readonly string[]): string[] {
294
+ if (values.length === 0) return [`${key}: []`];
295
+ const lines = [`${key}:`];
296
+ for (const value of values) lines.push(` - ${JSON.stringify(value)}`);
297
+ return lines;
298
+ }
299
+
300
+ export function serializeLocationDay(meta: LocationDayMeta, body: string): string {
301
+ const lines: string[] = ["---"];
302
+ lines.push(frontmatterScalar("kind", meta.kind));
303
+ lines.push(frontmatterScalar("date", meta.date));
304
+ lines.push(frontmatterScalar("timezone", meta.timezone));
305
+ lines.push(frontmatterScalar("formatVersion", meta.formatVersion));
306
+ lines.push(...frontmatterList("sources", meta.sources));
307
+ lines.push(frontmatterScalar("observationCount", meta.observationCount));
308
+ lines.push(frontmatterScalar("contentHash", meta.contentHash));
309
+ lines.push("---", "", body);
310
+ return lines.join("\n");
311
+ }
312
+
313
+ function parseScalar(raw: string): string {
314
+ try {
315
+ const parsed = JSON.parse(raw);
316
+ return typeof parsed === "string" ? parsed : raw;
317
+ } catch {
318
+ return raw;
319
+ }
320
+ }
321
+
322
+ /** A day document's frontmatter, parsed for idempotency checks and status. */
323
+ export interface LocationDaySummary {
324
+ date: string;
325
+ timezone: string;
326
+ sources: string[];
327
+ observationCount: number;
328
+ contentHash: string;
329
+ }
330
+
331
+ /**
332
+ * Parse a day document's frontmatter. Returns null when the content is not a
333
+ * location day document (wrong kind, missing frontmatter) so callers can
334
+ * distinguish a foreign file from a corrupt one.
335
+ */
336
+ export function parseLocationDaySummary(raw: string): LocationDaySummary | null {
337
+ if (!raw.startsWith("---\n")) return null;
338
+ const closeIndex = raw.indexOf("\n---\n", 4);
339
+ if (closeIndex === -1) return null;
340
+ const scalars: Record<string, string> = {};
341
+ const lists: Record<string, string[]> = {};
342
+ let currentList: string | null = null;
343
+ for (const line of raw.slice(4, closeIndex).split("\n")) {
344
+ const listItem = /^ {2}- (.*)$/.exec(line);
345
+ if (listItem !== null && currentList !== null) {
346
+ lists[currentList]!.push(parseScalar(listItem[1]!));
347
+ continue;
348
+ }
349
+ currentList = null;
350
+ const separator = line.indexOf(":");
351
+ if (separator === -1) continue;
352
+ const key = line.slice(0, separator);
353
+ const value = line.slice(separator + 1).trim();
354
+ if (value === "" || value === "[]") {
355
+ lists[key] = [];
356
+ currentList = key;
357
+ continue;
358
+ }
359
+ if (value.startsWith("[")) {
360
+ lists[key] = [parseScalar(value)];
361
+ currentList = key;
362
+ continue;
363
+ }
364
+ scalars[key] = value;
365
+ }
366
+ if (scalars.kind === undefined || parseScalar(scalars.kind) !== LOCATION_DAY_KIND) return null;
367
+ if (scalars.date === undefined || scalars.contentHash === undefined) return null;
368
+ return {
369
+ date: parseScalar(scalars.date),
370
+ timezone: scalars.timezone === undefined ? "UTC" : parseScalar(scalars.timezone),
371
+ sources: lists.sources ?? [],
372
+ observationCount: scalars.observationCount === undefined ? 0 : Number(scalars.observationCount) || 0,
373
+ contentHash: parseScalar(scalars.contentHash),
374
+ };
375
+ }
376
+
377
+ // ── Containment-checked day-file IO ─────────────────────────────────────────
378
+
379
+ function isErrnoCode(err: unknown, code: string): boolean {
380
+ return err instanceof Error && (err as NodeJS.ErrnoException).code === code;
381
+ }
382
+
383
+ async function assertNotSymlink(target: string, kind: "directory" | "day file"): Promise<void> {
384
+ let stat: { isSymbolicLink(): boolean };
385
+ try {
386
+ stat = await lstat(target);
387
+ } catch (err) {
388
+ if (isErrnoCode(err, "ENOENT")) return;
389
+ throw err;
390
+ }
391
+ if (stat.isSymbolicLink()) {
392
+ throw new Error(
393
+ `locations ${kind} '${path.basename(target)}' is a symbolic link — refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`,
394
+ );
395
+ }
396
+ }
397
+
398
+ async function assertNoEscape(rootReal: string, candidate: string): Promise<void> {
399
+ let candidateReal: string;
400
+ try {
401
+ candidateReal = await realpath(candidate);
402
+ } catch {
403
+ return;
404
+ }
405
+ if (!pathIsInside(rootReal, candidateReal)) {
406
+ throw new Error(
407
+ "location day path resolves outside the memory dir — refusing to follow a symlink/traversal (AGENTS.md pattern #3)",
408
+ );
409
+ }
410
+ }
411
+
412
+ async function assertDayPathContained(memoryDir: string, targetPath: string): Promise<void> {
413
+ let rootReal: string;
414
+ try {
415
+ rootReal = await realpath(memoryDir);
416
+ } catch {
417
+ rootReal = path.resolve(memoryDir);
418
+ }
419
+ const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);
420
+ await assertNoEscape(rootReal, locationsDir);
421
+ await assertNoEscape(rootReal, targetPath);
422
+ // Reject a symlinked locations root or day file outright — even when the
423
+ // link resolves inside the memory dir (aliasing / tampering) — before IO.
424
+ await assertNotSymlink(locationsDir, "directory");
425
+ await assertNotSymlink(targetPath, "day file");
426
+ }
427
+
428
+ /**
429
+ * Idempotently persist a day document: skip the write when the on-disk
430
+ * contentHash already matches. The write is atomic and containment-checked;
431
+ * the caller (pipeline) advances sync state only after this resolves.
432
+ */
433
+ export async function writeLocationDay(
434
+ memoryDir: string,
435
+ date: string,
436
+ serialized: string,
437
+ ): Promise<boolean> {
438
+ const target = locationDayFilePath(memoryDir, date);
439
+ await assertDayPathContained(memoryDir, target);
440
+ try {
441
+ if ((await readFile(target, "utf-8")) === serialized) return false;
442
+ } catch (err) {
443
+ if (!isErrnoCode(err, "ENOENT")) throw err;
444
+ }
445
+ await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
446
+ await writeFileAtomically(target, serialized);
447
+ return true;
448
+ }
449
+
450
+ /** Read a stored day document's raw markdown; null when absent. */
451
+ export async function readLocationDay(memoryDir: string, date: string): Promise<string | null> {
452
+ const target = locationDayFilePath(memoryDir, date);
453
+ await assertDayPathContained(memoryDir, target);
454
+ try {
455
+ return await readFile(target, "utf-8");
456
+ } catch (err) {
457
+ if (isErrnoCode(err, "ENOENT")) return null;
458
+ throw err;
459
+ }
460
+ }
461
+
462
+ /** List dates that have a stored day document, newest first. */
463
+ export async function listLocationDayDates(memoryDir: string): Promise<string[]> {
464
+ const locationsDir = path.join(memoryDir, LOCATIONS_DIR_NAME);
465
+ await assertDayPathContained(memoryDir, locationsDir);
466
+ let entries: string[];
467
+ try {
468
+ entries = await readdir(locationsDir);
469
+ } catch (err) {
470
+ if (isErrnoCode(err, "ENOENT")) return [];
471
+ throw err;
472
+ }
473
+ const dates = entries
474
+ .filter((entry) => DAY_FILE_NAME_PATTERN.test(entry))
475
+ .map((entry) => entry.slice(0, -3));
476
+ dates.sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));
477
+ return dates;
478
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Location context subsystem — shared types (issue #2044).
3
+ *
4
+ * A host-agnostic foundation: the provider contract, the observation/day
5
+ * shapes, and health-check results. No HTTP client lives here — providers are
6
+ * registered from host adapters (a future provider package is one such
7
+ * adapter), keeping `@remnic/core` free of host SDKs and network specifics.
8
+ *
9
+ * All timestamps are UTC ISO-8601; day bucketing is half-open [start, end).
10
+ */
11
+
12
+ /** A named place the user was observed at. */
13
+ export interface LocationPlace {
14
+ /** Provider-stable place identifier. */
15
+ id: string;
16
+ /** Human-readable label, already redacted by the provider. */
17
+ label: string;
18
+ kind?: "home" | "work" | "poi" | "transit" | "other";
19
+ /** Present only when coordinate retention is enabled. */
20
+ latitude?: number;
21
+ longitude?: number;
22
+ }
23
+
24
+ /** One place observation at a single instant, from a single provider. */
25
+ export interface LocationObservation {
26
+ /** UTC ISO-8601 observation instant. */
27
+ observedAtUtc: string;
28
+ place: LocationPlace;
29
+ /** Provider confidence in [0,1], when reported. */
30
+ confidence?: number;
31
+ }
32
+
33
+ /** One page of observations from a provider fetch. */
34
+ export interface LocationObservationPage {
35
+ observations: LocationObservation[];
36
+ nextCursor: string | null;
37
+ }
38
+
39
+ /** Result of a provider auth/health probe. */
40
+ export interface LocationProviderCheck {
41
+ ok: boolean;
42
+ detail?: string;
43
+ }
44
+
45
+ /**
46
+ * Provider contract for a location source. Implemented by host adapters and
47
+ * registered in `registry.ts`; core never statically imports a provider
48
+ * package, so à-la-carte installs stay partial (a source whose provider is
49
+ * not registered is skipped, not an error).
50
+ */
51
+ export interface LocationProvider {
52
+ /** Stable provider id (registry key; matches a config source id). */
53
+ id: string;
54
+ displayName: string;
55
+ /** Probe connectivity/auth without mutating anything. */
56
+ verify(signal?: AbortSignal): Promise<LocationProviderCheck>;
57
+ /** Fetch every observation in the half-open [startUtc, endUtc) window. */
58
+ fetchObservations(opts: {
59
+ startUtc: string;
60
+ endUtc: string;
61
+ cursor?: string | null;
62
+ signal?: AbortSignal;
63
+ }): Promise<LocationObservationPage>;
64
+ }
65
+
66
+ /** One configured location source: a registered provider plus its toggle. */
67
+ export interface LocationSourceConfig {
68
+ /** Must match a registered provider id. */
69
+ id: string;
70
+ /** `false` short-circuits this source only. */
71
+ enabled: boolean;
72
+ }
73
+
74
+ /** Opt-in location synchronization settings. */
75
+ export interface LocationConfig {
76
+ /** Master gate: `false` short-circuits every location path. */
77
+ enabled: boolean;
78
+ /** IANA timezone used to bucket observations into local days. */
79
+ timezone: string;
80
+ /** Number of local days to synchronize per run; integer 1..90. */
81
+ syncDays: number;
82
+ /**
83
+ * When `false` (the default), place coordinates are dropped before any
84
+ * persistence, rendering, or error reporting — only place labels remain.
85
+ */
86
+ retainCoordinates: boolean;
87
+ sources: LocationSourceConfig[];
88
+ }
89
+
90
+ /** A time-resolved place visit derived from consecutive observations. */
91
+ export interface LocationSegment {
92
+ startUtc: string;
93
+ endUtc: string;
94
+ place: LocationPlace;
95
+ }
@@ -6,6 +6,7 @@ import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
6
6
  import {
7
7
  isActivityDigestPath,
8
8
  isArtifactMemoryPath,
9
+ isLocationDayPath,
9
10
  isMeetingRecordPath,
10
11
  } from "./orchestrator-helpers.js";
11
12
 
@@ -133,6 +134,7 @@ export function isSearchExcludedPath(
133
134
  if (
134
135
  isArtifactMemoryPath(candidate) ||
135
136
  isNamespacedActivityDigestPath(candidate, policy.memoryDir) ||
137
+ isNamespacedLocationDayPath(candidate, policy.memoryDir) ||
136
138
  isMeetingRecordPath(candidate)
137
139
  ) {
138
140
  return true;
@@ -160,6 +162,21 @@ function isNamespacedActivityDigestPath(candidate: string, memoryDir?: string):
160
162
  return isActivityDigestPath(namespaced[2] ?? "", memoryDir);
161
163
  }
162
164
 
165
+ /**
166
+ * The location day-file check, applied against the NAMESPACE root a hit came
167
+ * from — same rationale as `isNamespacedActivityDigestPath`: a namespaced
168
+ * search rewrites hits to absolute paths beneath `…/namespaces/<ns>/`, so the
169
+ * root-aware predicate is re-measured against the namespace's own root.
170
+ */
171
+ function isNamespacedLocationDayPath(candidate: string, memoryDir?: string): boolean {
172
+ if (isLocationDayPath(candidate, memoryDir)) return true;
173
+ if (memoryDir === undefined || memoryDir.length === 0) return false;
174
+ const relative = path.relative(memoryDir, path.resolve(memoryDir, candidate));
175
+ const namespaced = /^namespaces[\\/]([^\\/]+)[\\/](.*)$/.exec(relative);
176
+ if (namespaced === null) return false;
177
+ return isLocationDayPath(namespaced[2] ?? "", memoryDir);
178
+ }
179
+
163
180
  /** A path as given, plus its form with a leading COLLECTION segment removed. */
164
181
  function collectionSpellings(
165
182
  filePath: string,
@@ -643,6 +643,28 @@ export function isActivityDigestPath(filePath: string, memoryRoot?: string): boo
643
643
  return ACTIVITY_DIGEST_ANYWHERE.test(filePath);
644
644
  }
645
645
 
646
+ /** Exactly `locations/<YYYY-MM-DD>.md`, at the top level of a memory root. */
647
+ const LOCATION_DAY_TOPLEVEL = /^locations[\\/]\d{4}-\d{2}-\d{2}\.md$/i;
648
+ /** The day-file shape anywhere in a path (root-unaware best-effort). */
649
+ const LOCATION_DAY_ANYWHERE = /(?:^|[\\/])locations[\\/]\d{4}-\d{2}-\d{2}\.md$/i;
650
+
651
+ /**
652
+ * Location day documents (issue #2044) live at
653
+ * `<memoryRoot>/locations/<YYYY-MM-DD>.md` — a dedicated searchable surface,
654
+ * never generic recall (place history must not auto-inject into ordinary
655
+ * prompts). Root-aware exactly like `isActivityDigestPath`: with a root, only
656
+ * the top-level `locations/<date>.md` matches, so an ordinary memory nested
657
+ * under a category (e.g. `facts/locations-note.md`) stays recallable; without
658
+ * a root, falls back to the day-file shape anywhere (best-effort).
659
+ */
660
+ export function isLocationDayPath(filePath: string, memoryRoot?: string): boolean {
661
+ if (memoryRoot !== undefined && memoryRoot.length > 0) {
662
+ const relative = path.relative(memoryRoot, path.resolve(memoryRoot, filePath));
663
+ return LOCATION_DAY_TOPLEVEL.test(relative);
664
+ }
665
+ return LOCATION_DAY_ANYWHERE.test(filePath);
666
+ }
667
+
646
668
  export function buildCompressionGuidelinesMarkdown(
647
669
  events: MemoryActionEvent[],
648
670
  generatedAtIso: string = new Date().toISOString(),
@@ -47,6 +47,7 @@ const INTERNAL_QMD_ROOTS = new Set([
47
47
  "config",
48
48
  "entities",
49
49
  "identity",
50
+ "locations",
50
51
  "meetings",
51
52
  "namespaces",
52
53
  "state",
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { BoundedJsonlStateConfig } from "./bounded-jsonl-state.js";
2
2
  import type { MeetingsConfig } from "./meetings/types.js";
3
+ import type { LocationConfig } from "./location/types.js";
3
4
  import type { ActivityConfig } from "./activity/types.js";
4
5
  import type { WearablesConfig } from "./wearables/types.js";
5
6
  import type { ExtractionLivenessConfig } from "./extraction-liveness.js";
@@ -1232,10 +1233,8 @@ export interface PluginConfig extends BoundedJsonlStateConfig, SecurityConfig {
1232
1233
  */
1233
1234
  wearables: WearablesConfig;
1234
1235
  activity: ActivityConfig;
1235
- /** Retrospective meeting intelligence (issue #1900): detect + fuse meetings
1236
- * from already-ingested audio + screen activity. Disabled by default.
1237
- */
1238
1236
  meetings: MeetingsConfig;
1237
+ location: LocationConfig;
1239
1238
  /**
1240
1239
  * At-rest encryption configuration (issue #690 PR 3/4).
1241
1240
  *