@remnic/core 9.69.12 → 9.69.13

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 (357) hide show
  1. package/dist/access-admin-ops-surface.d.ts +11 -11
  2. package/dist/access-admin-ops-surface.js +4 -4
  3. package/dist/access-authorization-probe.d.ts +11 -11
  4. package/dist/access-authorization-probe.js +5 -5
  5. package/dist/access-boundary.d.ts +11 -11
  6. package/dist/access-boundary.js +4 -4
  7. package/dist/access-cli.js +28 -28
  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 +4 -4
  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 +4 -4
  17. package/dist/access-http.d.ts +12 -12
  18. package/dist/access-http.js +15 -15
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +4 -4
  21. package/dist/access-lcm-surface.d.ts +11 -11
  22. package/dist/access-lcm-surface.js +4 -4
  23. package/dist/access-mcp.d.ts +11 -11
  24. package/dist/access-mcp.js +11 -11
  25. package/dist/access-memory-search-fanout.d.ts +2 -2
  26. package/dist/{access-namespace-preflight-Cy0yPsaE.d.ts → access-namespace-preflight-Rzuo1nGR.d.ts} +1 -1
  27. package/dist/access-namespace-preflight.d.ts +3 -3
  28. package/dist/access-namespace-preflight.js +4 -4
  29. package/dist/access-observe-write-surface.d.ts +11 -11
  30. package/dist/access-observe-write-surface.js +4 -4
  31. package/dist/access-offline-manifest.d.ts +9 -9
  32. package/dist/access-operations-batch.js +5 -5
  33. package/dist/access-operations.d.ts +12 -12
  34. package/dist/access-operations.js +10 -10
  35. package/dist/access-recall-concurrency.d.ts +11 -11
  36. package/dist/access-recall-concurrency.js +4 -4
  37. package/dist/access-recall-response.d.ts +11 -11
  38. package/dist/access-recall-response.js +4 -4
  39. package/dist/access-recall-surface.d.ts +11 -11
  40. package/dist/access-recall-surface.js +4 -4
  41. package/dist/{access-service-Baz5xW5X.d.ts → access-service-Bb2Fykzc.d.ts} +7 -7
  42. package/dist/access-service-helpers.d.ts +11 -11
  43. package/dist/access-service.d.ts +11 -11
  44. package/dist/access-service.js +4 -4
  45. package/dist/access-surface-catalog.d.ts +11 -11
  46. package/dist/access-surface-catalog.js +6 -6
  47. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  48. package/dist/action-confidence.d.ts +1 -1
  49. package/dist/active-memory-bridge.d.ts +1 -1
  50. package/dist/active-recall.d.ts +1 -1
  51. package/dist/active-recall.js +4 -4
  52. package/dist/ambient-provenance.d.ts +1 -1
  53. package/dist/artifact-search.d.ts +1 -1
  54. package/dist/behavior-learner.d.ts +1 -1
  55. package/dist/behavior-signals.d.ts +1 -1
  56. package/dist/bootstrap.d.ts +9 -9
  57. package/dist/briefing-window.d.ts +2 -2
  58. package/dist/briefing.d.ts +2 -2
  59. package/dist/buffer-surprise-report.d.ts +1 -1
  60. package/dist/buffer-turn-helpers.d.ts +1 -1
  61. package/dist/buffer.d.ts +2 -2
  62. package/dist/buffer.js +4 -4
  63. package/dist/bulk-import/index.d.ts +3 -3
  64. package/dist/calibration.d.ts +1 -1
  65. package/dist/capabilities.d.ts +1 -1
  66. package/dist/{catalog-9X8WpZNO.d.ts → catalog-DjTLVOYp.d.ts} +1 -1
  67. package/dist/causal-behavior.d.ts +1 -1
  68. package/dist/causal-consolidation.d.ts +1 -1
  69. package/dist/causal-trajectory-graph.d.ts +1 -1
  70. package/dist/{chunk-TEZ2HTC7.js → chunk-33JKYLPX.js} +2 -2
  71. package/dist/{chunk-MNH2GM4O.js → chunk-3BS2OGDT.js} +6 -19
  72. package/dist/{chunk-MNH2GM4O.js.map → chunk-3BS2OGDT.js.map} +1 -1
  73. package/dist/{chunk-5PTS43CH.js → chunk-4LG4CPOC.js} +2 -2
  74. package/dist/{chunk-Z33VUMKY.js → chunk-5H2G5X4R.js} +2 -2
  75. package/dist/{chunk-XFPHV65N.js → chunk-5JQV4T5Q.js} +2 -2
  76. package/dist/{chunk-OASGWNYL.js → chunk-63I7LMLG.js} +2 -2
  77. package/dist/{chunk-J3NGQ5SC.js → chunk-7GSSHAXC.js} +2 -2
  78. package/dist/{chunk-X4FIZAVQ.js → chunk-7JV4JST3.js} +854 -42
  79. package/dist/chunk-7JV4JST3.js.map +1 -0
  80. package/dist/{chunk-VHAKBUBD.js → chunk-B64IVEOH.js} +2 -2
  81. package/dist/{chunk-R7FXPG4T.js → chunk-C25N5HWX.js} +2 -2
  82. package/dist/{chunk-DH3V7RCQ.js → chunk-F2ERDKY2.js} +7 -7
  83. package/dist/{chunk-LJNLLNEM.js → chunk-HODFGQGT.js} +2 -2
  84. package/dist/{chunk-B75VCEX5.js → chunk-K2IJH4D3.js} +2 -2
  85. package/dist/{chunk-227WJVPY.js → chunk-KLSPKMIO.js} +2 -2
  86. package/dist/{chunk-5P2KNFJZ.js → chunk-MSDIETFC.js} +3 -3
  87. package/dist/{chunk-HFRGCNFY.js → chunk-OAKATRAN.js} +2 -2
  88. package/dist/chunk-OGUKSLYZ.js +560 -0
  89. package/dist/chunk-OGUKSLYZ.js.map +1 -0
  90. package/dist/{chunk-M6L7HI7R.js → chunk-P6FHFTZ7.js} +16 -1
  91. package/dist/chunk-P6FHFTZ7.js.map +1 -0
  92. package/dist/{chunk-LWBSEYW3.js → chunk-PAWIY5OQ.js} +5 -5
  93. package/dist/{chunk-3ITTF7YH.js → chunk-QVI63XSO.js} +90 -383
  94. package/dist/chunk-QVI63XSO.js.map +1 -0
  95. package/dist/{chunk-6TOWUN2R.js → chunk-RE3H6Y2X.js} +2 -2
  96. package/dist/{chunk-V3HUBF7T.js → chunk-S35IIP5P.js} +2 -2
  97. package/dist/{chunk-3YKOFCKP.js → chunk-SXHBZXHK.js} +2 -2
  98. package/dist/{chunk-7NCUHD5U.js → chunk-VAMZYOOV.js} +2 -2
  99. package/dist/{chunk-PGCZLHRB.js → chunk-W272SWN7.js} +3 -3
  100. package/dist/{chunk-5HO3QBAD.js → chunk-W6GEMUZM.js} +2 -2
  101. package/dist/{chunk-UCNQV43K.js → chunk-XQYTSZW3.js} +2 -2
  102. package/dist/{chunk-GR4C3FHT.js → chunk-ZKZCJRRO.js} +2 -2
  103. package/dist/{cli-CMtkGBdz.d.ts → cli-DXXy60A4.d.ts} +5 -5
  104. package/dist/cli.d.ts +13 -13
  105. package/dist/cli.js +21 -21
  106. package/dist/coding/export-okf-codegraph.d.ts +1 -1
  107. package/dist/coding/pre-action-gate.d.ts +1 -1
  108. package/dist/compounding/engine.d.ts +2 -2
  109. package/dist/compounding/preference-consolidator.d.ts +1 -1
  110. package/dist/compression-optimizer.d.ts +1 -1
  111. package/dist/config.d.ts +1 -1
  112. package/dist/config.js +4 -4
  113. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  114. package/dist/connectors/codex-materialize.d.ts +1 -1
  115. package/dist/connectors/index.d.ts +1 -1
  116. package/dist/consolidation-provenance-check.d.ts +2 -2
  117. package/dist/consolidation-undo.d.ts +2 -2
  118. package/dist/contradiction/index.d.ts +3 -3
  119. package/dist/converge-config.d.ts +1 -1
  120. package/dist/convergence-refresh.d.ts +2 -2
  121. package/dist/conversation-index/backend.d.ts +1 -1
  122. package/dist/conversation-index/chunker.d.ts +1 -1
  123. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  124. package/dist/conversation-index/indexer.d.ts +1 -1
  125. package/dist/conversation-index/search.d.ts +1 -1
  126. package/dist/corpus-watermark.d.ts +1 -1
  127. package/dist/cross-lingual-recall.d.ts +1 -1
  128. package/dist/day-summary.d.ts +1 -1
  129. package/dist/deep-recall-http-glue.d.ts +11 -11
  130. package/dist/deep-recall-mcp-tools.d.ts +11 -11
  131. package/dist/deep-recall-policy-llm.d.ts +1 -1
  132. package/dist/deep-recall-seeds.d.ts +1 -1
  133. package/dist/delinearize.d.ts +1 -1
  134. package/dist/dependency-propagation-config.d.ts +1 -1
  135. package/dist/{dependency-propagation-delivery-BZGUecZf.d.ts → dependency-propagation-delivery-37JdQv_3.d.ts} +1 -1
  136. package/dist/direct-answer-wiring.d.ts +1 -1
  137. package/dist/direct-answer.d.ts +1 -1
  138. package/dist/embedding-fallback.d.ts +1 -1
  139. package/dist/enrichment/index.d.ts +1 -1
  140. package/dist/entity-origin-fields.d.ts +1 -1
  141. package/dist/entity-retrieval.d.ts +2 -2
  142. package/dist/entity-schema.d.ts +1 -1
  143. package/dist/episodic-context.d.ts +1 -1
  144. package/dist/eval-trajectory-contracts.d.ts +1 -1
  145. package/dist/eval-trajectory-store.d.ts +1 -1
  146. package/dist/eval-trajectory.d.ts +1 -1
  147. package/dist/explicit-capture.d.ts +9 -9
  148. package/dist/external-wiki-access.d.ts +11 -11
  149. package/dist/external-wiki-access.js +5 -5
  150. package/dist/external-wiki-collection-registration.d.ts +1 -1
  151. package/dist/external-wiki-collection.d.ts +1 -1
  152. package/dist/external-wiki-mcp-tools.d.ts +11 -11
  153. package/dist/extraction-error-classification.d.ts +1 -1
  154. package/dist/extraction-faithfulness.d.ts +1 -1
  155. package/dist/extraction-judge-telemetry.d.ts +1 -1
  156. package/dist/extraction-judge-training.d.ts +1 -1
  157. package/dist/extraction-judge.d.ts +1 -1
  158. package/dist/extraction-liveness.d.ts +1 -1
  159. package/dist/extraction-normalization.d.ts +1 -1
  160. package/dist/extraction-prompt.d.ts +1 -1
  161. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  162. package/dist/extraction-source-grounding.d.ts +1 -1
  163. package/dist/extraction.d.ts +1 -1
  164. package/dist/extraction.js +5 -5
  165. package/dist/fallback-llm.d.ts +1 -1
  166. package/dist/graph-dashboard-diff.d.ts +1 -1
  167. package/dist/graph-dashboard-key.d.ts +1 -1
  168. package/dist/graph-dashboard-parser.d.ts +1 -1
  169. package/dist/graph-edge-reinforcement.d.ts +1 -1
  170. package/dist/graph-path-reconstruction.d.ts +1 -1
  171. package/dist/graph-path-scoring.d.ts +1 -1
  172. package/dist/graph-snapshot.d.ts +1 -1
  173. package/dist/graph.d.ts +1 -1
  174. package/dist/importance.d.ts +1 -1
  175. package/dist/importers/index.d.ts +1 -1
  176. package/dist/in-flight-reads.d.ts +1 -1
  177. package/dist/{index-B4iTvT-w.d.ts → index-DquhYqsc.d.ts} +2 -2
  178. package/dist/index.d.ts +200 -62
  179. package/dist/index.js +59 -37
  180. package/dist/intent.d.ts +1 -1
  181. package/dist/lcm/engine.d.ts +1 -1
  182. package/dist/lcm/index.d.ts +1 -1
  183. package/dist/lcm/tools.d.ts +1 -1
  184. package/dist/lifecycle.d.ts +1 -1
  185. package/dist/live-connectors-runner.d.ts +1 -1
  186. package/dist/local-llm.d.ts +1 -1
  187. package/dist/local-model-endpoint.d.ts +1 -1
  188. package/dist/location/index.d.ts +3 -3
  189. package/dist/location/index.js +5 -5
  190. package/dist/maintenance/memory-governance.d.ts +1 -1
  191. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  192. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  193. package/dist/{maintenance-BBpNqEeJ.d.ts → maintenance-CizZOycr.d.ts} +3 -3
  194. package/dist/mcp-memory-inspector-app.d.ts +11 -11
  195. package/dist/memory-action-policy.d.ts +1 -1
  196. package/dist/memory-cache.d.ts +1 -1
  197. package/dist/memory-controller.d.ts +1 -1
  198. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  199. package/dist/memory-projection-store.d.ts +1 -1
  200. package/dist/memory-provenance.d.ts +1 -1
  201. package/dist/memory-snapshot.d.ts +1 -1
  202. package/dist/memory-subject-cli.d.ts +11 -11
  203. package/dist/memory-subject.d.ts +11 -11
  204. package/dist/memory-worth-outcomes.d.ts +2 -2
  205. package/dist/models-json.d.ts +1 -1
  206. package/dist/namespaces/migrate.d.ts +3 -3
  207. package/dist/namespaces/principal.d.ts +1 -1
  208. package/dist/namespaces/search.d.ts +1 -1
  209. package/dist/namespaces/storage.d.ts +3 -3
  210. package/dist/native-knowledge.d.ts +1 -1
  211. package/dist/offline-sync-impression-drain.d.ts +1 -1
  212. package/dist/operator-doctor-corpus.d.ts +1 -1
  213. package/dist/operator-doctor-replica.d.ts +1 -1
  214. package/dist/operator-toolkit.d.ts +3 -3
  215. package/dist/operator-toolkit.js +5 -5
  216. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  217. package/dist/orchestration/maintenance.d.ts +4 -4
  218. package/dist/orchestration/maintenance.js +6 -6
  219. package/dist/{orchestrator-DglY1H0w.d.ts → orchestrator-DqLcuNMP.d.ts} +8 -8
  220. package/dist/orchestrator.d.ts +9 -9
  221. package/dist/orchestrator.js +28 -28
  222. package/dist/patterns-cli.d.ts +1 -1
  223. package/dist/{pipeline-C7qRsy6g.d.ts → pipeline-DbJpmcRu.d.ts} +1 -1
  224. package/dist/policy-runtime.d.ts +1 -1
  225. package/dist/proactive-contention.d.ts +1 -1
  226. package/dist/provenance.d.ts +1 -1
  227. package/dist/{public-http-CU-Cwwdb.d.ts → public-http-NQ3UUp6W.d.ts} +1 -1
  228. package/dist/{qmd-YRLTa3AE.d.ts → qmd-DA_7fbSz.d.ts} +1 -1
  229. package/dist/qmd-preflight.d.ts +2 -2
  230. package/dist/qmd-recall-cache.d.ts +1 -1
  231. package/dist/qmd.d.ts +2 -2
  232. package/dist/recall-concurrency-config.d.ts +1 -1
  233. package/dist/recall-disclosure-escalation.d.ts +1 -1
  234. package/dist/recall-explain-renderer.d.ts +1 -1
  235. package/dist/recall-memory-map.d.ts +1 -1
  236. package/dist/recall-navigate-access.d.ts +1 -1
  237. package/dist/recall-navigate-disclosure.d.ts +1 -1
  238. package/dist/recall-navigate.d.ts +1 -1
  239. package/dist/recall-planner-llm.d.ts +1 -1
  240. package/dist/recall-state.d.ts +1 -1
  241. package/dist/recall-tag-filter.d.ts +1 -1
  242. package/dist/recall-timings.d.ts +1 -1
  243. package/dist/recall-xray-cli.d.ts +1 -1
  244. package/dist/recall-xray-renderer.d.ts +1 -1
  245. package/dist/recall-xray.d.ts +1 -1
  246. package/dist/reconcile/cursor.d.ts +1 -1
  247. package/dist/reconcile/manifest.d.ts +1 -1
  248. package/dist/reconcile/plan.d.ts +1 -1
  249. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  250. package/dist/replay/normalizers/claude.d.ts +1 -1
  251. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  252. package/dist/replay/normalizers/shared.d.ts +1 -1
  253. package/dist/replay/runner.d.ts +1 -1
  254. package/dist/replay/types.d.ts +1 -1
  255. package/dist/replica-divergence.d.ts +1 -1
  256. package/dist/replica-peers-config.d.ts +1 -1
  257. package/dist/resolve-auth-token.d.ts +1 -1
  258. package/dist/resume-bundles.js +5 -5
  259. package/dist/retrieval-agents.d.ts +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/salvage-envelope.d.ts +1 -1
  264. package/dist/{scope-profiles-BVvqVzJX.d.ts → scope-profiles-BpiWKZzx.d.ts} +1 -1
  265. package/dist/search/embed-helper.d.ts +1 -1
  266. package/dist/search/factory.d.ts +1 -1
  267. package/dist/search/index.d.ts +1 -1
  268. package/dist/search/lancedb-backend.d.ts +1 -1
  269. package/dist/search/meilisearch-backend.d.ts +1 -1
  270. package/dist/search/noop-backend.d.ts +1 -1
  271. package/dist/search/orama-backend.d.ts +1 -1
  272. package/dist/search/port.d.ts +1 -1
  273. package/dist/search/remote-backend.d.ts +1 -1
  274. package/dist/{semantic-consolidation-BzCI6Kuu.d.ts → semantic-consolidation-NJpaZpkY.d.ts} +1 -1
  275. package/dist/semantic-consolidation.d.ts +2 -2
  276. package/dist/semantic-rule-verifier.d.ts +1 -1
  277. package/dist/{service-DQ3Gscy5.d.ts → service-DdlZjX1D.d.ts} +2 -2
  278. package/dist/session-observer-bands.d.ts +1 -1
  279. package/dist/session-observer-state.d.ts +1 -1
  280. package/dist/shared-context/manager.d.ts +1 -1
  281. package/dist/signal.d.ts +1 -1
  282. package/dist/source-agent-qualifier.d.ts +1 -1
  283. package/dist/source-agent-qualifier.js +4 -4
  284. package/dist/standup/build.js +2 -2
  285. package/dist/{storage-DmTbQdD0.d.ts → storage-C7BWrpU9.d.ts} +1 -1
  286. package/dist/storage.d.ts +2 -2
  287. package/dist/subject-config.d.ts +1 -1
  288. package/dist/summarizer.d.ts +1 -1
  289. package/dist/summary-snapshot.d.ts +1 -1
  290. package/dist/support-passport/index.d.ts +14 -14
  291. package/dist/support-passport/index.js +7 -7
  292. package/dist/temporal-supersession.d.ts +2 -2
  293. package/dist/temporal-timeline-recall.d.ts +1 -1
  294. package/dist/temporal-validity.d.ts +1 -1
  295. package/dist/threading.d.ts +1 -1
  296. package/dist/tier-migration.d.ts +2 -2
  297. package/dist/tier-routing.d.ts +1 -1
  298. package/dist/topics.d.ts +1 -1
  299. package/dist/transcript.d.ts +1 -1
  300. package/dist/transfer/export-okf.d.ts +1 -1
  301. package/dist/trust-score-stage.d.ts +1 -1
  302. package/dist/trust-score.d.ts +1 -1
  303. package/dist/{types-CLRTESSD.d.ts → types-Cy1l6fzp.d.ts} +48 -1
  304. package/dist/types.d.ts +1 -1
  305. package/dist/utility-runtime.d.ts +1 -1
  306. package/dist/who-knows-cli.d.ts +11 -11
  307. package/dist/who-knows.d.ts +11 -11
  308. package/dist/write-envelope.d.ts +1 -1
  309. package/package.json +2 -2
  310. package/src/activity/config.test.ts +94 -1
  311. package/src/activity/config.ts +256 -3
  312. package/src/activity/digest.ts +21 -0
  313. package/src/activity/index.ts +2 -0
  314. package/src/activity/runner.ts +2 -17
  315. package/src/activity/timeline/publish-cli.test.ts +169 -0
  316. package/src/activity/timeline/publish-cli.ts +144 -0
  317. package/src/activity/timeline/query.ts +51 -15
  318. package/src/activity/types.ts +45 -0
  319. package/src/activity/vault-insert.ts +14 -33
  320. package/src/activity/vault-path.ts +100 -22
  321. package/src/activity/vault-publish.test.ts +51 -0
  322. package/src/activity/vault-publish.ts +157 -17
  323. package/src/activity/vault-publisher.test.ts +881 -0
  324. package/src/activity/vault-publisher.ts +597 -0
  325. package/src/activity/vault-region.test.ts +5 -0
  326. package/src/activity/vault-region.ts +7 -2
  327. package/src/cli/timeline-publish-commands.ts +34 -0
  328. package/src/cli.ts +2 -2
  329. package/src/config.test.ts +24 -1
  330. package/dist/chunk-3ITTF7YH.js.map +0 -1
  331. package/dist/chunk-KJUWWN3T.js +0 -237
  332. package/dist/chunk-KJUWWN3T.js.map +0 -1
  333. package/dist/chunk-M6L7HI7R.js.map +0 -1
  334. package/dist/chunk-X4FIZAVQ.js.map +0 -1
  335. /package/dist/{chunk-TEZ2HTC7.js.map → chunk-33JKYLPX.js.map} +0 -0
  336. /package/dist/{chunk-5PTS43CH.js.map → chunk-4LG4CPOC.js.map} +0 -0
  337. /package/dist/{chunk-Z33VUMKY.js.map → chunk-5H2G5X4R.js.map} +0 -0
  338. /package/dist/{chunk-XFPHV65N.js.map → chunk-5JQV4T5Q.js.map} +0 -0
  339. /package/dist/{chunk-OASGWNYL.js.map → chunk-63I7LMLG.js.map} +0 -0
  340. /package/dist/{chunk-J3NGQ5SC.js.map → chunk-7GSSHAXC.js.map} +0 -0
  341. /package/dist/{chunk-VHAKBUBD.js.map → chunk-B64IVEOH.js.map} +0 -0
  342. /package/dist/{chunk-R7FXPG4T.js.map → chunk-C25N5HWX.js.map} +0 -0
  343. /package/dist/{chunk-DH3V7RCQ.js.map → chunk-F2ERDKY2.js.map} +0 -0
  344. /package/dist/{chunk-LJNLLNEM.js.map → chunk-HODFGQGT.js.map} +0 -0
  345. /package/dist/{chunk-B75VCEX5.js.map → chunk-K2IJH4D3.js.map} +0 -0
  346. /package/dist/{chunk-227WJVPY.js.map → chunk-KLSPKMIO.js.map} +0 -0
  347. /package/dist/{chunk-5P2KNFJZ.js.map → chunk-MSDIETFC.js.map} +0 -0
  348. /package/dist/{chunk-HFRGCNFY.js.map → chunk-OAKATRAN.js.map} +0 -0
  349. /package/dist/{chunk-LWBSEYW3.js.map → chunk-PAWIY5OQ.js.map} +0 -0
  350. /package/dist/{chunk-6TOWUN2R.js.map → chunk-RE3H6Y2X.js.map} +0 -0
  351. /package/dist/{chunk-V3HUBF7T.js.map → chunk-S35IIP5P.js.map} +0 -0
  352. /package/dist/{chunk-3YKOFCKP.js.map → chunk-SXHBZXHK.js.map} +0 -0
  353. /package/dist/{chunk-7NCUHD5U.js.map → chunk-VAMZYOOV.js.map} +0 -0
  354. /package/dist/{chunk-PGCZLHRB.js.map → chunk-W272SWN7.js.map} +0 -0
  355. /package/dist/{chunk-5HO3QBAD.js.map → chunk-W6GEMUZM.js.map} +0 -0
  356. /package/dist/{chunk-UCNQV43K.js.map → chunk-XQYTSZW3.js.map} +0 -0
  357. /package/dist/{chunk-GR4C3FHT.js.map → chunk-ZKZCJRRO.js.map} +0 -0
@@ -17,7 +17,30 @@ test("parseActivityConfig defaults to an inert, search-only configuration", () =
17
17
  minConfidence: 0.7,
18
18
  minImportance: "normal",
19
19
  maxMemoriesPerDay: 0,
20
- timeline: { enabled: false, journal: { enabled: false, source: "file" }, qa: { enabled: false, maxRangeDays: 31 } },
20
+ timeline: {
21
+ enabled: false,
22
+ journal: { enabled: false, source: "file" },
23
+ qa: { enabled: false, maxRangeDays: 31 },
24
+ vault: {
25
+ enabled: false,
26
+ vaultPath: "",
27
+ dailyNotePath: "{yyyy}-{MM}-{dd}.md",
28
+ weeklyNotePath: "",
29
+ createMissingNotes: false,
30
+ noteTemplate: "",
31
+ sectionStrategy: "markers",
32
+ publish: {
33
+ timeline: { enabled: true, target: "daily", section: "Timeline" },
34
+ standup: { enabled: false, target: "daily", section: "Standup" },
35
+ weekly: { enabled: false, target: "weekly", section: "Weekly Review" },
36
+ locations: { enabled: false, target: "daily", section: "Locations" },
37
+ },
38
+ insertUnderHeading: "",
39
+ wikilinks: { places: false, placesFolder: "Places" },
40
+ properties: { mode: "off", prefix: "remnic_" },
41
+ autoPublish: true,
42
+ },
43
+ },
21
44
  });
22
45
  assert.deepEqual(parseActivityConfig(undefined), defaultActivityConfig());
23
46
  });
@@ -150,3 +173,73 @@ test("activity config rejects a null extractionMode instead of silently defaulti
150
173
  test("activity config rejects a null maxMemoriesPerDay instead of silently uncapping", () => {
151
174
  assert.throws(() => parseActivityConfig({ maxMemoriesPerDay: null }), /maxMemoriesPerDay/);
152
175
  });
176
+
177
+ test("vault publish section names are rejected at config load when the publisher would reject them", () => {
178
+ // `Timeline-->` and an embedded newline both pass a trimmed-string check
179
+ // but break the `<!-- remnic:<name>:start -->` marker, so `publishVaultNote`
180
+ // throws. The accepted config domain must equal the publisher's.
181
+ for (const section of ["Timeline-->", "Time\nline"]) {
182
+ assert.throws(
183
+ () =>
184
+ parseActivityConfig({
185
+ timeline: { vault: { publish: { timeline: { section } } } },
186
+ }),
187
+ /publish\.timeline\.section must not contain a line break or "-->"/,
188
+ );
189
+ }
190
+ const ok = parseActivityConfig({
191
+ timeline: { vault: { publish: { timeline: { section: "Timeline" } } } },
192
+ });
193
+ assert.equal(ok.timeline.vault.publish.timeline.section, "Timeline");
194
+ });
195
+
196
+ test("noteTemplate path shape is validated at config load when createMissingNotes is true", () => {
197
+ // The field is documented vault-relative; an absolute or `..`-bearing
198
+ // value would only fail later as a publish-time `template_escape`.
199
+ for (const noteTemplate of ["/vault/daily.md", "../daily.md", "Daily/../daily.md"]) {
200
+ assert.throws(
201
+ () => parseActivityConfig({ timeline: { vault: { createMissingNotes: true, noteTemplate } } }),
202
+ /activity\.timeline\.vault\.noteTemplate:/,
203
+ );
204
+ }
205
+ const ok = parseActivityConfig({
206
+ timeline: { vault: { createMissingNotes: true, noteTemplate: "Templates/daily.md" } },
207
+ });
208
+ assert.equal(ok.timeline.vault.noteTemplate, "Templates/daily.md");
209
+ });
210
+
211
+ test("an explicitly disabled weekly target loads identically to an omitted one", () => {
212
+ // Tools that serialize schema defaults write `publish.weekly.enabled: false`;
213
+ // that must behave exactly like leaving the object out, not demand a
214
+ // weeklyNotePath the disabled target never uses.
215
+ const explicit = parseActivityConfig({
216
+ timeline: { vault: { publish: { weekly: { enabled: false } } } },
217
+ });
218
+ const omitted = parseActivityConfig({ timeline: { vault: {} } });
219
+ assert.deepEqual(explicit.timeline.vault, omitted.timeline.vault);
220
+
221
+ assert.throws(
222
+ () => parseActivityConfig({ timeline: { vault: { publish: { weekly: { enabled: true } } } } }),
223
+ /weeklyNotePath is empty/,
224
+ );
225
+ });
226
+
227
+ test("an enabled vault rejects relative or whitespace-only vaultPath at config load", () => {
228
+ // A relative root resolves against the process working directory, so the
229
+ // same config would update different files depending on how the daemon or
230
+ // CLI was launched. The contract is absolute or `~`-rooted, enforced at
231
+ // parse time — not as a publish-time surprise.
232
+ for (const vaultPath of [".", "vault", "../notes", " "]) {
233
+ assert.throws(
234
+ () => parseActivityConfig({ timeline: { vault: { enabled: true, vaultPath } } }),
235
+ /vaultPath must be an absolute or `~`-rooted path/,
236
+ );
237
+ }
238
+ for (const vaultPath of ["/home/user/notes", "~", "~/notes"]) {
239
+ const ok = parseActivityConfig({ timeline: { vault: { enabled: true, vaultPath } } });
240
+ assert.equal(ok.timeline.vault.vaultPath, vaultPath);
241
+ }
242
+ // A disabled vault may carry any placeholder — nothing resolves it.
243
+ const inert = parseActivityConfig({ timeline: { vault: { enabled: false, vaultPath: "." } } });
244
+ assert.equal(inert.timeline.vault.vaultPath, ".");
245
+ });
@@ -1,7 +1,10 @@
1
+ import path from "node:path";
2
+
1
3
  import { coerceBooleanLike, coerceNumber } from "../connectors/coerce.js";
2
4
  import { assertValidTimezone } from "./digest.js";
3
5
  import { resolveJournalSource } from "./journal-source.js";
4
- import type { ImportanceLevel } from "../types.js";
6
+ import { validateVaultNoteTemplate } from "./vault-path.js";
7
+ import { validateRegionName } from "./vault-region.js";
5
8
  import type {
6
9
  ActivityConfig,
7
10
  ActivityExtractionMode,
@@ -9,7 +12,10 @@ import type {
9
12
  ActivityTimelineConfig,
10
13
  ActivityTimelineJournalConfig,
11
14
  ActivityTimelineQaConfig,
15
+ ActivityTimelineVaultConfig,
16
+ ActivityTimelineVaultTargetConfig,
12
17
  } from "./types.js";
18
+ import type { ImportanceLevel } from "../types.js";
13
19
 
14
20
  const EXTRACTION_MODES: readonly ActivityExtractionMode[] = ["off", "smart"];
15
21
  const IMPORTANCE_LEVELS: readonly ImportanceLevel[] = ["critical", "high", "normal", "low", "trivial"];
@@ -30,7 +36,12 @@ export function defaultActivityConfig(): ActivityConfig {
30
36
  minConfidence: 0.7,
31
37
  minImportance: "normal",
32
38
  maxMemoriesPerDay: 0,
33
- timeline: { enabled: false, journal: { enabled: false, source: "file" }, qa: { enabled: false, maxRangeDays: 31 } },
39
+ timeline: {
40
+ enabled: false,
41
+ journal: { enabled: false, source: "file" },
42
+ qa: { enabled: false, maxRangeDays: 31 },
43
+ vault: parseTimelineVaultConfig(undefined),
44
+ },
34
45
  };
35
46
  }
36
47
 
@@ -201,7 +212,14 @@ export function parseActivityConfig(raw: unknown): ActivityConfig {
201
212
  * is malformed and must fail rather than silently disabling the layer.
202
213
  */
203
214
  function parseTimelineConfig(raw: unknown): ActivityTimelineConfig {
204
- if (raw === undefined) return { enabled: false, journal: { enabled: false, source: "file" }, qa: parseTimelineQaConfig(undefined) };
215
+ if (raw === undefined) {
216
+ return {
217
+ enabled: false,
218
+ journal: { enabled: false, source: "file" },
219
+ qa: parseTimelineQaConfig(undefined),
220
+ vault: parseTimelineVaultConfig(undefined),
221
+ };
222
+ }
205
223
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
206
224
  throw new TypeError("activity.timeline must be an object");
207
225
  }
@@ -214,9 +232,243 @@ function parseTimelineConfig(raw: unknown): ActivityTimelineConfig {
214
232
  enabled: enabledValue ?? false,
215
233
  journal: parseTimelineJournalConfig(timeline.journal),
216
234
  qa: parseTimelineQaConfig(timeline.qa),
235
+ vault: parseTimelineVaultConfig(timeline.vault),
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Parse the `activity.timeline.vault.*` block (issue #1985). Default off.
241
+ * Invalid values are rejected at parse time naming the missing or invalid
242
+ * prerequisite — never silently defaulted.
243
+ */
244
+ export function parseTimelineVaultConfig(raw: unknown): ActivityTimelineVaultConfig {
245
+ if (raw === undefined) return defaultVaultConfig();
246
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
247
+ throw new TypeError("activity.timeline.vault must be an object");
248
+ }
249
+ const vault = raw as Record<string, unknown>;
250
+ const enabled = requireBool(vault.enabled, "activity.timeline.vault.enabled", false);
251
+ const createMissingNotes = requireBool(vault.createMissingNotes, "activity.timeline.vault.createMissingNotes", false);
252
+ const autoPublish = requireBool(vault.autoPublish, "activity.timeline.vault.autoPublish", true);
253
+ const vaultPath = optionalNonEmptyString(vault.vaultPath, "activity.timeline.vault.vaultPath") ?? "";
254
+ const dailyNotePath =
255
+ optionalNonEmptyString(vault.dailyNotePath, "activity.timeline.vault.dailyNotePath") ?? "{yyyy}-{MM}-{dd}.md";
256
+ const weeklyNotePath = optionalNonEmptyString(vault.weeklyNotePath, "activity.timeline.vault.weeklyNotePath") ?? "";
257
+ const noteTemplate = optionalNonEmptyString(vault.noteTemplate, "activity.timeline.vault.noteTemplate") ?? "";
258
+ const insertUnderHeading =
259
+ optionalNonEmptyString(vault.insertUnderHeading, "activity.timeline.vault.insertUnderHeading") ?? "";
260
+
261
+ if (typeof vault.sectionStrategy !== "undefined" && typeof vault.sectionStrategy !== "string") {
262
+ throw new TypeError("activity.timeline.vault.sectionStrategy must be a string");
263
+ }
264
+ const sectionStrategy = (vault.sectionStrategy as string | undefined) ?? "markers";
265
+ if (sectionStrategy !== "markers" && sectionStrategy !== "heading") {
266
+ throw new RangeError('activity.timeline.vault.sectionStrategy must be one of "markers", "heading"');
267
+ }
268
+
269
+ // The documented `vaultPath` contract: absolute on the host platform, or
270
+ // `~` / `~`-rooted for `expandTildePath`. Everything else — `.`, `vault`,
271
+ // `../notes`, whitespace — is relative and would resolve against whatever
272
+ // directory the daemon or CLI happened to launch from, so an enabled vault
273
+ // rejects it at parse time.
274
+ const tildeRooted = vaultPath === "~" || vaultPath.startsWith("~/") || vaultPath.startsWith("~\\");
275
+ if (enabled && (vaultPath.trim().length === 0 || (!tildeRooted && !path.isAbsolute(vaultPath)))) {
276
+ throw new RangeError(
277
+ "activity.timeline.vault.vaultPath must be an absolute or `~`-rooted path when activity.timeline.vault.enabled is true; " +
278
+ `a relative path resolves against the process working directory (got ${JSON.stringify(vaultPath)})`,
279
+ );
280
+ }
281
+ try {
282
+ validateVaultNoteTemplate(dailyNotePath);
283
+ } catch (err) {
284
+ throw new RangeError(`activity.timeline.vault.dailyNotePath: ${(err as Error).message}`);
285
+ }
286
+ if (weeklyNotePath.length > 0) {
287
+ try {
288
+ validateVaultNoteTemplate(weeklyNotePath);
289
+ } catch (err) {
290
+ throw new RangeError(`activity.timeline.vault.weeklyNotePath: ${(err as Error).message}`);
291
+ }
292
+ }
293
+ if (createMissingNotes) {
294
+ if (noteTemplate.length === 0) {
295
+ throw new RangeError(
296
+ "activity.timeline.vault.noteTemplate must name a vault-relative template file when activity.timeline.vault.createMissingNotes is true",
297
+ );
298
+ }
299
+ try {
300
+ validateVaultNoteTemplate(noteTemplate);
301
+ } catch (err) {
302
+ throw new RangeError(`activity.timeline.vault.noteTemplate: ${(err as Error).message}`);
303
+ }
304
+ }
305
+
306
+ const publish = parseVaultPublishBlock(vault.publish, weeklyNotePath);
307
+ const wikilinksRaw = vault.wikilinks;
308
+ const wikilinks =
309
+ wikilinksRaw === undefined
310
+ ? { places: false, placesFolder: "Places" }
311
+ : parseVaultWikilinks(wikilinksRaw);
312
+ const propertiesRaw = vault.properties;
313
+ const properties: ActivityTimelineVaultConfig["properties"] =
314
+ propertiesRaw === undefined ? { mode: "off", prefix: "remnic_" } : parseVaultProperties(propertiesRaw);
315
+
316
+ return {
317
+ enabled,
318
+ vaultPath,
319
+ dailyNotePath,
320
+ weeklyNotePath,
321
+ createMissingNotes,
322
+ noteTemplate,
323
+ sectionStrategy,
324
+ publish,
325
+ insertUnderHeading,
326
+ wikilinks,
327
+ properties,
328
+ autoPublish,
217
329
  };
218
330
  }
219
331
 
332
+ function defaultVaultConfig(): ActivityTimelineVaultConfig {
333
+ return {
334
+ enabled: false,
335
+ vaultPath: "",
336
+ dailyNotePath: "{yyyy}-{MM}-{dd}.md",
337
+ weeklyNotePath: "",
338
+ createMissingNotes: false,
339
+ noteTemplate: "",
340
+ sectionStrategy: "markers",
341
+ publish: {
342
+ timeline: { enabled: true, target: "daily", section: "Timeline" },
343
+ standup: { enabled: false, target: "daily", section: "Standup" },
344
+ weekly: { enabled: false, target: "weekly", section: "Weekly Review" },
345
+ locations: { enabled: false, target: "daily", section: "Locations" },
346
+ },
347
+ insertUnderHeading: "",
348
+ wikilinks: { places: false, placesFolder: "Places" },
349
+ properties: { mode: "off", prefix: "remnic_" },
350
+ autoPublish: true,
351
+ };
352
+ }
353
+
354
+ function requireBool(value: unknown, key: string, fallback: boolean): boolean {
355
+ const coerced = coerceBooleanLike(value, key);
356
+ if (value === undefined) return fallback;
357
+ if (coerced === undefined) {
358
+ throw new TypeError(`${key} must be a boolean`);
359
+ }
360
+ return coerced;
361
+ }
362
+
363
+ function optionalNonEmptyString(value: unknown, key: string): string | undefined {
364
+ if (value === undefined) return undefined;
365
+ if (typeof value !== "string") {
366
+ throw new TypeError(`${key} must be a string`);
367
+ }
368
+ return value;
369
+ }
370
+
371
+ function parseVaultPublishBlock(
372
+ raw: unknown,
373
+ weeklyNotePath: string,
374
+ ): ActivityTimelineVaultConfig["publish"] {
375
+ const defaults = defaultVaultConfig().publish;
376
+ if (raw === undefined) return defaults;
377
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
378
+ throw new TypeError("activity.timeline.vault.publish must be an object");
379
+ }
380
+ const block = raw as Record<string, unknown>;
381
+ // Each kind is read at its literal path so the config-contract extractor
382
+ // can attribute publish.<kind>.<leaf> to real parse sites (§40).
383
+ const publish = {
384
+ timeline: parseVaultTarget(block.timeline, "timeline", defaults.timeline, weeklyNotePath),
385
+ standup: parseVaultTarget(block.standup, "standup", defaults.standup, weeklyNotePath),
386
+ weekly: parseVaultTarget(block.weekly, "weekly", defaults.weekly, weeklyNotePath),
387
+ locations: parseVaultTarget(block.locations, "locations", defaults.locations, weeklyNotePath),
388
+ };
389
+ for (const targetFile of ["daily", "weekly"] as const) {
390
+ const sections = (["timeline", "standup", "weekly", "locations"] as const)
391
+ .filter((kind) => publish[kind].target === targetFile && publish[kind].enabled)
392
+ .map((kind) => publish[kind].section);
393
+ const duplicates = sections.filter((section, index) => sections.indexOf(section) !== index);
394
+ if (duplicates.length > 0) {
395
+ throw new RangeError(
396
+ `activity.timeline.vault.publish section names must be unique per target file; duplicate ${targetFile} section(s): ${[...new Set(duplicates)].join(", ")}`,
397
+ );
398
+ }
399
+ }
400
+ return publish;
401
+ }
402
+
403
+ function parseVaultTarget(
404
+ raw: unknown,
405
+ kind: string,
406
+ fallback: ActivityTimelineVaultTargetConfig,
407
+ weeklyNotePath: string,
408
+ ): ActivityTimelineVaultTargetConfig {
409
+ if (raw === undefined) return fallback;
410
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
411
+ throw new TypeError(`activity.timeline.vault.publish.${kind} must be an object`);
412
+ }
413
+ const entry = raw as Record<string, unknown>;
414
+ const enabled = requireBool(entry.enabled, `activity.timeline.vault.publish.${kind}.enabled`, fallback.enabled);
415
+ const target = entry.target === undefined ? fallback.target : entry.target;
416
+ if (target !== "daily" && target !== "weekly") {
417
+ throw new RangeError(`activity.timeline.vault.publish.${kind}.target must be one of "daily", "weekly"`);
418
+ }
419
+ const section = entry.section === undefined ? fallback.section : entry.section;
420
+ if (typeof section !== "string" || section.trim().length === 0 || section !== section.trim()) {
421
+ throw new RangeError(`activity.timeline.vault.publish.${kind}.section must be a non-empty trimmed string`);
422
+ }
423
+ // The accepted config domain must equal what the publisher accepts: a name
424
+ // containing `-->` or a line break passes the trimmed-string check above
425
+ // but is rejected downstream by `publishVaultNote`, which would turn an
426
+ // apparently valid config into a runtime failure.
427
+ if (!validateRegionName(section).ok) {
428
+ throw new RangeError(
429
+ `activity.timeline.vault.publish.${kind}.section must not contain a line break or "-->"`,
430
+ );
431
+ }
432
+ // The path is required only by an entry that will actually publish to the
433
+ // weekly file; a disabled entry must load the same whether its object is
434
+ // explicit (serialized schema default) or omitted entirely.
435
+ if (enabled && target === "weekly" && weeklyNotePath.length === 0) {
436
+ throw new RangeError(
437
+ `activity.timeline.vault.publish.${kind}.target is "weekly" but activity.timeline.vault.weeklyNotePath is empty; configure weeklyNotePath first`,
438
+ );
439
+ }
440
+ return { enabled, target, section };
441
+ }
442
+
443
+ function parseVaultWikilinks(raw: unknown): ActivityTimelineVaultConfig["wikilinks"] {
444
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
445
+ throw new TypeError("activity.timeline.vault.wikilinks must be an object");
446
+ }
447
+ const block = raw as Record<string, unknown>;
448
+ const places = requireBool(block.places, "activity.timeline.vault.wikilinks.places", false);
449
+ const placesFolder =
450
+ optionalNonEmptyString(block.placesFolder, "activity.timeline.vault.wikilinks.placesFolder") ?? "Places";
451
+ return { places, placesFolder };
452
+ }
453
+
454
+ function parseVaultProperties(raw: unknown): ActivityTimelineVaultConfig["properties"] {
455
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
456
+ throw new TypeError("activity.timeline.vault.properties must be an object");
457
+ }
458
+ const block = raw as Record<string, unknown>;
459
+ const mode = block.mode === undefined ? "off" : block.mode;
460
+ if (mode !== "off" && mode !== "frontmatter" && mode !== "dataview-inline") {
461
+ throw new RangeError(
462
+ 'activity.timeline.vault.properties.mode must be one of "off", "frontmatter", "dataview-inline"',
463
+ );
464
+ }
465
+ const prefix = optionalNonEmptyString(block.prefix, "activity.timeline.vault.properties.prefix") ?? "remnic_";
466
+ if (prefix.trim().length === 0 || prefix !== prefix.trim()) {
467
+ throw new RangeError("activity.timeline.vault.properties.prefix must be non-empty and trimmed");
468
+ }
469
+ return { mode, prefix };
470
+ }
471
+
220
472
  function parseTimelineJournalConfig(raw: unknown): ActivityTimelineJournalConfig {
221
473
  if (raw === undefined) return { enabled: false, source: "file" };
222
474
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
@@ -265,3 +517,4 @@ function parseTimelineQaConfig(raw: unknown): ActivityTimelineQaConfig {
265
517
  }
266
518
  return { enabled: enabledValue ?? false, maxRangeDays };
267
519
  }
520
+
@@ -141,6 +141,27 @@ export function activityDayWindow(date: string, timezone: string): { startUtc: s
141
141
  };
142
142
  }
143
143
 
144
+ /**
145
+ * The local calendar day (YYYY-MM-DD) an instant falls in for an IANA
146
+ * timezone — the day key `activityDayWindow` buckets on. Callers that need
147
+ * "today" for activity artifacts MUST use this rather than the host-local
148
+ * date, which drifts a day near either timezone's midnight.
149
+ */
150
+ export function activityDateInTimezone(instant: Date, timezone: string): string {
151
+ try {
152
+ const parts = new Intl.DateTimeFormat("en-CA", {
153
+ timeZone: timezone,
154
+ year: "numeric",
155
+ month: "2-digit",
156
+ day: "2-digit",
157
+ }).formatToParts(instant);
158
+ const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? "";
159
+ return `${get("year")}-${get("month")}-${get("day")}`;
160
+ } catch {
161
+ return instant.toISOString().slice(0, 10);
162
+ }
163
+ }
164
+
144
165
  // ── Rendering ───────────────────────────────────────────────────────────────
145
166
 
146
167
  function sortedByTime(snapshots: ActivitySnapshot[]): ActivitySnapshot[] {
@@ -27,6 +27,8 @@ export * from "./privacy-status.js";
27
27
  export * from "./privacy-gate-resolve.js";
28
28
  export * from "./privacy-window.js";
29
29
  export * from "./vault-path.js";
30
+ export * from "./vault-publisher.js";
31
+ export * from "./vault-region-pair.js";
30
32
  export * from "./vault-publish.js";
31
33
  export * from "./vault-status.js";
32
34
  export * from "./vault-insert.js";
@@ -24,6 +24,7 @@
24
24
  import { ActivityStore } from "./store.js";
25
25
  import { syncActivitySource } from "./pipeline.js";
26
26
  import { ActivityHttpSourceClient } from "./source-client.js";
27
+ import { activityDateInTimezone } from "./digest.js";
27
28
  import { displayErrorDetail } from "../runtime/better-sqlite.js";
28
29
  import type { ActivityConfig, ActivitySourceClient, ActivitySourceConfig } from "./types.js";
29
30
 
@@ -87,22 +88,6 @@ export interface ActivitySyncRunOptions {
87
88
  reindexSearch?: (signal?: AbortSignal) => Promise<void>;
88
89
  }
89
90
 
90
- /** Format a Date as YYYY-MM-DD in an IANA timezone (local calendar day). */
91
- function localDateInTimezone(instant: Date, timezone: string): string {
92
- try {
93
- const parts = new Intl.DateTimeFormat("en-CA", {
94
- timeZone: timezone,
95
- year: "numeric",
96
- month: "2-digit",
97
- day: "2-digit",
98
- }).formatToParts(instant);
99
- const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? "";
100
- return `${get("year")}-${get("month")}-${get("day")}`;
101
- } catch {
102
- return instant.toISOString().slice(0, 10);
103
- }
104
- }
105
-
106
91
  /** Previous calendar date via pure date arithmetic (no DST wall-clock drift). */
107
92
  function previousIsoDate(date: string): string {
108
93
  const parsed = new Date(`${date}T00:00:00Z`);
@@ -117,7 +102,7 @@ function previousIsoDate(date: string): string {
117
102
  */
118
103
  export function resolveActivitySyncDates(syncDays: number, timezone: string, now: Date): string[] {
119
104
  const dates: string[] = [];
120
- let cursor = localDateInTimezone(now, timezone);
105
+ let cursor = activityDateInTimezone(now, timezone);
121
106
  for (let count = 0; count < syncDays; count++) {
122
107
  dates.unshift(cursor);
123
108
  cursor = previousIsoDate(cursor);
@@ -0,0 +1,169 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+
7
+ import { parseConfig } from "../../config.js";
8
+ import type { PluginConfig } from "../../types.js";
9
+ import { runTimelineCliCommand } from "./query.js";
10
+
11
+ const DATE = "2026-08-21";
12
+ const RECAP = "- 09:00-10:00 code review (60m)";
13
+
14
+ interface Capture {
15
+ out: string;
16
+ err: string;
17
+ io: { stdout: { write(chunk: string): unknown }; stderr: { write(chunk: string): unknown } };
18
+ }
19
+
20
+ function capture(): Capture {
21
+ const sink: Capture = {
22
+ out: "",
23
+ err: "",
24
+ io: {
25
+ stdout: {
26
+ write(chunk: string) {
27
+ sink.out += chunk;
28
+ return true;
29
+ },
30
+ },
31
+ stderr: {
32
+ write(chunk: string) {
33
+ sink.err += chunk;
34
+ return true;
35
+ },
36
+ },
37
+ },
38
+ };
39
+ return sink;
40
+ }
41
+
42
+ function makeFixture(): { config: PluginConfig; notePath: string; note: string } {
43
+ const root = mkdtempSync(path.join(tmpdir(), "remnic-publish-cli-"));
44
+ const memoryDir = path.join(root, "memory");
45
+ const vaultPath = path.join(root, "vault");
46
+ mkdirSync(path.join(memoryDir, "journal"), { recursive: true });
47
+ mkdirSync(vaultPath, { recursive: true });
48
+ writeFileSync(path.join(memoryDir, "journal", `${DATE}.md`), `${RECAP}\n`, "utf8");
49
+
50
+ const note = [
51
+ "# Daily",
52
+ "",
53
+ "human text that must survive",
54
+ "",
55
+ "<!-- remnic:Timeline:start -->",
56
+ "stale",
57
+ "<!-- remnic:Timeline:end -->",
58
+ "",
59
+ ].join("\n");
60
+ const notePath = path.join(vaultPath, `${DATE}.md`);
61
+ writeFileSync(notePath, note, "utf8");
62
+
63
+ const config = parseConfig({
64
+ memoryDir,
65
+ activity: {
66
+ timezone: "UTC",
67
+ timeline: { vault: { enabled: true, vaultPath, dailyNotePath: "{yyyy}-{MM}-{dd}.md" } },
68
+ },
69
+ });
70
+ return { config, notePath, note };
71
+ }
72
+
73
+ test("the shared timeline runner dispatches publish and writes the managed region", async () => {
74
+ const { config, notePath } = makeFixture();
75
+ const sink = capture();
76
+
77
+ const code = await runTimelineCliCommand(
78
+ // qa stays disabled: publish is not a qa surface and must not be gated by it.
79
+ { cards: null, qa: { enabled: false, maxRangeDays: 31 }, timelineEnabled: true, config },
80
+ ["publish", "--date", DATE],
81
+ sink.io,
82
+ );
83
+
84
+ assert.equal(code, 0, sink.err);
85
+ assert.doesNotMatch(sink.err, /unknown command publish/);
86
+ assert.match(sink.out, /updated=1/);
87
+ const after = readFileSync(notePath, "utf8");
88
+ assert.ok(after.includes(RECAP), "the recap is published into the managed region");
89
+ assert.ok(after.includes("human text that must survive"));
90
+ });
91
+
92
+ test("the shared timeline runner reports publish as a valid command", async () => {
93
+ const sink = capture();
94
+ const code = await runTimelineCliCommand(
95
+ { cards: null, qa: { enabled: true, maxRangeDays: 31 }, timelineEnabled: true },
96
+ ["bogus"],
97
+ sink.io,
98
+ );
99
+ assert.equal(code, 1);
100
+ assert.match(sink.err, /valid: range, search, publish/);
101
+ });
102
+
103
+ test("publish without a config refuses instead of throwing", async () => {
104
+ const sink = capture();
105
+ const code = await runTimelineCliCommand(
106
+ { cards: null, qa: { enabled: true, maxRangeDays: 31 }, timelineEnabled: true },
107
+ ["publish"],
108
+ sink.io,
109
+ );
110
+ assert.equal(code, 1);
111
+ assert.match(sink.err, /requires the Remnic config/);
112
+ });
113
+
114
+ test("--what with only separators is rejected with a validation error", async () => {
115
+ const { config, notePath, note } = makeFixture();
116
+ const sink = capture();
117
+
118
+ const code = await runTimelineCliCommand(
119
+ { cards: null, qa: { enabled: false, maxRangeDays: 31 }, timelineEnabled: true, config },
120
+ ["publish", "--date", DATE, "--what", ",,"],
121
+ sink.io,
122
+ );
123
+
124
+ assert.equal(code, 1);
125
+ assert.match(sink.err, /--what must name at least one artifact/);
126
+ assert.equal(readFileSync(notePath, "utf8"), note, "nothing is published");
127
+ });
128
+
129
+ test("publish rejects query-only flags instead of silently publishing today's note", async () => {
130
+ const { config, notePath, note } = makeFixture();
131
+ const sink = capture();
132
+
133
+ const code = await runTimelineCliCommand(
134
+ { cards: null, qa: { enabled: false, maxRangeDays: 31 }, timelineEnabled: true, config },
135
+ ["publish", "--from", "2026-08-01"],
136
+ sink.io,
137
+ );
138
+
139
+ assert.equal(code, 1);
140
+ assert.match(sink.err, /unknown flag --from \(valid: --date, --dry-run, --week, --what\)/);
141
+ assert.equal(readFileSync(notePath, "utf8"), note, "no note is touched by unsupported syntax");
142
+ });
143
+
144
+ test("range and search reject publish-only flags instead of ignoring them", async () => {
145
+ const { config } = makeFixture();
146
+ const deps = {
147
+ cards: null,
148
+ qa: { enabled: true, maxRangeDays: 31 },
149
+ timelineEnabled: true,
150
+ config,
151
+ } as const;
152
+
153
+ const rangeSink = capture();
154
+ const rangeCode = await runTimelineCliCommand(
155
+ deps,
156
+ ["range", "--date", DATE, "--from", `${DATE}T00:00:00.000Z`, "--to", `${DATE}T00:00:00.000Z`],
157
+ rangeSink.io,
158
+ );
159
+ assert.equal(rangeCode, 1);
160
+ assert.match(
161
+ rangeSink.err,
162
+ /unknown flag --date \(valid: --categories, --format, --from, --include-distractions, --to\)/,
163
+ );
164
+
165
+ const searchSink = capture();
166
+ const searchCode = await runTimelineCliCommand(deps, ["search", "--query", "x", "--dry-run"], searchSink.io);
167
+ assert.equal(searchCode, 1);
168
+ assert.match(searchSink.err, /unknown flag --dry-run \(valid: --from, --limit, --query, --to\)/);
169
+ });