@remnic/core 9.66.1 → 9.66.3
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 +30 -30
- package/dist/access-authorization-probe.js +31 -31
- package/dist/access-boundary.js +30 -30
- package/dist/access-cli.js +96 -94
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +30 -30
- package/dist/access-http-query.js +32 -32
- package/dist/access-http.js +45 -45
- package/dist/access-identity-continuity-surface.js +30 -30
- package/dist/access-lcm-surface.js +30 -30
- package/dist/access-mcp.js +38 -38
- package/dist/access-namespace-preflight.js +30 -30
- package/dist/access-observe-write-surface.js +30 -30
- package/dist/access-operations-batch.js +33 -33
- package/dist/access-operations.js +37 -37
- package/dist/access-recall-concurrency.js +30 -30
- package/dist/access-recall-response.js +30 -30
- package/dist/access-recall-surface.js +30 -30
- package/dist/access-schema.js +3 -3
- package/dist/access-service.js +30 -30
- package/dist/access-surface-catalog.js +32 -32
- package/dist/active-recall.js +3 -3
- package/dist/briefing.js +13 -13
- package/dist/buffer.js +4 -4
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +16 -16
- 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-XKNHFDTT.js → chunk-3A2Z4OOS.js} +2 -2
- package/dist/{chunk-CHGG74CY.js → chunk-3HHTN5X7.js} +2 -2
- package/dist/{chunk-TCF4MU7L.js → chunk-3PO35HUU.js} +1218 -104
- package/dist/chunk-3PO35HUU.js.map +1 -0
- package/dist/{chunk-XOHYGIWF.js → chunk-3Z4SFJA5.js} +2 -2
- package/dist/{chunk-RDGKVH6T.js → chunk-4NKIGSJM.js} +2 -2
- package/dist/{chunk-GPFZFCRN.js → chunk-4PTTMTTQ.js} +1 -1
- package/dist/{chunk-VG4SSPAW.js → chunk-4XESAT4V.js} +2 -2
- package/dist/{chunk-S4CDUXQY.js → chunk-5KGLNBUB.js} +2 -2
- package/dist/{chunk-2X6BGKQL.js → chunk-5XRZANCW.js} +10 -10
- package/dist/{chunk-TCQRIIIN.js → chunk-6DODJYBY.js} +2 -2
- package/dist/{chunk-KWIGDKM2.js → chunk-73CAEYI3.js} +2 -2
- package/dist/{chunk-J2R275FN.js → chunk-7DXW5V2W.js} +2 -2
- package/dist/{chunk-LYUUA6QB.js → chunk-7JJHIVMG.js} +2 -2
- package/dist/{chunk-2FA7U5XY.js → chunk-7NNMRMLZ.js} +2 -2
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-EWKVYCFP.js → chunk-B2OKCCXX.js} +1 -1
- package/dist/{chunk-NSTNGMSS.js → chunk-B7AGLMGP.js} +7 -7
- package/dist/chunk-BLUYQFRH.js +36 -0
- package/dist/chunk-BLUYQFRH.js.map +1 -0
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-CEH2AFXG.js → chunk-C24UZSFW.js} +6 -6
- package/dist/{chunk-LTBKKSCX.js → chunk-C2JCVZWX.js} +2 -2
- package/dist/{chunk-65JOGLIS.js → chunk-D4R3OUZA.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/chunk-E4X2YX2F.js +22 -0
- package/dist/chunk-E4X2YX2F.js.map +1 -0
- package/dist/{chunk-IGZCAHFL.js → chunk-EGGNFLAT.js} +2 -2
- package/dist/{chunk-H2IRSXZN.js → chunk-ESDOAP7W.js} +3 -3
- package/dist/{chunk-D3UXGE5D.js → chunk-EZWIEFYD.js} +8 -8
- package/dist/{chunk-ZPS724LT.js → chunk-F5H5QINZ.js} +2 -2
- package/dist/{chunk-S4Z2OBTX.js → chunk-FPSOXMLK.js} +3 -3
- package/dist/chunk-FYDW33ZB.js +13 -0
- package/dist/chunk-FYDW33ZB.js.map +1 -0
- package/dist/{chunk-CX4OLNIH.js → chunk-G5UKF2GJ.js} +2 -2
- package/dist/{chunk-OJUEUQDJ.js → chunk-GGY2747C.js} +2 -2
- package/dist/{chunk-TNGH4ZYI.js → chunk-GNWLTCKC.js} +2 -2
- package/dist/{chunk-YWPLOJ7T.js → chunk-GZPGI45F.js} +2 -2
- package/dist/{chunk-NFP4KPTA.js → chunk-IDEPEHUG.js} +2 -2
- package/dist/{chunk-LJABGXC4.js → chunk-IN5EUWCA.js} +3 -3
- package/dist/{chunk-U6NGKS32.js → chunk-JJAOVJ3E.js} +9 -9
- package/dist/{chunk-RPIS5FQ7.js → chunk-JWCS3VYQ.js} +50 -50
- package/dist/{chunk-U65VJSCJ.js → chunk-KE23ZIEW.js} +13 -13
- package/dist/{chunk-NACLNNFQ.js → chunk-KSZKQTZS.js} +2 -2
- package/dist/{chunk-KJXCD7VR.js → chunk-KWJNEWTM.js} +5 -5
- package/dist/chunk-MAG6M3B6.js +51 -0
- package/dist/chunk-MAG6M3B6.js.map +1 -0
- package/dist/chunk-MKD3OMPS.js +20 -0
- package/dist/chunk-MKD3OMPS.js.map +1 -0
- package/dist/{chunk-UW4RYPWC.js → chunk-MLTGALDY.js} +2 -2
- package/dist/{chunk-MUEGIKKY.js → chunk-OKGQDI4Y.js} +17 -16
- package/dist/chunk-OKGQDI4Y.js.map +1 -0
- package/dist/{chunk-YMOPYG3K.js → chunk-OLT3YTDH.js} +4 -4
- package/dist/{chunk-2PSX5BJU.js → chunk-OPQKVVYG.js} +2 -2
- package/dist/{chunk-D6Q5EPOV.js → chunk-PGD3DXZP.js} +2 -2
- package/dist/chunk-Q3SHEAXR.js +80 -0
- package/dist/chunk-Q3SHEAXR.js.map +1 -0
- package/dist/{chunk-GBEOUCBB.js → chunk-Q6LC3ZD5.js} +2 -2
- package/dist/{chunk-QBNE55VA.js → chunk-QELYSDAU.js} +2 -2
- package/dist/{chunk-INUBU6I5.js → chunk-S6PRJGQL.js} +2 -2
- package/dist/{chunk-B4AOWRYF.js → chunk-TCJO7LOG.js} +5 -5
- package/dist/{chunk-SZMYGDSM.js → chunk-TO5U47YK.js} +2 -2
- package/dist/{chunk-GAJK3DQ5.js → chunk-U3L6GRUX.js} +2 -2
- package/dist/{chunk-4XLTUR6R.js → chunk-U4RGSLI4.js} +3 -3
- package/dist/{chunk-M5ERTI4I.js → chunk-UH53XVQ3.js} +2 -2
- package/dist/{chunk-NUF7VSEX.js → chunk-UXERIBOJ.js} +2 -2
- package/dist/{chunk-FFUMTH27.js → chunk-VA2CSIPC.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-AF5H7TKI.js → chunk-VPJNGE2R.js} +2 -2
- package/dist/{chunk-OMX6TKXH.js → chunk-VTJZSLDR.js} +2 -2
- package/dist/{chunk-TBCDF556.js → chunk-WHFFAS4A.js} +4 -4
- package/dist/{chunk-7UOXCCCI.js → chunk-WVNETF4Q.js} +2 -2
- package/dist/{chunk-EH65UV2Z.js → chunk-XSTMPZMY.js} +2 -2
- package/dist/{chunk-PD66CVSD.js → chunk-Z3SQEEB6.js} +2 -2
- package/dist/{chunk-JKTV3JNX.js → chunk-ZBYOXIIF.js} +3 -3
- package/dist/{chunk-2ITZ74A6.js → chunk-ZC5IHL5X.js} +11 -11
- package/dist/{chunk-KI64TTBL.js → chunk-ZDMTCO7N.js} +2 -2
- package/dist/{chunk-C5IUOBCN.js → chunk-ZET6A3OF.js} +2 -2
- package/dist/{chunk-ZZZWEDF4.js → chunk-ZVAKRZZD.js} +2 -2
- package/dist/cli.js +67 -67
- package/dist/coding/export-okf-codegraph.js +13 -13
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +13 -13
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +13 -13
- package/dist/connectors/index.js +13 -13
- package/dist/convergence-refresh.js +15 -15
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +15 -15
- package/dist/entity-retrieval.js +13 -13
- package/dist/external-wiki-access.js +31 -31
- 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-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-span-wire.d.ts +17 -0
- package/dist/extraction-span-wire.js +13 -0
- package/dist/extraction-span-wire.js.map +1 -0
- package/dist/extraction-span.d.ts +17 -0
- package/dist/extraction-span.js +8 -0
- package/dist/extraction.js +16 -16
- package/dist/{graph-edge-decay-A4SDFMMS.js → graph-edge-decay-DGTBFALQ.js} +4 -4
- package/dist/graph-edge-decay-DGTBFALQ.js.map +1 -0
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +387 -1
- package/dist/index.js +203 -122
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +9 -9
- package/dist/location/index.js +15 -15
- package/dist/maintenance/memory-governance.js +13 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +13 -13
- package/dist/maintenance/rebuild-memory-projection.js +14 -14
- package/dist/namespaces/migrate.js +14 -14
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +13 -13
- package/dist/operator-doctor-corpus.js +16 -16
- package/dist/operator-toolkit.js +26 -26
- package/dist/orchestration/maintenance.js +21 -21
- package/dist/orchestrator.js +98 -96
- package/dist/qmd.js +4 -4
- package/dist/recall-deep-access.d.ts +30 -0
- package/dist/recall-deep-access.js +50 -0
- package/dist/recall-deep-access.js.map +1 -0
- 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-render.d.ts +11 -0
- package/dist/recall-deep-render.js +28 -0
- package/dist/recall-deep-render.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-deep.js +6 -73
- package/dist/recall-deep.js.map +1 -1
- 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-navigate.js +6 -42
- package/dist/recall-navigate.js.map +1 -1
- 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.d.ts +18 -0
- package/dist/recall-state-view-render.js +11 -0
- package/dist/recall-state-view-render.js.map +1 -0
- 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/recall-state-view-widen.d.ts +13 -0
- package/dist/recall-state-view-widen.js +11 -0
- package/dist/recall-state-view-widen.js.map +1 -0
- package/dist/recall-state-view-wire.d.ts +3 -4
- package/dist/recall-state-view-wire.js +2 -1
- package/dist/resume-bundles.js +4 -4
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +14 -14
- package/dist/semantic-consolidation.js +14 -14
- package/dist/semantic-rule-promotion.js +13 -13
- package/dist/semantic-rule-verifier.js +13 -13
- package/dist/source-agent-qualifier.js +15 -15
- package/dist/storage.js +12 -12
- package/dist/support-passport/index.js +33 -33
- 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 +14 -14
- package/dist/verified-recall.js +13 -13
- package/dist/who-knows-cli.js +14 -14
- package/dist/who-knows.js +13 -13
- 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 +9 -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/journal-vault-read.test.ts +153 -0
- package/src/activity/journal-vault-read.ts +214 -0
- package/src/activity/journal.ts +1 -1
- 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-batch.test.ts +56 -0
- package/src/activity/timeline/analysis-batch.ts +45 -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-prompt.test.ts +50 -0
- package/src/activity/timeline/analysis-prompt.ts +24 -0
- package/src/activity/timeline/analysis.test.ts +413 -0
- package/src/activity/timeline/analysis.ts +362 -0
- package/src/activity/timeline/index.ts +13 -0
- package/src/activity/timeline/journal-recap-persist.test.ts +148 -0
- package/src/activity/timeline/journal-recap-persist.ts +37 -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-sunday.test.ts +17 -0
- package/src/activity/timeline/week-sunday.ts +12 -0
- package/src/activity/timeline/weekly-persist.test.ts +121 -0
- package/src/activity/timeline/weekly-persist.ts +103 -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-path.test.ts +38 -0
- package/src/activity/vault-path.ts +59 -0
- package/src/activity/vault-publish.test.ts +102 -0
- package/src/activity/vault-publish.ts +92 -13
- 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-on-write-wire.test.ts +101 -0
- package/src/dedup/merge-on-write-wire.ts +34 -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-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/extraction-span-wire.test.ts +20 -0
- package/src/extraction-span-wire.ts +17 -0
- package/src/extraction-span.test.ts +59 -0
- package/src/extraction-span.ts +39 -0
- package/src/index.ts +1 -0
- package/src/recall-deep-access.test.ts +58 -0
- package/src/recall-deep-access.ts +71 -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-render.test.ts +68 -0
- package/src/recall-deep-render.ts +32 -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-render.test.ts +94 -0
- package/src/recall-state-view-render.ts +24 -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/recall-state-view-widen.test.ts +66 -0
- package/src/recall-state-view-widen.ts +49 -0
- package/src/recall-state-view-wire.ts +6 -16
- 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/src/shared-context/staleness.test.ts +36 -0
- package/src/shared-context/staleness.ts +38 -0
- package/src/shared-context/trajectories.test.ts +77 -0
- package/src/shared-context/trajectories.ts +49 -0
- package/dist/chunk-MUEGIKKY.js.map +0 -1
- package/dist/chunk-TCF4MU7L.js.map +0 -1
- package/dist/chunk-WFCWP4XU.js +0 -17
- package/dist/chunk-WFCWP4XU.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-XKNHFDTT.js.map → chunk-3A2Z4OOS.js.map} +0 -0
- /package/dist/{chunk-CHGG74CY.js.map → chunk-3HHTN5X7.js.map} +0 -0
- /package/dist/{chunk-XOHYGIWF.js.map → chunk-3Z4SFJA5.js.map} +0 -0
- /package/dist/{chunk-RDGKVH6T.js.map → chunk-4NKIGSJM.js.map} +0 -0
- /package/dist/{chunk-GPFZFCRN.js.map → chunk-4PTTMTTQ.js.map} +0 -0
- /package/dist/{chunk-VG4SSPAW.js.map → chunk-4XESAT4V.js.map} +0 -0
- /package/dist/{chunk-S4CDUXQY.js.map → chunk-5KGLNBUB.js.map} +0 -0
- /package/dist/{chunk-2X6BGKQL.js.map → chunk-5XRZANCW.js.map} +0 -0
- /package/dist/{chunk-TCQRIIIN.js.map → chunk-6DODJYBY.js.map} +0 -0
- /package/dist/{chunk-KWIGDKM2.js.map → chunk-73CAEYI3.js.map} +0 -0
- /package/dist/{chunk-J2R275FN.js.map → chunk-7DXW5V2W.js.map} +0 -0
- /package/dist/{chunk-LYUUA6QB.js.map → chunk-7JJHIVMG.js.map} +0 -0
- /package/dist/{chunk-2FA7U5XY.js.map → chunk-7NNMRMLZ.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-EWKVYCFP.js.map → chunk-B2OKCCXX.js.map} +0 -0
- /package/dist/{chunk-NSTNGMSS.js.map → chunk-B7AGLMGP.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-CEH2AFXG.js.map → chunk-C24UZSFW.js.map} +0 -0
- /package/dist/{chunk-LTBKKSCX.js.map → chunk-C2JCVZWX.js.map} +0 -0
- /package/dist/{chunk-65JOGLIS.js.map → chunk-D4R3OUZA.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-IGZCAHFL.js.map → chunk-EGGNFLAT.js.map} +0 -0
- /package/dist/{chunk-H2IRSXZN.js.map → chunk-ESDOAP7W.js.map} +0 -0
- /package/dist/{chunk-D3UXGE5D.js.map → chunk-EZWIEFYD.js.map} +0 -0
- /package/dist/{chunk-ZPS724LT.js.map → chunk-F5H5QINZ.js.map} +0 -0
- /package/dist/{chunk-S4Z2OBTX.js.map → chunk-FPSOXMLK.js.map} +0 -0
- /package/dist/{chunk-CX4OLNIH.js.map → chunk-G5UKF2GJ.js.map} +0 -0
- /package/dist/{chunk-OJUEUQDJ.js.map → chunk-GGY2747C.js.map} +0 -0
- /package/dist/{chunk-TNGH4ZYI.js.map → chunk-GNWLTCKC.js.map} +0 -0
- /package/dist/{chunk-YWPLOJ7T.js.map → chunk-GZPGI45F.js.map} +0 -0
- /package/dist/{chunk-NFP4KPTA.js.map → chunk-IDEPEHUG.js.map} +0 -0
- /package/dist/{chunk-LJABGXC4.js.map → chunk-IN5EUWCA.js.map} +0 -0
- /package/dist/{chunk-U6NGKS32.js.map → chunk-JJAOVJ3E.js.map} +0 -0
- /package/dist/{chunk-RPIS5FQ7.js.map → chunk-JWCS3VYQ.js.map} +0 -0
- /package/dist/{chunk-U65VJSCJ.js.map → chunk-KE23ZIEW.js.map} +0 -0
- /package/dist/{chunk-NACLNNFQ.js.map → chunk-KSZKQTZS.js.map} +0 -0
- /package/dist/{chunk-KJXCD7VR.js.map → chunk-KWJNEWTM.js.map} +0 -0
- /package/dist/{chunk-UW4RYPWC.js.map → chunk-MLTGALDY.js.map} +0 -0
- /package/dist/{chunk-YMOPYG3K.js.map → chunk-OLT3YTDH.js.map} +0 -0
- /package/dist/{chunk-2PSX5BJU.js.map → chunk-OPQKVVYG.js.map} +0 -0
- /package/dist/{chunk-D6Q5EPOV.js.map → chunk-PGD3DXZP.js.map} +0 -0
- /package/dist/{chunk-GBEOUCBB.js.map → chunk-Q6LC3ZD5.js.map} +0 -0
- /package/dist/{chunk-QBNE55VA.js.map → chunk-QELYSDAU.js.map} +0 -0
- /package/dist/{chunk-INUBU6I5.js.map → chunk-S6PRJGQL.js.map} +0 -0
- /package/dist/{chunk-B4AOWRYF.js.map → chunk-TCJO7LOG.js.map} +0 -0
- /package/dist/{chunk-SZMYGDSM.js.map → chunk-TO5U47YK.js.map} +0 -0
- /package/dist/{chunk-GAJK3DQ5.js.map → chunk-U3L6GRUX.js.map} +0 -0
- /package/dist/{chunk-4XLTUR6R.js.map → chunk-U4RGSLI4.js.map} +0 -0
- /package/dist/{chunk-M5ERTI4I.js.map → chunk-UH53XVQ3.js.map} +0 -0
- /package/dist/{chunk-NUF7VSEX.js.map → chunk-UXERIBOJ.js.map} +0 -0
- /package/dist/{chunk-FFUMTH27.js.map → chunk-VA2CSIPC.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-AF5H7TKI.js.map → chunk-VPJNGE2R.js.map} +0 -0
- /package/dist/{chunk-OMX6TKXH.js.map → chunk-VTJZSLDR.js.map} +0 -0
- /package/dist/{chunk-TBCDF556.js.map → chunk-WHFFAS4A.js.map} +0 -0
- /package/dist/{chunk-7UOXCCCI.js.map → chunk-WVNETF4Q.js.map} +0 -0
- /package/dist/{chunk-EH65UV2Z.js.map → chunk-XSTMPZMY.js.map} +0 -0
- /package/dist/{chunk-PD66CVSD.js.map → chunk-Z3SQEEB6.js.map} +0 -0
- /package/dist/{chunk-JKTV3JNX.js.map → chunk-ZBYOXIIF.js.map} +0 -0
- /package/dist/{chunk-2ITZ74A6.js.map → chunk-ZC5IHL5X.js.map} +0 -0
- /package/dist/{chunk-KI64TTBL.js.map → chunk-ZDMTCO7N.js.map} +0 -0
- /package/dist/{chunk-C5IUOBCN.js.map → chunk-ZET6A3OF.js.map} +0 -0
- /package/dist/{chunk-ZZZWEDF4.js.map → chunk-ZVAKRZZD.js.map} +0 -0
- /package/dist/{graph-edge-decay-A4SDFMMS.js.map → extraction-span.js.map} +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trim a managed-region name (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Empty after trim throws. Newline throws. Else the trimmed string.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function trimRegionName(value: string): string {
|
|
8
|
+
const name = value.trim();
|
|
9
|
+
if (name.length === 0) {
|
|
10
|
+
throw new RangeError("Region name must be non-empty.");
|
|
11
|
+
}
|
|
12
|
+
if (name.includes("\n") || name.includes("\r")) {
|
|
13
|
+
throw new RangeError("Region name must not contain a newline.");
|
|
14
|
+
}
|
|
15
|
+
return name;
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { resolveVaultNotePath } from "./vault-path.js";
|
|
5
|
+
|
|
6
|
+
test("resolveVaultNotePath expands date tokens", () => {
|
|
7
|
+
assert.equal(resolveVaultNotePath("{yyyy}-{MM}-{dd}.md", "2026-08-18"), "2026-08-18.md");
|
|
8
|
+
assert.equal(
|
|
9
|
+
resolveVaultNotePath("Daily Notes/{yyyy}/{MM}/{yyyy}-{MM}-{dd}.md", "2026-08-18"),
|
|
10
|
+
"Daily Notes/2026/08/2026-08-18.md",
|
|
11
|
+
);
|
|
12
|
+
assert.equal(resolveVaultNotePath("{yy}/{M}/{d}.md", "2026-08-08"), "26/8/8.md");
|
|
13
|
+
assert.equal(
|
|
14
|
+
resolveVaultNotePath("{yyyy}-{MM}-{dd}.md", "2026-08-18", { timezone: "America/Chicago" }),
|
|
15
|
+
"2026-08-18.md",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("resolveVaultNotePath expands ISO week {ww} as zero-padded", () => {
|
|
20
|
+
assert.equal(resolveVaultNotePath("W{ww}.md", "2021-01-01"), "W53.md");
|
|
21
|
+
assert.equal(resolveVaultNotePath("W{ww}.md", "2024-01-01"), "W01.md");
|
|
22
|
+
assert.equal(
|
|
23
|
+
resolveVaultNotePath("{yyyy}/W{ww}/{yyyy}-{MM}-{dd}.md", "2021-01-01"),
|
|
24
|
+
"2021/W53/2021-01-01.md",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("resolveVaultNotePath rejects traversal and absolute templates", () => {
|
|
29
|
+
assert.throws(() => resolveVaultNotePath("../outside/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
|
|
30
|
+
assert.throws(() => resolveVaultNotePath("{yyyy}/../../secret.md", "2026-08-18"), /\.\.|absolute|traversal/i);
|
|
31
|
+
assert.throws(() => resolveVaultNotePath("/abs/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
|
|
32
|
+
assert.throws(() => resolveVaultNotePath("C:/vault/{yyyy}.md", "2026-08-18"), /\.\.|absolute|traversal/i);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("resolveVaultNotePath requires a real YYYY-MM-DD date", () => {
|
|
36
|
+
assert.throws(() => resolveVaultNotePath("{yyyy}.md", "2026-02-30"), /YYYY-MM-DD/);
|
|
37
|
+
assert.throws(() => resolveVaultNotePath("{yyyy}.md", "20260818"), /YYYY-MM-DD/);
|
|
38
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault note path templates (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Expands date tokens on a YYYY-MM-DD day. Returns a vault-relative path.
|
|
5
|
+
* Rejects `..` segments and absolute templates.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
import { assertValidTimezone, isValidActivityDate } from "./digest.js";
|
|
10
|
+
|
|
11
|
+
const TOKEN_RE = /\{yyyy\}|\{yy\}|\{MM\}|\{dd\}|\{M\}|\{d\}|\{ww\}/g;
|
|
12
|
+
|
|
13
|
+
export function resolveVaultNotePath(
|
|
14
|
+
template: string,
|
|
15
|
+
date: string,
|
|
16
|
+
options?: { timezone?: string },
|
|
17
|
+
): string {
|
|
18
|
+
if (!isValidActivityDate(date)) {
|
|
19
|
+
throw new RangeError(`Invalid vault date "${date}"; expected YYYY-MM-DD.`);
|
|
20
|
+
}
|
|
21
|
+
if (options?.timezone !== undefined) {
|
|
22
|
+
assertValidTimezone(options.timezone);
|
|
23
|
+
}
|
|
24
|
+
if (typeof template !== "string" || template.length === 0) {
|
|
25
|
+
throw new RangeError("Vault note path template must be a non-empty relative path.");
|
|
26
|
+
}
|
|
27
|
+
if (path.posix.isAbsolute(template) || path.win32.isAbsolute(template)) {
|
|
28
|
+
throw new RangeError("Vault note path template must be relative; absolute templates are rejected.");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [yyyy, mm, dd] = date.split("-");
|
|
32
|
+
const tokens: Record<string, string> = {
|
|
33
|
+
"{yyyy}": yyyy,
|
|
34
|
+
"{yy}": yyyy.slice(2),
|
|
35
|
+
"{MM}": mm,
|
|
36
|
+
"{dd}": dd,
|
|
37
|
+
"{M}": String(Number(mm)),
|
|
38
|
+
"{d}": String(Number(dd)),
|
|
39
|
+
"{ww}": isoWeekNumber(date),
|
|
40
|
+
};
|
|
41
|
+
const expanded = template.replace(TOKEN_RE, (token) => tokens[token] ?? token);
|
|
42
|
+
if (expanded.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
43
|
+
throw new RangeError("Vault note path template must not contain `..` segments.");
|
|
44
|
+
}
|
|
45
|
+
if (path.posix.isAbsolute(expanded) || path.win32.isAbsolute(expanded)) {
|
|
46
|
+
throw new RangeError("Vault note path template must be relative; absolute templates are rejected.");
|
|
47
|
+
}
|
|
48
|
+
return expanded;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isoWeekNumber(date: string): string {
|
|
52
|
+
const [year, month, day] = date.split("-").map(Number);
|
|
53
|
+
const utc = new Date(Date.UTC(year, month - 1, day));
|
|
54
|
+
const dayNum = utc.getUTCDay() || 7;
|
|
55
|
+
utc.setUTCDate(utc.getUTCDate() + 4 - dayNum);
|
|
56
|
+
const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
|
|
57
|
+
const weekNo = Math.ceil(((utc.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
58
|
+
return String(weekNo).padStart(2, "0");
|
|
59
|
+
}
|
|
@@ -164,3 +164,105 @@ test("publishVaultRegion never creates a missing file", () => {
|
|
|
164
164
|
assert.equal(existsSync(path.join(vaultPath, relativeFile)), false);
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
|
+
|
|
168
|
+
test("applyManagedRegion heading replaces only the named section", () => {
|
|
169
|
+
const fileText = [
|
|
170
|
+
"---",
|
|
171
|
+
"title: daily",
|
|
172
|
+
"---",
|
|
173
|
+
"",
|
|
174
|
+
"scratchpad",
|
|
175
|
+
"## Timeline",
|
|
176
|
+
"old cards",
|
|
177
|
+
"## Notes",
|
|
178
|
+
"keep me",
|
|
179
|
+
"",
|
|
180
|
+
].join("\n");
|
|
181
|
+
const result = applyManagedRegion(fileText, {
|
|
182
|
+
strategy: "heading",
|
|
183
|
+
name: "Timeline",
|
|
184
|
+
content: "new cards",
|
|
185
|
+
});
|
|
186
|
+
assert.equal(result.ok, true);
|
|
187
|
+
if (!result.ok) return;
|
|
188
|
+
assert.equal(
|
|
189
|
+
result.text,
|
|
190
|
+
[
|
|
191
|
+
"---",
|
|
192
|
+
"title: daily",
|
|
193
|
+
"---",
|
|
194
|
+
"",
|
|
195
|
+
"scratchpad",
|
|
196
|
+
"## Timeline",
|
|
197
|
+
"new cards",
|
|
198
|
+
"## Notes",
|
|
199
|
+
"keep me",
|
|
200
|
+
"",
|
|
201
|
+
].join("\n"),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const crlf = fileText.replace(/\n/g, "\r\n");
|
|
205
|
+
const crlfResult = applyManagedRegion(crlf, {
|
|
206
|
+
strategy: "heading",
|
|
207
|
+
name: "Timeline",
|
|
208
|
+
content: "new cards\nline two",
|
|
209
|
+
});
|
|
210
|
+
assert.equal(crlfResult.ok, true);
|
|
211
|
+
if (!crlfResult.ok) return;
|
|
212
|
+
assert.equal(
|
|
213
|
+
crlfResult.text,
|
|
214
|
+
[
|
|
215
|
+
"---",
|
|
216
|
+
"title: daily",
|
|
217
|
+
"---",
|
|
218
|
+
"",
|
|
219
|
+
"scratchpad",
|
|
220
|
+
"## Timeline",
|
|
221
|
+
"new cards",
|
|
222
|
+
"line two",
|
|
223
|
+
"## Notes",
|
|
224
|
+
"keep me",
|
|
225
|
+
"",
|
|
226
|
+
].join("\r\n"),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
const prefix = "---\nkeep: me\n---\n\nscratchpad\n## Timeline";
|
|
230
|
+
const suffix = "\n## Notes\nkeep me\n";
|
|
231
|
+
const outside = `${prefix}\nold\n${suffix}`;
|
|
232
|
+
const headingAt = outside.indexOf("## Timeline");
|
|
233
|
+
const notesAt = outside.indexOf("\n## Notes");
|
|
234
|
+
const headingLineEnd = headingAt + "## Timeline".length;
|
|
235
|
+
const before = Buffer.from(outside.slice(0, headingLineEnd), "utf8");
|
|
236
|
+
const after = Buffer.from(outside.slice(notesAt), "utf8");
|
|
237
|
+
const byteResult = applyManagedRegion(outside, {
|
|
238
|
+
strategy: "heading",
|
|
239
|
+
name: "Timeline",
|
|
240
|
+
content: "replacement",
|
|
241
|
+
});
|
|
242
|
+
assert.equal(byteResult.ok, true);
|
|
243
|
+
if (!byteResult.ok) return;
|
|
244
|
+
const nextHeading = byteResult.text.indexOf("## Timeline");
|
|
245
|
+
const nextNotes = byteResult.text.indexOf("\n## Notes");
|
|
246
|
+
assert.deepEqual(Buffer.from(byteResult.text.slice(0, nextHeading + "## Timeline".length), "utf8"), before);
|
|
247
|
+
assert.deepEqual(Buffer.from(byteResult.text.slice(nextNotes), "utf8"), after);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("applyManagedRegion heading is a no-op when the heading is missing", () => {
|
|
251
|
+
const fileText = "# Daily\n\nno timeline here\n";
|
|
252
|
+
const result = applyManagedRegion(fileText, {
|
|
253
|
+
strategy: "heading",
|
|
254
|
+
name: "Timeline",
|
|
255
|
+
content: "new cards",
|
|
256
|
+
});
|
|
257
|
+
assert.deepEqual(result, { ok: false, reason: "no_heading", text: fileText });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("applyManagedRegion heading refuses duplicate headings and lists line numbers", () => {
|
|
261
|
+
const fileText = ["# Daily", "", "## Timeline", "first", "", "## Timeline", "second", ""].join("\n");
|
|
262
|
+
const result = applyManagedRegion(fileText, {
|
|
263
|
+
strategy: "heading",
|
|
264
|
+
name: "Timeline",
|
|
265
|
+
content: "new cards",
|
|
266
|
+
});
|
|
267
|
+
assert.deepEqual(result, { ok: false, reason: "duplicate_heading", lines: [3, 6], text: fileText });
|
|
268
|
+
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Managed-region vault publisher (issue #1985
|
|
2
|
+
* Managed-region vault publisher (issue #1985).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `<!-- remnic:<name>:end -->`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Markers replace bytes between `<!-- remnic:<name>:start -->` and
|
|
5
|
+
* `<!-- remnic:<name>:end -->`. Heading replaces the body after a unique
|
|
6
|
+
* ATX heading until the next same-or-higher heading. The heading line stays.
|
|
7
|
+
* This module does not read config.
|
|
8
8
|
*/
|
|
9
9
|
import { createHash, randomBytes } from "node:crypto";
|
|
10
10
|
import { readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -14,7 +14,8 @@ import { expandTildePath } from "../utils/path.js";
|
|
|
14
14
|
|
|
15
15
|
export type ApplyManagedRegionResult =
|
|
16
16
|
| { ok: true; text: string }
|
|
17
|
-
| { ok: false; reason: "no_marker"; text: string }
|
|
17
|
+
| { ok: false; reason: "no_marker" | "no_heading"; text: string }
|
|
18
|
+
| { ok: false; reason: "duplicate_heading"; lines: readonly number[]; text: string };
|
|
18
19
|
|
|
19
20
|
export type PublishVaultRegionResult =
|
|
20
21
|
| { ok: true; status: "updated" | "unchanged" }
|
|
@@ -22,8 +23,10 @@ export type PublishVaultRegionResult =
|
|
|
22
23
|
|
|
23
24
|
export function applyManagedRegion(
|
|
24
25
|
fileText: string,
|
|
25
|
-
opts: { strategy: "markers"; name: string; content: string },
|
|
26
|
+
opts: { strategy: "markers" | "heading"; name: string; content: string },
|
|
26
27
|
): ApplyManagedRegionResult {
|
|
28
|
+
if (opts.strategy === "heading") return applyHeadingRegion(fileText, opts.name, opts.content);
|
|
29
|
+
|
|
27
30
|
const startMarker = `<!-- remnic:${opts.name}:start -->`;
|
|
28
31
|
const endMarker = `<!-- remnic:${opts.name}:end -->`;
|
|
29
32
|
const startIdx = fileText.indexOf(startMarker);
|
|
@@ -31,17 +34,93 @@ export function applyManagedRegion(
|
|
|
31
34
|
const endIdx = fileText.indexOf(endMarker, startIdx + startMarker.length);
|
|
32
35
|
if (endIdx === -1) return { ok: false, reason: "no_marker", text: fileText };
|
|
33
36
|
|
|
34
|
-
const eol = fileText.
|
|
35
|
-
let body = opts.content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
36
|
-
if (eol === "\r\n") body = body.replace(/\n/g, "\r\n");
|
|
37
|
-
if (body.endsWith(eol)) body = body.slice(0, -eol.length);
|
|
38
|
-
|
|
37
|
+
const { body, eol } = ownedBody(fileText, opts.content);
|
|
39
38
|
return {
|
|
40
39
|
ok: true,
|
|
41
40
|
text: `${fileText.slice(0, startIdx + startMarker.length)}${eol}${body}${eol}${fileText.slice(endIdx)}`,
|
|
42
41
|
};
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
function applyHeadingRegion(fileText: string, name: string, content: string): ApplyManagedRegionResult {
|
|
45
|
+
const wanted = name.trim();
|
|
46
|
+
const hits: Array<{ line: number; level: number; next: number }> = [];
|
|
47
|
+
for (const row of fileLines(fileText)) {
|
|
48
|
+
const heading = parseAtxHeading(row.line);
|
|
49
|
+
if (heading && heading.text === wanted) {
|
|
50
|
+
hits.push({ line: row.number, level: heading.level, next: row.next });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (hits.length === 0) return { ok: false, reason: "no_heading", text: fileText };
|
|
54
|
+
if (hits.length > 1) {
|
|
55
|
+
return { ok: false, reason: "duplicate_heading", lines: hits.map((hit) => hit.line), text: fileText };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const hit = hits[0]!;
|
|
59
|
+
let sectionEnd = fileText.length;
|
|
60
|
+
for (const row of fileLines(fileText)) {
|
|
61
|
+
if (row.start < hit.next) continue;
|
|
62
|
+
const heading = parseAtxHeading(row.line);
|
|
63
|
+
if (heading && heading.level <= hit.level) {
|
|
64
|
+
sectionEnd = row.start;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { body, eol } = ownedBody(fileText, content);
|
|
70
|
+
const prefix =
|
|
71
|
+
hit.next === fileText.length && !fileText.endsWith("\n") && !fileText.endsWith("\r")
|
|
72
|
+
? `${fileText}${eol}`
|
|
73
|
+
: fileText.slice(0, hit.next);
|
|
74
|
+
return { ok: true, text: `${prefix}${body}${eol}${fileText.slice(sectionEnd)}` };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function ownedBody(fileText: string, content: string): { body: string; eol: string } {
|
|
78
|
+
const eol = fileText.includes("\r\n") ? "\r\n" : "\n";
|
|
79
|
+
let body = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
80
|
+
if (eol === "\r\n") body = body.replace(/\n/g, "\r\n");
|
|
81
|
+
if (body.endsWith(eol)) body = body.slice(0, -eol.length);
|
|
82
|
+
return { body, eol };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fileLines(fileText: string): Array<{ line: string; start: number; next: number; number: number }> {
|
|
86
|
+
const rows: Array<{ line: string; start: number; next: number; number: number }> = [];
|
|
87
|
+
let start = 0;
|
|
88
|
+
let number = 1;
|
|
89
|
+
while (start < fileText.length) {
|
|
90
|
+
let i = start;
|
|
91
|
+
while (i < fileText.length && fileText[i] !== "\n" && fileText[i] !== "\r") i++;
|
|
92
|
+
let next = i;
|
|
93
|
+
if (fileText[i] === "\r" && fileText[i + 1] === "\n") next = i + 2;
|
|
94
|
+
else if (fileText[i] === "\n" || fileText[i] === "\r") next = i + 1;
|
|
95
|
+
else next = fileText.length;
|
|
96
|
+
rows.push({ line: fileText.slice(start, i), start, next, number });
|
|
97
|
+
number += 1;
|
|
98
|
+
start = next;
|
|
99
|
+
}
|
|
100
|
+
return rows;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseAtxHeading(line: string): { level: number; text: string } | null {
|
|
104
|
+
let level = 0;
|
|
105
|
+
while (level < line.length && level < 6 && line[level] === "#") level++;
|
|
106
|
+
if (level === 0) return null;
|
|
107
|
+
const after = line[level];
|
|
108
|
+
if (after !== " " && after !== "\t") return null;
|
|
109
|
+
|
|
110
|
+
let start = level + 1;
|
|
111
|
+
let end = line.length;
|
|
112
|
+
while (start < end && (line[start] === " " || line[start] === "\t")) start++;
|
|
113
|
+
while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
|
|
114
|
+
|
|
115
|
+
let hashEnd = end;
|
|
116
|
+
while (hashEnd > start && line[hashEnd - 1] === "#") hashEnd--;
|
|
117
|
+
if (hashEnd < end && hashEnd > start && (line[hashEnd - 1] === " " || line[hashEnd - 1] === "\t")) {
|
|
118
|
+
end = hashEnd;
|
|
119
|
+
while (end > start && (line[end - 1] === " " || line[end - 1] === "\t")) end--;
|
|
120
|
+
}
|
|
121
|
+
return { level, text: line.slice(start, end) };
|
|
122
|
+
}
|
|
123
|
+
|
|
45
124
|
export function publishVaultRegion(input: {
|
|
46
125
|
vaultPath: string;
|
|
47
126
|
relativeFile: string;
|
|
@@ -79,7 +158,7 @@ export function publishVaultRegion(input: {
|
|
|
79
158
|
name: input.name,
|
|
80
159
|
content: input.content,
|
|
81
160
|
});
|
|
82
|
-
if (!applied.ok) return { ok: false, reason:
|
|
161
|
+
if (!applied.ok) return { ok: false, reason: "no_marker" };
|
|
83
162
|
|
|
84
163
|
const prevHash = createHash("sha256").update(existing, "utf8").digest("hex");
|
|
85
164
|
const nextHash = createHash("sha256").update(applied.text, "utf8").digest("hex");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { assertBeginEndPair } from "./vault-region-pair.js";
|
|
5
|
+
|
|
6
|
+
test("assertBeginEndPair accepts a matching pair", () => {
|
|
7
|
+
assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: "timeline" }), { ok: true });
|
|
8
|
+
assert.deepEqual(assertBeginEndPair({ beginName: " timeline ", endName: "timeline" }), {
|
|
9
|
+
ok: true,
|
|
10
|
+
});
|
|
11
|
+
assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: " timeline " }), {
|
|
12
|
+
ok: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("assertBeginEndPair rejects a name mismatch", () => {
|
|
17
|
+
assert.deepEqual(assertBeginEndPair({ beginName: "timeline", endName: "weekly" }), {
|
|
18
|
+
ok: false,
|
|
19
|
+
error: "name_mismatch",
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("assertBeginEndPair throws on an empty name", () => {
|
|
24
|
+
assert.throws(() => assertBeginEndPair({ beginName: "", endName: "timeline" }), /empty/i);
|
|
25
|
+
assert.throws(() => assertBeginEndPair({ beginName: "timeline", endName: " " }), /empty/i);
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert a managed-region begin/end name pair (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Trim both names. Empty throws. Mismatch is name_mismatch. Else ok.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type BeginEndPairInput = {
|
|
8
|
+
beginName: string;
|
|
9
|
+
endName: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type BeginEndPairResult = { ok: true } | { ok: false; error: "name_mismatch" };
|
|
13
|
+
|
|
14
|
+
export function assertBeginEndPair({ beginName, endName }: BeginEndPairInput): BeginEndPairResult {
|
|
15
|
+
const begin = beginName.trim();
|
|
16
|
+
const end = endName.trim();
|
|
17
|
+
if (begin.length === 0 || end.length === 0) {
|
|
18
|
+
throw new RangeError("Region name must be non-empty.");
|
|
19
|
+
}
|
|
20
|
+
if (begin !== end) {
|
|
21
|
+
return { ok: false, error: "name_mismatch" };
|
|
22
|
+
}
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { validateRegionName } from "./vault-region.js";
|
|
5
|
+
|
|
6
|
+
test("validateRegionName trims a valid name", () => {
|
|
7
|
+
assert.deepEqual(validateRegionName("timeline"), { ok: true, name: "timeline" });
|
|
8
|
+
assert.deepEqual(validateRegionName(" weekly "), { ok: true, name: "weekly" });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("validateRegionName rejects empty and whitespace-only names", () => {
|
|
12
|
+
assert.deepEqual(validateRegionName(""), { ok: false, error: "invalid_name" });
|
|
13
|
+
assert.deepEqual(validateRegionName(" "), { ok: false, error: "invalid_name" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("validateRegionName rejects names with newlines", () => {
|
|
17
|
+
assert.deepEqual(validateRegionName("time\nline"), { ok: false, error: "invalid_name" });
|
|
18
|
+
assert.deepEqual(validateRegionName("time\rline"), { ok: false, error: "invalid_name" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("validateRegionName rejects the HTML comment closer", () => {
|
|
22
|
+
assert.deepEqual(validateRegionName("foo-->bar"), { ok: false, error: "invalid_name" });
|
|
23
|
+
assert.deepEqual(validateRegionName("timeline-->"), { ok: false, error: "invalid_name" });
|
|
24
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a managed-region name for vault markers (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Empty, whitespace-only, newline, and `-->` names are rejected so they
|
|
5
|
+
* cannot break `<!-- remnic:<name>:start -->` markers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ValidateRegionNameResult =
|
|
9
|
+
| { ok: true; name: string }
|
|
10
|
+
| { ok: false; error: "invalid_name" };
|
|
11
|
+
|
|
12
|
+
export function validateRegionName(name: string): ValidateRegionNameResult {
|
|
13
|
+
if (typeof name !== "string") {
|
|
14
|
+
return { ok: false, error: "invalid_name" };
|
|
15
|
+
}
|
|
16
|
+
if (name.includes("\n") || name.includes("\r") || name.includes("-->")) {
|
|
17
|
+
return { ok: false, error: "invalid_name" };
|
|
18
|
+
}
|
|
19
|
+
const trimmed = name.trim();
|
|
20
|
+
if (trimmed.length === 0) {
|
|
21
|
+
return { ok: false, error: "invalid_name" };
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, name: trimmed };
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { stripVaultMdSuffix } from "./vault-suffix.js";
|
|
5
|
+
|
|
6
|
+
test("stripVaultMdSuffix drops a trailing .md", () => {
|
|
7
|
+
assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18.md"), "Daily Notes/2026-08-18");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("stripVaultMdSuffix leaves a path with no suffix unchanged", () => {
|
|
11
|
+
assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18"), "Daily Notes/2026-08-18");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("stripVaultMdSuffix throws on empty input", () => {
|
|
15
|
+
assert.throws(() => stripVaultMdSuffix(""), /empty/i);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("stripVaultMdSuffix does not strip .markdown", () => {
|
|
19
|
+
assert.equal(stripVaultMdSuffix("Daily Notes/2026-08-18.markdown"), "Daily Notes/2026-08-18.markdown");
|
|
20
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip a trailing `.md` suffix from a vault note path (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Case-sensitive. Does not strip `.markdown`. Empty input is rejected.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function stripVaultMdSuffix(notePath: string): string {
|
|
8
|
+
if (typeof notePath !== "string" || notePath.length === 0) {
|
|
9
|
+
throw new RangeError("Vault path must be non-empty.");
|
|
10
|
+
}
|
|
11
|
+
return notePath.endsWith(".md") ? notePath.slice(0, -3) : notePath;
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { formatVaultWikilink } from "./vault-wikilink.js";
|
|
5
|
+
|
|
6
|
+
test("formatVaultWikilink returns a wikilink without the .md suffix", () => {
|
|
7
|
+
assert.equal(formatVaultWikilink("Daily Notes/2026-08-18.md"), "[[Daily Notes/2026-08-18]]");
|
|
8
|
+
assert.equal(formatVaultWikilink("Places/ACME Office"), "[[Places/ACME Office]]");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("formatVaultWikilink rejects absolute paths", () => {
|
|
12
|
+
assert.throws(() => formatVaultWikilink("/abs/note.md"), /absolute/i);
|
|
13
|
+
assert.throws(() => formatVaultWikilink("C:/vault/note.md"), /absolute/i);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("formatVaultWikilink rejects parent segments", () => {
|
|
17
|
+
assert.throws(() => formatVaultWikilink("../outside.md"), /\.\./);
|
|
18
|
+
assert.throws(() => formatVaultWikilink("notes/../../secret.md"), /\.\./);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("formatVaultWikilink rejects empty and newline paths", () => {
|
|
22
|
+
assert.throws(() => formatVaultWikilink(""), /empty/i);
|
|
23
|
+
assert.throws(() => formatVaultWikilink(" "), /empty/i);
|
|
24
|
+
assert.throws(() => formatVaultWikilink(".md"), /empty/i);
|
|
25
|
+
assert.throws(() => formatVaultWikilink("foo\nbar.md"), /newline/i);
|
|
26
|
+
assert.throws(() => formatVaultWikilink("foo\rbar.md"), /newline/i);
|
|
27
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a vault-relative note path as an Obsidian wikilink (issue #1985).
|
|
3
|
+
*
|
|
4
|
+
* Relative paths only. Rejects absolute paths, `..` segments, empty input,
|
|
5
|
+
* and newlines. Strips a trailing `.md` suffix.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
export function formatVaultWikilink(notePath: string): string {
|
|
10
|
+
if (typeof notePath !== "string" || notePath.trim().length === 0) {
|
|
11
|
+
throw new RangeError("Vault wikilink path must be a non-empty relative path.");
|
|
12
|
+
}
|
|
13
|
+
if (notePath.includes("\n") || notePath.includes("\r")) {
|
|
14
|
+
throw new RangeError("Vault wikilink path must not contain newlines.");
|
|
15
|
+
}
|
|
16
|
+
if (path.posix.isAbsolute(notePath) || path.win32.isAbsolute(notePath)) {
|
|
17
|
+
throw new RangeError("Vault wikilink path must be relative; absolute paths are rejected.");
|
|
18
|
+
}
|
|
19
|
+
if (notePath.split(/[\\/]/).some((segment) => segment === "..")) {
|
|
20
|
+
throw new RangeError("Vault wikilink path must not contain `..` segments.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const stripped = notePath.endsWith(".md") ? notePath.slice(0, -3) : notePath;
|
|
24
|
+
if (stripped.length === 0) {
|
|
25
|
+
throw new RangeError("Vault wikilink path must be a non-empty relative path.");
|
|
26
|
+
}
|
|
27
|
+
return `[[${stripped}]]`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { assertHalfOpenWindow } from "./window-order.js";
|
|
5
|
+
|
|
6
|
+
test("ordered window is ok", () => {
|
|
7
|
+
assert.deepEqual(assertHalfOpenWindow({ fromMs: 0, toMs: 1 }), { ok: true });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("equal bounds are empty_window", () => {
|
|
11
|
+
assert.deepEqual(assertHalfOpenWindow({ fromMs: 10, toMs: 10 }), {
|
|
12
|
+
ok: false,
|
|
13
|
+
error: "empty_window",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("inverted bounds are empty_window", () => {
|
|
18
|
+
assert.deepEqual(assertHalfOpenWindow({ fromMs: 20, toMs: 10 }), {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: "empty_window",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("NaN bounds throw", () => {
|
|
25
|
+
assert.throws(() => assertHalfOpenWindow({ fromMs: Number.NaN, toMs: 1 }), /finite/);
|
|
26
|
+
assert.throws(() => assertHalfOpenWindow({ fromMs: 0, toMs: Number.NaN }), /finite/);
|
|
27
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert a half-open activity window [fromMs, toMs) (issue #2053 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Non-finite bounds throw. `toMs <= fromMs` is empty_window.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type AssertHalfOpenWindowResult =
|
|
8
|
+
| { ok: true }
|
|
9
|
+
| { ok: false; error: "empty_window" };
|
|
10
|
+
|
|
11
|
+
/** Validate `[fromMs, toMs)`. Empty or inverted windows fail closed. */
|
|
12
|
+
export function assertHalfOpenWindow(window: {
|
|
13
|
+
fromMs: number;
|
|
14
|
+
toMs: number;
|
|
15
|
+
}): AssertHalfOpenWindowResult {
|
|
16
|
+
const { fromMs, toMs } = window;
|
|
17
|
+
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) {
|
|
18
|
+
throw new RangeError(
|
|
19
|
+
`activity window bounds must be finite; got fromMs=${fromMs} toMs=${toMs}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (toMs <= fromMs) return { ok: false, error: "empty_window" };
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { countMergeCandidates } from "./merge-count.js";
|
|
5
|
+
|
|
6
|
+
test("empty list is 0", () => {
|
|
7
|
+
assert.equal(countMergeCandidates([]), 0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("counts unique ids", () => {
|
|
11
|
+
assert.equal(countMergeCandidates(["alpha", "beta", "alpha"]), 2);
|
|
12
|
+
assert.equal(countMergeCandidates(["alpha", "beta", "gamma"]), 3);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("drops empty strings and does not mutate", () => {
|
|
16
|
+
const ids = ["alpha", "", "beta", "", "alpha"];
|
|
17
|
+
assert.equal(countMergeCandidates(ids), 2);
|
|
18
|
+
assert.deepEqual(ids, ["alpha", "", "beta", "", "alpha"]);
|
|
19
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge-candidate counter (issue #2330 leftover).
|
|
3
|
+
*
|
|
4
|
+
* Unique ids after dropping empty strings. Does not mutate input.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function countMergeCandidates(ids: readonly string[]): number {
|
|
8
|
+
const unique = new Set<string>();
|
|
9
|
+
for (const id of ids) {
|
|
10
|
+
if (id !== "") unique.add(id);
|
|
11
|
+
}
|
|
12
|
+
return unique.size;
|
|
13
|
+
}
|