@remnic/core 9.66.2 → 9.66.4
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.js +31 -31
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.js +32 -32
- package/dist/access-boundary.js +31 -31
- package/dist/access-cli.js +97 -97
- package/dist/access-extraction-force-flush.js +31 -31
- package/dist/access-http-query.js +33 -33
- package/dist/access-http.js +49 -49
- package/dist/access-identity-continuity-surface.js +31 -31
- package/dist/access-lcm-surface.js +31 -31
- package/dist/access-mcp.js +39 -39
- package/dist/access-namespace-preflight.js +31 -31
- package/dist/access-observe-write-surface.js +31 -31
- package/dist/access-offline-manifest.js +3 -3
- package/dist/access-operations-batch.js +34 -34
- package/dist/access-operations.js +38 -38
- package/dist/access-recall-concurrency.js +31 -31
- package/dist/access-recall-response.js +31 -31
- package/dist/access-recall-surface.js +31 -31
- package/dist/access-schema.js +5 -5
- package/dist/access-service.js +31 -31
- package/dist/access-surface-catalog.js +33 -33
- package/dist/active-recall.js +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/briefing.js +8 -8
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-36HJ36FK.js → chunk-27JH2KKM.js} +3 -3
- package/dist/{chunk-NZPFCP5A.js → chunk-2PSX5BJU.js} +2 -2
- package/dist/{chunk-RCPKBW7K.js → chunk-322MSJYS.js} +5 -5
- package/dist/{chunk-C3ZYZKDJ.js → chunk-3E4REX7E.js} +2 -2
- package/dist/{chunk-Y2YM4C75.js → chunk-4BRBSON5.js} +2 -2
- package/dist/{chunk-B4B3QDV3.js → chunk-4EX26ZS3.js} +24 -24
- package/dist/{chunk-ER2UPUMG.js → chunk-562Q236T.js} +2 -2
- package/dist/{chunk-F2JXJABQ.js → chunk-5BIOXQGN.js} +2 -2
- package/dist/{chunk-DSEHPUID.js → chunk-5C3OQ253.js} +2 -2
- package/dist/{chunk-LSR5LQXN.js → chunk-5VLXQFOS.js} +2 -2
- package/dist/{chunk-2QSGJTDG.js → chunk-636UUVOZ.js} +10 -10
- package/dist/{chunk-AI5M4FVQ.js → chunk-6DTQSLER.js} +2 -2
- package/dist/{chunk-CDLPF3PX.js → chunk-6IL5FFHH.js} +2 -2
- package/dist/{chunk-VKDBDJIX.js → chunk-6L6V2OIX.js} +3 -3
- package/dist/{chunk-ZXROBS53.js → chunk-6NA527W2.js} +48 -48
- package/dist/{chunk-R2Q6FTOX.js → chunk-7IEFFOIC.js} +2 -2
- package/dist/{chunk-4PG2CKI6.js → chunk-AQHDMA4A.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-AU7Q3LSC.js} +7 -7
- package/dist/{chunk-6BYJQ3L2.js → chunk-BE4Y6GK6.js} +8 -8
- package/dist/{chunk-PW3GKWH3.js → chunk-BQVWC67C.js} +4 -4
- package/dist/{chunk-KQAFEZQX.js → chunk-BUKK5SWA.js} +2 -2
- package/dist/{chunk-UVYL5G7A.js → chunk-C3LUYZLX.js} +3 -3
- package/dist/{chunk-JMXHVJ2V.js → chunk-DFWO37KT.js} +14 -14
- package/dist/{chunk-WKWY7YF3.js → chunk-F5FHDDOV.js} +2 -2
- package/dist/{chunk-U4HZEW2B.js → chunk-FH4BB7F4.js} +7 -7
- package/dist/{chunk-UHQE37NI.js → chunk-G5TYMG7R.js} +2 -2
- package/dist/{chunk-PP7R754B.js → chunk-GPFZFCRN.js} +1 -1
- package/dist/{chunk-EH7BNIUN.js → chunk-HAF3PUMO.js} +3 -3
- package/dist/{chunk-J2R275FN.js → chunk-HQJM3BKH.js} +2 -2
- package/dist/{chunk-O7ERGZDA.js → chunk-I7NBTRLR.js} +2 -2
- package/dist/{chunk-6545JDIM.js → chunk-IHK5KTOC.js} +2 -2
- package/dist/{chunk-25OLNWYG.js → chunk-IP7KIW7W.js} +2 -2
- package/dist/{chunk-XJJYSX6F.js → chunk-JCUG7AXZ.js} +4 -4
- package/dist/{chunk-OERZXNSK.js → chunk-JGXMA6HL.js} +2 -2
- package/dist/{chunk-CQ6IWZDI.js → chunk-JVDAIASW.js} +2 -2
- package/dist/{chunk-F4W5HTED.js → chunk-K3CQQNMX.js} +2 -2
- package/dist/{chunk-D2YNNDML.js → chunk-KWIGDKM2.js} +2 -2
- package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
- package/dist/{chunk-OLBZ2WGG.js → chunk-LTBKKSCX.js} +2 -2
- package/dist/{chunk-VSMSYDIH.js → chunk-MARZW7DB.js} +2 -2
- package/dist/chunk-MKD3OMPS.js +20 -0
- package/dist/chunk-MKD3OMPS.js.map +1 -0
- package/dist/{chunk-W5IYHQA5.js → chunk-MS2I7JVN.js} +3 -3
- package/dist/{chunk-BLHKR4GJ.js → chunk-NHACZAM3.js} +2 -2
- package/dist/{chunk-2VZHB5US.js → chunk-NTZ3452G.js} +2 -2
- package/dist/{chunk-65HXVEVG.js → chunk-OT3GGOFW.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-OUKBMQ2O.js → chunk-PD66CVSD.js} +2 -2
- package/dist/{chunk-EQRD4NMH.js → chunk-PMMNP5GZ.js} +2 -2
- package/dist/{chunk-47WWTCJY.js → chunk-RNRDH4ZO.js} +308 -94
- package/dist/chunk-RNRDH4ZO.js.map +1 -0
- package/dist/{chunk-PRRUK2ZH.js → chunk-RNVXKTIA.js} +2 -2
- package/dist/{chunk-ZBIEX4XN.js → chunk-SACAOBDB.js} +1 -1
- package/dist/{chunk-JJ6ZEFXC.js → chunk-SNFWXG5G.js} +2 -2
- package/dist/{chunk-NTFXBILO.js → chunk-SY7ASLC6.js} +5 -5
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-YZ5VC2JJ.js → chunk-VVWPY5U7.js} +2 -2
- package/dist/{chunk-OIDICRH7.js → chunk-W7O7PH5E.js} +2 -2
- package/dist/{chunk-RH3XLQG4.js → chunk-WTRFM4SF.js} +2 -2
- package/dist/{chunk-6PUF7LUO.js → chunk-Y5U72X3D.js} +2 -2
- package/dist/{chunk-FFWONAAW.js → chunk-Y7LFWOX6.js} +3 -3
- package/dist/{chunk-D3DUA64S.js → chunk-YJDKSDIU.js} +2 -2
- package/dist/{chunk-BIJAWZYW.js → chunk-YMOPYG3K.js} +6 -6
- package/dist/{chunk-ZMVW2HJJ.js → chunk-YUYARGKM.js} +2 -2
- package/dist/{chunk-UMZUKOJU.js → chunk-Z3YEGSR4.js} +4 -4
- package/dist/{chunk-D6Q5EPOV.js → chunk-ZYKNDRUJ.js} +3 -3
- package/dist/cli.js +73 -73
- package/dist/coding/export-okf-codegraph.js +8 -8
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +8 -8
- package/dist/config.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +8 -8
- package/dist/connectors/index.js +8 -8
- package/dist/convergence-refresh.js +10 -10
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +10 -10
- package/dist/entity-retrieval.js +8 -8
- package/dist/external-wiki-access.js +32 -32
- package/dist/extraction-span-bench.d.ts +14 -0
- package/dist/extraction-span-bench.js +17 -0
- package/dist/extraction-span-bench.js.map +1 -0
- package/dist/extraction-span-clip.d.ts +22 -0
- package/dist/extraction-span-clip.js +19 -0
- package/dist/extraction-span-clip.js.map +1 -0
- package/dist/extraction-span-contains.d.ts +12 -0
- package/dist/extraction-span-contains.js +17 -0
- package/dist/extraction-span-contains.js.map +1 -0
- package/dist/extraction-span-gap.d.ts +14 -0
- package/dist/extraction-span-gap.js +18 -0
- package/dist/extraction-span-gap.js.map +1 -0
- package/dist/extraction-span-join.d.ts +23 -0
- package/dist/extraction-span-join.js +19 -0
- package/dist/extraction-span-join.js.map +1 -0
- package/dist/extraction-span-length.d.ts +11 -0
- package/dist/extraction-span-length.js +17 -0
- package/dist/extraction-span-length.js.map +1 -0
- package/dist/extraction-span-overlap.d.ts +14 -0
- package/dist/extraction-span-overlap.js +16 -0
- package/dist/extraction-span-overlap.js.map +1 -0
- package/dist/extraction-span-shift.d.ts +15 -0
- package/dist/extraction-span-shift.js +17 -0
- package/dist/extraction-span-shift.js.map +1 -0
- package/dist/extraction-span-slice.d.ts +12 -0
- package/dist/extraction-span-slice.js +17 -0
- package/dist/extraction-span-slice.js.map +1 -0
- package/dist/extraction-span-validate.d.ts +20 -0
- package/dist/extraction-span-validate.js +20 -0
- package/dist/extraction-span-validate.js.map +1 -0
- package/dist/extraction.js +8 -8
- package/dist/{graph-edge-decay-FXGELBFA.js → graph-edge-decay-A4SDFMMS.js} +4 -4
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +3 -3
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +181 -1
- package/dist/index.js +140 -112
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +7 -7
- package/dist/location/index.js +10 -10
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -8
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- package/dist/namespaces/migrate.js +9 -9
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.js +8 -8
- package/dist/offline-sync.js +2 -2
- package/dist/operator-doctor-corpus.js +11 -11
- package/dist/operator-toolkit.js +23 -23
- package/dist/orchestration/maintenance.js +16 -16
- package/dist/orchestrator.js +97 -97
- package/dist/recall-deep-budget-left.d.ts +8 -0
- package/dist/recall-deep-budget-left.js +15 -0
- package/dist/recall-deep-budget-left.js.map +1 -0
- package/dist/recall-deep-budget.d.ts +9 -0
- package/dist/recall-deep-budget.js +15 -0
- package/dist/recall-deep-budget.js.map +1 -0
- package/dist/recall-deep-expand.d.ts +12 -0
- package/dist/recall-deep-expand.js +17 -0
- package/dist/recall-deep-expand.js.map +1 -0
- package/dist/recall-deep-policy.d.ts +17 -0
- package/dist/recall-deep-policy.js +18 -0
- package/dist/recall-deep-policy.js.map +1 -0
- package/dist/recall-deep-query.d.ts +15 -0
- package/dist/recall-deep-query.js +12 -0
- package/dist/recall-deep-query.js.map +1 -0
- package/dist/recall-deep-refine.d.ts +12 -0
- package/dist/recall-deep-refine.js +17 -0
- package/dist/recall-deep-refine.js.map +1 -0
- package/dist/recall-deep-step-count.d.ts +8 -0
- package/dist/recall-deep-step-count.js +15 -0
- package/dist/recall-deep-step-count.js.map +1 -0
- package/dist/recall-deep-stop-reason.d.ts +17 -0
- package/dist/recall-deep-stop-reason.js +23 -0
- package/dist/recall-deep-stop-reason.js.map +1 -0
- package/dist/recall-deep-stop.d.ts +17 -0
- package/dist/recall-deep-stop.js +23 -0
- package/dist/recall-deep-stop.js.map +1 -0
- package/dist/recall-deep-working-set.d.ts +9 -0
- package/dist/recall-deep-working-set.js +14 -0
- package/dist/recall-deep-working-set.js.map +1 -0
- package/dist/recall-navigate-access.d.ts +39 -0
- package/dist/recall-navigate-access.js +31 -0
- package/dist/recall-navigate-access.js.map +1 -0
- package/dist/recall-navigate-action.d.ts +17 -0
- package/dist/recall-navigate-action.js +14 -0
- package/dist/recall-navigate-action.js.map +1 -0
- package/dist/recall-navigate-budget.d.ts +16 -0
- package/dist/recall-navigate-budget.js +18 -0
- package/dist/recall-navigate-budget.js.map +1 -0
- package/dist/recall-navigate-cost.d.ts +8 -0
- package/dist/recall-navigate-cost.js +15 -0
- package/dist/recall-navigate-cost.js.map +1 -0
- package/dist/recall-navigate-depth.d.ts +8 -0
- package/dist/recall-navigate-depth.js +15 -0
- package/dist/recall-navigate-depth.js.map +1 -0
- package/dist/recall-navigate-link.d.ts +17 -0
- package/dist/recall-navigate-link.js +20 -0
- package/dist/recall-navigate-link.js.map +1 -0
- package/dist/recall-navigate-max-children.d.ts +8 -0
- package/dist/recall-navigate-max-children.js +15 -0
- package/dist/recall-navigate-max-children.js.map +1 -0
- package/dist/recall-navigate-node.d.ts +15 -0
- package/dist/recall-navigate-node.js +13 -0
- package/dist/recall-navigate-node.js.map +1 -0
- package/dist/recall-navigate-path.d.ts +9 -0
- package/dist/recall-navigate-path.js +14 -0
- package/dist/recall-navigate-path.js.map +1 -0
- package/dist/recall-navigate-render.d.ts +15 -0
- package/dist/recall-navigate-render.js +23 -0
- package/dist/recall-navigate-render.js.map +1 -0
- package/dist/recall-state-view-current.d.ts +18 -0
- package/dist/recall-state-view-current.js +14 -0
- package/dist/recall-state-view-current.js.map +1 -0
- package/dist/recall-state-view-inject.d.ts +15 -0
- package/dist/recall-state-view-inject.js +16 -0
- package/dist/recall-state-view-inject.js.map +1 -0
- package/dist/recall-state-view-kind.d.ts +17 -0
- package/dist/recall-state-view-kind.js +14 -0
- package/dist/recall-state-view-kind.js.map +1 -0
- package/dist/recall-state-view-label.d.ts +17 -0
- package/dist/recall-state-view-label.js +16 -0
- package/dist/recall-state-view-label.js.map +1 -0
- package/dist/recall-state-view-render.js +3 -14
- package/dist/recall-state-view-render.js.map +1 -1
- package/dist/recall-state-view-successor.d.ts +15 -0
- package/dist/recall-state-view-successor.js +13 -0
- package/dist/recall-state-view-successor.js.map +1 -0
- package/dist/recall-state-view-superseded.d.ts +15 -0
- package/dist/recall-state-view-superseded.js +14 -0
- package/dist/recall-state-view-superseded.js.map +1 -0
- package/dist/recall-state-view-transition.d.ts +18 -0
- package/dist/recall-state-view-transition.js +14 -0
- package/dist/recall-state-view-transition.js.map +1 -0
- package/dist/resume-bundles.js +6 -6
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +9 -9
- package/dist/semantic-rule-promotion.js +8 -8
- package/dist/semantic-rule-verifier.js +8 -8
- package/dist/source-agent-qualifier.js +8 -8
- package/dist/storage.js +7 -7
- package/dist/support-passport/index.js +34 -34
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +9 -9
- package/dist/verified-recall.js +8 -8
- package/dist/who-knows-cli.js +9 -9
- package/dist/who-knows.js +8 -8
- package/package.json +2 -2
- package/src/activity/dwell.test.ts +28 -0
- package/src/activity/dwell.ts +22 -0
- package/src/activity/from-ms.test.ts +25 -0
- package/src/activity/from-ms.ts +13 -0
- package/src/activity/heading-level.test.ts +23 -0
- package/src/activity/heading-level.ts +21 -0
- package/src/activity/heading-title.test.ts +21 -0
- package/src/activity/heading-title.ts +26 -0
- package/src/activity/index.ts +6 -0
- package/src/activity/journal-heading.test.ts +22 -0
- package/src/activity/journal-heading.ts +18 -0
- package/src/activity/journal-mode.test.ts +32 -0
- package/src/activity/journal-mode.ts +20 -0
- package/src/activity/journal-path.test.ts +25 -0
- package/src/activity/journal-path.ts +27 -0
- package/src/activity/journal-section.test.ts +37 -0
- package/src/activity/journal-section.ts +75 -0
- package/src/activity/journal-source.test.ts +37 -0
- package/src/activity/journal-source.ts +23 -0
- package/src/activity/privacy-enabled.test.ts +30 -0
- package/src/activity/privacy-enabled.ts +33 -0
- package/src/activity/privacy-keys.test.ts +22 -0
- package/src/activity/privacy-keys.ts +12 -0
- package/src/activity/privacy-redact.test.ts +25 -0
- package/src/activity/privacy-redact.ts +23 -0
- package/src/activity/privacy-window.test.ts +49 -0
- package/src/activity/privacy-window.ts +32 -0
- package/src/activity/retain-days.test.ts +20 -0
- package/src/activity/retain-days.ts +14 -0
- package/src/activity/timeline/analysis-claim.test.ts +33 -0
- package/src/activity/timeline/analysis-claim.ts +16 -0
- package/src/activity/timeline/analysis-dup.test.ts +28 -0
- package/src/activity/timeline/analysis-dup.ts +10 -0
- package/src/activity/timeline/analysis-empty.test.ts +21 -0
- package/src/activity/timeline/analysis-empty.ts +14 -0
- package/src/activity/timeline/analysis-evidence.test.ts +32 -0
- package/src/activity/timeline/analysis-evidence.ts +20 -0
- package/src/activity/timeline/analysis-max.test.ts +26 -0
- package/src/activity/timeline/analysis-max.ts +14 -0
- package/src/activity/timeline/analysis-order.test.ts +28 -0
- package/src/activity/timeline/analysis-order.ts +10 -0
- package/src/activity/timeline/analysis-overlap.test.ts +26 -0
- package/src/activity/timeline/analysis-overlap.ts +17 -0
- package/src/activity/timeline/analysis-prompt.test.ts +50 -0
- package/src/activity/timeline/analysis-prompt.ts +24 -0
- package/src/activity/timeline/index.ts +8 -0
- package/src/activity/timeline/recap-card-count.test.ts +19 -0
- package/src/activity/timeline/recap-card-count.ts +13 -0
- package/src/activity/timeline/recap-card-id.test.ts +21 -0
- package/src/activity/timeline/recap-card-id.ts +16 -0
- package/src/activity/timeline/recap-date.test.ts +21 -0
- package/src/activity/timeline/recap-date.ts +19 -0
- package/src/activity/timeline/recap-empty.test.ts +21 -0
- package/src/activity/timeline/recap-empty.ts +14 -0
- package/src/activity/timeline/recap-export.test.ts +78 -0
- package/src/activity/timeline/recap-export.ts +41 -0
- package/src/activity/timeline/recap-id.test.ts +28 -0
- package/src/activity/timeline/recap-id.ts +10 -0
- package/src/activity/timeline/recap-markdown.test.ts +56 -0
- package/src/activity/timeline/recap-markdown.ts +26 -0
- package/src/activity/timeline/recap-sort-key.test.ts +18 -0
- package/src/activity/timeline/recap-sort-key.ts +14 -0
- package/src/activity/timeline/recap-tz.test.ts +35 -0
- package/src/activity/timeline/recap-tz.ts +19 -0
- package/src/activity/timeline/week-count.test.ts +19 -0
- package/src/activity/timeline/week-count.ts +13 -0
- package/src/activity/timeline/week-days.test.ts +26 -0
- package/src/activity/timeline/week-days.ts +23 -0
- package/src/activity/timeline/week-export.test.ts +59 -0
- package/src/activity/timeline/week-export.ts +44 -0
- package/src/activity/timeline/week-iso.test.ts +29 -0
- package/src/activity/timeline/week-iso.ts +20 -0
- package/src/activity/timeline/week-monday.test.ts +17 -0
- package/src/activity/timeline/week-monday.ts +12 -0
- package/src/activity/timeline/week-range.test.ts +23 -0
- package/src/activity/timeline/week-range.ts +26 -0
- package/src/activity/timeline/week-snapshot.test.ts +66 -0
- package/src/activity/timeline/week-snapshot.ts +40 -0
- package/src/activity/timeline/week-span.test.ts +21 -0
- package/src/activity/timeline/week-span.ts +18 -0
- package/src/activity/timeline/week-sunday.test.ts +17 -0
- package/src/activity/timeline/week-sunday.ts +12 -0
- package/src/activity/to-ms.test.ts +25 -0
- package/src/activity/to-ms.ts +13 -0
- package/src/activity/vault-begin.test.ts +20 -0
- package/src/activity/vault-begin.ts +13 -0
- package/src/activity/vault-end.test.ts +20 -0
- package/src/activity/vault-end.ts +13 -0
- package/src/activity/vault-frontmatter.test.ts +28 -0
- package/src/activity/vault-frontmatter.ts +39 -0
- package/src/activity/vault-join.test.ts +21 -0
- package/src/activity/vault-join.ts +31 -0
- package/src/activity/vault-marker.test.ts +23 -0
- package/src/activity/vault-marker.ts +27 -0
- package/src/activity/vault-name-trim.test.ts +22 -0
- package/src/activity/vault-name-trim.ts +16 -0
- package/src/activity/vault-region-pair.test.ts +26 -0
- package/src/activity/vault-region-pair.ts +24 -0
- package/src/activity/vault-region.test.ts +24 -0
- package/src/activity/vault-region.ts +24 -0
- package/src/activity/vault-suffix.test.ts +20 -0
- package/src/activity/vault-suffix.ts +12 -0
- package/src/activity/vault-wikilink.test.ts +27 -0
- package/src/activity/vault-wikilink.ts +28 -0
- package/src/activity/window-order.test.ts +27 -0
- package/src/activity/window-order.ts +24 -0
- package/src/dedup/merge-count.test.ts +19 -0
- package/src/dedup/merge-count.ts +13 -0
- package/src/dedup/merge-dup.test.ts +21 -0
- package/src/dedup/merge-dup.ts +16 -0
- package/src/dedup/merge-empty.test.ts +22 -0
- package/src/dedup/merge-empty.ts +18 -0
- package/src/dedup/merge-id.test.ts +18 -0
- package/src/dedup/merge-id.ts +14 -0
- package/src/dedup/merge-min-score.test.ts +37 -0
- package/src/dedup/merge-min-score.ts +22 -0
- package/src/dedup/merge-pair.test.ts +19 -0
- package/src/dedup/merge-pair.ts +13 -0
- package/src/dedup/merge-score.test.ts +20 -0
- package/src/dedup/merge-score.ts +14 -0
- package/src/dedup/merge-self.test.ts +19 -0
- package/src/dedup/merge-self.ts +17 -0
- package/src/dedup/merge-threshold.test.ts +46 -0
- package/src/dedup/merge-threshold.ts +26 -0
- package/src/dedup/merge-tie.test.ts +22 -0
- package/src/dedup/merge-tie.ts +13 -0
- package/src/extraction-span-bench.test.ts +27 -0
- package/src/extraction-span-bench.ts +28 -0
- package/src/extraction-span-clip.test.ts +71 -0
- package/src/extraction-span-clip.ts +26 -0
- package/src/extraction-span-contains.test.ts +30 -0
- package/src/extraction-span-contains.ts +20 -0
- package/src/extraction-span-gap.test.ts +29 -0
- package/src/extraction-span-gap.ts +25 -0
- package/src/extraction-span-join.test.ts +50 -0
- package/src/extraction-span-join.ts +30 -0
- package/src/extraction-span-length.test.ts +25 -0
- package/src/extraction-span-length.ts +16 -0
- package/src/extraction-span-overlap.test.ts +34 -0
- package/src/extraction-span-overlap.ts +23 -0
- package/src/extraction-span-shift.test.ts +25 -0
- package/src/extraction-span-shift.ts +20 -0
- package/src/extraction-span-slice.test.ts +32 -0
- package/src/extraction-span-slice.ts +20 -0
- package/src/extraction-span-validate.test.ts +42 -0
- package/src/extraction-span-validate.ts +27 -0
- package/src/recall-deep-budget-left.test.ts +20 -0
- package/src/recall-deep-budget-left.ts +13 -0
- package/src/recall-deep-budget.test.ts +19 -0
- package/src/recall-deep-budget.ts +14 -0
- package/src/recall-deep-expand.test.ts +23 -0
- package/src/recall-deep-expand.ts +19 -0
- package/src/recall-deep-policy.test.ts +32 -0
- package/src/recall-deep-policy.ts +22 -0
- package/src/recall-deep-query.test.ts +23 -0
- package/src/recall-deep-query.ts +15 -0
- package/src/recall-deep-refine.test.ts +23 -0
- package/src/recall-deep-refine.ts +19 -0
- package/src/recall-deep-step-count.test.ts +20 -0
- package/src/recall-deep-step-count.ts +13 -0
- package/src/recall-deep-stop-reason.test.ts +46 -0
- package/src/recall-deep-stop-reason.ts +27 -0
- package/src/recall-deep-stop.test.ts +34 -0
- package/src/recall-deep-stop.ts +31 -0
- package/src/recall-deep-working-set.test.ts +21 -0
- package/src/recall-deep-working-set.ts +14 -0
- package/src/recall-navigate-access.test.ts +73 -0
- package/src/recall-navigate-access.ts +48 -0
- package/src/recall-navigate-action.test.ts +32 -0
- package/src/recall-navigate-action.ts +20 -0
- package/src/recall-navigate-budget.test.ts +23 -0
- package/src/recall-navigate-budget.ts +22 -0
- package/src/recall-navigate-cost.test.ts +20 -0
- package/src/recall-navigate-cost.ts +13 -0
- package/src/recall-navigate-depth.test.ts +20 -0
- package/src/recall-navigate-depth.ts +13 -0
- package/src/recall-navigate-link.test.ts +19 -0
- package/src/recall-navigate-link.ts +26 -0
- package/src/recall-navigate-max-children.test.ts +20 -0
- package/src/recall-navigate-max-children.ts +13 -0
- package/src/recall-navigate-node.test.ts +21 -0
- package/src/recall-navigate-node.ts +16 -0
- package/src/recall-navigate-path.test.ts +21 -0
- package/src/recall-navigate-path.ts +14 -0
- package/src/recall-navigate-render.test.ts +63 -0
- package/src/recall-navigate-render.ts +31 -0
- package/src/recall-state-view-current.test.ts +23 -0
- package/src/recall-state-view-current.ts +24 -0
- package/src/recall-state-view-inject.test.ts +32 -0
- package/src/recall-state-view-inject.ts +14 -0
- package/src/recall-state-view-kind.test.ts +19 -0
- package/src/recall-state-view-kind.ts +20 -0
- package/src/recall-state-view-label.test.ts +36 -0
- package/src/recall-state-view-label.ts +22 -0
- package/src/recall-state-view-successor.test.ts +21 -0
- package/src/recall-state-view-successor.ts +16 -0
- package/src/recall-state-view-superseded.test.ts +20 -0
- package/src/recall-state-view-superseded.ts +17 -0
- package/src/recall-state-view-transition.test.ts +30 -0
- package/src/recall-state-view-transition.ts +24 -0
- package/src/shared-context/authority.test.ts +35 -0
- package/src/shared-context/authority.ts +25 -0
- package/src/shared-context/claim-key.test.ts +21 -0
- package/src/shared-context/claim-key.ts +16 -0
- package/src/shared-context/contradictions.test.ts +39 -0
- package/src/shared-context/contradictions.ts +32 -0
- package/src/shared-context/envelope-actor.test.ts +21 -0
- package/src/shared-context/envelope-actor.ts +16 -0
- package/src/shared-context/envelope-at.test.ts +19 -0
- package/src/shared-context/envelope-at.ts +17 -0
- package/src/shared-context/envelope-id.test.ts +21 -0
- package/src/shared-context/envelope-id.ts +16 -0
- package/src/shared-context/envelope-required.test.ts +24 -0
- package/src/shared-context/envelope-required.ts +15 -0
- package/src/shared-context/envelope-ttl.test.ts +29 -0
- package/src/shared-context/envelope-ttl.ts +35 -0
- package/src/shared-context/provenance.test.ts +35 -0
- package/src/shared-context/provenance.ts +68 -0
- package/dist/chunk-47WWTCJY.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{chunk-36HJ36FK.js.map → chunk-27JH2KKM.js.map} +0 -0
- /package/dist/{chunk-NZPFCP5A.js.map → chunk-2PSX5BJU.js.map} +0 -0
- /package/dist/{chunk-RCPKBW7K.js.map → chunk-322MSJYS.js.map} +0 -0
- /package/dist/{chunk-C3ZYZKDJ.js.map → chunk-3E4REX7E.js.map} +0 -0
- /package/dist/{chunk-Y2YM4C75.js.map → chunk-4BRBSON5.js.map} +0 -0
- /package/dist/{chunk-B4B3QDV3.js.map → chunk-4EX26ZS3.js.map} +0 -0
- /package/dist/{chunk-ER2UPUMG.js.map → chunk-562Q236T.js.map} +0 -0
- /package/dist/{chunk-F2JXJABQ.js.map → chunk-5BIOXQGN.js.map} +0 -0
- /package/dist/{chunk-DSEHPUID.js.map → chunk-5C3OQ253.js.map} +0 -0
- /package/dist/{chunk-LSR5LQXN.js.map → chunk-5VLXQFOS.js.map} +0 -0
- /package/dist/{chunk-2QSGJTDG.js.map → chunk-636UUVOZ.js.map} +0 -0
- /package/dist/{chunk-AI5M4FVQ.js.map → chunk-6DTQSLER.js.map} +0 -0
- /package/dist/{chunk-CDLPF3PX.js.map → chunk-6IL5FFHH.js.map} +0 -0
- /package/dist/{chunk-VKDBDJIX.js.map → chunk-6L6V2OIX.js.map} +0 -0
- /package/dist/{chunk-ZXROBS53.js.map → chunk-6NA527W2.js.map} +0 -0
- /package/dist/{chunk-R2Q6FTOX.js.map → chunk-7IEFFOIC.js.map} +0 -0
- /package/dist/{chunk-4PG2CKI6.js.map → chunk-AQHDMA4A.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-6BYJQ3L2.js.map → chunk-BE4Y6GK6.js.map} +0 -0
- /package/dist/{chunk-PW3GKWH3.js.map → chunk-BQVWC67C.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-UVYL5G7A.js.map → chunk-C3LUYZLX.js.map} +0 -0
- /package/dist/{chunk-JMXHVJ2V.js.map → chunk-DFWO37KT.js.map} +0 -0
- /package/dist/{chunk-WKWY7YF3.js.map → chunk-F5FHDDOV.js.map} +0 -0
- /package/dist/{chunk-U4HZEW2B.js.map → chunk-FH4BB7F4.js.map} +0 -0
- /package/dist/{chunk-UHQE37NI.js.map → chunk-G5TYMG7R.js.map} +0 -0
- /package/dist/{chunk-PP7R754B.js.map → chunk-GPFZFCRN.js.map} +0 -0
- /package/dist/{chunk-EH7BNIUN.js.map → chunk-HAF3PUMO.js.map} +0 -0
- /package/dist/{chunk-J2R275FN.js.map → chunk-HQJM3BKH.js.map} +0 -0
- /package/dist/{chunk-O7ERGZDA.js.map → chunk-I7NBTRLR.js.map} +0 -0
- /package/dist/{chunk-6545JDIM.js.map → chunk-IHK5KTOC.js.map} +0 -0
- /package/dist/{chunk-25OLNWYG.js.map → chunk-IP7KIW7W.js.map} +0 -0
- /package/dist/{chunk-XJJYSX6F.js.map → chunk-JCUG7AXZ.js.map} +0 -0
- /package/dist/{chunk-OERZXNSK.js.map → chunk-JGXMA6HL.js.map} +0 -0
- /package/dist/{chunk-CQ6IWZDI.js.map → chunk-JVDAIASW.js.map} +0 -0
- /package/dist/{chunk-F4W5HTED.js.map → chunk-K3CQQNMX.js.map} +0 -0
- /package/dist/{chunk-D2YNNDML.js.map → chunk-KWIGDKM2.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-OLBZ2WGG.js.map → chunk-LTBKKSCX.js.map} +0 -0
- /package/dist/{chunk-VSMSYDIH.js.map → chunk-MARZW7DB.js.map} +0 -0
- /package/dist/{chunk-W5IYHQA5.js.map → chunk-MS2I7JVN.js.map} +0 -0
- /package/dist/{chunk-BLHKR4GJ.js.map → chunk-NHACZAM3.js.map} +0 -0
- /package/dist/{chunk-2VZHB5US.js.map → chunk-NTZ3452G.js.map} +0 -0
- /package/dist/{chunk-65HXVEVG.js.map → chunk-OT3GGOFW.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-OUKBMQ2O.js.map → chunk-PD66CVSD.js.map} +0 -0
- /package/dist/{chunk-EQRD4NMH.js.map → chunk-PMMNP5GZ.js.map} +0 -0
- /package/dist/{chunk-PRRUK2ZH.js.map → chunk-RNVXKTIA.js.map} +0 -0
- /package/dist/{chunk-ZBIEX4XN.js.map → chunk-SACAOBDB.js.map} +0 -0
- /package/dist/{chunk-JJ6ZEFXC.js.map → chunk-SNFWXG5G.js.map} +0 -0
- /package/dist/{chunk-NTFXBILO.js.map → chunk-SY7ASLC6.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-YZ5VC2JJ.js.map → chunk-VVWPY5U7.js.map} +0 -0
- /package/dist/{chunk-OIDICRH7.js.map → chunk-W7O7PH5E.js.map} +0 -0
- /package/dist/{chunk-RH3XLQG4.js.map → chunk-WTRFM4SF.js.map} +0 -0
- /package/dist/{chunk-6PUF7LUO.js.map → chunk-Y5U72X3D.js.map} +0 -0
- /package/dist/{chunk-FFWONAAW.js.map → chunk-Y7LFWOX6.js.map} +0 -0
- /package/dist/{chunk-D3DUA64S.js.map → chunk-YJDKSDIU.js.map} +0 -0
- /package/dist/{chunk-BIJAWZYW.js.map → chunk-YMOPYG3K.js.map} +0 -0
- /package/dist/{chunk-ZMVW2HJJ.js.map → chunk-YUYARGKM.js.map} +0 -0
- /package/dist/{chunk-UMZUKOJU.js.map → chunk-Z3YEGSR4.js.map} +0 -0
- /package/dist/{chunk-D6Q5EPOV.js.map → chunk-ZYKNDRUJ.js.map} +0 -0
- /package/dist/{graph-edge-decay-FXGELBFA.js.map → graph-edge-decay-A4SDFMMS.js.map} +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse journal mode (issue #1987 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Pure. Surfaces wait. Allow file or vault. Unknown or empty is unknown_mode.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const JOURNAL_MODES = ["file", "vault"] as const;
|
|
8
|
+
|
|
9
|
+
export type JournalMode = (typeof JOURNAL_MODES)[number];
|
|
10
|
+
|
|
11
|
+
export type ParseJournalModeResult =
|
|
12
|
+
| { ok: true; mode: JournalMode }
|
|
13
|
+
| { ok: false; error: "unknown_mode" };
|
|
14
|
+
|
|
15
|
+
export function parseJournalMode(value: string): ParseJournalModeResult {
|
|
16
|
+
if ((JOURNAL_MODES as readonly string[]).includes(value)) {
|
|
17
|
+
return { ok: true, mode: value as JournalMode };
|
|
18
|
+
}
|
|
19
|
+
return { ok: false, error: "unknown_mode" };
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parseJournalFilePath } from "./journal-path.js";
|
|
5
|
+
|
|
6
|
+
test("parseJournalFilePath accepts a relative path", () => {
|
|
7
|
+
assert.deepEqual(parseJournalFilePath("journal/2026-08-19.md"), {
|
|
8
|
+
ok: true,
|
|
9
|
+
path: "journal/2026-08-19.md",
|
|
10
|
+
});
|
|
11
|
+
assert.deepEqual(parseJournalFilePath(" notes/today.md "), {
|
|
12
|
+
ok: true,
|
|
13
|
+
path: "notes/today.md",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("parseJournalFilePath rejects an empty path", () => {
|
|
18
|
+
assert.deepEqual(parseJournalFilePath(""), { ok: false, error: "missing_path" });
|
|
19
|
+
assert.deepEqual(parseJournalFilePath(" "), { ok: false, error: "missing_path" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("parseJournalFilePath rejects ..", () => {
|
|
23
|
+
assert.deepEqual(parseJournalFilePath("../secret"), { ok: false, error: "invalid_path" });
|
|
24
|
+
assert.deepEqual(parseJournalFilePath("foo/../bar"), { ok: false, error: "invalid_path" });
|
|
25
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a journal file path (issue #1987 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Trim. Empty is missing_path. Absolute, `..`, backslash, or newline
|
|
5
|
+
* is invalid_path.
|
|
6
|
+
*/
|
|
7
|
+
import { posix, win32 } from "node:path";
|
|
8
|
+
|
|
9
|
+
export type ParseJournalFilePathResult =
|
|
10
|
+
| { ok: true; path: string }
|
|
11
|
+
| { ok: false; error: "missing_path" | "invalid_path" };
|
|
12
|
+
|
|
13
|
+
export function parseJournalFilePath(value: string): ParseJournalFilePathResult {
|
|
14
|
+
const journalPath = value.trim();
|
|
15
|
+
if (journalPath.length === 0) return { ok: false, error: "missing_path" };
|
|
16
|
+
if (
|
|
17
|
+
journalPath.includes("\\") ||
|
|
18
|
+
journalPath.includes("\n") ||
|
|
19
|
+
journalPath.includes("\r") ||
|
|
20
|
+
posix.isAbsolute(journalPath) ||
|
|
21
|
+
win32.isAbsolute(journalPath) ||
|
|
22
|
+
journalPath.split("/").includes("..")
|
|
23
|
+
) {
|
|
24
|
+
return { ok: false, error: "invalid_path" };
|
|
25
|
+
}
|
|
26
|
+
return { ok: true, path: journalPath };
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { extractJournalSection } from "./journal-section.js";
|
|
5
|
+
|
|
6
|
+
test("extractJournalSection returns null when the heading is missing", () => {
|
|
7
|
+
const markdown = ["# Day", "## Notes", "cards", ""].join("\n");
|
|
8
|
+
assert.equal(extractJournalSection(markdown, "Journal"), null);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("extractJournalSection returns the unique heading body until the next same-or-higher heading", () => {
|
|
12
|
+
const markdown = [
|
|
13
|
+
"# Day",
|
|
14
|
+
"## Journal",
|
|
15
|
+
"walked the dog",
|
|
16
|
+
"### Nested",
|
|
17
|
+
"still journal",
|
|
18
|
+
"## Notes",
|
|
19
|
+
"not journal",
|
|
20
|
+
"",
|
|
21
|
+
].join("\n");
|
|
22
|
+
assert.equal(
|
|
23
|
+
extractJournalSection(markdown, "Journal"),
|
|
24
|
+
["walked the dog", "### Nested", "still journal"].join("\n"),
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("extractJournalSection refuses a duplicate heading", () => {
|
|
29
|
+
const markdown = ["# Day", "## Journal", "first", "## Notes", "## Journal", "second", ""].join("\n");
|
|
30
|
+
assert.deepEqual(extractJournalSection(markdown, "Journal"), { error: "duplicate_heading" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("extractJournalSection rejects an empty heading", () => {
|
|
34
|
+
const markdown = ["## Journal", "hi", ""].join("\n");
|
|
35
|
+
assert.deepEqual(extractJournalSection(markdown, ""), { error: "empty_heading" });
|
|
36
|
+
assert.deepEqual(extractJournalSection(markdown, " "), { error: "empty_heading" });
|
|
37
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a unique ATX heading section (issue #1987).
|
|
3
|
+
*
|
|
4
|
+
* Pure string helper. No filesystem. Missing heading returns null.
|
|
5
|
+
* Duplicate headings refuse. Empty headings are rejected.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ExtractJournalSectionResult =
|
|
9
|
+
| string
|
|
10
|
+
| null
|
|
11
|
+
| { error: "duplicate_heading" }
|
|
12
|
+
| { error: "empty_heading" };
|
|
13
|
+
|
|
14
|
+
export function extractJournalSection(
|
|
15
|
+
markdown: string,
|
|
16
|
+
heading: string,
|
|
17
|
+
): ExtractJournalSectionResult {
|
|
18
|
+
const wanted = heading.trim();
|
|
19
|
+
if (wanted.length === 0) return { error: "empty_heading" };
|
|
20
|
+
|
|
21
|
+
const lines = splitFileLines(markdown);
|
|
22
|
+
const hits: number[] = [];
|
|
23
|
+
const levels: number[] = [];
|
|
24
|
+
for (let i = 0; i < lines.length; i++) {
|
|
25
|
+
const parsed = parseAtxHeading(lines[i]!);
|
|
26
|
+
if (parsed && parsed.text === wanted) {
|
|
27
|
+
hits.push(i);
|
|
28
|
+
levels.push(parsed.level);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (hits.length === 0) return null;
|
|
32
|
+
if (hits.length > 1) return { error: "duplicate_heading" };
|
|
33
|
+
|
|
34
|
+
const headingIndex = hits[0]!;
|
|
35
|
+
const level = levels[0]!;
|
|
36
|
+
let endIndex = lines.length;
|
|
37
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
38
|
+
const parsed = parseAtxHeading(lines[i]!);
|
|
39
|
+
if (parsed && parsed.level <= level) {
|
|
40
|
+
endIndex = i;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return lines.slice(headingIndex + 1, endIndex).join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function splitFileLines(fileText: string): string[] {
|
|
48
|
+
const lines = fileText.split("\n");
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
const line = lines[i]!;
|
|
51
|
+
if (line.endsWith("\r")) lines[i] = line.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseAtxHeading(line: string): { level: number; text: string } | null {
|
|
57
|
+
let level = 0;
|
|
58
|
+
while (level < line.length && level < 6 && line[level] === "#") level++;
|
|
59
|
+
if (level === 0) return null;
|
|
60
|
+
const after = line[level];
|
|
61
|
+
if (after !== " " && after !== "\t") return null;
|
|
62
|
+
|
|
63
|
+
let start = level + 1;
|
|
64
|
+
let end = line.length;
|
|
65
|
+
while (start < end && (line[start] === " " || line[start] === "\t")) start++;
|
|
66
|
+
while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
|
|
67
|
+
|
|
68
|
+
let hashEnd = end;
|
|
69
|
+
while (hashEnd > start && line[hashEnd - 1] === "#") hashEnd--;
|
|
70
|
+
if (hashEnd < end && hashEnd > start && (line[hashEnd - 1] === " " || line[hashEnd - 1] === "\t")) {
|
|
71
|
+
end = hashEnd;
|
|
72
|
+
while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
|
|
73
|
+
}
|
|
74
|
+
return { level, text: line.slice(start, end) };
|
|
75
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { resolveJournalSource } from "./journal-source.js";
|
|
5
|
+
|
|
6
|
+
test("source file resolves to file mode", () => {
|
|
7
|
+
assert.deepEqual(resolveJournalSource({ source: "file", heading: "" }), {
|
|
8
|
+
ok: true,
|
|
9
|
+
mode: "file",
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("source vault with a heading resolves to vault mode", () => {
|
|
14
|
+
assert.deepEqual(resolveJournalSource({ source: "vault", heading: "Journal" }), {
|
|
15
|
+
ok: true,
|
|
16
|
+
mode: "vault",
|
|
17
|
+
heading: "Journal",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("source vault with an empty heading is missing_heading", () => {
|
|
22
|
+
assert.deepEqual(resolveJournalSource({ source: "vault", heading: "" }), {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: "missing_heading",
|
|
25
|
+
});
|
|
26
|
+
assert.deepEqual(resolveJournalSource({ source: "vault", heading: " " }), {
|
|
27
|
+
ok: false,
|
|
28
|
+
error: "missing_heading",
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("unknown source is unknown_source", () => {
|
|
33
|
+
assert.deepEqual(resolveJournalSource({ source: "memoryDir", heading: "Journal" }), {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: "unknown_source",
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve journal source mode (issue #1987 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Pure. File mode ignores heading. Vault mode needs a non-empty heading.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type JournalSourceResult =
|
|
8
|
+
| { ok: true; mode: "file" }
|
|
9
|
+
| { ok: true; mode: "vault"; heading: string }
|
|
10
|
+
| { ok: false; error: "missing_heading" | "unknown_source" };
|
|
11
|
+
|
|
12
|
+
export function resolveJournalSource(input: {
|
|
13
|
+
source: string;
|
|
14
|
+
heading: string;
|
|
15
|
+
}): JournalSourceResult {
|
|
16
|
+
if (input.source === "file") return { ok: true, mode: "file" };
|
|
17
|
+
if (input.source === "vault") {
|
|
18
|
+
const heading = input.heading.trim();
|
|
19
|
+
if (heading.length === 0) return { ok: false, error: "missing_heading" };
|
|
20
|
+
return { ok: true, mode: "vault", heading };
|
|
21
|
+
}
|
|
22
|
+
return { ok: false, error: "unknown_source" };
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parsePrivacyEnabled } from "./privacy-enabled.js";
|
|
5
|
+
|
|
6
|
+
test("boolean passthrough returns enabled", () => {
|
|
7
|
+
assert.deepEqual(parsePrivacyEnabled(true), { ok: true, enabled: true });
|
|
8
|
+
assert.deepEqual(parsePrivacyEnabled(false), { ok: true, enabled: false });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("string false tokens return enabled false", () => {
|
|
12
|
+
assert.deepEqual(parsePrivacyEnabled("false"), { ok: true, enabled: false });
|
|
13
|
+
assert.deepEqual(parsePrivacyEnabled("0"), { ok: true, enabled: false });
|
|
14
|
+
assert.deepEqual(parsePrivacyEnabled("no"), { ok: true, enabled: false });
|
|
15
|
+
assert.deepEqual(parsePrivacyEnabled("off"), { ok: true, enabled: false });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("string true tokens return enabled true", () => {
|
|
19
|
+
assert.deepEqual(parsePrivacyEnabled("true"), { ok: true, enabled: true });
|
|
20
|
+
assert.deepEqual(parsePrivacyEnabled("1"), { ok: true, enabled: true });
|
|
21
|
+
assert.deepEqual(parsePrivacyEnabled("yes"), { ok: true, enabled: true });
|
|
22
|
+
assert.deepEqual(parsePrivacyEnabled("on"), { ok: true, enabled: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("unknown values are invalid_enabled", () => {
|
|
26
|
+
assert.deepEqual(parsePrivacyEnabled("maybe"), { ok: false, error: "invalid_enabled" });
|
|
27
|
+
assert.deepEqual(parsePrivacyEnabled(2), { ok: false, error: "invalid_enabled" });
|
|
28
|
+
assert.deepEqual(parsePrivacyEnabled(null), { ok: false, error: "invalid_enabled" });
|
|
29
|
+
assert.deepEqual(parsePrivacyEnabled({}), { ok: false, error: "invalid_enabled" });
|
|
30
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the activity privacy enabled flag (issue #2053 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Boolean passthrough. String tokens true/1/yes/on and false/0/no/off.
|
|
5
|
+
* Unknown values fail closed as invalid_enabled.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ParsePrivacyEnabledResult =
|
|
9
|
+
| { ok: true; enabled: boolean }
|
|
10
|
+
| { ok: false; error: "invalid_enabled" };
|
|
11
|
+
|
|
12
|
+
const ENABLED_TOKENS: Record<string, boolean> = {
|
|
13
|
+
true: true,
|
|
14
|
+
"1": true,
|
|
15
|
+
yes: true,
|
|
16
|
+
on: true,
|
|
17
|
+
false: false,
|
|
18
|
+
"0": false,
|
|
19
|
+
no: false,
|
|
20
|
+
off: false,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Accept a boolean or known token. Unknown values are invalid_enabled. */
|
|
24
|
+
export function parsePrivacyEnabled(value: unknown): ParsePrivacyEnabledResult {
|
|
25
|
+
if (typeof value === "boolean") return { ok: true, enabled: value };
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
const token = value.trim().toLowerCase();
|
|
28
|
+
if (Object.hasOwn(ENABLED_TOKENS, token)) {
|
|
29
|
+
return { ok: true, enabled: ENABLED_TOKENS[token] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { ok: false, error: "invalid_enabled" };
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { normalizeDropKeys } from "./privacy-keys.js";
|
|
5
|
+
|
|
6
|
+
test("empty keys return []", () => {
|
|
7
|
+
assert.deepEqual(normalizeDropKeys([]), []);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("trims keys and drops empty", () => {
|
|
11
|
+
assert.deepEqual(normalizeDropKeys([" secret ", "", " ", "title"]), ["secret", "title"]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("unique keys sort by localeCompare", () => {
|
|
15
|
+
assert.deepEqual(normalizeDropKeys(["zeta", "alpha", "zeta", "beta"]), ["alpha", "beta", "zeta"]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("does not mutate the input list", () => {
|
|
19
|
+
const keys = [" zeta ", "alpha", "alpha"];
|
|
20
|
+
normalizeDropKeys(keys);
|
|
21
|
+
assert.deepEqual(keys, [" zeta ", "alpha", "alpha"]);
|
|
22
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize activity privacy drop keys (issue #2053).
|
|
3
|
+
*
|
|
4
|
+
* Trim, drop empty, unique, localeCompare sort.
|
|
5
|
+
* Does not mutate the input list.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function normalizeDropKeys(keys: readonly string[]): string[] {
|
|
9
|
+
const unique = [...new Set(keys.map((key) => key.trim()).filter((key) => key.length > 0))];
|
|
10
|
+
unique.sort((a, b) => a.localeCompare(b));
|
|
11
|
+
return unique;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { redactActivityFields } from "./privacy-redact.js";
|
|
5
|
+
|
|
6
|
+
test("drops listed keys and ignores unknown keys", () => {
|
|
7
|
+
const item = { id: "a", title: "kept", secret: "drop-me" };
|
|
8
|
+
const redacted = redactActivityFields(item, {
|
|
9
|
+
dropKeys: ["secret", "missing"],
|
|
10
|
+
});
|
|
11
|
+
assert.deepEqual(redacted, { id: "a", title: "kept" });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("does not mutate the input item", () => {
|
|
15
|
+
const item = { id: "a", secret: "drop-me" };
|
|
16
|
+
redactActivityFields(item, { dropKeys: ["secret"] });
|
|
17
|
+
assert.deepEqual(item, { id: "a", secret: "drop-me" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("empty dropKeys returns a shallow copy", () => {
|
|
21
|
+
const item = { id: "a", title: "kept" };
|
|
22
|
+
const redacted = redactActivityFields(item, { dropKeys: [] });
|
|
23
|
+
assert.deepEqual(redacted, item);
|
|
24
|
+
assert.notEqual(redacted, item);
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop listed keys from one activity item (issue #2053).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ActivityRedactOptions {
|
|
6
|
+
dropKeys: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Return a shallow copy without `dropKeys`.
|
|
11
|
+
* Unknown keys are ignored. Empty `dropKeys` is a shallow copy.
|
|
12
|
+
* Does not mutate `item`.
|
|
13
|
+
*/
|
|
14
|
+
export function redactActivityFields<T extends Record<string, unknown>>(
|
|
15
|
+
item: T,
|
|
16
|
+
options: ActivityRedactOptions,
|
|
17
|
+
): T {
|
|
18
|
+
const result: Record<string, unknown> = { ...item };
|
|
19
|
+
for (const key of options.dropKeys) {
|
|
20
|
+
delete result[key];
|
|
21
|
+
}
|
|
22
|
+
return result as T;
|
|
23
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { MS_PER_DAY } from "./privacy.js";
|
|
5
|
+
import { filterPrivacyWindow } from "./privacy-window.js";
|
|
6
|
+
|
|
7
|
+
function item(capturedAtMs: number, id = capturedAtMs) {
|
|
8
|
+
return { id, capturedAtMs };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test("window is half-open [fromMs, toMs)", () => {
|
|
12
|
+
const fromMs = 1_000;
|
|
13
|
+
const toMs = 2_000;
|
|
14
|
+
const kept = filterPrivacyWindow(
|
|
15
|
+
[item(fromMs - 1), item(fromMs), item(toMs - 1), item(toMs)],
|
|
16
|
+
{ fromMs, toMs, retainDays: 0 },
|
|
17
|
+
);
|
|
18
|
+
assert.deepEqual(
|
|
19
|
+
kept.map((entry) => entry.capturedAtMs),
|
|
20
|
+
[fromMs, toMs - 1],
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("retainDays 0 keeps all ages inside the window", () => {
|
|
25
|
+
const toMs = 400 * MS_PER_DAY;
|
|
26
|
+
const old = item(toMs - 390 * MS_PER_DAY, 1);
|
|
27
|
+
const kept = filterPrivacyWindow([old], { fromMs: 0, toMs, retainDays: 0 });
|
|
28
|
+
assert.deepEqual(kept, [old]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("retainDays N drops older than N days before toMs", () => {
|
|
32
|
+
const toMs = 10 * MS_PER_DAY;
|
|
33
|
+
const fromMs = 0;
|
|
34
|
+
const cutoff = toMs - 3 * MS_PER_DAY;
|
|
35
|
+
const kept = filterPrivacyWindow(
|
|
36
|
+
[item(cutoff - 1, 1), item(cutoff, 2), item(cutoff + 1, 3), item(toMs - 1, 4)],
|
|
37
|
+
{ fromMs, toMs, retainDays: 3 },
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
kept.map((entry) => entry.id),
|
|
41
|
+
[3, 4],
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("empty items or inverted window return []", () => {
|
|
46
|
+
assert.deepEqual(filterPrivacyWindow([], { fromMs: 0, toMs: 10, retainDays: 0 }), []);
|
|
47
|
+
assert.deepEqual(filterPrivacyWindow([item(5)], { fromMs: 10, toMs: 10, retainDays: 0 }), []);
|
|
48
|
+
assert.deepEqual(filterPrivacyWindow([item(5)], { fromMs: 10, toMs: 0, retainDays: 0 }), []);
|
|
49
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Half-open activity privacy window (issue #2053).
|
|
3
|
+
*/
|
|
4
|
+
import { shouldRetain } from "./privacy.js";
|
|
5
|
+
|
|
6
|
+
export interface PrivacyWindowItem {
|
|
7
|
+
capturedAtMs: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PrivacyWindowOptions {
|
|
11
|
+
fromMs: number;
|
|
12
|
+
toMs: number;
|
|
13
|
+
retainDays: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keep items in `[fromMs, toMs)`.
|
|
18
|
+
* `retainDays` 0 keeps all ages. `retainDays` N drops older than N days before `toMs`.
|
|
19
|
+
* `toMs <= fromMs` returns `[]`.
|
|
20
|
+
*/
|
|
21
|
+
export function filterPrivacyWindow<T extends PrivacyWindowItem>(
|
|
22
|
+
items: readonly T[],
|
|
23
|
+
options: PrivacyWindowOptions,
|
|
24
|
+
): T[] {
|
|
25
|
+
const { fromMs, toMs, retainDays } = options;
|
|
26
|
+
if (toMs <= fromMs) return [];
|
|
27
|
+
return items.filter((item) => {
|
|
28
|
+
const capturedAtMs = item.capturedAtMs;
|
|
29
|
+
if (capturedAtMs < fromMs || capturedAtMs >= toMs) return false;
|
|
30
|
+
return shouldRetain(capturedAtMs, toMs, retainDays);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parseRetainDays } from "./retain-days.js";
|
|
5
|
+
|
|
6
|
+
test("retainDays 0 keeps forever", () => {
|
|
7
|
+
assert.equal(parseRetainDays(0), 0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("retainDays 7 is accepted", () => {
|
|
11
|
+
assert.equal(parseRetainDays(7), 7);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("negative retainDays throws", () => {
|
|
15
|
+
assert.throws(() => parseRetainDays(-1), /non-negative integer/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("float retainDays throws", () => {
|
|
19
|
+
assert.throws(() => parseRetainDays(1.5), /non-negative integer/);
|
|
20
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse activity retain-days (issue #2053 leftover).
|
|
3
|
+
*
|
|
4
|
+
* `0` keeps forever. Finite integer ≥ 0 is returned.
|
|
5
|
+
* Negative and non-integer values throw.
|
|
6
|
+
*/
|
|
7
|
+
export function parseRetainDays(value: number): number {
|
|
8
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
|
|
9
|
+
throw new RangeError(
|
|
10
|
+
`activity.retainDays must be a non-negative integer; got ${JSON.stringify(value)}`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { parseClaimObservationId } from "./analysis-claim.js";
|
|
5
|
+
|
|
6
|
+
test("ok claim returns trimmed observationId", () => {
|
|
7
|
+
assert.deepEqual(parseClaimObservationId({ observationId: "obs-1" }), {
|
|
8
|
+
ok: true,
|
|
9
|
+
observationId: "obs-1",
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("missing observationId is missing_observation", () => {
|
|
14
|
+
assert.deepEqual(parseClaimObservationId({}), { ok: false, error: "missing_observation" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("empty observationId is missing_observation", () => {
|
|
18
|
+
assert.deepEqual(parseClaimObservationId({ observationId: "" }), {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: "missing_observation",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("trims observationId and treats whitespace as missing", () => {
|
|
25
|
+
assert.deepEqual(parseClaimObservationId({ observationId: " obs-1 " }), {
|
|
26
|
+
ok: true,
|
|
27
|
+
observationId: "obs-1",
|
|
28
|
+
});
|
|
29
|
+
assert.deepEqual(parseClaimObservationId({ observationId: " " }), {
|
|
30
|
+
ok: false,
|
|
31
|
+
error: "missing_observation",
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a timeline analysis claim's observationId (issue #2050).
|
|
3
|
+
*/
|
|
4
|
+
export type ParseClaimObservationIdResult =
|
|
5
|
+
| { ok: true; observationId: string }
|
|
6
|
+
| { ok: false; error: "missing_observation" };
|
|
7
|
+
|
|
8
|
+
export function parseClaimObservationId(claim: {
|
|
9
|
+
observationId?: string | null;
|
|
10
|
+
}): ParseClaimObservationIdResult {
|
|
11
|
+
const raw = claim.observationId;
|
|
12
|
+
if (raw == null) return { ok: false, error: "missing_observation" };
|
|
13
|
+
const observationId = raw.trim();
|
|
14
|
+
if (observationId.length === 0) return { ok: false, error: "missing_observation" };
|
|
15
|
+
return { ok: true, observationId };
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { dedupeObservationIds } from "./analysis-dup.js";
|
|
5
|
+
|
|
6
|
+
test("empty input returns an empty array", () => {
|
|
7
|
+
assert.deepEqual(dedupeObservationIds([]), []);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("keeps first-seen order and drops later duplicates", () => {
|
|
11
|
+
assert.deepEqual(dedupeObservationIds(["obs-b", "obs-a", "obs-b", "obs-c", "obs-a"]), [
|
|
12
|
+
"obs-b",
|
|
13
|
+
"obs-a",
|
|
14
|
+
"obs-c",
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("does not mutate the input array", () => {
|
|
19
|
+
const input = ["obs-b", "obs-a", "obs-b"];
|
|
20
|
+
const snapshot = input.slice();
|
|
21
|
+
const deduped = dedupeObservationIds(input);
|
|
22
|
+
assert.deepEqual(input, snapshot);
|
|
23
|
+
assert.notEqual(deduped, input);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("drops empty strings", () => {
|
|
27
|
+
assert.deepEqual(dedupeObservationIds(["b", "", "a", "", "b"]), ["b", "a"]);
|
|
28
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-seen observation-id dedupe for timeline analysis (issue #2050 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Insertion order. Empty input → []. Does not mutate input. Drops empty strings.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Return a new first-seen id list. Empty strings are dropped. */
|
|
8
|
+
export function dedupeObservationIds(ids: readonly string[]): string[] {
|
|
9
|
+
return [...new Set(ids.filter((id) => id !== ""))];
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { isEmptyObservationSet } from "./analysis-empty.js";
|
|
5
|
+
|
|
6
|
+
test("null, undefined, and empty array are empty", () => {
|
|
7
|
+
assert.equal(isEmptyObservationSet(null), true);
|
|
8
|
+
assert.equal(isEmptyObservationSet(undefined), true);
|
|
9
|
+
assert.equal(isEmptyObservationSet([]), true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("a non-empty array is not empty", () => {
|
|
13
|
+
assert.equal(isEmptyObservationSet([{ id: 1 }]), false);
|
|
14
|
+
assert.equal(isEmptyObservationSet([0]), false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("a non-array throws", () => {
|
|
18
|
+
assert.throws(() => isEmptyObservationSet({}), TypeError);
|
|
19
|
+
assert.throws(() => isEmptyObservationSet("obs"), TypeError);
|
|
20
|
+
assert.throws(() => isEmptyObservationSet(1), TypeError);
|
|
21
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty-set guard for timeline analysis observations (issue #2050 leftover).
|
|
3
|
+
*
|
|
4
|
+
* null, undefined, and [] are empty. A non-array throws. Any other array is not empty.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** True when the observation set is absent or has no items. */
|
|
8
|
+
export function isEmptyObservationSet(items: unknown): boolean {
|
|
9
|
+
if (items == null) return true;
|
|
10
|
+
if (!Array.isArray(items)) {
|
|
11
|
+
throw new TypeError("items must be an array");
|
|
12
|
+
}
|
|
13
|
+
return items.length === 0;
|
|
14
|
+
}
|