@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.
- package/dist/access-admin-ops-surface.d.ts +11 -11
- package/dist/access-admin-ops-surface.js +4 -4
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +5 -5
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +4 -4
- package/dist/access-cli.js +28 -28
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +11 -11
- package/dist/access-extraction-force-flush.js +4 -4
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http-lifecycle-flush.d.ts +11 -11
- package/dist/access-http-offline-stream.d.ts +11 -11
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +4 -4
- package/dist/access-http.d.ts +12 -12
- package/dist/access-http.js +15 -15
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +4 -4
- package/dist/access-lcm-surface.d.ts +11 -11
- package/dist/access-lcm-surface.js +4 -4
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +11 -11
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-Cy0yPsaE.d.ts → access-namespace-preflight-Rzuo1nGR.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +4 -4
- package/dist/access-observe-write-surface.d.ts +11 -11
- package/dist/access-observe-write-surface.js +4 -4
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-operations-batch.js +5 -5
- package/dist/access-operations.d.ts +12 -12
- package/dist/access-operations.js +10 -10
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +4 -4
- package/dist/access-recall-response.d.ts +11 -11
- package/dist/access-recall-response.js +4 -4
- package/dist/access-recall-surface.d.ts +11 -11
- package/dist/access-recall-surface.js +4 -4
- package/dist/{access-service-Baz5xW5X.d.ts → access-service-Bb2Fykzc.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +11 -11
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +4 -4
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-surface-catalog.js +6 -6
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -4
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +4 -4
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-9X8WpZNO.d.ts → catalog-DjTLVOYp.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-TEZ2HTC7.js → chunk-33JKYLPX.js} +2 -2
- package/dist/{chunk-MNH2GM4O.js → chunk-3BS2OGDT.js} +6 -19
- package/dist/{chunk-MNH2GM4O.js.map → chunk-3BS2OGDT.js.map} +1 -1
- package/dist/{chunk-5PTS43CH.js → chunk-4LG4CPOC.js} +2 -2
- package/dist/{chunk-Z33VUMKY.js → chunk-5H2G5X4R.js} +2 -2
- package/dist/{chunk-XFPHV65N.js → chunk-5JQV4T5Q.js} +2 -2
- package/dist/{chunk-OASGWNYL.js → chunk-63I7LMLG.js} +2 -2
- package/dist/{chunk-J3NGQ5SC.js → chunk-7GSSHAXC.js} +2 -2
- package/dist/{chunk-X4FIZAVQ.js → chunk-7JV4JST3.js} +854 -42
- package/dist/chunk-7JV4JST3.js.map +1 -0
- package/dist/{chunk-VHAKBUBD.js → chunk-B64IVEOH.js} +2 -2
- package/dist/{chunk-R7FXPG4T.js → chunk-C25N5HWX.js} +2 -2
- package/dist/{chunk-DH3V7RCQ.js → chunk-F2ERDKY2.js} +7 -7
- package/dist/{chunk-LJNLLNEM.js → chunk-HODFGQGT.js} +2 -2
- package/dist/{chunk-B75VCEX5.js → chunk-K2IJH4D3.js} +2 -2
- package/dist/{chunk-227WJVPY.js → chunk-KLSPKMIO.js} +2 -2
- package/dist/{chunk-5P2KNFJZ.js → chunk-MSDIETFC.js} +3 -3
- package/dist/{chunk-HFRGCNFY.js → chunk-OAKATRAN.js} +2 -2
- package/dist/chunk-OGUKSLYZ.js +560 -0
- package/dist/chunk-OGUKSLYZ.js.map +1 -0
- package/dist/{chunk-M6L7HI7R.js → chunk-P6FHFTZ7.js} +16 -1
- package/dist/chunk-P6FHFTZ7.js.map +1 -0
- package/dist/{chunk-LWBSEYW3.js → chunk-PAWIY5OQ.js} +5 -5
- package/dist/{chunk-3ITTF7YH.js → chunk-QVI63XSO.js} +90 -383
- package/dist/chunk-QVI63XSO.js.map +1 -0
- package/dist/{chunk-6TOWUN2R.js → chunk-RE3H6Y2X.js} +2 -2
- package/dist/{chunk-V3HUBF7T.js → chunk-S35IIP5P.js} +2 -2
- package/dist/{chunk-3YKOFCKP.js → chunk-SXHBZXHK.js} +2 -2
- package/dist/{chunk-7NCUHD5U.js → chunk-VAMZYOOV.js} +2 -2
- package/dist/{chunk-PGCZLHRB.js → chunk-W272SWN7.js} +3 -3
- package/dist/{chunk-5HO3QBAD.js → chunk-W6GEMUZM.js} +2 -2
- package/dist/{chunk-UCNQV43K.js → chunk-XQYTSZW3.js} +2 -2
- package/dist/{chunk-GR4C3FHT.js → chunk-ZKZCJRRO.js} +2 -2
- package/dist/{cli-CMtkGBdz.d.ts → cli-DXXy60A4.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +21 -21
- package/dist/coding/export-okf-codegraph.d.ts +1 -1
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -4
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +3 -3
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/cross-lingual-recall.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/deep-recall-http-glue.d.ts +11 -11
- package/dist/deep-recall-mcp-tools.d.ts +11 -11
- package/dist/deep-recall-policy-llm.d.ts +1 -1
- package/dist/deep-recall-seeds.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-BZGUecZf.d.ts → dependency-propagation-delivery-37JdQv_3.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/eval-trajectory-contracts.d.ts +1 -1
- package/dist/eval-trajectory-store.d.ts +1 -1
- package/dist/eval-trajectory.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/external-wiki-access.d.ts +11 -11
- package/dist/external-wiki-access.js +5 -5
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +11 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +5 -5
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/{index-B4iTvT-w.d.ts → index-DquhYqsc.d.ts} +2 -2
- package/dist/index.d.ts +200 -62
- package/dist/index.js +59 -37
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/location/index.d.ts +3 -3
- package/dist/location/index.js +5 -5
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/{maintenance-BBpNqEeJ.d.ts → maintenance-CizZOycr.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-controller.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +11 -11
- package/dist/memory-subject.d.ts +11 -11
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +5 -5
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +6 -6
- package/dist/{orchestrator-DglY1H0w.d.ts → orchestrator-DqLcuNMP.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +28 -28
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-C7qRsy6g.d.ts → pipeline-DbJpmcRu.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-CU-Cwwdb.d.ts → public-http-NQ3UUp6W.d.ts} +1 -1
- package/dist/{qmd-YRLTa3AE.d.ts → qmd-DA_7fbSz.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-navigate-access.d.ts +1 -1
- package/dist/recall-navigate-disclosure.d.ts +1 -1
- package/dist/recall-navigate.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +5 -5
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/{scope-profiles-BVvqVzJX.d.ts → scope-profiles-BpiWKZzx.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BzCI6Kuu.d.ts → semantic-consolidation-NJpaZpkY.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/{service-DQ3Gscy5.d.ts → service-DdlZjX1D.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +4 -4
- package/dist/standup/build.js +2 -2
- package/dist/{storage-DmTbQdD0.d.ts → storage-C7BWrpU9.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/subject-config.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +14 -14
- package/dist/support-passport/index.js +7 -7
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/export-okf.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CLRTESSD.d.ts → types-Cy1l6fzp.d.ts} +48 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/who-knows-cli.d.ts +11 -11
- package/dist/who-knows.d.ts +11 -11
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/activity/config.test.ts +94 -1
- package/src/activity/config.ts +256 -3
- package/src/activity/digest.ts +21 -0
- package/src/activity/index.ts +2 -0
- package/src/activity/runner.ts +2 -17
- package/src/activity/timeline/publish-cli.test.ts +169 -0
- package/src/activity/timeline/publish-cli.ts +144 -0
- package/src/activity/timeline/query.ts +51 -15
- package/src/activity/types.ts +45 -0
- package/src/activity/vault-insert.ts +14 -33
- package/src/activity/vault-path.ts +100 -22
- package/src/activity/vault-publish.test.ts +51 -0
- package/src/activity/vault-publish.ts +157 -17
- package/src/activity/vault-publisher.test.ts +881 -0
- package/src/activity/vault-publisher.ts +597 -0
- package/src/activity/vault-region.test.ts +5 -0
- package/src/activity/vault-region.ts +7 -2
- package/src/cli/timeline-publish-commands.ts +34 -0
- package/src/cli.ts +2 -2
- package/src/config.test.ts +24 -1
- package/dist/chunk-3ITTF7YH.js.map +0 -1
- package/dist/chunk-KJUWWN3T.js +0 -237
- package/dist/chunk-KJUWWN3T.js.map +0 -1
- package/dist/chunk-M6L7HI7R.js.map +0 -1
- package/dist/chunk-X4FIZAVQ.js.map +0 -1
- /package/dist/{chunk-TEZ2HTC7.js.map → chunk-33JKYLPX.js.map} +0 -0
- /package/dist/{chunk-5PTS43CH.js.map → chunk-4LG4CPOC.js.map} +0 -0
- /package/dist/{chunk-Z33VUMKY.js.map → chunk-5H2G5X4R.js.map} +0 -0
- /package/dist/{chunk-XFPHV65N.js.map → chunk-5JQV4T5Q.js.map} +0 -0
- /package/dist/{chunk-OASGWNYL.js.map → chunk-63I7LMLG.js.map} +0 -0
- /package/dist/{chunk-J3NGQ5SC.js.map → chunk-7GSSHAXC.js.map} +0 -0
- /package/dist/{chunk-VHAKBUBD.js.map → chunk-B64IVEOH.js.map} +0 -0
- /package/dist/{chunk-R7FXPG4T.js.map → chunk-C25N5HWX.js.map} +0 -0
- /package/dist/{chunk-DH3V7RCQ.js.map → chunk-F2ERDKY2.js.map} +0 -0
- /package/dist/{chunk-LJNLLNEM.js.map → chunk-HODFGQGT.js.map} +0 -0
- /package/dist/{chunk-B75VCEX5.js.map → chunk-K2IJH4D3.js.map} +0 -0
- /package/dist/{chunk-227WJVPY.js.map → chunk-KLSPKMIO.js.map} +0 -0
- /package/dist/{chunk-5P2KNFJZ.js.map → chunk-MSDIETFC.js.map} +0 -0
- /package/dist/{chunk-HFRGCNFY.js.map → chunk-OAKATRAN.js.map} +0 -0
- /package/dist/{chunk-LWBSEYW3.js.map → chunk-PAWIY5OQ.js.map} +0 -0
- /package/dist/{chunk-6TOWUN2R.js.map → chunk-RE3H6Y2X.js.map} +0 -0
- /package/dist/{chunk-V3HUBF7T.js.map → chunk-S35IIP5P.js.map} +0 -0
- /package/dist/{chunk-3YKOFCKP.js.map → chunk-SXHBZXHK.js.map} +0 -0
- /package/dist/{chunk-7NCUHD5U.js.map → chunk-VAMZYOOV.js.map} +0 -0
- /package/dist/{chunk-PGCZLHRB.js.map → chunk-W272SWN7.js.map} +0 -0
- /package/dist/{chunk-5HO3QBAD.js.map → chunk-W6GEMUZM.js.map} +0 -0
- /package/dist/{chunk-UCNQV43K.js.map → chunk-XQYTSZW3.js.map} +0 -0
- /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: {
|
|
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
|
+
});
|
package/src/activity/config.ts
CHANGED
|
@@ -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
|
|
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: {
|
|
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)
|
|
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
|
+
|
package/src/activity/digest.ts
CHANGED
|
@@ -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[] {
|
package/src/activity/index.ts
CHANGED
|
@@ -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";
|
package/src/activity/runner.ts
CHANGED
|
@@ -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 =
|
|
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
|
+
});
|