@remnic/core 9.26.0 → 9.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +13 -10
- package/dist/access-admin-ops-surface.js +43 -38
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +13 -10
- package/dist/access-authorization-probe.js +44 -39
- package/dist/access-boundary.d.ts +13 -10
- package/dist/access-boundary.js +43 -38
- package/dist/access-cli.js +100 -94
- package/dist/access-cli.js.map +1 -1
- package/dist/access-health-types.d.ts +10 -1
- package/dist/access-http-lcm-compaction.d.ts +13 -10
- package/dist/access-http.d.ts +13 -10
- package/dist/access-http.js +53 -48
- package/dist/access-identity-continuity-surface.d.ts +9 -8
- package/dist/access-identity-continuity-surface.js +43 -38
- package/dist/access-lcm-surface.d.ts +13 -10
- package/dist/access-lcm-surface.js +43 -38
- package/dist/access-mcp.d.ts +13 -10
- package/dist/access-mcp.js +48 -43
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-wRSZEO5V.d.ts → access-namespace-preflight-BiQ_-iUJ.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +43 -38
- package/dist/access-observe-write-surface.d.ts +13 -10
- package/dist/access-observe-write-surface.js +43 -38
- package/dist/access-operations-batch.js +45 -40
- package/dist/access-operations.d.ts +16 -13
- package/dist/access-operations.js +47 -42
- package/dist/access-recall-concurrency.d.ts +13 -10
- package/dist/access-recall-concurrency.js +43 -38
- package/dist/access-recall-response.d.ts +13 -10
- package/dist/access-recall-response.js +43 -38
- package/dist/access-recall-surface.d.ts +13 -10
- package/dist/access-recall-surface.js +43 -38
- package/dist/access-schema.d.ts +80 -80
- package/dist/access-schema.js +4 -4
- package/dist/{access-service-DL8FJnTj.d.ts → access-service-CXkIgcGh.d.ts} +12 -8
- package/dist/access-service.d.ts +13 -10
- package/dist/access-service.js +43 -38
- package/dist/access-surface-catalog.d.ts +13 -10
- package/dist/access-wearables-meetings-surface.d.ts +4 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -8
- package/dist/briefing.d.ts +3 -2
- package/dist/briefing.js +12 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{catalog-BLLxZ5L5.d.ts → catalog-Bouj8Gfg.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -11
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-7RZJWZ46.js → chunk-227UFSEQ.js} +5 -5
- package/dist/{chunk-VGEH3STZ.js → chunk-2BEJ4XCF.js} +2 -2
- package/dist/{chunk-LIIQPDFN.js → chunk-2FYNLDKJ.js} +24 -3
- package/dist/chunk-2FYNLDKJ.js.map +1 -0
- package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-6NNNURLK.js → chunk-2PHX3I5C.js} +8 -8
- package/dist/{chunk-YN4AUGNF.js → chunk-2TQ2UFKH.js} +65 -60
- package/dist/chunk-2TQ2UFKH.js.map +1 -0
- package/dist/{chunk-XAAIUXJO.js → chunk-3BSJBIZ3.js} +2 -2
- package/dist/{chunk-JCVQV6QX.js → chunk-3UOSVTFR.js} +11 -11
- package/dist/{chunk-AJ7QCSIS.js → chunk-7CPOZH6Y.js} +2 -2
- package/dist/{chunk-EXM2Q555.js → chunk-AHFZXUZM.js} +7 -5
- package/dist/chunk-AHFZXUZM.js.map +1 -0
- package/dist/{chunk-M2KGXXTL.js → chunk-ALTP3MFS.js} +2 -2
- package/dist/{chunk-77CNV7DY.js → chunk-BLAKS3CP.js} +4 -4
- package/dist/{chunk-FE5FKTCI.js → chunk-BR4UFMLO.js} +2 -2
- package/dist/chunk-CVILPI77.js +432 -0
- package/dist/chunk-CVILPI77.js.map +1 -0
- package/dist/{chunk-H2PBM562.js → chunk-DKQM5Q6Q.js} +2 -2
- package/dist/{chunk-CSXHRAJJ.js → chunk-EG64LXKL.js} +3 -3
- package/dist/{chunk-PTN6UAUA.js → chunk-ES36HTSC.js} +4 -4
- package/dist/{chunk-WMDRLTNA.js → chunk-EZWWCN7I.js} +2 -2
- package/dist/{chunk-XKGPIWGI.js → chunk-FBZTWUK5.js} +5 -5
- package/dist/{chunk-GJJBPI6I.js → chunk-FOCEI3TN.js} +6 -3
- package/dist/chunk-FOCEI3TN.js.map +1 -0
- package/dist/{chunk-Y6E37WRG.js → chunk-GJPCP5CA.js} +2 -2
- package/dist/{chunk-IHMXPG4P.js → chunk-GQSMYXKL.js} +6 -4
- package/dist/{chunk-IHMXPG4P.js.map → chunk-GQSMYXKL.js.map} +1 -1
- package/dist/{chunk-TGQ2NTWH.js → chunk-IJ6WHMFB.js} +16 -2
- package/dist/{chunk-TGQ2NTWH.js.map → chunk-IJ6WHMFB.js.map} +1 -1
- package/dist/{chunk-VDX2J7OX.js → chunk-IPLYGWQF.js} +6 -6
- package/dist/{chunk-XNUBF6RF.js → chunk-IYT632D5.js} +3 -3
- package/dist/{chunk-2QSZNTDO.js → chunk-JBPKEARU.js} +7 -7
- package/dist/{chunk-67DSDRUB.js → chunk-JIF3VRKQ.js} +8 -8
- package/dist/{chunk-V25OWXAC.js → chunk-JQ7T5VDC.js} +1 -1
- package/dist/chunk-JQ7T5VDC.js.map +1 -0
- package/dist/{chunk-MIFF37NN.js → chunk-KKBTBUVT.js} +2 -2
- package/dist/{chunk-ZHP4SUPB.js → chunk-LIHC3T43.js} +2 -2
- package/dist/{chunk-PX7VDW7R.js → chunk-MJPV4LBH.js} +79 -79
- package/dist/{chunk-PX7VDW7R.js.map → chunk-MJPV4LBH.js.map} +1 -1
- package/dist/{chunk-UGH6VPPO.js → chunk-MWTMTW2T.js} +2 -2
- package/dist/{chunk-F6LSNPPB.js → chunk-MXJ3VP6S.js} +6 -6
- package/dist/{chunk-KPWLZBXZ.js → chunk-NBNDVNJ2.js} +186 -25
- package/dist/chunk-NBNDVNJ2.js.map +1 -0
- package/dist/{chunk-M5EPMNS4.js → chunk-P2CLYN6H.js} +2 -2
- package/dist/{chunk-7XXCIO6X.js → chunk-PXQM2HOG.js} +13 -6
- package/dist/{chunk-7XXCIO6X.js.map → chunk-PXQM2HOG.js.map} +1 -1
- package/dist/{chunk-UZK7VVPD.js → chunk-Q5VJUNXA.js} +1858 -272
- package/dist/chunk-Q5VJUNXA.js.map +1 -0
- package/dist/{chunk-KGTD2KZJ.js → chunk-Q6YJW6KE.js} +2 -2
- package/dist/{chunk-X3SQYAPP.js → chunk-R372P2EH.js} +2 -2
- package/dist/{chunk-HBVOJNV4.js → chunk-RKXZ7P4V.js} +3 -3
- package/dist/chunk-RMMVQQA6.js +70 -0
- package/dist/chunk-RMMVQQA6.js.map +1 -0
- package/dist/{chunk-E6EAB4J6.js → chunk-S24E4MBQ.js} +14 -14
- package/dist/chunk-S24E4MBQ.js.map +1 -0
- package/dist/{chunk-RYHWZ52A.js → chunk-S2BBISHU.js} +4 -4
- package/dist/{chunk-LLISELSY.js → chunk-TBMRQZDY.js} +2 -2
- package/dist/{chunk-FMLKOL2Z.js → chunk-TC3VNKGX.js} +4 -4
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-2XP3PWL3.js → chunk-UR5SIN7H.js} +53 -35
- package/dist/chunk-UR5SIN7H.js.map +1 -0
- package/dist/{chunk-NKDVXEFU.js → chunk-VFNI3RPT.js} +2 -2
- package/dist/chunk-VNA7W7QF.js +134 -0
- package/dist/chunk-VNA7W7QF.js.map +1 -0
- package/dist/{chunk-PQPSZICJ.js → chunk-VYYNBJXH.js} +5 -5
- package/dist/chunk-WNIUPHWV.js +152 -0
- package/dist/chunk-WNIUPHWV.js.map +1 -0
- package/dist/chunk-WRJVB3C2.js +118 -0
- package/dist/chunk-WRJVB3C2.js.map +1 -0
- package/dist/{chunk-SN3ZESWQ.js → chunk-WVVK2RFM.js} +6 -6
- package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-4KKV7W62.js → chunk-YOBNCD3Z.js} +6 -6
- package/dist/chunk-YXZG5TVN.js +40 -0
- package/dist/chunk-YXZG5TVN.js.map +1 -0
- package/dist/{chunk-4DJQYKMN.js → chunk-ZIKENA2C.js} +2 -2
- package/dist/chunk-ZIKENA2C.js.map +1 -0
- package/dist/{chunk-SK3EYPKC.js → chunk-ZN4UZWMT.js} +2 -2
- package/dist/{cli-BoouX6LX.d.ts → cli-DJbkaZBU.d.ts} +5 -5
- package/dist/cli.d.ts +14 -12
- package/dist/cli.js +79 -74
- package/dist/compounding/engine.d.ts +3 -2
- package/dist/compounding/engine.js +12 -10
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -10
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -10
- package/dist/consolidation-provenance-check.d.ts +3 -2
- package/dist/consolidation-undo.d.ts +3 -2
- package/dist/contradiction/index.d.ts +3 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +15 -2
- package/dist/corpus-watermark.js +25 -21
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-id-normalization.d.ts +4 -0
- package/dist/entity-id-normalization.js +10 -0
- package/dist/entity-id-normalization.js.map +1 -0
- package/dist/entity-retrieval-boundaries.d.ts +3 -0
- package/dist/entity-retrieval-boundaries.js +8 -0
- package/dist/entity-retrieval-boundaries.js.map +1 -0
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +13 -10
- package/dist/entity-schema.d.ts +1 -1
- package/dist/entity-schema.js +1 -1
- package/dist/explicit-capture.d.ts +9 -8
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +409 -407
- package/dist/index.js +147 -141
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +9 -9
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
- package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +13 -11
- package/dist/{maintenance-DD22oIx9.d.ts → maintenance-DveW6azo.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +13 -10
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-mutations.d.ts +4 -0
- package/dist/memory-projection-mutations.js +15 -0
- package/dist/memory-projection-mutations.js.map +1 -0
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +3 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +22 -20
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +4 -3
- package/dist/namespaces/storage.js +12 -10
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.d.ts +22 -3
- package/dist/operator-doctor-corpus.js +32 -22
- package/dist/operator-doctor-replica.d.ts +52 -0
- package/dist/operator-doctor-replica.js +15 -0
- package/dist/operator-doctor-replica.js.map +1 -0
- package/dist/operator-toolkit.d.ts +7 -3
- package/dist/operator-toolkit.js +33 -27
- package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
- package/dist/orchestration/maintenance.d.ts +5 -4
- package/dist/orchestration/maintenance.js +17 -15
- package/dist/{orchestrator-CFJ_TG6D.d.ts → orchestrator-Cvlgpe6I.d.ts} +7 -7
- package/dist/orchestrator.d.ts +9 -8
- package/dist/orchestrator.js +99 -93
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-Bq5zU6Bp.d.ts → pipeline-BveYVaz7.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-3ILJkC70.d.ts → qmd-CLWDsbYR.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-state.js +2 -2
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +279 -0
- package/dist/replica-divergence.js +28 -0
- package/dist/replica-divergence.js.map +1 -0
- package/dist/replica-peers-config.d.ts +5 -0
- package/dist/replica-peers-config.js +13 -0
- package/dist/replica-peers-config.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +21 -2
- package/dist/resolve-auth-token.js +5 -1
- package/dist/resume-bundles.js +5 -3
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/{scope-profiles-D0-ff4xT.d.ts → scope-profiles-BVf3DZ7z.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-4Tfh7c1g.d.ts → semantic-consolidation-DXLV47y2.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -11
- package/dist/semantic-rule-promotion.js +12 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -10
- package/dist/{service-Z8uzoAST.d.ts → service-DbgPTeMJ.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-BZVMn8yJ.d.ts → storage-DOzTm8wG.d.ts} +13 -22
- package/dist/storage.d.ts +3 -2
- package/dist/storage.js +13 -10
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +3 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +3 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DHBxIF0s.d.ts → types-BA7W-_xy.d.ts} +53 -4
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +12 -10
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-health-types.ts +8 -0
- package/src/access-http.test.ts +4 -0
- package/src/access-service-health.test.ts +23 -0
- package/src/access-service.ts +27 -4
- package/src/cli.ts +9 -7
- package/src/config.ts +2 -1
- package/src/corpus-watermark.test.ts +21 -0
- package/src/corpus-watermark.ts +25 -3
- package/src/entity-id-normalization.ts +55 -0
- package/src/entity-retrieval-boundaries.ts +134 -0
- package/src/entity-retrieval.ts +253 -27
- package/src/entity-schema.ts +2 -1
- package/src/memory-projection-mutations.ts +132 -0
- package/src/namespaces/storage.ts +5 -3
- package/src/operator-doctor-corpus.ts +41 -0
- package/src/operator-doctor-replica.test.ts +261 -0
- package/src/operator-doctor-replica.ts +129 -0
- package/src/operator-toolkit.ts +5 -4
- package/src/orchestration/orchestrator-init.ts +2 -2
- package/src/replica-divergence.test.ts +1475 -0
- package/src/replica-divergence.ts +910 -0
- package/src/replica-peers-config.ts +250 -0
- package/src/resolve-auth-token.ts +38 -0
- package/src/storage/entity-canonical-id-migration-adapter.ts +72 -0
- package/src/storage/entity-canonical-id-migration-runner.ts +68 -0
- package/src/storage/entity-canonical-id-migration.ts +904 -0
- package/src/storage/entity-store.ts +44 -0
- package/src/storage/memory-frontmatter-metadata.ts +17 -0
- package/src/storage/memory-migration-serialization.ts +37 -0
- package/src/storage/memory-read-store.ts +3 -2
- package/src/storage/profile-header.ts +898 -0
- package/src/storage-profile-timestamp.test.ts +3358 -0
- package/src/storage.ts +73 -77
- package/src/testing/subjects/profile-header-write.test.ts +71 -0
- package/src/types.ts +3 -3
- package/dist/chunk-2XP3PWL3.js.map +0 -1
- package/dist/chunk-4DJQYKMN.js.map +0 -1
- package/dist/chunk-E6EAB4J6.js.map +0 -1
- package/dist/chunk-EXM2Q555.js.map +0 -1
- package/dist/chunk-GJJBPI6I.js.map +0 -1
- package/dist/chunk-KPWLZBXZ.js.map +0 -1
- package/dist/chunk-LIIQPDFN.js.map +0 -1
- package/dist/chunk-UZK7VVPD.js.map +0 -1
- package/dist/chunk-V25OWXAC.js.map +0 -1
- package/dist/chunk-YN4AUGNF.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-227UFSEQ.js.map} +0 -0
- /package/dist/{chunk-VGEH3STZ.js.map → chunk-2BEJ4XCF.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-2PHX3I5C.js.map} +0 -0
- /package/dist/{chunk-XAAIUXJO.js.map → chunk-3BSJBIZ3.js.map} +0 -0
- /package/dist/{chunk-JCVQV6QX.js.map → chunk-3UOSVTFR.js.map} +0 -0
- /package/dist/{chunk-AJ7QCSIS.js.map → chunk-7CPOZH6Y.js.map} +0 -0
- /package/dist/{chunk-M2KGXXTL.js.map → chunk-ALTP3MFS.js.map} +0 -0
- /package/dist/{chunk-77CNV7DY.js.map → chunk-BLAKS3CP.js.map} +0 -0
- /package/dist/{chunk-FE5FKTCI.js.map → chunk-BR4UFMLO.js.map} +0 -0
- /package/dist/{chunk-H2PBM562.js.map → chunk-DKQM5Q6Q.js.map} +0 -0
- /package/dist/{chunk-CSXHRAJJ.js.map → chunk-EG64LXKL.js.map} +0 -0
- /package/dist/{chunk-PTN6UAUA.js.map → chunk-ES36HTSC.js.map} +0 -0
- /package/dist/{chunk-WMDRLTNA.js.map → chunk-EZWWCN7I.js.map} +0 -0
- /package/dist/{chunk-XKGPIWGI.js.map → chunk-FBZTWUK5.js.map} +0 -0
- /package/dist/{chunk-Y6E37WRG.js.map → chunk-GJPCP5CA.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-XNUBF6RF.js.map → chunk-IYT632D5.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-67DSDRUB.js.map → chunk-JIF3VRKQ.js.map} +0 -0
- /package/dist/{chunk-MIFF37NN.js.map → chunk-KKBTBUVT.js.map} +0 -0
- /package/dist/{chunk-ZHP4SUPB.js.map → chunk-LIHC3T43.js.map} +0 -0
- /package/dist/{chunk-UGH6VPPO.js.map → chunk-MWTMTW2T.js.map} +0 -0
- /package/dist/{chunk-F6LSNPPB.js.map → chunk-MXJ3VP6S.js.map} +0 -0
- /package/dist/{chunk-M5EPMNS4.js.map → chunk-P2CLYN6H.js.map} +0 -0
- /package/dist/{chunk-KGTD2KZJ.js.map → chunk-Q6YJW6KE.js.map} +0 -0
- /package/dist/{chunk-X3SQYAPP.js.map → chunk-R372P2EH.js.map} +0 -0
- /package/dist/{chunk-HBVOJNV4.js.map → chunk-RKXZ7P4V.js.map} +0 -0
- /package/dist/{chunk-RYHWZ52A.js.map → chunk-S2BBISHU.js.map} +0 -0
- /package/dist/{chunk-LLISELSY.js.map → chunk-TBMRQZDY.js.map} +0 -0
- /package/dist/{chunk-FMLKOL2Z.js.map → chunk-TC3VNKGX.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-NKDVXEFU.js.map → chunk-VFNI3RPT.js.map} +0 -0
- /package/dist/{chunk-PQPSZICJ.js.map → chunk-VYYNBJXH.js.map} +0 -0
- /package/dist/{chunk-SN3ZESWQ.js.map → chunk-WVVK2RFM.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-4KKV7W62.js.map → chunk-YOBNCD3Z.js.map} +0 -0
- /package/dist/{chunk-SK3EYPKC.js.map → chunk-ZN4UZWMT.js.map} +0 -0
- /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TombstoneStore,
|
|
3
|
+
buildRetiredFactTombstoneInputs,
|
|
4
|
+
collectRetiredMemoriesForRebuild
|
|
5
|
+
} from "./chunk-2QMKMVHH.js";
|
|
1
6
|
import {
|
|
2
7
|
WEARABLES_DIR_NAME,
|
|
3
8
|
escapeSegmentText,
|
|
@@ -10,26 +15,23 @@ import {
|
|
|
10
15
|
unescapeSpeakerLabel
|
|
11
16
|
} from "./chunk-UHVXE24Y.js";
|
|
12
17
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
collectRetiredMemoriesForRebuild
|
|
16
|
-
} from "./chunk-2QMKMVHH.js";
|
|
17
|
-
import {
|
|
18
|
+
computeSupersessionKey,
|
|
19
|
+
normalizeSupersessionKey,
|
|
18
20
|
supersessionKeysForFact
|
|
19
21
|
} from "./chunk-DNHVPZOD.js";
|
|
20
|
-
import {
|
|
21
|
-
assertMemoryFrontmatterId,
|
|
22
|
-
warnProjectionFallback
|
|
23
|
-
} from "./chunk-HKF5VASQ.js";
|
|
24
22
|
import {
|
|
25
23
|
DEFAULT_CITATION_FORMAT,
|
|
26
24
|
hasCitation,
|
|
27
25
|
stripCitationForTemplate
|
|
28
26
|
} from "./chunk-J6A3CX5N.js";
|
|
27
|
+
import {
|
|
28
|
+
assertMemoryFrontmatterId,
|
|
29
|
+
warnProjectionFallback
|
|
30
|
+
} from "./chunk-HKF5VASQ.js";
|
|
29
31
|
import {
|
|
30
32
|
SPECULATIVE_TTL_DAYS,
|
|
31
33
|
confidenceTier
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-JQ7T5VDC.js";
|
|
33
35
|
import {
|
|
34
36
|
appendLifecycleEventsSerialized,
|
|
35
37
|
drainPendingLifecycleLedgerForSync,
|
|
@@ -40,14 +42,10 @@ import {
|
|
|
40
42
|
readMaybeEncryptedLines,
|
|
41
43
|
readMemoryActionEventRowsFromLines,
|
|
42
44
|
serializeLifecycleAppendPayload
|
|
43
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-FBZTWUK5.js";
|
|
44
46
|
import {
|
|
45
47
|
namespaceIdentityFromToken
|
|
46
48
|
} from "./chunk-EGGE52UU.js";
|
|
47
|
-
import {
|
|
48
|
-
assertPathInsideRoot,
|
|
49
|
-
pathIsInside
|
|
50
|
-
} from "./chunk-GCASGYIO.js";
|
|
51
49
|
import {
|
|
52
50
|
withHeldFileLock
|
|
53
51
|
} from "./chunk-WL5EBRDH.js";
|
|
@@ -60,6 +58,14 @@ import {
|
|
|
60
58
|
normalizeProjectionPreview,
|
|
61
59
|
normalizeProjectionTags
|
|
62
60
|
} from "./chunk-SCU65EZI.js";
|
|
61
|
+
import {
|
|
62
|
+
rewriteProjectedEntityReferences,
|
|
63
|
+
rewriteProjectedMemoryEntityReference
|
|
64
|
+
} from "./chunk-WRJVB3C2.js";
|
|
65
|
+
import {
|
|
66
|
+
assertPathInsideRoot,
|
|
67
|
+
pathIsInside
|
|
68
|
+
} from "./chunk-GCASGYIO.js";
|
|
63
69
|
import {
|
|
64
70
|
markProjectedMemoryPathInvalid,
|
|
65
71
|
openProjectionReadonly,
|
|
@@ -81,6 +87,10 @@ import {
|
|
|
81
87
|
serializeContinuityIncident,
|
|
82
88
|
upsertContinuityLoopInMarkdown
|
|
83
89
|
} from "./chunk-LDXUBPMO.js";
|
|
90
|
+
import {
|
|
91
|
+
parseFaithfulnessField,
|
|
92
|
+
serializeFaithfulnessFields
|
|
93
|
+
} from "./chunk-JYIJNNN3.js";
|
|
84
94
|
import {
|
|
85
95
|
composeMemoryEnvelope,
|
|
86
96
|
isSealedMemoryEnvelope,
|
|
@@ -91,9 +101,8 @@ import {
|
|
|
91
101
|
stripAttributesSuffix
|
|
92
102
|
} from "./chunk-R3DJVOSM.js";
|
|
93
103
|
import {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} from "./chunk-JYIJNNN3.js";
|
|
104
|
+
normalizeEntityName
|
|
105
|
+
} from "./chunk-YXZG5TVN.js";
|
|
97
106
|
import {
|
|
98
107
|
createVersion
|
|
99
108
|
} from "./chunk-5VQJBH2G.js";
|
|
@@ -131,14 +140,14 @@ import {
|
|
|
131
140
|
reconcileProvenanceRead,
|
|
132
141
|
serializeProvenanceFields
|
|
133
142
|
} from "./chunk-5I4M26Q7.js";
|
|
134
|
-
import {
|
|
135
|
-
sanitizeMemoryContent
|
|
136
|
-
} from "./chunk-5GKPFL3S.js";
|
|
137
143
|
import {
|
|
138
144
|
matchEntitySchemaSection,
|
|
139
145
|
normalizeEntityStructuredSection,
|
|
140
146
|
sortStructuredSectionsBySchema
|
|
141
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-ZIKENA2C.js";
|
|
148
|
+
import {
|
|
149
|
+
sanitizeMemoryContent
|
|
150
|
+
} from "./chunk-5GKPFL3S.js";
|
|
142
151
|
import {
|
|
143
152
|
parseFlexibleIsoTimestamp
|
|
144
153
|
} from "./chunk-P7FMDTKL.js";
|
|
@@ -195,24 +204,40 @@ function assertInjectedStorageRooted(label, memoryDir, injected) {
|
|
|
195
204
|
|
|
196
205
|
// src/storage.ts
|
|
197
206
|
import {
|
|
198
|
-
lstat as
|
|
199
|
-
readdir as
|
|
200
|
-
readFile,
|
|
201
|
-
realpath as
|
|
207
|
+
lstat as lstat3,
|
|
208
|
+
readdir as readdir5,
|
|
209
|
+
readFile as readFile2,
|
|
210
|
+
realpath as realpath3,
|
|
202
211
|
stat as stat3,
|
|
203
|
-
writeFile,
|
|
212
|
+
writeFile as writeFile2,
|
|
204
213
|
mkdir as mkdir2,
|
|
205
|
-
unlink as
|
|
214
|
+
unlink as unlink3,
|
|
206
215
|
appendFile,
|
|
207
216
|
open
|
|
208
217
|
} from "fs/promises";
|
|
209
|
-
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
|
|
218
|
+
import { appendFileSync, createReadStream, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
210
219
|
import { createHash as createHash5 } from "crypto";
|
|
211
|
-
import
|
|
220
|
+
import path10 from "path";
|
|
212
221
|
|
|
213
222
|
// src/storage/memory-read-store.ts
|
|
214
223
|
import { lstat, readdir, realpath, stat } from "fs/promises";
|
|
215
224
|
import path2 from "path";
|
|
225
|
+
|
|
226
|
+
// src/storage/memory-frontmatter-metadata.ts
|
|
227
|
+
var rawFrontmatterByMemory = /* @__PURE__ */ new WeakMap();
|
|
228
|
+
function extractRawFrontmatter(raw) {
|
|
229
|
+
return raw.match(/^---\n([\s\S]*?)\n---\n?/)?.[1] ?? null;
|
|
230
|
+
}
|
|
231
|
+
function rememberRawFrontmatter(owner, raw) {
|
|
232
|
+
const frontmatter = extractRawFrontmatter(raw);
|
|
233
|
+
if (frontmatter !== null) rawFrontmatterByMemory.set(owner, frontmatter);
|
|
234
|
+
return owner;
|
|
235
|
+
}
|
|
236
|
+
function readRawFrontmatter(owner) {
|
|
237
|
+
return rawFrontmatterByMemory.get(owner);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/storage/memory-read-store.ts
|
|
216
241
|
function isPropagatableReadError(err) {
|
|
217
242
|
const code = err.code;
|
|
218
243
|
return typeof code === "string" && code !== "ENOENT";
|
|
@@ -491,7 +516,7 @@ var MemoryReadStore = class {
|
|
|
491
516
|
const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
|
|
492
517
|
const parsed = parseFrontmatter(raw);
|
|
493
518
|
if (parsed) {
|
|
494
|
-
return {
|
|
519
|
+
return rememberRawFrontmatter({
|
|
495
520
|
path: filePath,
|
|
496
521
|
frontmatter: normalizeFrontmatterForPath(
|
|
497
522
|
parsed.frontmatter,
|
|
@@ -499,7 +524,7 @@ var MemoryReadStore = class {
|
|
|
499
524
|
parsed.content
|
|
500
525
|
),
|
|
501
526
|
content: parsed.content
|
|
502
|
-
};
|
|
527
|
+
}, raw);
|
|
503
528
|
}
|
|
504
529
|
const normalizedPath = filePath.split(path2.sep).join("/");
|
|
505
530
|
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
@@ -705,6 +730,673 @@ var MemoryReadStore = class {
|
|
|
705
730
|
}
|
|
706
731
|
};
|
|
707
732
|
|
|
733
|
+
// src/storage/profile-header.ts
|
|
734
|
+
var LAST_UPDATED_HEADER = /^\*Last updated:[^*]*\*$/;
|
|
735
|
+
var PROFILE_TITLE = /^ {0,3}#(?:[ \t]+|$)/;
|
|
736
|
+
var MARKDOWN_HEADING = /^ {0,3}#{1,6}(?:[ \t]+|$)/;
|
|
737
|
+
var MARKDOWN_LIST_ITEM = /^(?:[-+*]|\d{1,9}[.)])[ \t]+/;
|
|
738
|
+
var MARKDOWN_LIST_ITEM_CAN_INTERRUPT = /^(?:[-+*]|1[.)])[ \t]+/;
|
|
739
|
+
var MARKDOWN_THEMATIC_BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
|
|
740
|
+
var MARKDOWN_SETEXT_UNDERLINE = /^(?:=+|-+)$/;
|
|
741
|
+
var MARKDOWN_BLOCK_QUOTE = /^>/;
|
|
742
|
+
var MARKDOWN_LINK_REFERENCE = /^\[(?:\\.|[^\\\[\]])+\]:[ \t]*(?:<[^>\n]*>|([^\s]+))(?:[ \t]+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?$/;
|
|
743
|
+
var MARKDOWN_LINK_REFERENCE_LABEL = /^\[(?:\\.|[^\\\[\]])+\]:[ \t]*$/;
|
|
744
|
+
var MARKDOWN_LINK_REFERENCE_CONTINUATION = /^ {1,3}(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\))$/;
|
|
745
|
+
var MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION = /^ {1,3}(?:<[^>\n]*>|(?!["'(])[^\s]+(?:[ \t]+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?)$/;
|
|
746
|
+
var UTF8_BOM = "\uFEFF";
|
|
747
|
+
function isLastUpdatedHeader(line) {
|
|
748
|
+
const withoutBom = line.startsWith(UTF8_BOM) ? line.slice(1) : line;
|
|
749
|
+
if (isIndentedCodeLine(withoutBom)) return false;
|
|
750
|
+
return LAST_UPDATED_HEADER.test(withoutBom.trim());
|
|
751
|
+
}
|
|
752
|
+
function isIndentedCodeLine(line) {
|
|
753
|
+
let indentation = 0;
|
|
754
|
+
while (line[indentation] === " ") indentation += 1;
|
|
755
|
+
return indentation >= 4 || line[indentation] === " ";
|
|
756
|
+
}
|
|
757
|
+
function getFenceMarker(line) {
|
|
758
|
+
let indentation = 0;
|
|
759
|
+
while (line[indentation] === " ") indentation += 1;
|
|
760
|
+
if (indentation > 3 || line[indentation] === " ") return null;
|
|
761
|
+
const trimmed = line.slice(indentation);
|
|
762
|
+
const character = trimmed[0];
|
|
763
|
+
if (character !== "`" && character !== "~") return null;
|
|
764
|
+
let length = 0;
|
|
765
|
+
while (trimmed[length] === character) length += 1;
|
|
766
|
+
if (length < 3) return null;
|
|
767
|
+
const trailing = trimmed.slice(length);
|
|
768
|
+
if (character === "`" && trailing.includes("`")) return null;
|
|
769
|
+
return { indentation, character, length, trailing };
|
|
770
|
+
}
|
|
771
|
+
function isClosingFence(fence, openFence) {
|
|
772
|
+
return fence.character === openFence.character && fence.length >= openFence.length && fence.trailing.trim() === "";
|
|
773
|
+
}
|
|
774
|
+
var RAW_HTML_BLOCK_TAGS = ["pre", "textarea", "script", "style"];
|
|
775
|
+
var HTML_BLOCK_TAGS = [
|
|
776
|
+
"address",
|
|
777
|
+
"article",
|
|
778
|
+
"aside",
|
|
779
|
+
"base",
|
|
780
|
+
"basefont",
|
|
781
|
+
"blockquote",
|
|
782
|
+
"body",
|
|
783
|
+
"caption",
|
|
784
|
+
"center",
|
|
785
|
+
"col",
|
|
786
|
+
"colgroup",
|
|
787
|
+
"dd",
|
|
788
|
+
"details",
|
|
789
|
+
"dialog",
|
|
790
|
+
"dir",
|
|
791
|
+
"div",
|
|
792
|
+
"dl",
|
|
793
|
+
"dt",
|
|
794
|
+
"fieldset",
|
|
795
|
+
"figcaption",
|
|
796
|
+
"figure",
|
|
797
|
+
"footer",
|
|
798
|
+
"form",
|
|
799
|
+
"frame",
|
|
800
|
+
"frameset",
|
|
801
|
+
"h1",
|
|
802
|
+
"h2",
|
|
803
|
+
"h3",
|
|
804
|
+
"h4",
|
|
805
|
+
"h5",
|
|
806
|
+
"h6",
|
|
807
|
+
"head",
|
|
808
|
+
"header",
|
|
809
|
+
"hgroup",
|
|
810
|
+
"hr",
|
|
811
|
+
"html",
|
|
812
|
+
"iframe",
|
|
813
|
+
"legend",
|
|
814
|
+
"li",
|
|
815
|
+
"link",
|
|
816
|
+
"main",
|
|
817
|
+
"menu",
|
|
818
|
+
"menuitem",
|
|
819
|
+
"nav",
|
|
820
|
+
"noframes",
|
|
821
|
+
"ol",
|
|
822
|
+
"optgroup",
|
|
823
|
+
"option",
|
|
824
|
+
"p",
|
|
825
|
+
"param",
|
|
826
|
+
"search",
|
|
827
|
+
"section",
|
|
828
|
+
"summary",
|
|
829
|
+
"table",
|
|
830
|
+
"tbody",
|
|
831
|
+
"td",
|
|
832
|
+
"tfoot",
|
|
833
|
+
"th",
|
|
834
|
+
"thead",
|
|
835
|
+
"title",
|
|
836
|
+
"tr",
|
|
837
|
+
"track",
|
|
838
|
+
"ul"
|
|
839
|
+
];
|
|
840
|
+
function findHtmlBlockTag(line, tags) {
|
|
841
|
+
const isClosing = line.startsWith("</");
|
|
842
|
+
const nameStart = isClosing ? 2 : 1;
|
|
843
|
+
for (const tag of tags) {
|
|
844
|
+
const nextCharacter = line[nameStart + tag.length];
|
|
845
|
+
const isSelfClosingSlash = !isClosing && nextCharacter === "/" && line[nameStart + tag.length + 1] === ">";
|
|
846
|
+
if (line.startsWith(`${isClosing ? "</" : "<"}${tag}`) && (nextCharacter === void 0 || nextCharacter === ">" || isSelfClosingSlash || nextCharacter === " " || nextCharacter === " ")) {
|
|
847
|
+
return tag;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
function isAsciiLetter(character) {
|
|
853
|
+
return character >= "a" && character <= "z" || character >= "A" && character <= "Z";
|
|
854
|
+
}
|
|
855
|
+
function isHtmlTagNameCharacter(character) {
|
|
856
|
+
return isAsciiLetter(character) || character >= "0" && character <= "9" || character === "-";
|
|
857
|
+
}
|
|
858
|
+
var HTML_ATTRIBUTES = /^(?:[A-Za-z_:][A-Za-z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)(?:\s+[A-Za-z_:][A-Za-z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*$/;
|
|
859
|
+
function hasValidHtmlTagAttributes(attributeText, isClosing) {
|
|
860
|
+
const trimmed = attributeText.trim();
|
|
861
|
+
if (isClosing || trimmed === "") return trimmed === "";
|
|
862
|
+
const withoutSelfClosingSlash = trimmed.endsWith("/") ? trimmed.slice(0, -1).trimEnd() : trimmed;
|
|
863
|
+
return withoutSelfClosingSlash === "" || HTML_ATTRIBUTES.test(withoutSelfClosingSlash);
|
|
864
|
+
}
|
|
865
|
+
function findCompleteHtmlTag(line) {
|
|
866
|
+
let index = 1;
|
|
867
|
+
const isClosing = line[index] === "/";
|
|
868
|
+
if (isClosing) index += 1;
|
|
869
|
+
const nameStart = index;
|
|
870
|
+
if (!isAsciiLetter(line[index] ?? "")) return null;
|
|
871
|
+
while (isHtmlTagNameCharacter(line[index] ?? "")) index += 1;
|
|
872
|
+
const nextCharacter = line[index] ?? "";
|
|
873
|
+
if (nextCharacter !== ">" && nextCharacter !== "/" && nextCharacter !== " " && nextCharacter !== " ") {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
const name = line.slice(nameStart, index).toLowerCase();
|
|
877
|
+
let quote = null;
|
|
878
|
+
for (; index < line.length; index += 1) {
|
|
879
|
+
const character = line[index];
|
|
880
|
+
if (quote) {
|
|
881
|
+
if (character === quote) quote = null;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (character === '"' || character === "'") {
|
|
885
|
+
quote = character;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (character === ">" && line.slice(index + 1).trim() === "") {
|
|
889
|
+
const tagBody = line.slice(nameStart, index).trimEnd();
|
|
890
|
+
if (!hasValidHtmlTagAttributes(tagBody.slice(name.length), isClosing)) return null;
|
|
891
|
+
return {
|
|
892
|
+
name,
|
|
893
|
+
isClosing,
|
|
894
|
+
isSelfClosing: !isClosing && tagBody.endsWith("/")
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
function findHtmlTagPrefix(line) {
|
|
901
|
+
let quote = null;
|
|
902
|
+
for (let index = 1; index < line.length; index += 1) {
|
|
903
|
+
const character = line[index];
|
|
904
|
+
if (quote) {
|
|
905
|
+
if (character === quote) quote = null;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if (character === '"' || character === "'") {
|
|
909
|
+
quote = character;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (character !== ">") continue;
|
|
913
|
+
return findCompleteHtmlTag(line.slice(0, index + 1));
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
function findHtmlTags(line) {
|
|
918
|
+
const tags = [];
|
|
919
|
+
let index = 0;
|
|
920
|
+
while (index < line.length) {
|
|
921
|
+
if (line[index] !== "<") {
|
|
922
|
+
index += 1;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
let cursor = index + 1;
|
|
926
|
+
const isClosing = line[cursor] === "/";
|
|
927
|
+
if (isClosing) cursor += 1;
|
|
928
|
+
const nameStart = cursor;
|
|
929
|
+
if (!isAsciiLetter(line[cursor] ?? "")) {
|
|
930
|
+
index += 1;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
while (isHtmlTagNameCharacter(line[cursor] ?? "")) cursor += 1;
|
|
934
|
+
const name = line.slice(nameStart, cursor).toLowerCase();
|
|
935
|
+
let quote = null;
|
|
936
|
+
for (; cursor < line.length; cursor += 1) {
|
|
937
|
+
const character = line[cursor] ?? "";
|
|
938
|
+
if (quote) {
|
|
939
|
+
if (character === quote) quote = null;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (character === '"' || character === "'") {
|
|
943
|
+
quote = character;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (character !== ">") continue;
|
|
947
|
+
const tagBody = line.slice(nameStart, cursor).trimEnd();
|
|
948
|
+
tags.push({
|
|
949
|
+
name,
|
|
950
|
+
isClosing,
|
|
951
|
+
isSelfClosing: !isClosing && tagBody.endsWith("/")
|
|
952
|
+
});
|
|
953
|
+
index = cursor + 1;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
if (cursor >= line.length) break;
|
|
957
|
+
}
|
|
958
|
+
return tags;
|
|
959
|
+
}
|
|
960
|
+
function updateHtmlBlockDepth(htmlBlock, line, skipFirstOpening) {
|
|
961
|
+
if (!htmlBlock.tagName) return htmlBlock;
|
|
962
|
+
let depth = htmlBlock.depth;
|
|
963
|
+
let skippedFirstOpening = !skipFirstOpening;
|
|
964
|
+
for (const tag of findHtmlTags(line)) {
|
|
965
|
+
if (tag.name !== htmlBlock.tagName) continue;
|
|
966
|
+
if (tag.isClosing) {
|
|
967
|
+
depth -= 1;
|
|
968
|
+
} else if (!tag.isSelfClosing) {
|
|
969
|
+
if (!skippedFirstOpening) {
|
|
970
|
+
skippedFirstOpening = true;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
depth += 1;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (depth > 0) return { ...htmlBlock, depth };
|
|
977
|
+
return htmlBlock.endsAtBlankLine ? { ...htmlBlock, endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 } : null;
|
|
978
|
+
}
|
|
979
|
+
function hasSpacedSelfClosingSlash(line) {
|
|
980
|
+
return /\s\/\s*>/.test(line);
|
|
981
|
+
}
|
|
982
|
+
function findHtmlBlockStart(normalizedLine, trimmedLine, allowGenericHtmlBlock) {
|
|
983
|
+
const rawTag = findHtmlBlockTag(normalizedLine, RAW_HTML_BLOCK_TAGS);
|
|
984
|
+
if (rawTag) {
|
|
985
|
+
const completeTag = findCompleteHtmlTag(trimmedLine) ?? findHtmlTagPrefix(trimmedLine);
|
|
986
|
+
if (!completeTag && normalizedLine.startsWith("</")) return null;
|
|
987
|
+
if (completeTag?.isSelfClosing && !hasSpacedSelfClosingSlash(trimmedLine)) return null;
|
|
988
|
+
const endMarker = `</${rawTag}>`;
|
|
989
|
+
if (hasHtmlBlockEndMarker(normalizedLine, endMarker)) {
|
|
990
|
+
if (!completeTag || completeTag.isClosing) return null;
|
|
991
|
+
return { endMarker, endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
992
|
+
}
|
|
993
|
+
return { endMarker, endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
994
|
+
}
|
|
995
|
+
if (normalizedLine.startsWith("<!--")) {
|
|
996
|
+
return { endMarker: "-->", endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
997
|
+
}
|
|
998
|
+
if (normalizedLine.startsWith("<?")) {
|
|
999
|
+
return { endMarker: "?>", endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
1000
|
+
}
|
|
1001
|
+
if (trimmedLine.startsWith("<![CDATA[")) {
|
|
1002
|
+
return { endMarker: "]]>", endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
1003
|
+
}
|
|
1004
|
+
const declarationFirst = trimmedLine[2];
|
|
1005
|
+
if (trimmedLine.startsWith("<!") && declarationFirst && declarationFirst >= "A" && declarationFirst <= "Z") {
|
|
1006
|
+
return { endMarker: ">", endsAtBlankLine: false, tagName: null, depth: 0 };
|
|
1007
|
+
}
|
|
1008
|
+
const blockTag = findHtmlBlockTag(normalizedLine, HTML_BLOCK_TAGS);
|
|
1009
|
+
if (blockTag) {
|
|
1010
|
+
const completeTag = findCompleteHtmlTag(trimmedLine) ?? findHtmlTagPrefix(trimmedLine);
|
|
1011
|
+
if (!completeTag) {
|
|
1012
|
+
return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
|
|
1013
|
+
}
|
|
1014
|
+
if (completeTag.isSelfClosing) {
|
|
1015
|
+
return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
|
|
1016
|
+
}
|
|
1017
|
+
return { endMarker: `</${blockTag}>`, endsAtBlankLine: true, tagName: blockTag, depth: 1 };
|
|
1018
|
+
}
|
|
1019
|
+
if (!allowGenericHtmlBlock) return null;
|
|
1020
|
+
const genericTag = findCompleteHtmlTag(trimmedLine);
|
|
1021
|
+
if (!genericTag) return null;
|
|
1022
|
+
if (genericTag.isClosing || genericTag.isSelfClosing) {
|
|
1023
|
+
return { endMarker: null, endsAtBlankLine: true, tagName: null, depth: 0 };
|
|
1024
|
+
}
|
|
1025
|
+
return { endMarker: `</${genericTag.name}>`, endsAtBlankLine: true, tagName: genericTag.name, depth: 1 };
|
|
1026
|
+
}
|
|
1027
|
+
function parseProfileLines(content) {
|
|
1028
|
+
const segments = content.split(/(\r\n|\n|\r)/);
|
|
1029
|
+
const lines = [];
|
|
1030
|
+
for (let index = 0; index < segments.length; index += 2) {
|
|
1031
|
+
lines.push({
|
|
1032
|
+
content: segments[index] ?? "",
|
|
1033
|
+
ending: segments[index + 1] ?? ""
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
return lines;
|
|
1037
|
+
}
|
|
1038
|
+
function renderProfileLines(lines) {
|
|
1039
|
+
let rendered = "";
|
|
1040
|
+
for (const line of lines) {
|
|
1041
|
+
rendered += line.content + line.ending;
|
|
1042
|
+
}
|
|
1043
|
+
return rendered;
|
|
1044
|
+
}
|
|
1045
|
+
function findFrontmatterEnd(lines) {
|
|
1046
|
+
const firstLine = lines[0]?.content.startsWith(UTF8_BOM) ? lines[0].content.slice(1) : lines[0]?.content;
|
|
1047
|
+
if (firstLine !== "---") return -1;
|
|
1048
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
1049
|
+
const line = lines[index]?.content.trimEnd();
|
|
1050
|
+
if (line === "---" || line === "...") return index;
|
|
1051
|
+
}
|
|
1052
|
+
return -1;
|
|
1053
|
+
}
|
|
1054
|
+
function hasHtmlBlockEndMarker(line, endMarker) {
|
|
1055
|
+
return line.includes(endMarker);
|
|
1056
|
+
}
|
|
1057
|
+
function shouldCloseHtmlBlock(line, endMarker) {
|
|
1058
|
+
const normalizedLine = line.trimStart().toLowerCase();
|
|
1059
|
+
return normalizedLine.includes(endMarker.toLowerCase());
|
|
1060
|
+
}
|
|
1061
|
+
function canStartGenericHtmlBlock(lines, index, previousHtmlTerminator) {
|
|
1062
|
+
const previousLine = lines[index - 1]?.content.trim() ?? "";
|
|
1063
|
+
if (previousLine === "") return true;
|
|
1064
|
+
return isCompletedIndentedCodeBlock(lines, index) || isMetadataBoundary(previousLine, previousHtmlTerminator) && !isLastUpdatedHeader(previousLine) && !MARKDOWN_LIST_ITEM.test(previousLine) && !MARKDOWN_BLOCK_QUOTE.test(previousLine);
|
|
1065
|
+
}
|
|
1066
|
+
function isHtmlBlockBoundary(lines, index, htmlTerminatorIndexes) {
|
|
1067
|
+
if (htmlTerminatorIndexes.has(index)) return true;
|
|
1068
|
+
const line = lines[index]?.content ?? "";
|
|
1069
|
+
const parserLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
|
|
1070
|
+
if (isIndentedCodeLine(parserLine) || getFenceMarker(parserLine)) return false;
|
|
1071
|
+
const trimmedLine = parserLine.trimStart();
|
|
1072
|
+
return findHtmlBlockStart(
|
|
1073
|
+
trimmedLine.toLowerCase(),
|
|
1074
|
+
trimmedLine,
|
|
1075
|
+
canStartGenericHtmlBlock(lines, index, htmlTerminatorIndexes.has(index - 1))
|
|
1076
|
+
) !== null;
|
|
1077
|
+
}
|
|
1078
|
+
function visitProfileMetadataLines(lines, visit, onHtmlBlockTerminator, onFenceBlockTerminator) {
|
|
1079
|
+
const frontmatterEnd = findFrontmatterEnd(lines);
|
|
1080
|
+
const htmlTerminatorIndexes = /* @__PURE__ */ new Set();
|
|
1081
|
+
const markHtmlTerminator = (index) => {
|
|
1082
|
+
htmlTerminatorIndexes.add(index);
|
|
1083
|
+
onHtmlBlockTerminator?.(index);
|
|
1084
|
+
};
|
|
1085
|
+
let openFence = null;
|
|
1086
|
+
let openHtmlBlock = null;
|
|
1087
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1088
|
+
const line = lines[index]?.content ?? "";
|
|
1089
|
+
const parserLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
|
|
1090
|
+
if (index <= frontmatterEnd) continue;
|
|
1091
|
+
const fence = getFenceMarker(parserLine);
|
|
1092
|
+
if (openFence) {
|
|
1093
|
+
const indentation = parserLine.length - parserLine.trimStart().length;
|
|
1094
|
+
const leavesListContainer = openFence.listIndentation !== null && parserLine.trim() !== "" && indentation < openFence.listIndentation;
|
|
1095
|
+
if (!leavesListContainer) {
|
|
1096
|
+
if (fence && isClosingFence(fence, openFence)) openFence = null;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
openFence = null;
|
|
1100
|
+
if (index > 0) onFenceBlockTerminator?.(index - 1);
|
|
1101
|
+
}
|
|
1102
|
+
const trimmedLine = parserLine.trimStart();
|
|
1103
|
+
const normalizedLine = trimmedLine.toLowerCase();
|
|
1104
|
+
if (openHtmlBlock) {
|
|
1105
|
+
const indentation = parserLine.length - parserLine.trimStart().length;
|
|
1106
|
+
const leavesListContainer = openHtmlBlock.listIndentation !== null && parserLine.trim() !== "" && indentation < openHtmlBlock.listIndentation;
|
|
1107
|
+
if (!leavesListContainer) {
|
|
1108
|
+
if (openHtmlBlock.endsAtBlankLine && normalizedLine === "") {
|
|
1109
|
+
openHtmlBlock = null;
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (openHtmlBlock.tagName) {
|
|
1113
|
+
const nextHtmlBlock = updateHtmlBlockDepth(openHtmlBlock, normalizedLine, false);
|
|
1114
|
+
if (!nextHtmlBlock) markHtmlTerminator(index);
|
|
1115
|
+
openHtmlBlock = nextHtmlBlock;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (openHtmlBlock.endMarker && shouldCloseHtmlBlock(normalizedLine, openHtmlBlock.endMarker)) {
|
|
1119
|
+
openHtmlBlock = null;
|
|
1120
|
+
markHtmlTerminator(index);
|
|
1121
|
+
}
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
openHtmlBlock = null;
|
|
1125
|
+
if (index > 0) markHtmlTerminator(index - 1);
|
|
1126
|
+
}
|
|
1127
|
+
if (isIndentedCodeLine(parserLine)) continue;
|
|
1128
|
+
if (fence) {
|
|
1129
|
+
openFence = {
|
|
1130
|
+
...fence,
|
|
1131
|
+
listIndentation: isNestedListContent(lines, index, fence.indentation) ? fence.indentation : null
|
|
1132
|
+
};
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
const htmlBlock = findHtmlBlockStart(
|
|
1136
|
+
normalizedLine,
|
|
1137
|
+
trimmedLine,
|
|
1138
|
+
canStartGenericHtmlBlock(lines, index, htmlTerminatorIndexes.has(index - 1))
|
|
1139
|
+
);
|
|
1140
|
+
if (htmlBlock) {
|
|
1141
|
+
markHtmlTerminator(index);
|
|
1142
|
+
const indentation = parserLine.length - parserLine.trimStart().length;
|
|
1143
|
+
const htmlBlockWithContainer = {
|
|
1144
|
+
...htmlBlock,
|
|
1145
|
+
listIndentation: isNestedListContent(lines, index, indentation) ? indentation : null
|
|
1146
|
+
};
|
|
1147
|
+
if (htmlBlock.tagName) {
|
|
1148
|
+
openHtmlBlock = updateHtmlBlockDepth(htmlBlockWithContainer, normalizedLine, true);
|
|
1149
|
+
if (!openHtmlBlock) markHtmlTerminator(index);
|
|
1150
|
+
} else if (htmlBlock.endMarker && shouldCloseHtmlBlock(normalizedLine, htmlBlock.endMarker)) {
|
|
1151
|
+
openHtmlBlock = null;
|
|
1152
|
+
markHtmlTerminator(index);
|
|
1153
|
+
} else {
|
|
1154
|
+
openHtmlBlock = htmlBlockWithContainer;
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
visit(line, index);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function isCompletedIndentedCodeBlock(lines, index) {
|
|
1162
|
+
if (!isIndentedCodeLine(lines[index - 1]?.content ?? "")) return false;
|
|
1163
|
+
let sawBlank = false;
|
|
1164
|
+
for (let previousIndex = index - 2; previousIndex >= 0; previousIndex -= 1) {
|
|
1165
|
+
const previousLine = lines[previousIndex]?.content ?? "";
|
|
1166
|
+
if (previousLine.trim() === "") {
|
|
1167
|
+
sawBlank = true;
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (isIndentedCodeLine(previousLine)) continue;
|
|
1171
|
+
return sawBlank;
|
|
1172
|
+
}
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
function isListContinuation(lines, index, indentation) {
|
|
1176
|
+
for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
1177
|
+
const previousLine = lines[previousIndex]?.content ?? "";
|
|
1178
|
+
if (previousLine.trim() === "") return false;
|
|
1179
|
+
const previousIndentation = previousLine.length - previousLine.trimStart().length;
|
|
1180
|
+
if (previousIndentation > indentation) continue;
|
|
1181
|
+
const previousBoundaryLine = previousLine.trimStart();
|
|
1182
|
+
if (MARKDOWN_LIST_ITEM.test(previousBoundaryLine)) return true;
|
|
1183
|
+
if (MARKDOWN_HEADING.test(previousBoundaryLine) || MARKDOWN_THEMATIC_BREAK.test(previousBoundaryLine) || MARKDOWN_BLOCK_QUOTE.test(previousBoundaryLine) || getFenceMarker(previousLine) !== null) {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
function isNestedListContent(lines, index, indentation) {
|
|
1190
|
+
for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
1191
|
+
const previousLine = lines[previousIndex]?.content ?? "";
|
|
1192
|
+
if (previousLine.trim() === "") continue;
|
|
1193
|
+
const previousIndentation = previousLine.length - previousLine.trimStart().length;
|
|
1194
|
+
if (previousIndentation >= indentation) continue;
|
|
1195
|
+
if (MARKDOWN_LIST_ITEM.test(previousLine.trimStart()) || isListContinuation(lines, previousIndex, indentation)) {
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
function isValidBareLinkDestination(destination) {
|
|
1203
|
+
let parentheses = 0;
|
|
1204
|
+
for (let index = 0; index < destination.length; index += 1) {
|
|
1205
|
+
const character = destination[index];
|
|
1206
|
+
if (character === "\\") {
|
|
1207
|
+
if (index + 1 >= destination.length) return false;
|
|
1208
|
+
index += 1;
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (character === "<" || character === ">") return false;
|
|
1212
|
+
if (character === "(") {
|
|
1213
|
+
parentheses += 1;
|
|
1214
|
+
} else if (character === ")") {
|
|
1215
|
+
if (parentheses === 0) return false;
|
|
1216
|
+
parentheses -= 1;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return parentheses === 0;
|
|
1220
|
+
}
|
|
1221
|
+
function isMarkdownLinkReferenceDefinition(line) {
|
|
1222
|
+
const match = MARKDOWN_LINK_REFERENCE.exec(line);
|
|
1223
|
+
return match !== null && (match[1] === void 0 || isValidBareLinkDestination(match[1]));
|
|
1224
|
+
}
|
|
1225
|
+
function isMarkdownLinkReferenceDefinitionEnd(lines, index) {
|
|
1226
|
+
const line = lines[index]?.content ?? "";
|
|
1227
|
+
const boundaryLine = /^ {0,3}(?=\S)/.test(line) ? line.trimStart() : line;
|
|
1228
|
+
if (isMarkdownLinkReferenceDefinition(boundaryLine)) return true;
|
|
1229
|
+
if (MARKDOWN_LINK_REFERENCE_CONTINUATION.test(line)) {
|
|
1230
|
+
const previousLine2 = lines[index - 1]?.content ?? "";
|
|
1231
|
+
const previousBoundaryLine2 = /^ {0,3}(?=\S)/.test(previousLine2) ? previousLine2.trimStart() : previousLine2;
|
|
1232
|
+
if (isMarkdownLinkReferenceDefinition(previousBoundaryLine2)) return true;
|
|
1233
|
+
const destinationLine = lines[index - 1]?.content ?? "";
|
|
1234
|
+
if (!MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION.test(destinationLine)) return false;
|
|
1235
|
+
const labelLine = lines[index - 2]?.content ?? "";
|
|
1236
|
+
const labelBoundaryLine = /^ {0,3}(?=\S)/.test(labelLine) ? labelLine.trimStart() : labelLine;
|
|
1237
|
+
return MARKDOWN_LINK_REFERENCE_LABEL.test(labelBoundaryLine);
|
|
1238
|
+
}
|
|
1239
|
+
if (!MARKDOWN_LINK_REFERENCE_DESTINATION_CONTINUATION.test(line)) return false;
|
|
1240
|
+
const previousLine = lines[index - 1]?.content ?? "";
|
|
1241
|
+
const previousBoundaryLine = /^ {0,3}(?=\S)/.test(previousLine) ? previousLine.trimStart() : previousLine;
|
|
1242
|
+
return MARKDOWN_LINK_REFERENCE_LABEL.test(previousBoundaryLine);
|
|
1243
|
+
}
|
|
1244
|
+
function isProfileTitleLine(lines, index, line) {
|
|
1245
|
+
const titleLine = index === 0 && line.startsWith(UTF8_BOM) ? line.slice(1) : line;
|
|
1246
|
+
if (!PROFILE_TITLE.test(titleLine)) return false;
|
|
1247
|
+
const indentation = titleLine.length - titleLine.trimStart().length;
|
|
1248
|
+
if (indentation === 0 || index === 0) return true;
|
|
1249
|
+
const previousLine = lines[index - 1]?.content.trim() ?? "";
|
|
1250
|
+
return previousLine === "" ? !isNestedListContent(lines, index, indentation) : previousLine === "---" || previousLine === "...";
|
|
1251
|
+
}
|
|
1252
|
+
function findProfileTitleIndex(lines) {
|
|
1253
|
+
let titleIndex = -1;
|
|
1254
|
+
visitProfileMetadataLines(lines, (line, index) => {
|
|
1255
|
+
if (titleIndex >= 0) return;
|
|
1256
|
+
if (isProfileTitleLine(lines, index, line)) titleIndex = index;
|
|
1257
|
+
});
|
|
1258
|
+
return titleIndex;
|
|
1259
|
+
}
|
|
1260
|
+
function isSetextHeadingText(line) {
|
|
1261
|
+
const withoutBom = line.startsWith(UTF8_BOM) ? line.slice(1) : line;
|
|
1262
|
+
const boundaryLine = withoutBom.trim();
|
|
1263
|
+
return boundaryLine !== "" && !MARKDOWN_HEADING.test(boundaryLine) && !MARKDOWN_LIST_ITEM.test(boundaryLine) && !MARKDOWN_BLOCK_QUOTE.test(boundaryLine) && !MARKDOWN_THEMATIC_BREAK.test(boundaryLine) && !isMarkdownLinkReferenceDefinition(boundaryLine) && getFenceMarker(withoutBom) === null;
|
|
1264
|
+
}
|
|
1265
|
+
function isMetadataBoundary(line, htmlTerminator = false, allowSetext = true, allowParagraphListInterrupt = false, setextPreviousLine = "") {
|
|
1266
|
+
const listPattern = allowParagraphListInterrupt ? MARKDOWN_LIST_ITEM_CAN_INTERRUPT : MARKDOWN_LIST_ITEM;
|
|
1267
|
+
const boundaryLine = /^ {0,3}(?=\S)/.test(line) ? line.trimStart() : line;
|
|
1268
|
+
const isSetextUnderline = !allowSetext && MARKDOWN_SETEXT_UNDERLINE.test(boundaryLine) && isSetextHeadingText(setextPreviousLine);
|
|
1269
|
+
return line.trim() === "" || MARKDOWN_HEADING.test(boundaryLine) || listPattern.test(boundaryLine) || allowSetext && MARKDOWN_SETEXT_UNDERLINE.test(boundaryLine) || MARKDOWN_THEMATIC_BREAK.test(boundaryLine) && !isSetextUnderline || MARKDOWN_BLOCK_QUOTE.test(boundaryLine) || isMarkdownLinkReferenceDefinition(boundaryLine) || isLastUpdatedHeader(line) || getFenceMarker(line) !== null || htmlTerminator;
|
|
1270
|
+
}
|
|
1271
|
+
function isStandaloneMetadataLine(lines, index, frontmatterEnd, htmlTerminatorIndexes, fenceTerminatorIndexes, standaloneHeaderIndexes) {
|
|
1272
|
+
const currentLine = lines[index]?.content ?? "";
|
|
1273
|
+
const currentWithoutBom = currentLine.startsWith(UTF8_BOM) ? currentLine.slice(1) : currentLine;
|
|
1274
|
+
const indentation = currentWithoutBom.length - currentWithoutBom.trimStart().length;
|
|
1275
|
+
const previousLine = lines[index - 1]?.content.trimEnd() ?? "";
|
|
1276
|
+
const nextLine = lines[index + 1]?.content.trimEnd() ?? "";
|
|
1277
|
+
const previousWithoutBom = previousLine.startsWith(UTF8_BOM) ? previousLine.slice(1) : previousLine;
|
|
1278
|
+
const nextWithoutBom = nextLine.startsWith(UTF8_BOM) ? nextLine.slice(1) : nextLine;
|
|
1279
|
+
const previousLinkReferenceBoundary = isMarkdownLinkReferenceDefinitionEnd(lines, index - 1);
|
|
1280
|
+
const previousMetadataBoundary = isCompletedIndentedCodeBlock(lines, index) || previousLinkReferenceBoundary || fenceTerminatorIndexes.has(index - 1) || isMetadataBoundary(
|
|
1281
|
+
previousWithoutBom,
|
|
1282
|
+
htmlTerminatorIndexes.has(index - 1)
|
|
1283
|
+
) && (!isLastUpdatedHeader(previousWithoutBom) || standaloneHeaderIndexes.has(index - 1));
|
|
1284
|
+
const nextHtmlBlockBoundary = isHtmlBlockBoundary(
|
|
1285
|
+
lines,
|
|
1286
|
+
index + 1,
|
|
1287
|
+
htmlTerminatorIndexes
|
|
1288
|
+
);
|
|
1289
|
+
const nextMetadataBoundary = nextHtmlBlockBoundary || isMetadataBoundary(
|
|
1290
|
+
nextWithoutBom,
|
|
1291
|
+
htmlTerminatorIndexes.has(index + 1),
|
|
1292
|
+
false,
|
|
1293
|
+
true,
|
|
1294
|
+
currentWithoutBom
|
|
1295
|
+
);
|
|
1296
|
+
const previousContainerMarker = !MARKDOWN_THEMATIC_BREAK.test(previousWithoutBom) && (MARKDOWN_LIST_ITEM.test(previousWithoutBom) || MARKDOWN_BLOCK_QUOTE.test(previousWithoutBom));
|
|
1297
|
+
const nestedListContent = indentation > 0 && isNestedListContent(lines, index, indentation);
|
|
1298
|
+
const startsBlock = (index === 0 || index === frontmatterEnd + 1 || previousMetadataBoundary) && !previousContainerMarker && !nestedListContent;
|
|
1299
|
+
const endsBlock = index === lines.length - 1 || nextMetadataBoundary;
|
|
1300
|
+
const compactHeaderAfterTitle = isProfileTitleLine(lines, index - 1, previousWithoutBom);
|
|
1301
|
+
const compactHeaderAfterHeader = isLastUpdatedHeader(previousWithoutBom) && standaloneHeaderIndexes.has(index - 1);
|
|
1302
|
+
return startsBlock && (endsBlock || compactHeaderAfterTitle || compactHeaderAfterHeader);
|
|
1303
|
+
}
|
|
1304
|
+
function findProfileHeaderIndexes(lines) {
|
|
1305
|
+
const indexes = [];
|
|
1306
|
+
const frontmatterEnd = findFrontmatterEnd(lines);
|
|
1307
|
+
const htmlTerminatorIndexes = /* @__PURE__ */ new Set();
|
|
1308
|
+
const fenceTerminatorIndexes = /* @__PURE__ */ new Set();
|
|
1309
|
+
const standaloneHeaderIndexes = /* @__PURE__ */ new Set();
|
|
1310
|
+
visitProfileMetadataLines(
|
|
1311
|
+
lines,
|
|
1312
|
+
(line, index) => {
|
|
1313
|
+
if (isLastUpdatedHeader(line) && isStandaloneMetadataLine(
|
|
1314
|
+
lines,
|
|
1315
|
+
index,
|
|
1316
|
+
frontmatterEnd,
|
|
1317
|
+
htmlTerminatorIndexes,
|
|
1318
|
+
fenceTerminatorIndexes,
|
|
1319
|
+
standaloneHeaderIndexes
|
|
1320
|
+
)) {
|
|
1321
|
+
indexes.push(index);
|
|
1322
|
+
standaloneHeaderIndexes.add(index);
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
(index) => htmlTerminatorIndexes.add(index),
|
|
1326
|
+
(index) => fenceTerminatorIndexes.add(index)
|
|
1327
|
+
);
|
|
1328
|
+
return indexes;
|
|
1329
|
+
}
|
|
1330
|
+
function insertHeaderAfter(lines, index, header) {
|
|
1331
|
+
const insertAt = index + 1;
|
|
1332
|
+
const ending = lines[index]?.ending || lines[insertAt]?.ending || "\n";
|
|
1333
|
+
if (lines[insertAt]?.content.trim() === "") {
|
|
1334
|
+
lines.splice(
|
|
1335
|
+
insertAt + 1,
|
|
1336
|
+
0,
|
|
1337
|
+
{ content: header, ending },
|
|
1338
|
+
{ content: "", ending }
|
|
1339
|
+
);
|
|
1340
|
+
} else {
|
|
1341
|
+
lines.splice(
|
|
1342
|
+
insertAt,
|
|
1343
|
+
0,
|
|
1344
|
+
{ content: "", ending },
|
|
1345
|
+
{ content: header, ending },
|
|
1346
|
+
{ content: "", ending }
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function prependHeader(lines, header) {
|
|
1351
|
+
const frontmatterEnd = findFrontmatterEnd(lines);
|
|
1352
|
+
if (frontmatterEnd >= 0) {
|
|
1353
|
+
insertHeaderAfter(lines, frontmatterEnd, header);
|
|
1354
|
+
return renderProfileLines(lines);
|
|
1355
|
+
}
|
|
1356
|
+
const ending = lines[0]?.ending || "\n";
|
|
1357
|
+
if (lines[0]?.content.startsWith(UTF8_BOM)) {
|
|
1358
|
+
lines[0].content = lines[0].content.slice(1);
|
|
1359
|
+
lines.unshift(
|
|
1360
|
+
{ content: `${UTF8_BOM}${header}`, ending },
|
|
1361
|
+
{ content: "", ending }
|
|
1362
|
+
);
|
|
1363
|
+
return renderProfileLines(lines);
|
|
1364
|
+
}
|
|
1365
|
+
lines.unshift(
|
|
1366
|
+
{ content: header, ending },
|
|
1367
|
+
{ content: "", ending }
|
|
1368
|
+
);
|
|
1369
|
+
return renderProfileLines(lines);
|
|
1370
|
+
}
|
|
1371
|
+
function renderProfileWithLastUpdated(content, updatedAt) {
|
|
1372
|
+
const lines = parseProfileLines(content);
|
|
1373
|
+
const header = `*Last updated: ${updatedAt}*`;
|
|
1374
|
+
const titleIndex = findProfileTitleIndex(lines);
|
|
1375
|
+
const headerIndexes = findProfileHeaderIndexes(lines);
|
|
1376
|
+
if (headerIndexes.length > 0) {
|
|
1377
|
+
const firstHeaderIndex = headerIndexes[0];
|
|
1378
|
+
if (firstHeaderIndex === void 0) return renderProfileLines(lines);
|
|
1379
|
+
const canonicalIndex = titleIndex >= 0 ? headerIndexes.find((index) => index > titleIndex) ?? firstHeaderIndex : firstHeaderIndex;
|
|
1380
|
+
const preserveBomAtFileStart = headerIndexes.some(
|
|
1381
|
+
(index) => lines[index].content.startsWith(UTF8_BOM)
|
|
1382
|
+
);
|
|
1383
|
+
const bomPrefix = canonicalIndex === 0 && lines[canonicalIndex].content.startsWith(UTF8_BOM) ? UTF8_BOM : "";
|
|
1384
|
+
lines[canonicalIndex].content = `${bomPrefix}${header}`;
|
|
1385
|
+
for (let index = headerIndexes.length - 1; index >= 0; index -= 1) {
|
|
1386
|
+
const headerIndex = headerIndexes[index];
|
|
1387
|
+
if (headerIndex === void 0 || headerIndex === canonicalIndex) continue;
|
|
1388
|
+
lines.splice(headerIndex, 1);
|
|
1389
|
+
}
|
|
1390
|
+
if (preserveBomAtFileStart && !lines[0].content.startsWith(UTF8_BOM)) {
|
|
1391
|
+
lines[0].content = `${UTF8_BOM}${lines[0].content}`;
|
|
1392
|
+
}
|
|
1393
|
+
return renderProfileLines(lines);
|
|
1394
|
+
}
|
|
1395
|
+
if (titleIndex < 0) return prependHeader(lines, header);
|
|
1396
|
+
insertHeaderAfter(lines, titleIndex, header);
|
|
1397
|
+
return renderProfileLines(lines);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
708
1400
|
// src/orchestration/self-deps.ts
|
|
709
1401
|
function selfDeps(self) {
|
|
710
1402
|
const source = self;
|
|
@@ -725,8 +1417,748 @@ function selfDeps(self) {
|
|
|
725
1417
|
}
|
|
726
1418
|
|
|
727
1419
|
// src/storage/entity-store.ts
|
|
728
|
-
import { readdir as
|
|
1420
|
+
import { readdir as readdir3, unlink as unlink2 } from "fs/promises";
|
|
1421
|
+
import path4 from "path";
|
|
1422
|
+
|
|
1423
|
+
// src/storage/entity-canonical-id-migration.ts
|
|
1424
|
+
import { randomUUID } from "crypto";
|
|
1425
|
+
import {
|
|
1426
|
+
access,
|
|
1427
|
+
lstat as lstat2,
|
|
1428
|
+
readFile,
|
|
1429
|
+
readdir as readdir2,
|
|
1430
|
+
realpath as realpath2,
|
|
1431
|
+
rename,
|
|
1432
|
+
stat as statFile,
|
|
1433
|
+
unlink,
|
|
1434
|
+
writeFile
|
|
1435
|
+
} from "fs/promises";
|
|
729
1436
|
import path3 from "path";
|
|
1437
|
+
import { readFileSync } from "fs";
|
|
1438
|
+
import { z } from "zod";
|
|
1439
|
+
var ENTITY_CANONICAL_ID_MIGRATION_VERSION = 1;
|
|
1440
|
+
var ENTITY_CANONICAL_ID_MIGRATION_FILE = "entity-canonical-id-migration-v1.json";
|
|
1441
|
+
var ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS = 6e4;
|
|
1442
|
+
var ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS = 3e5;
|
|
1443
|
+
var TOMBSTONE_LOCK_STALE_MS = 3e4;
|
|
1444
|
+
var TOMBSTONE_LOCK_MAX_WAIT_MS = 5e3;
|
|
1445
|
+
var ENTITY_CANONICAL_ID_MUTATION_LOCK_STALE_MS = 6e4;
|
|
1446
|
+
var ENTITY_CANONICAL_ID_MUTATION_LOCK_MAX_WAIT_MS = 3e5;
|
|
1447
|
+
var MEMORY_REWRITE_MAX_PASSES = 3;
|
|
1448
|
+
var ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;
|
|
1449
|
+
function loadHistoricalEntityCanonicalIds(stateDir) {
|
|
1450
|
+
const statePath2 = path3.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
|
|
1451
|
+
try {
|
|
1452
|
+
const parsed = JSON.parse(readFileSync(statePath2, "utf-8"));
|
|
1453
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
1454
|
+
const mappings = parsed.mappings;
|
|
1455
|
+
if (!mappings || typeof mappings !== "object" || Array.isArray(mappings)) return {};
|
|
1456
|
+
const cleaned = {};
|
|
1457
|
+
for (const [legacyId, canonicalId] of Object.entries(mappings)) {
|
|
1458
|
+
if (legacyId.length > 0 && typeof canonicalId === "string" && canonicalId.length > 0) {
|
|
1459
|
+
cleaned[legacyId] = canonicalId;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return cleaned;
|
|
1463
|
+
} catch {
|
|
1464
|
+
return {};
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function resolveHistoricalEntityCanonicalId(normalized, mappings) {
|
|
1468
|
+
let current = normalized;
|
|
1469
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1470
|
+
while (!seen.has(current)) {
|
|
1471
|
+
seen.add(current);
|
|
1472
|
+
const next = mappings[current];
|
|
1473
|
+
if (!next || next === current) break;
|
|
1474
|
+
current = next;
|
|
1475
|
+
}
|
|
1476
|
+
return current;
|
|
1477
|
+
}
|
|
1478
|
+
var EntityCanonicalIdMigrationStateSchema = z.object({
|
|
1479
|
+
version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
|
|
1480
|
+
complete: z.boolean(),
|
|
1481
|
+
mappings: z.record(z.string().min(1))
|
|
1482
|
+
});
|
|
1483
|
+
async function fingerprintPath(filePath) {
|
|
1484
|
+
let fileStat;
|
|
1485
|
+
try {
|
|
1486
|
+
fileStat = await lstat2(filePath);
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
1489
|
+
}
|
|
1490
|
+
return fileStat ? `${fileStat.dev}:${fileStat.ino}:${fileStat.mtimeMs}:${fileStat.ctimeMs}:${fileStat.size}` : "missing";
|
|
1491
|
+
}
|
|
1492
|
+
async function getFingerprint(baseDir, entitiesDir, getCorpusScanVersion) {
|
|
1493
|
+
const [entityFingerprint, aliasFingerprint] = await Promise.all([
|
|
1494
|
+
fingerprintPath(entitiesDir),
|
|
1495
|
+
fingerprintPath(path3.join(baseDir, "config", "aliases.json"))
|
|
1496
|
+
]);
|
|
1497
|
+
return `${entityFingerprint}:${aliasFingerprint}:${getCorpusScanVersion()}`;
|
|
1498
|
+
}
|
|
1499
|
+
async function validateRoots(baseDir, entitiesDir, stateDir, archiveDir) {
|
|
1500
|
+
let baseStat;
|
|
1501
|
+
try {
|
|
1502
|
+
baseStat = await lstat2(baseDir);
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {
|
|
1508
|
+
throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${baseDir}.`);
|
|
1509
|
+
}
|
|
1510
|
+
const memoryRoot = await realpath2(baseDir);
|
|
1511
|
+
const roots = [
|
|
1512
|
+
entitiesDir,
|
|
1513
|
+
stateDir,
|
|
1514
|
+
...RECALL_FALLBACK_DIRS.map((directory) => path3.join(baseDir, directory)),
|
|
1515
|
+
path3.join(baseDir, "cold"),
|
|
1516
|
+
archiveDir
|
|
1517
|
+
];
|
|
1518
|
+
for (const root of roots) {
|
|
1519
|
+
let rootStat;
|
|
1520
|
+
try {
|
|
1521
|
+
rootStat = await lstat2(root);
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
if (isErrnoCode(error, "ENOENT")) continue;
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
1527
|
+
throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${root}.`);
|
|
1528
|
+
}
|
|
1529
|
+
assertPathInsideRoot(memoryRoot, await realpath2(root), root);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
function statePath(deps) {
|
|
1533
|
+
return path3.join(deps.stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
|
|
1534
|
+
}
|
|
1535
|
+
async function assertSafeMigrationStateFile(deps) {
|
|
1536
|
+
const migrationStatePath = statePath(deps);
|
|
1537
|
+
let fileStat;
|
|
1538
|
+
try {
|
|
1539
|
+
fileStat = await lstat2(migrationStatePath);
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
1542
|
+
throw error;
|
|
1543
|
+
}
|
|
1544
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
|
|
1545
|
+
throw new Error(
|
|
1546
|
+
`Refusing entity canonical-id migration through unsafe migration state file: ${migrationStatePath}.`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
const [stateRoot, stateFile] = await Promise.all([
|
|
1550
|
+
realpath2(deps.stateDir),
|
|
1551
|
+
realpath2(migrationStatePath)
|
|
1552
|
+
]);
|
|
1553
|
+
assertPathInsideRoot(stateRoot, stateFile, migrationStatePath);
|
|
1554
|
+
}
|
|
1555
|
+
async function assertSafeTombstoneFile(deps, tombstonePath) {
|
|
1556
|
+
let fileStat;
|
|
1557
|
+
try {
|
|
1558
|
+
fileStat = await lstat2(tombstonePath);
|
|
1559
|
+
} catch (error) {
|
|
1560
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
1561
|
+
throw error;
|
|
1562
|
+
}
|
|
1563
|
+
if (fileStat.isSymbolicLink() || !fileStat.isFile()) {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`Refusing entity canonical-id migration through unsafe tombstone ledger: ${tombstonePath}.`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
const [stateRoot, tombstoneFile] = await Promise.all([
|
|
1569
|
+
realpath2(deps.stateDir),
|
|
1570
|
+
realpath2(tombstonePath)
|
|
1571
|
+
]);
|
|
1572
|
+
assertPathInsideRoot(stateRoot, tombstoneFile, tombstonePath);
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
function lockPath(deps) {
|
|
1576
|
+
return path3.join(deps.stateDir, "entity-canonical-id-migration.lock");
|
|
1577
|
+
}
|
|
1578
|
+
async function withEntityCanonicalMutationLock(stateDir, task) {
|
|
1579
|
+
return withHeldFileLock(
|
|
1580
|
+
path3.join(stateDir, "entity-canonical-id-mutation.lock"),
|
|
1581
|
+
{
|
|
1582
|
+
staleMs: ENTITY_CANONICAL_ID_MUTATION_LOCK_STALE_MS,
|
|
1583
|
+
maxWaitMs: ENTITY_CANONICAL_ID_MUTATION_LOCK_MAX_WAIT_MS
|
|
1584
|
+
},
|
|
1585
|
+
async (acquired, lock) => {
|
|
1586
|
+
if (!acquired) throw new Error("Timed out waiting for entity mutation lock.");
|
|
1587
|
+
return task(() => lock.refresh());
|
|
1588
|
+
}
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
async function readState(deps) {
|
|
1592
|
+
await assertSafeMigrationStateFile(deps);
|
|
1593
|
+
let raw;
|
|
1594
|
+
try {
|
|
1595
|
+
raw = await readFile(statePath(deps), "utf-8");
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
if (isErrnoCode(error, "ENOENT")) return null;
|
|
1598
|
+
throw error;
|
|
1599
|
+
}
|
|
1600
|
+
let parsed;
|
|
1601
|
+
try {
|
|
1602
|
+
parsed = JSON.parse(raw);
|
|
1603
|
+
} catch {
|
|
1604
|
+
throw new Error("Invalid entity canonical-id migration state.");
|
|
1605
|
+
}
|
|
1606
|
+
const result = EntityCanonicalIdMigrationStateSchema.safeParse(parsed);
|
|
1607
|
+
if (!result.success) throw new Error("Invalid entity canonical-id migration state.");
|
|
1608
|
+
return result.data;
|
|
1609
|
+
}
|
|
1610
|
+
async function writeState(deps, state) {
|
|
1611
|
+
const destination = statePath(deps);
|
|
1612
|
+
const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;
|
|
1613
|
+
try {
|
|
1614
|
+
await writeFile(temporary, `${JSON.stringify(state, null, 2)}
|
|
1615
|
+
`, "utf-8");
|
|
1616
|
+
await rename(temporary, destination);
|
|
1617
|
+
} finally {
|
|
1618
|
+
await unlink(temporary).catch(() => void 0);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
async function fileExists(filePath) {
|
|
1622
|
+
try {
|
|
1623
|
+
await access(filePath);
|
|
1624
|
+
return true;
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
1627
|
+
throw error;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async function sameFileIdentity(leftPath, rightPath) {
|
|
1631
|
+
try {
|
|
1632
|
+
const [left, right] = await Promise.all([statFile(leftPath), statFile(rightPath)]);
|
|
1633
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
1636
|
+
throw error;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function assertSafeEntityId(entityId) {
|
|
1640
|
+
if (/[\/\\\0]/.test(entityId)) {
|
|
1641
|
+
throw new Error(`Refusing entity canonical-id migration through unsafe entity id: ${entityId}.`);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
async function assertNotSymlink(filePath, label) {
|
|
1645
|
+
let stat4;
|
|
1646
|
+
try {
|
|
1647
|
+
stat4 = await lstat2(filePath);
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
1650
|
+
throw error;
|
|
1651
|
+
}
|
|
1652
|
+
if (stat4.isSymbolicLink()) {
|
|
1653
|
+
throw new Error(`Refusing entity canonical-id migration through symlink: ${label}.`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
async function assertNotMemorySymlink(filePath) {
|
|
1657
|
+
let stat4;
|
|
1658
|
+
try {
|
|
1659
|
+
stat4 = await lstat2(filePath);
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1664
|
+
if (stat4.isSymbolicLink()) {
|
|
1665
|
+
throw new Error(`Refusing entity canonical-id migration through symlinked memory: ${filePath}.`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
async function assertEntityContentUnchanged(deps, filePath, expectedContent, legacyId) {
|
|
1669
|
+
const latestContent = await deps.readStorageSecureFile(filePath);
|
|
1670
|
+
if (latestContent !== expectedContent) {
|
|
1671
|
+
throw new Error(`Legacy entity id ${legacyId} changed while migration was running; retry migration.`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function readSafeEntityEntries(entitiesDir) {
|
|
1675
|
+
const rootStat = await lstat2(entitiesDir);
|
|
1676
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
1677
|
+
throw new Error("Refusing entity canonical-id migration through a symlinked or non-directory entities root.");
|
|
1678
|
+
}
|
|
1679
|
+
const entries = await readdir2(entitiesDir);
|
|
1680
|
+
for (const entry of entries) {
|
|
1681
|
+
await assertNotSymlink(path3.join(entitiesDir, entry), entry);
|
|
1682
|
+
}
|
|
1683
|
+
return entries;
|
|
1684
|
+
}
|
|
1685
|
+
async function discoverMappings(deps, knownMappings = {}) {
|
|
1686
|
+
const mappings = {};
|
|
1687
|
+
const canonicalOwners = /* @__PURE__ */ new Map();
|
|
1688
|
+
const entityEntries = await readSafeEntityEntries(deps.entitiesDir);
|
|
1689
|
+
for (const entry of entityEntries) {
|
|
1690
|
+
if (!entry.endsWith(".md")) continue;
|
|
1691
|
+
const legacyId = entry.slice(0, -".md".length);
|
|
1692
|
+
assertSafeEntityId(legacyId);
|
|
1693
|
+
const legacyPath = deps.resolveEntityFilePath(legacyId);
|
|
1694
|
+
if (legacyPath === null) continue;
|
|
1695
|
+
const entityContent = await deps.readStorageSecureFile(legacyPath);
|
|
1696
|
+
if (!/^#[ \t]+[^\r\n \t]/m.test(entityContent) || !/^\*\*Type:\*\*[ \t]*[^\r\n \t]/m.test(entityContent)) {
|
|
1697
|
+
throw new Error(`Cannot migrate malformed entity file ${entry}: missing name heading or type.`);
|
|
1698
|
+
}
|
|
1699
|
+
const entity = deps.parseEntityFile(entityContent);
|
|
1700
|
+
if (!entity.name.trim() || !entity.type.trim()) {
|
|
1701
|
+
throw new Error(`Cannot migrate malformed entity file ${entry}: missing name or type.`);
|
|
1702
|
+
}
|
|
1703
|
+
const canonicalId = deps.normalizeEntityName(entity.name, entity.type);
|
|
1704
|
+
assertSafeEntityId(canonicalId);
|
|
1705
|
+
if (legacyId === canonicalId) continue;
|
|
1706
|
+
const canonicalPath = deps.resolveEntityFilePath(canonicalId);
|
|
1707
|
+
if (canonicalPath === null) continue;
|
|
1708
|
+
const canonicalExists = await fileExists(canonicalPath);
|
|
1709
|
+
if (canonicalExists && await sameFileIdentity(legacyPath, canonicalPath) && knownMappings[legacyId] === canonicalId) continue;
|
|
1710
|
+
if (canonicalExists) {
|
|
1711
|
+
const canonicalContent2 = await deps.readStorageSecureFile(canonicalPath);
|
|
1712
|
+
if (entityContent !== canonicalContent2) {
|
|
1713
|
+
throw new Error(`Cannot migrate legacy entity id ${legacyId}: ${canonicalId} already exists.`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
const previousOwner = canonicalOwners.get(canonicalId);
|
|
1717
|
+
if (previousOwner !== void 0 && previousOwner !== legacyId) {
|
|
1718
|
+
throw new Error(
|
|
1719
|
+
`Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
canonicalOwners.set(canonicalId, legacyId);
|
|
1723
|
+
mappings[legacyId] = canonicalId;
|
|
1724
|
+
}
|
|
1725
|
+
return mappings;
|
|
1726
|
+
}
|
|
1727
|
+
async function rewriteReferences(deps, mappings, refreshLock) {
|
|
1728
|
+
let rewroteColdMemory = false;
|
|
1729
|
+
for (let pass = 0; pass < MEMORY_REWRITE_MAX_PASSES; pass += 1) {
|
|
1730
|
+
deps.invalidateAllMemoriesCache();
|
|
1731
|
+
deps.invalidateColdMemoriesCache();
|
|
1732
|
+
const [hotMemories, coldMemories, archivedMemories] = await Promise.all([
|
|
1733
|
+
deps.readAllMemories(),
|
|
1734
|
+
deps.readAllColdMemories(),
|
|
1735
|
+
deps.readArchivedMemories()
|
|
1736
|
+
]);
|
|
1737
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
1738
|
+
let rewroteAny = false;
|
|
1739
|
+
for (const memory of [...hotMemories, ...coldMemories, ...archivedMemories]) {
|
|
1740
|
+
const memoryPath = path3.resolve(memory.path);
|
|
1741
|
+
if (seenPaths.has(memoryPath)) continue;
|
|
1742
|
+
seenPaths.add(memoryPath);
|
|
1743
|
+
const previousEntityRef = memory.frontmatter.entityRef;
|
|
1744
|
+
const canonicalId = previousEntityRef ? mappings[previousEntityRef] : void 0;
|
|
1745
|
+
if (!previousEntityRef || !canonicalId) continue;
|
|
1746
|
+
await assertNotMemorySymlink(memory.path);
|
|
1747
|
+
const observedMemoryFingerprint = await fingerprintPath(memory.path);
|
|
1748
|
+
if (observedMemoryFingerprint === "missing") continue;
|
|
1749
|
+
if (!await refreshLock()) throw new Error("Lost entity canonical-id migration lock.");
|
|
1750
|
+
const latestMemory = await deps.readMemoryByPath(memory.path);
|
|
1751
|
+
if (!latestMemory) continue;
|
|
1752
|
+
const latestMemoryFingerprint = await fingerprintPath(latestMemory.path);
|
|
1753
|
+
if (latestMemoryFingerprint !== observedMemoryFingerprint) continue;
|
|
1754
|
+
const latestPreviousEntityRef = latestMemory.frontmatter.entityRef;
|
|
1755
|
+
const latestCanonicalId = latestPreviousEntityRef ? mappings[latestPreviousEntityRef] : void 0;
|
|
1756
|
+
if (!latestPreviousEntityRef || !latestCanonicalId) continue;
|
|
1757
|
+
const memoryEncrypted = await deps.isEncryptedStorageFile(latestMemory.path);
|
|
1758
|
+
if (await fingerprintPath(latestMemory.path) !== latestMemoryFingerprint) continue;
|
|
1759
|
+
await deps.snapshotBeforeWrite(latestMemory.path, "write");
|
|
1760
|
+
if (await fingerprintPath(latestMemory.path) !== latestMemoryFingerprint) continue;
|
|
1761
|
+
await deps.writeStorageSecureFile(
|
|
1762
|
+
latestMemory.path,
|
|
1763
|
+
deps.serializeMemoryWithEntityRef(latestMemory, latestCanonicalId),
|
|
1764
|
+
memoryEncrypted
|
|
1765
|
+
);
|
|
1766
|
+
await deps.rewriteProjectedMemoryEntityReference(
|
|
1767
|
+
latestMemory.frontmatter.id,
|
|
1768
|
+
latestPreviousEntityRef,
|
|
1769
|
+
latestCanonicalId
|
|
1770
|
+
);
|
|
1771
|
+
rewroteAny = true;
|
|
1772
|
+
rewroteColdMemory ||= latestMemory.path.includes(`${path3.sep}cold${path3.sep}`);
|
|
1773
|
+
}
|
|
1774
|
+
if (!rewroteAny) return { rewroteColdMemory };
|
|
1775
|
+
}
|
|
1776
|
+
throw new Error("Memory references changed while entity canonical-id migration was running; retry migration.");
|
|
1777
|
+
}
|
|
1778
|
+
function rewriteTombstoneLine(line, mappings, normalizedMappings) {
|
|
1779
|
+
let parsed;
|
|
1780
|
+
try {
|
|
1781
|
+
parsed = JSON.parse(line);
|
|
1782
|
+
} catch {
|
|
1783
|
+
return line;
|
|
1784
|
+
}
|
|
1785
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return line;
|
|
1786
|
+
const tombstone = parsed;
|
|
1787
|
+
let changed = false;
|
|
1788
|
+
if (typeof tombstone.entityRef === "string") {
|
|
1789
|
+
const canonicalId = mappings[tombstone.entityRef];
|
|
1790
|
+
if (canonicalId) {
|
|
1791
|
+
tombstone.entityRef = canonicalId;
|
|
1792
|
+
changed = true;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (typeof tombstone.supersessionKey === "string") {
|
|
1796
|
+
const separator = tombstone.supersessionKey.indexOf("::");
|
|
1797
|
+
if (separator < 0) return changed ? JSON.stringify(tombstone) : line;
|
|
1798
|
+
const normalizedLegacyId = normalizeSupersessionKey(tombstone.supersessionKey.slice(0, separator));
|
|
1799
|
+
const canonicalId = normalizedMappings.get(normalizedLegacyId);
|
|
1800
|
+
if (canonicalId) {
|
|
1801
|
+
const canonicalKey = computeSupersessionKey(canonicalId, tombstone.supersessionKey.slice(separator + 2));
|
|
1802
|
+
if (canonicalKey) {
|
|
1803
|
+
tombstone.supersessionKey = canonicalKey;
|
|
1804
|
+
changed = true;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return changed ? JSON.stringify(tombstone) : line;
|
|
1809
|
+
}
|
|
1810
|
+
async function rewriteTombstoneReferences(deps, mappings, refreshLock) {
|
|
1811
|
+
const tombstonePath = path3.join(deps.stateDir, "tombstones.jsonl");
|
|
1812
|
+
const normalizedMappings = /* @__PURE__ */ new Map();
|
|
1813
|
+
for (const [legacyId, canonicalId] of Object.entries(mappings)) {
|
|
1814
|
+
const normalizedLegacyId = normalizeSupersessionKey(legacyId);
|
|
1815
|
+
if (normalizedLegacyId && !normalizedMappings.has(normalizedLegacyId)) {
|
|
1816
|
+
normalizedMappings.set(normalizedLegacyId, canonicalId);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
await withHeldFileLock(
|
|
1820
|
+
path3.join(deps.stateDir, "tombstones.lock"),
|
|
1821
|
+
{
|
|
1822
|
+
staleMs: TOMBSTONE_LOCK_STALE_MS,
|
|
1823
|
+
maxWaitMs: TOMBSTONE_LOCK_MAX_WAIT_MS
|
|
1824
|
+
},
|
|
1825
|
+
async (acquired, tombstoneLock) => {
|
|
1826
|
+
if (!acquired) throw new Error("Timed out waiting for active tombstone migration.");
|
|
1827
|
+
if (!await assertSafeTombstoneFile(deps, tombstonePath)) return;
|
|
1828
|
+
const tombstoneEncrypted = await deps.isEncryptedStorageFile(tombstonePath);
|
|
1829
|
+
const original = await deps.readStorageSecureFile(tombstonePath);
|
|
1830
|
+
let changed = false;
|
|
1831
|
+
const rewritten = original.split("\n").map((line) => {
|
|
1832
|
+
const next = rewriteTombstoneLine(line, mappings, normalizedMappings);
|
|
1833
|
+
changed ||= next !== line;
|
|
1834
|
+
return next;
|
|
1835
|
+
}).join("\n");
|
|
1836
|
+
if (!changed) return;
|
|
1837
|
+
if (!await refreshLock()) throw new Error("Lost entity canonical-id migration lock.");
|
|
1838
|
+
if (!await tombstoneLock.refresh()) throw new Error("Lost tombstone migration lock.");
|
|
1839
|
+
await deps.snapshotBeforeWrite(tombstonePath, "write");
|
|
1840
|
+
await deps.writeStorageSecureFile(tombstonePath, rewritten, tombstoneEncrypted);
|
|
1841
|
+
}
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
async function rewriteRelationshipTargets(deps, mappings, refreshLock) {
|
|
1845
|
+
let rewritten = false;
|
|
1846
|
+
const entries = await readSafeEntityEntries(deps.entitiesDir);
|
|
1847
|
+
for (const entry of entries) {
|
|
1848
|
+
if (!entry.endsWith(".md")) continue;
|
|
1849
|
+
const entityPath = deps.resolveEntityFilePath(entry.slice(0, -".md".length));
|
|
1850
|
+
if (entityPath === null) continue;
|
|
1851
|
+
const observedEntityFingerprint = await fingerprintPath(entityPath);
|
|
1852
|
+
if (observedEntityFingerprint === "missing") continue;
|
|
1853
|
+
const entity = deps.parseEntityFile(await deps.readStorageSecureFile(entityPath));
|
|
1854
|
+
const relationships = entity.relationships.map((relationship) => {
|
|
1855
|
+
const target = mappings[relationship.target];
|
|
1856
|
+
return target ? { ...relationship, target } : relationship;
|
|
1857
|
+
});
|
|
1858
|
+
if (relationships.every((relationship, index) => relationship === entity.relationships[index])) continue;
|
|
1859
|
+
const rewroteEntity = await withEntityCanonicalMutationLock(
|
|
1860
|
+
deps.stateDir,
|
|
1861
|
+
async (refreshEntityLock) => {
|
|
1862
|
+
if (!await refreshLock()) throw new Error("Lost entity canonical-id migration lock.");
|
|
1863
|
+
const latestEntity = deps.parseEntityFile(await deps.readStorageSecureFile(entityPath));
|
|
1864
|
+
if (await fingerprintPath(entityPath) !== observedEntityFingerprint) return false;
|
|
1865
|
+
const latestRelationships = latestEntity.relationships.map((relationship) => {
|
|
1866
|
+
const target = mappings[relationship.target];
|
|
1867
|
+
return target ? { ...relationship, target } : relationship;
|
|
1868
|
+
});
|
|
1869
|
+
if (latestRelationships.every((relationship, index) => relationship === latestEntity.relationships[index])) {
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
const entityEncrypted = await deps.isEncryptedStorageFile(entityPath);
|
|
1873
|
+
if (!await refreshEntityLock()) throw new Error("Lost entity mutation lock.");
|
|
1874
|
+
if (await fingerprintPath(entityPath) !== observedEntityFingerprint) return false;
|
|
1875
|
+
await deps.snapshotBeforeWrite(entityPath, "write");
|
|
1876
|
+
if (await fingerprintPath(entityPath) !== observedEntityFingerprint) return false;
|
|
1877
|
+
await deps.writeStorageSecureFile(
|
|
1878
|
+
entityPath,
|
|
1879
|
+
deps.serializeEntityFile({ ...latestEntity, relationships: latestRelationships }),
|
|
1880
|
+
entityEncrypted
|
|
1881
|
+
);
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
);
|
|
1885
|
+
rewritten ||= rewroteEntity;
|
|
1886
|
+
}
|
|
1887
|
+
return rewritten;
|
|
1888
|
+
}
|
|
1889
|
+
async function rewriteKnownReferences(deps, mappings, refreshLock) {
|
|
1890
|
+
await rewriteRelationshipTargets(deps, mappings, refreshLock);
|
|
1891
|
+
let rewroteColdMemory = (await rewriteReferences(deps, mappings, refreshLock)).rewroteColdMemory;
|
|
1892
|
+
await rewriteTombstoneReferences(deps, mappings, refreshLock);
|
|
1893
|
+
await deps.rewriteProjectedEntityReferences(mappings);
|
|
1894
|
+
const finalMemoryPass = await rewriteReferences(deps, mappings, refreshLock);
|
|
1895
|
+
rewroteColdMemory ||= finalMemoryPass.rewroteColdMemory;
|
|
1896
|
+
return rewroteColdMemory;
|
|
1897
|
+
}
|
|
1898
|
+
function wouldCreateMappingCycle(mappings, legacyId, canonicalId) {
|
|
1899
|
+
let current = canonicalId;
|
|
1900
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1901
|
+
while (true) {
|
|
1902
|
+
if (current === legacyId) return true;
|
|
1903
|
+
if (seen.has(current)) return false;
|
|
1904
|
+
seen.add(current);
|
|
1905
|
+
const next = mappings[current];
|
|
1906
|
+
if (!next || next === current) return false;
|
|
1907
|
+
current = next;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function rejectMappingCycles(existing, discovered) {
|
|
1911
|
+
const accepted = {};
|
|
1912
|
+
const merged = { ...existing };
|
|
1913
|
+
for (const [legacyId, canonicalId] of Object.entries(discovered)) {
|
|
1914
|
+
if (merged[legacyId] === void 0 && wouldCreateMappingCycle(merged, legacyId, canonicalId)) continue;
|
|
1915
|
+
accepted[legacyId] = canonicalId;
|
|
1916
|
+
merged[legacyId] = canonicalId;
|
|
1917
|
+
}
|
|
1918
|
+
return accepted;
|
|
1919
|
+
}
|
|
1920
|
+
function mergeDiscoveredMappings(existing, discovered) {
|
|
1921
|
+
const merged = { ...existing };
|
|
1922
|
+
const canonicalOwners = /* @__PURE__ */ new Map();
|
|
1923
|
+
for (const [legacyId, canonicalId] of Object.entries(discovered)) {
|
|
1924
|
+
const previousMapping = merged[legacyId];
|
|
1925
|
+
if (previousMapping !== void 0 && previousMapping !== canonicalId) {
|
|
1926
|
+
throw new Error(`Legacy entity id ${legacyId} changed canonical target during migration.`);
|
|
1927
|
+
}
|
|
1928
|
+
if (previousMapping === void 0 && wouldCreateMappingCycle(merged, legacyId, canonicalId)) continue;
|
|
1929
|
+
const previousOwner = canonicalOwners.get(canonicalId);
|
|
1930
|
+
if (previousOwner !== void 0 && previousOwner !== legacyId) {
|
|
1931
|
+
throw new Error(
|
|
1932
|
+
`Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
merged[legacyId] = canonicalId;
|
|
1936
|
+
canonicalOwners.set(canonicalId, legacyId);
|
|
1937
|
+
}
|
|
1938
|
+
return merged;
|
|
1939
|
+
}
|
|
1940
|
+
function collapseMappings(mappings) {
|
|
1941
|
+
const collapsed = {};
|
|
1942
|
+
for (const legacyId of Object.keys(mappings)) {
|
|
1943
|
+
let current = legacyId;
|
|
1944
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1945
|
+
while (true) {
|
|
1946
|
+
const next = mappings[current];
|
|
1947
|
+
if (!next || next === current) break;
|
|
1948
|
+
if (seen.has(current)) {
|
|
1949
|
+
throw new Error(`Entity canonical-id migration mapping cycle includes ${legacyId}.`);
|
|
1950
|
+
}
|
|
1951
|
+
seen.add(current);
|
|
1952
|
+
current = next;
|
|
1953
|
+
}
|
|
1954
|
+
if (current !== legacyId) collapsed[legacyId] = current;
|
|
1955
|
+
}
|
|
1956
|
+
return collapsed;
|
|
1957
|
+
}
|
|
1958
|
+
async function migrateEntityFilePair(deps, legacyId, canonicalId, refreshMigrationLock) {
|
|
1959
|
+
return withEntityCanonicalMutationLock(deps.stateDir, async (refreshEntityLock) => {
|
|
1960
|
+
assertSafeEntityId(legacyId);
|
|
1961
|
+
assertSafeEntityId(canonicalId);
|
|
1962
|
+
const legacyPath = deps.resolveEntityFilePath(legacyId);
|
|
1963
|
+
const canonicalPath = deps.resolveEntityFilePath(canonicalId);
|
|
1964
|
+
if (legacyPath === null || canonicalPath === null) {
|
|
1965
|
+
throw new Error("Invalid entity canonical-id migration path.");
|
|
1966
|
+
}
|
|
1967
|
+
await assertNotSymlink(legacyPath, legacyId);
|
|
1968
|
+
await assertNotSymlink(canonicalPath, canonicalId);
|
|
1969
|
+
const legacyExists = await fileExists(legacyPath);
|
|
1970
|
+
const canonicalExists = await fileExists(canonicalPath);
|
|
1971
|
+
if (!legacyExists && !canonicalExists) return { deferred: true, progressed: false };
|
|
1972
|
+
if (legacyExists && canonicalExists) {
|
|
1973
|
+
if (await sameFileIdentity(legacyPath, canonicalPath)) return { deferred: false, progressed: false };
|
|
1974
|
+
const [legacyContent, canonicalContent2, legacyEncrypted, canonicalEncrypted] = await Promise.all([
|
|
1975
|
+
deps.readStorageSecureFile(legacyPath),
|
|
1976
|
+
deps.readStorageSecureFile(canonicalPath),
|
|
1977
|
+
deps.isEncryptedStorageFile(legacyPath),
|
|
1978
|
+
deps.isEncryptedStorageFile(canonicalPath)
|
|
1979
|
+
]);
|
|
1980
|
+
if (legacyContent !== canonicalContent2) {
|
|
1981
|
+
throw new Error(`Cannot migrate legacy entity id ${legacyId}: ${canonicalId} already exists.`);
|
|
1982
|
+
}
|
|
1983
|
+
if (legacyEncrypted && !canonicalEncrypted) {
|
|
1984
|
+
if (!await refreshMigrationLock() || !await refreshEntityLock()) {
|
|
1985
|
+
throw new Error("Lost entity canonical-id migration lock.");
|
|
1986
|
+
}
|
|
1987
|
+
await deps.snapshotBeforeWrite(canonicalPath, "write");
|
|
1988
|
+
await deps.writeStorageSecureFile(canonicalPath, legacyContent, true);
|
|
1989
|
+
if (await deps.readStorageSecureFile(canonicalPath) !== legacyContent) {
|
|
1990
|
+
throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
if (!await refreshMigrationLock() || !await refreshEntityLock()) {
|
|
1994
|
+
throw new Error("Lost entity canonical-id migration lock.");
|
|
1995
|
+
}
|
|
1996
|
+
await deps.snapshotBeforeWrite(legacyPath, "write");
|
|
1997
|
+
await assertNotSymlink(legacyPath, legacyId);
|
|
1998
|
+
await assertEntityContentUnchanged(deps, legacyPath, legacyContent, legacyId);
|
|
1999
|
+
await unlink(legacyPath);
|
|
2000
|
+
return { deferred: false, progressed: true };
|
|
2001
|
+
}
|
|
2002
|
+
if (!legacyExists) return { deferred: false, progressed: false };
|
|
2003
|
+
if (!await refreshMigrationLock() || !await refreshEntityLock()) {
|
|
2004
|
+
throw new Error("Lost entity canonical-id migration lock.");
|
|
2005
|
+
}
|
|
2006
|
+
const entityEncrypted = await deps.isEncryptedStorageFile(legacyPath);
|
|
2007
|
+
const entityContent = await deps.readStorageSecureFile(legacyPath);
|
|
2008
|
+
await assertNotSymlink(legacyPath, legacyId);
|
|
2009
|
+
await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);
|
|
2010
|
+
let canonicalFingerprintAfterWrite = null;
|
|
2011
|
+
try {
|
|
2012
|
+
await deps.writeStorageSecureFile(canonicalPath, entityContent, entityEncrypted);
|
|
2013
|
+
canonicalFingerprintAfterWrite = await fingerprintPath(canonicalPath);
|
|
2014
|
+
if (await deps.readStorageSecureFile(canonicalPath) !== entityContent) {
|
|
2015
|
+
throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);
|
|
2016
|
+
}
|
|
2017
|
+
if (!await refreshMigrationLock() || !await refreshEntityLock()) {
|
|
2018
|
+
throw new Error("Lost entity canonical-id migration lock.");
|
|
2019
|
+
}
|
|
2020
|
+
await deps.snapshotBeforeWrite(legacyPath, "write");
|
|
2021
|
+
await assertNotSymlink(legacyPath, legacyId);
|
|
2022
|
+
await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);
|
|
2023
|
+
await unlink(legacyPath);
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
if (!canonicalExists && canonicalFingerprintAfterWrite !== null) {
|
|
2026
|
+
try {
|
|
2027
|
+
if (await fingerprintPath(canonicalPath) === canonicalFingerprintAfterWrite) {
|
|
2028
|
+
await assertNotSymlink(canonicalPath, canonicalId);
|
|
2029
|
+
await unlink(canonicalPath);
|
|
2030
|
+
}
|
|
2031
|
+
} catch {
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
throw error;
|
|
2035
|
+
}
|
|
2036
|
+
return { deferred: false, progressed: true };
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
async function migrateLegacyEntityCanonicalIds(deps) {
|
|
2040
|
+
return withHeldFileLock(
|
|
2041
|
+
lockPath(deps),
|
|
2042
|
+
{
|
|
2043
|
+
staleMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS,
|
|
2044
|
+
maxWaitMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS
|
|
2045
|
+
},
|
|
2046
|
+
async (acquired, lock) => {
|
|
2047
|
+
if (!acquired) throw new Error("Timed out waiting for active entity canonical-id migration.");
|
|
2048
|
+
await deps.validateMemoryScanRoots?.();
|
|
2049
|
+
await readSafeEntityEntries(deps.entitiesDir);
|
|
2050
|
+
let state = await readState(deps);
|
|
2051
|
+
if (!state) {
|
|
2052
|
+
state = {
|
|
2053
|
+
version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,
|
|
2054
|
+
complete: false,
|
|
2055
|
+
mappings: {}
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
const previousMappings = state.mappings;
|
|
2059
|
+
const collapsedMappings = collapseMappings(previousMappings);
|
|
2060
|
+
const mappingsChanged = Object.keys(collapsedMappings).length !== Object.keys(previousMappings).length || Object.entries(collapsedMappings).some(
|
|
2061
|
+
([legacyId, canonicalId]) => previousMappings[legacyId] !== canonicalId
|
|
2062
|
+
);
|
|
2063
|
+
state = { ...state, mappings: collapsedMappings };
|
|
2064
|
+
for (let migrationPass = 0; migrationPass < ENTITY_MAPPING_RESCAN_MAX_PASSES; migrationPass += 1) {
|
|
2065
|
+
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2066
|
+
const discoveredBefore = rejectMappingCycles(
|
|
2067
|
+
state.mappings,
|
|
2068
|
+
await discoverMappings(deps, state.mappings)
|
|
2069
|
+
);
|
|
2070
|
+
const mergedBefore = collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredBefore));
|
|
2071
|
+
if (state.complete && Object.keys(discoveredBefore).length === 0 && !mappingsChanged) {
|
|
2072
|
+
if (Object.keys(state.mappings).length === 0) return deps.readMigrationFingerprint?.();
|
|
2073
|
+
const rewroteColdMemory2 = await rewriteKnownReferences(
|
|
2074
|
+
deps,
|
|
2075
|
+
state.mappings,
|
|
2076
|
+
() => lock.refresh()
|
|
2077
|
+
);
|
|
2078
|
+
deps.invalidateKnowledgeIndexCache();
|
|
2079
|
+
deps.invalidateAllMemoriesCache();
|
|
2080
|
+
if (rewroteColdMemory2) deps.invalidateColdMemoriesCache();
|
|
2081
|
+
deps.bumpMemoryStatusVersion();
|
|
2082
|
+
return deps.readMigrationFingerprint?.();
|
|
2083
|
+
}
|
|
2084
|
+
if (state.complete || Object.keys(discoveredBefore).length > 0 || Object.keys(mergedBefore).length !== Object.keys(state.mappings).length) {
|
|
2085
|
+
state = { ...state, complete: false, mappings: mergedBefore };
|
|
2086
|
+
await writeState(deps, state);
|
|
2087
|
+
}
|
|
2088
|
+
if (Object.keys(state.mappings).length === 0) {
|
|
2089
|
+
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2090
|
+
const discoveredAfter2 = rejectMappingCycles(
|
|
2091
|
+
state.mappings,
|
|
2092
|
+
await discoverMappings(deps, state.mappings)
|
|
2093
|
+
);
|
|
2094
|
+
if (Object.keys(discoveredAfter2).length === 0) {
|
|
2095
|
+
await writeState(deps, { ...state, complete: true });
|
|
2096
|
+
return deps.readMigrationFingerprint?.();
|
|
2097
|
+
}
|
|
2098
|
+
state = {
|
|
2099
|
+
...state,
|
|
2100
|
+
complete: false,
|
|
2101
|
+
mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter2))
|
|
2102
|
+
};
|
|
2103
|
+
await writeState(deps, state);
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
let pendingMappings = Object.entries(state.mappings);
|
|
2107
|
+
while (pendingMappings.length > 0) {
|
|
2108
|
+
const deferredMappings = [];
|
|
2109
|
+
let progressed = false;
|
|
2110
|
+
for (const [legacyId, canonicalId] of pendingMappings) {
|
|
2111
|
+
const result = await migrateEntityFilePair(
|
|
2112
|
+
deps,
|
|
2113
|
+
legacyId,
|
|
2114
|
+
canonicalId,
|
|
2115
|
+
() => lock.refresh()
|
|
2116
|
+
);
|
|
2117
|
+
if (result.deferred) {
|
|
2118
|
+
deferredMappings.push([legacyId, canonicalId]);
|
|
2119
|
+
}
|
|
2120
|
+
progressed ||= result.progressed;
|
|
2121
|
+
}
|
|
2122
|
+
if (deferredMappings.length === 0) break;
|
|
2123
|
+
if (!progressed) {
|
|
2124
|
+
const [legacyId, canonicalId] = deferredMappings[0];
|
|
2125
|
+
throw new Error(
|
|
2126
|
+
`Cannot migrate legacy entity id ${legacyId}: both files are missing and no intermediate mapping can advance migration.`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
pendingMappings = deferredMappings;
|
|
2130
|
+
}
|
|
2131
|
+
const rewroteColdMemory = await rewriteKnownReferences(
|
|
2132
|
+
deps,
|
|
2133
|
+
state.mappings,
|
|
2134
|
+
() => lock.refresh()
|
|
2135
|
+
);
|
|
2136
|
+
deps.invalidateKnowledgeIndexCache();
|
|
2137
|
+
deps.invalidateAllMemoriesCache();
|
|
2138
|
+
if (rewroteColdMemory) deps.invalidateColdMemoriesCache();
|
|
2139
|
+
deps.bumpMemoryStatusVersion();
|
|
2140
|
+
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2141
|
+
const discoveredAfter = rejectMappingCycles(
|
|
2142
|
+
state.mappings,
|
|
2143
|
+
await discoverMappings(deps, state.mappings)
|
|
2144
|
+
);
|
|
2145
|
+
if (Object.keys(discoveredAfter).length === 0) {
|
|
2146
|
+
await writeState(deps, { ...state, complete: true });
|
|
2147
|
+
return deps.readMigrationFingerprint?.();
|
|
2148
|
+
}
|
|
2149
|
+
state = {
|
|
2150
|
+
...state,
|
|
2151
|
+
complete: false,
|
|
2152
|
+
mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter))
|
|
2153
|
+
};
|
|
2154
|
+
await writeState(deps, state);
|
|
2155
|
+
}
|
|
2156
|
+
throw new Error("Entity canonical-id migration mappings changed while migration was running; retry migration.");
|
|
2157
|
+
}
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
// src/storage/entity-store.ts
|
|
730
2162
|
var EntityStore = class {
|
|
731
2163
|
constructor(deps) {
|
|
732
2164
|
this.deps = deps;
|
|
@@ -738,6 +2170,12 @@ var EntityStore = class {
|
|
|
738
2170
|
}
|
|
739
2171
|
async writeEntity(name, type, facts, options = {}) {
|
|
740
2172
|
await this.deps.ensureDirectories();
|
|
2173
|
+
return withEntityCanonicalMutationLock(
|
|
2174
|
+
path4.join(this.deps.baseDir, "state"),
|
|
2175
|
+
() => this.writeEntityUnlocked(name, type, facts, options)
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
async writeEntityUnlocked(name, type, facts, options = {}) {
|
|
741
2179
|
if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
|
|
742
2180
|
log.warn("writeEntity: invalid entity payload, skipping", {
|
|
743
2181
|
nameType: typeof name,
|
|
@@ -754,7 +2192,7 @@ var EntityStore = class {
|
|
|
754
2192
|
log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
|
|
755
2193
|
normalized = match;
|
|
756
2194
|
}
|
|
757
|
-
const filePath =
|
|
2195
|
+
const filePath = path4.join(this.deps.entitiesDir, `${normalized}.md`);
|
|
758
2196
|
let entity = {
|
|
759
2197
|
name,
|
|
760
2198
|
type,
|
|
@@ -855,7 +2293,7 @@ var EntityStore = class {
|
|
|
855
2293
|
/** Return sorted list of entity filenames (without .md extension) */
|
|
856
2294
|
async listEntityNames() {
|
|
857
2295
|
try {
|
|
858
|
-
const entries = await
|
|
2296
|
+
const entries = await readdir3(this.deps.entitiesDir);
|
|
859
2297
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
|
|
860
2298
|
} catch {
|
|
861
2299
|
return [];
|
|
@@ -865,7 +2303,13 @@ var EntityStore = class {
|
|
|
865
2303
|
* Set or rewrite the synthesis layer of an entity file.
|
|
866
2304
|
*/
|
|
867
2305
|
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
868
|
-
|
|
2306
|
+
return withEntityCanonicalMutationLock(
|
|
2307
|
+
path4.join(this.deps.baseDir, "state"),
|
|
2308
|
+
() => this.updateEntitySynthesisUnlocked(name, synthesis, options)
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
async updateEntitySynthesisUnlocked(name, synthesis, options = {}) {
|
|
2312
|
+
const filePath = path4.join(this.deps.entitiesDir, `${name}.md`);
|
|
869
2313
|
let entity;
|
|
870
2314
|
try {
|
|
871
2315
|
const content = await this.deps.readStorageSecureFile(filePath);
|
|
@@ -907,7 +2351,7 @@ var EntityStore = class {
|
|
|
907
2351
|
const cached = getCachedEntities(this.deps.baseDir, currentVersion, cacheKey);
|
|
908
2352
|
if (cached) return cached;
|
|
909
2353
|
try {
|
|
910
|
-
const entries = await
|
|
2354
|
+
const entries = await readdir3(this.deps.entitiesDir);
|
|
911
2355
|
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
912
2356
|
if (mdFiles.length === 0) return [];
|
|
913
2357
|
const BATCH_SIZE = 100;
|
|
@@ -917,7 +2361,7 @@ var EntityStore = class {
|
|
|
917
2361
|
const results = await Promise.all(
|
|
918
2362
|
batch.map(async (entry) => {
|
|
919
2363
|
try {
|
|
920
|
-
return await this.deps.readStorageSecureFile(
|
|
2364
|
+
return await this.deps.readStorageSecureFile(path4.join(this.deps.entitiesDir, entry));
|
|
921
2365
|
} catch (err) {
|
|
922
2366
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
923
2367
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -990,9 +2434,15 @@ ${rows.join("\n")}
|
|
|
990
2434
|
* Returns count of files merged.
|
|
991
2435
|
*/
|
|
992
2436
|
async mergeFragmentedEntities() {
|
|
2437
|
+
return withEntityCanonicalMutationLock(
|
|
2438
|
+
path4.join(this.deps.baseDir, "state"),
|
|
2439
|
+
() => this.mergeFragmentedEntitiesUnlocked()
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
async mergeFragmentedEntitiesUnlocked() {
|
|
993
2443
|
let merged = 0;
|
|
994
2444
|
try {
|
|
995
|
-
const entries = await
|
|
2445
|
+
const entries = await readdir3(this.deps.entitiesDir);
|
|
996
2446
|
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
997
2447
|
const groups = /* @__PURE__ */ new Map();
|
|
998
2448
|
for (const file of mdFiles) {
|
|
@@ -1030,7 +2480,7 @@ ${rows.join("\n")}
|
|
|
1030
2480
|
extraSections: []
|
|
1031
2481
|
};
|
|
1032
2482
|
for (const file of files) {
|
|
1033
|
-
const filePath =
|
|
2483
|
+
const filePath = path4.join(this.deps.entitiesDir, file);
|
|
1034
2484
|
try {
|
|
1035
2485
|
const content = await this.deps.readStorageSecureFile(filePath);
|
|
1036
2486
|
const parsed = parseEntityFile(content, this.deps.entitySchemas);
|
|
@@ -1161,13 +2611,13 @@ ${rows.join("\n")}
|
|
|
1161
2611
|
}
|
|
1162
2612
|
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
1163
2613
|
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
1164
|
-
const canonicalPath =
|
|
2614
|
+
const canonicalPath = path4.join(this.deps.entitiesDir, `${canonical}.md`);
|
|
1165
2615
|
await this.deps.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.deps.entitySchemas));
|
|
1166
2616
|
for (const file of files) {
|
|
1167
|
-
const filePath =
|
|
2617
|
+
const filePath = path4.join(this.deps.entitiesDir, file);
|
|
1168
2618
|
if (filePath !== canonicalPath) {
|
|
1169
2619
|
try {
|
|
1170
|
-
await
|
|
2620
|
+
await unlink2(filePath);
|
|
1171
2621
|
merged++;
|
|
1172
2622
|
log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
|
|
1173
2623
|
} catch {
|
|
@@ -1183,8 +2633,8 @@ ${rows.join("\n")}
|
|
|
1183
2633
|
};
|
|
1184
2634
|
|
|
1185
2635
|
// src/storage/identity-continuity-store.ts
|
|
1186
|
-
import { readdir as
|
|
1187
|
-
import
|
|
2636
|
+
import { readdir as readdir4 } from "fs/promises";
|
|
2637
|
+
import path5 from "path";
|
|
1188
2638
|
var IdentityContinuityStore = class {
|
|
1189
2639
|
constructor(deps) {
|
|
1190
2640
|
this.deps = deps;
|
|
@@ -1209,7 +2659,7 @@ var IdentityContinuityStore = class {
|
|
|
1209
2659
|
const date = nowIso.slice(0, 10);
|
|
1210
2660
|
const id = this.deps.generateId("incident");
|
|
1211
2661
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
1212
|
-
const filePath =
|
|
2662
|
+
const filePath = path5.join(this.deps.identityIncidentsDir, `${date}-${id}.md`);
|
|
1213
2663
|
await this.deps.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
1214
2664
|
return { ...incident, filePath };
|
|
1215
2665
|
}
|
|
@@ -1222,7 +2672,7 @@ var IdentityContinuityStore = class {
|
|
|
1222
2672
|
const incidents = [];
|
|
1223
2673
|
for (const file of candidates) {
|
|
1224
2674
|
if (incidents.length >= cappedLimit) break;
|
|
1225
|
-
const filePath =
|
|
2675
|
+
const filePath = path5.join(this.deps.identityIncidentsDir, file);
|
|
1226
2676
|
try {
|
|
1227
2677
|
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
1228
2678
|
const parsed = parseContinuityIncident(raw);
|
|
@@ -1253,7 +2703,7 @@ var IdentityContinuityStore = class {
|
|
|
1253
2703
|
await this.deps.ensureDirectories();
|
|
1254
2704
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
1255
2705
|
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
1256
|
-
const filePath =
|
|
2706
|
+
const filePath = path5.join(dir, `${safeKey}.md`);
|
|
1257
2707
|
await this.deps.writeStorageSecureFile(filePath, content);
|
|
1258
2708
|
return filePath;
|
|
1259
2709
|
}
|
|
@@ -1261,7 +2711,7 @@ var IdentityContinuityStore = class {
|
|
|
1261
2711
|
try {
|
|
1262
2712
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
1263
2713
|
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
1264
|
-
return await this.deps.readStorageSecureFile(
|
|
2714
|
+
return await this.deps.readStorageSecureFile(path5.join(dir, `${safeKey}.md`));
|
|
1265
2715
|
} catch (err) {
|
|
1266
2716
|
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
1267
2717
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -1300,7 +2750,7 @@ var IdentityContinuityStore = class {
|
|
|
1300
2750
|
return loop;
|
|
1301
2751
|
}
|
|
1302
2752
|
async readContinuityIncidentFileNames() {
|
|
1303
|
-
const files = await
|
|
2753
|
+
const files = await readdir4(this.deps.identityIncidentsDir);
|
|
1304
2754
|
return files.filter((file) => file.endsWith(".md")).sort().reverse();
|
|
1305
2755
|
}
|
|
1306
2756
|
async readContinuityIncidentFile(filePath) {
|
|
@@ -1317,12 +2767,12 @@ var IdentityContinuityStore = class {
|
|
|
1317
2767
|
const fileNames = await this.readContinuityIncidentFileNames();
|
|
1318
2768
|
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
1319
2769
|
if (directMatch) {
|
|
1320
|
-
const directPath =
|
|
2770
|
+
const directPath = path5.join(this.deps.identityIncidentsDir, directMatch);
|
|
1321
2771
|
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
1322
2772
|
if (parsed?.id === id) return directPath;
|
|
1323
2773
|
}
|
|
1324
2774
|
for (const fileName of fileNames) {
|
|
1325
|
-
const filePath =
|
|
2775
|
+
const filePath = path5.join(this.deps.identityIncidentsDir, fileName);
|
|
1326
2776
|
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
1327
2777
|
if (parsed?.id === id) return filePath;
|
|
1328
2778
|
}
|
|
@@ -1337,6 +2787,127 @@ var IdentityContinuityStore = class {
|
|
|
1337
2787
|
}
|
|
1338
2788
|
};
|
|
1339
2789
|
|
|
2790
|
+
// src/storage/entity-canonical-id-migration-adapter.ts
|
|
2791
|
+
async function runLegacyEntityCanonicalIdMigration(host, parseEntityFile2, serializeEntityFile2, serializeMemoryWithEntityRef2) {
|
|
2792
|
+
const readMigrationFingerprint = () => getFingerprint(host.baseDir, host.entitiesDir, () => host.getCorpusScanVersion());
|
|
2793
|
+
const completionFingerprint = await migrateLegacyEntityCanonicalIds({
|
|
2794
|
+
stateDir: host.stateDir,
|
|
2795
|
+
entitiesDir: host.entitiesDir,
|
|
2796
|
+
normalizeEntityName: host.normalizeEntityName.bind(host),
|
|
2797
|
+
resolveEntityFilePath: host.resolveEntityFilePath.bind(host),
|
|
2798
|
+
readStorageSecureFile: host.readStorageSecureFile.bind(host),
|
|
2799
|
+
writeStorageSecureFile: host.writeStorageSecureFile.bind(host),
|
|
2800
|
+
isEncryptedStorageFile: host.isEncryptedFileHeader.bind(host),
|
|
2801
|
+
snapshotBeforeWrite: host.snapshotBeforeWrite.bind(host),
|
|
2802
|
+
parseEntityFile: parseEntityFile2,
|
|
2803
|
+
serializeEntityFile: serializeEntityFile2,
|
|
2804
|
+
serializeMemoryWithEntityRef: serializeMemoryWithEntityRef2,
|
|
2805
|
+
readMemoryByPath: host.readMemoryByPath.bind(host),
|
|
2806
|
+
readAllMemories: host.readAllMemories.bind(host),
|
|
2807
|
+
readAllColdMemories: host.readAllColdMemories.bind(host),
|
|
2808
|
+
readArchivedMemories: host.readArchivedMemories.bind(host),
|
|
2809
|
+
validateMemoryScanRoots: () => validateRoots(host.baseDir, host.entitiesDir, host.stateDir, host.archiveDir),
|
|
2810
|
+
rewriteProjectedEntityReferences: (mappings) => rewriteProjectedEntityReferences(host.baseDir, mappings),
|
|
2811
|
+
rewriteProjectedMemoryEntityReference: (memoryId, previousEntityRef, nextEntityRef) => rewriteProjectedMemoryEntityReference(host.baseDir, memoryId, previousEntityRef, nextEntityRef),
|
|
2812
|
+
invalidateKnowledgeIndexCache: host.invalidateKnowledgeIndexCache.bind(host),
|
|
2813
|
+
invalidateAllMemoriesCache: host.invalidateAllMemoriesCache.bind(host),
|
|
2814
|
+
invalidateColdMemoriesCache: host.invalidateColdMemoriesCache.bind(host),
|
|
2815
|
+
bumpMemoryStatusVersion: host.bumpMemoryStatusVersion.bind(host),
|
|
2816
|
+
readMigrationFingerprint
|
|
2817
|
+
});
|
|
2818
|
+
return completionFingerprint ?? readMigrationFingerprint();
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// src/storage/entity-canonical-id-migration-runner.ts
|
|
2822
|
+
var EntityCanonicalIdMigrationRunner = class {
|
|
2823
|
+
constructor(canRun, runMigration, readFingerprint = async () => "static") {
|
|
2824
|
+
this.canRun = canRun;
|
|
2825
|
+
this.runMigration = runMigration;
|
|
2826
|
+
this.readFingerprint = readFingerprint;
|
|
2827
|
+
}
|
|
2828
|
+
canRun;
|
|
2829
|
+
runMigration;
|
|
2830
|
+
readFingerprint;
|
|
2831
|
+
inFlight = null;
|
|
2832
|
+
directoriesInitialized = false;
|
|
2833
|
+
migrationComplete = false;
|
|
2834
|
+
completedFingerprint = null;
|
|
2835
|
+
async markDirectoriesInitialized() {
|
|
2836
|
+
this.directoriesInitialized = true;
|
|
2837
|
+
await this.ensure();
|
|
2838
|
+
}
|
|
2839
|
+
ensure() {
|
|
2840
|
+
if (!this.canRun()) return Promise.resolve();
|
|
2841
|
+
if (this.inFlight) return this.inFlight;
|
|
2842
|
+
const migration = this.runOrReuse();
|
|
2843
|
+
this.inFlight = migration;
|
|
2844
|
+
void migration.then(
|
|
2845
|
+
() => this.clearInFlight(migration),
|
|
2846
|
+
() => this.clearInFlight(migration)
|
|
2847
|
+
);
|
|
2848
|
+
return migration;
|
|
2849
|
+
}
|
|
2850
|
+
triggerAfterUnlock() {
|
|
2851
|
+
if (!this.directoriesInitialized) return Promise.resolve();
|
|
2852
|
+
return this.ensure();
|
|
2853
|
+
}
|
|
2854
|
+
async runOrReuse() {
|
|
2855
|
+
let fingerprintAtStart = await this.readFingerprint();
|
|
2856
|
+
if (this.migrationComplete && fingerprintAtStart === this.completedFingerprint) return;
|
|
2857
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2858
|
+
const completionFingerprint = await this.runMigration();
|
|
2859
|
+
const fingerprintAfterMigration = await this.readFingerprint();
|
|
2860
|
+
const fingerprintAfterStabilityRead = await this.readFingerprint();
|
|
2861
|
+
const returnedFingerprint = typeof completionFingerprint === "string" ? completionFingerprint : fingerprintAfterMigration;
|
|
2862
|
+
if (returnedFingerprint === fingerprintAfterMigration && fingerprintAfterMigration === fingerprintAfterStabilityRead && (typeof completionFingerprint === "string" || fingerprintAfterMigration === fingerprintAtStart)) {
|
|
2863
|
+
this.migrationComplete = true;
|
|
2864
|
+
this.completedFingerprint = fingerprintAfterStabilityRead;
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
fingerprintAtStart = fingerprintAfterStabilityRead;
|
|
2868
|
+
}
|
|
2869
|
+
this.migrationComplete = false;
|
|
2870
|
+
this.completedFingerprint = null;
|
|
2871
|
+
throw new Error("Entity canonical-id migration fingerprint changed while migration was completing; retry migration.");
|
|
2872
|
+
}
|
|
2873
|
+
clearInFlight(migration) {
|
|
2874
|
+
if (this.inFlight === migration) this.inFlight = null;
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
|
|
2878
|
+
// src/storage/memory-migration-serialization.ts
|
|
2879
|
+
function serializeMemoryWithEntityRef(memory, entityRef, fallback) {
|
|
2880
|
+
const rawFrontmatter = readRawFrontmatter(memory);
|
|
2881
|
+
if (rawFrontmatter === void 0) return fallback(memory, entityRef);
|
|
2882
|
+
const lines = rawFrontmatter.split("\n");
|
|
2883
|
+
let entityRefLine = -1;
|
|
2884
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2885
|
+
if (/^\s*entityRef\s*:/.test(lines[index] ?? "")) entityRefLine = index;
|
|
2886
|
+
}
|
|
2887
|
+
if (entityRefLine >= 0) {
|
|
2888
|
+
const indent = lines[entityRefLine].match(/^\s*/)?.[0] ?? "";
|
|
2889
|
+
lines[entityRefLine] = `${indent}entityRef: ${entityRef}`;
|
|
2890
|
+
} else {
|
|
2891
|
+
lines.push(`entityRef: ${entityRef}`);
|
|
2892
|
+
}
|
|
2893
|
+
return `---
|
|
2894
|
+
${lines.join("\n")}
|
|
2895
|
+
---
|
|
2896
|
+
|
|
2897
|
+
${memory.content}
|
|
2898
|
+
`;
|
|
2899
|
+
}
|
|
2900
|
+
function createMemoryEntityRefSerializer(serializeFrontmatter2) {
|
|
2901
|
+
return (memory, entityRef) => serializeMemoryWithEntityRef(
|
|
2902
|
+
memory,
|
|
2903
|
+
entityRef,
|
|
2904
|
+
(fallbackMemory, fallbackEntityRef) => `${serializeFrontmatter2({ ...fallbackMemory.frontmatter, entityRef: fallbackEntityRef })}
|
|
2905
|
+
|
|
2906
|
+
${fallbackMemory.content}
|
|
2907
|
+
`
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
1340
2911
|
// src/utils/yaml-scalar.ts
|
|
1341
2912
|
function decodeYamlScalar(value) {
|
|
1342
2913
|
const trimmed = value.trim();
|
|
@@ -1352,9 +2923,9 @@ function decodeYamlScalar(value) {
|
|
|
1352
2923
|
}
|
|
1353
2924
|
|
|
1354
2925
|
// src/orchestration/qmd-result-resolver.ts
|
|
1355
|
-
import
|
|
2926
|
+
import path6 from "path";
|
|
1356
2927
|
function qmdCollectionPathParts(resultPath) {
|
|
1357
|
-
if (!resultPath ||
|
|
2928
|
+
if (!resultPath || path6.isAbsolute(resultPath)) return null;
|
|
1358
2929
|
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1359
2930
|
const slashIndex = normalized.indexOf("/");
|
|
1360
2931
|
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
@@ -1367,9 +2938,9 @@ function qmdCollectionPathParts(resultPath) {
|
|
|
1367
2938
|
}
|
|
1368
2939
|
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
1369
2940
|
const candidates = /* @__PURE__ */ new Set();
|
|
1370
|
-
const storageRoot =
|
|
2941
|
+
const storageRoot = path6.resolve(storageDir);
|
|
1371
2942
|
const addCandidate = (candidate) => {
|
|
1372
|
-
const resolved =
|
|
2943
|
+
const resolved = path6.resolve(candidate);
|
|
1373
2944
|
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
1374
2945
|
candidates.add(resolved);
|
|
1375
2946
|
}
|
|
@@ -1377,18 +2948,18 @@ function qmdResultPathCandidates(storageDir, resultPath) {
|
|
|
1377
2948
|
const addRelativeCandidates = (relativePath) => {
|
|
1378
2949
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1379
2950
|
if (!normalized) return;
|
|
1380
|
-
addCandidate(
|
|
2951
|
+
addCandidate(path6.join(storageRoot, normalized));
|
|
1381
2952
|
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
1382
|
-
addCandidate(
|
|
2953
|
+
addCandidate(path6.join(storageRoot, "facts", normalized));
|
|
1383
2954
|
}
|
|
1384
2955
|
};
|
|
1385
|
-
if (
|
|
2956
|
+
if (path6.isAbsolute(resultPath)) {
|
|
1386
2957
|
addCandidate(resultPath);
|
|
1387
|
-
const relative2 =
|
|
1388
|
-
if (relative2 && !relative2.startsWith("..") && !
|
|
1389
|
-
const normalized = relative2.split(
|
|
2958
|
+
const relative2 = path6.relative(storageRoot, path6.resolve(resultPath));
|
|
2959
|
+
if (relative2 && !relative2.startsWith("..") && !path6.isAbsolute(relative2)) {
|
|
2960
|
+
const normalized = relative2.split(path6.sep).join("/");
|
|
1390
2961
|
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
1391
|
-
addCandidate(
|
|
2962
|
+
addCandidate(path6.join(storageRoot, "facts", normalized));
|
|
1392
2963
|
}
|
|
1393
2964
|
}
|
|
1394
2965
|
} else {
|
|
@@ -1423,7 +2994,7 @@ var QmdResultResolver = class {
|
|
|
1423
2994
|
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
1424
2995
|
const addStorage = (storage) => {
|
|
1425
2996
|
const storageDir = storageDirOrNull(storage);
|
|
1426
|
-
const storageKey = storageDir ?
|
|
2997
|
+
const storageKey = storageDir ? path6.resolve(storageDir) : `storage-without-dir-${storages.length}`;
|
|
1427
2998
|
if (seenStorageDirs.has(storageKey)) return;
|
|
1428
2999
|
seenStorageDirs.add(storageKey);
|
|
1429
3000
|
storages.push(storage);
|
|
@@ -1453,7 +3024,7 @@ var QmdResultResolver = class {
|
|
|
1453
3024
|
continue;
|
|
1454
3025
|
}
|
|
1455
3026
|
try {
|
|
1456
|
-
const coldRoot =
|
|
3027
|
+
const coldRoot = path6.join(storageDir, "cold");
|
|
1457
3028
|
for (const candidate of qmdResultPathCandidates(
|
|
1458
3029
|
coldRoot,
|
|
1459
3030
|
parts.relativePath
|
|
@@ -1512,9 +3083,9 @@ var QmdResultResolver = class {
|
|
|
1512
3083
|
error: err.message
|
|
1513
3084
|
});
|
|
1514
3085
|
}
|
|
1515
|
-
if (!
|
|
3086
|
+
if (!path6.isAbsolute(resultPath)) return null;
|
|
1516
3087
|
}
|
|
1517
|
-
if (
|
|
3088
|
+
if (path6.isAbsolute(resultPath)) {
|
|
1518
3089
|
if (!fallbackStorageDir) {
|
|
1519
3090
|
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
1520
3091
|
}
|
|
@@ -1554,7 +3125,7 @@ var QmdResultResolver = class {
|
|
|
1554
3125
|
);
|
|
1555
3126
|
if (!memory) return null;
|
|
1556
3127
|
let ownerNamespace = null;
|
|
1557
|
-
if (
|
|
3128
|
+
if (path6.isAbsolute(memory.path)) {
|
|
1558
3129
|
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
1559
3130
|
memory.path,
|
|
1560
3131
|
fallbackStorage,
|
|
@@ -1578,17 +3149,17 @@ var QmdResultResolver = class {
|
|
|
1578
3149
|
};
|
|
1579
3150
|
}
|
|
1580
3151
|
async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
1581
|
-
const resolvedPath =
|
|
3152
|
+
const resolvedPath = path6.resolve(resultPath);
|
|
1582
3153
|
const config = this.getConfig();
|
|
1583
|
-
const memoryRoot =
|
|
1584
|
-
const namespacesRoot =
|
|
3154
|
+
const memoryRoot = path6.resolve(config.memoryDir);
|
|
3155
|
+
const namespacesRoot = path6.join(memoryRoot, "namespaces");
|
|
1585
3156
|
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
1586
3157
|
const matches = [];
|
|
1587
3158
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
1588
3159
|
const maybeAddStorage = (storage, namespace) => {
|
|
1589
3160
|
const storageDir = storageDirOrNull(storage);
|
|
1590
3161
|
if (!storageDir) return;
|
|
1591
|
-
const candidateRoot =
|
|
3162
|
+
const candidateRoot = path6.resolve(storageDir);
|
|
1592
3163
|
if (seenDirs.has(candidateRoot)) return;
|
|
1593
3164
|
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
1594
3165
|
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
@@ -1606,7 +3177,7 @@ var QmdResultResolver = class {
|
|
|
1606
3177
|
candidateNamespaces.add(ns);
|
|
1607
3178
|
}
|
|
1608
3179
|
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
1609
|
-
const relativeToNamespaces =
|
|
3180
|
+
const relativeToNamespaces = path6.relative(namespacesRoot, resolvedPath);
|
|
1610
3181
|
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
1611
3182
|
if (namespaceSegment) {
|
|
1612
3183
|
candidateNamespaces.add(
|
|
@@ -2255,7 +3826,7 @@ function fuseDay(date, inputs, options = {}) {
|
|
|
2255
3826
|
|
|
2256
3827
|
// src/wearables/fusion/store.ts
|
|
2257
3828
|
import { createHash as createHash2 } from "crypto";
|
|
2258
|
-
import * as
|
|
3829
|
+
import * as path7 from "path";
|
|
2259
3830
|
var FUSION_KIND = "wearable-fusion";
|
|
2260
3831
|
function hashFusionBody(conversations) {
|
|
2261
3832
|
return createHash2("sha256").update(JSON.stringify(conversations), "utf-8").digest("hex");
|
|
@@ -2442,7 +4013,7 @@ var FusionArtifactStore = class {
|
|
|
2442
4013
|
`invalid wearable fusion date '${String(date)}' \u2014 expected YYYY-MM-DD`
|
|
2443
4014
|
);
|
|
2444
4015
|
}
|
|
2445
|
-
return
|
|
4016
|
+
return path7.join(this.wearablesDir, FUSION_DIR_NAME, `${date}.md`);
|
|
2446
4017
|
}
|
|
2447
4018
|
async writeFusedDay(date, serialized) {
|
|
2448
4019
|
const filePath = this.fusedDayPath(date);
|
|
@@ -2479,7 +4050,7 @@ var FusionArtifactStore = class {
|
|
|
2479
4050
|
}
|
|
2480
4051
|
/** List dates with stored fused artifacts, newest first. */
|
|
2481
4052
|
async listFusedDays() {
|
|
2482
|
-
const fusionDir =
|
|
4053
|
+
const fusionDir = path7.join(this.wearablesDir, FUSION_DIR_NAME);
|
|
2483
4054
|
await this.assertPathContained(fusionDir);
|
|
2484
4055
|
let entries;
|
|
2485
4056
|
try {
|
|
@@ -2523,10 +4094,10 @@ var FusionArtifactStore = class {
|
|
|
2523
4094
|
try {
|
|
2524
4095
|
rootReal = await this.io.realpath(this.memoryDir);
|
|
2525
4096
|
} catch {
|
|
2526
|
-
rootReal =
|
|
4097
|
+
rootReal = path7.resolve(this.memoryDir);
|
|
2527
4098
|
}
|
|
2528
4099
|
await this.assertNoEscape(rootReal, this.wearablesDir);
|
|
2529
|
-
await this.assertNoEscape(rootReal,
|
|
4100
|
+
await this.assertNoEscape(rootReal, path7.dirname(targetPath));
|
|
2530
4101
|
await this.assertNoEscape(rootReal, targetPath);
|
|
2531
4102
|
await this.assertFusionDirNotSymlinked();
|
|
2532
4103
|
}
|
|
@@ -2540,7 +4111,7 @@ var FusionArtifactStore = class {
|
|
|
2540
4111
|
* (a fresh write creates a real directory lexically under the root).
|
|
2541
4112
|
*/
|
|
2542
4113
|
async assertFusionDirNotSymlinked() {
|
|
2543
|
-
const fusionDir =
|
|
4114
|
+
const fusionDir = path7.join(this.wearablesDir, FUSION_DIR_NAME);
|
|
2544
4115
|
let stat4;
|
|
2545
4116
|
try {
|
|
2546
4117
|
stat4 = await this.io.lstat(fusionDir);
|
|
@@ -2676,7 +4247,7 @@ function reconstructFusionInputs(date, dayTranscripts) {
|
|
|
2676
4247
|
|
|
2677
4248
|
// src/meetings/store.ts
|
|
2678
4249
|
import { createHash as createHash3 } from "crypto";
|
|
2679
|
-
import * as
|
|
4250
|
+
import * as path8 from "path";
|
|
2680
4251
|
var MEETINGS_DIR_NAME = "meetings";
|
|
2681
4252
|
var MEETING_KIND = "meeting";
|
|
2682
4253
|
var MEETING_FORMAT_VERSION = 1;
|
|
@@ -2845,7 +4416,7 @@ var MeetingRecordStore = class {
|
|
|
2845
4416
|
constructor(memoryDir, io) {
|
|
2846
4417
|
this.memoryDir = memoryDir;
|
|
2847
4418
|
this.io = io;
|
|
2848
|
-
this.meetingsDir =
|
|
4419
|
+
this.meetingsDir = path8.join(memoryDir, MEETINGS_DIR_NAME);
|
|
2849
4420
|
}
|
|
2850
4421
|
memoryDir;
|
|
2851
4422
|
io;
|
|
@@ -2861,7 +4432,7 @@ var MeetingRecordStore = class {
|
|
|
2861
4432
|
if (idDate !== date) {
|
|
2862
4433
|
throw new Error(`meeting id '${id}' does not belong to date '${date}'`);
|
|
2863
4434
|
}
|
|
2864
|
-
return
|
|
4435
|
+
return path8.join(this.meetingsDir, date, `${id}.md`);
|
|
2865
4436
|
}
|
|
2866
4437
|
/** Idempotently persist a record: skip the write when contentHash is unchanged. */
|
|
2867
4438
|
async saveMeetingRecord(record) {
|
|
@@ -2892,7 +4463,7 @@ var MeetingRecordStore = class {
|
|
|
2892
4463
|
if (!isValidTranscriptDate(date)) {
|
|
2893
4464
|
throw new Error(`invalid meeting date '${String(date)}' \u2014 expected YYYY-MM-DD`);
|
|
2894
4465
|
}
|
|
2895
|
-
const dayDir =
|
|
4466
|
+
const dayDir = path8.join(this.meetingsDir, date);
|
|
2896
4467
|
await this.assertPathContained(dayDir);
|
|
2897
4468
|
let entries;
|
|
2898
4469
|
try {
|
|
@@ -2960,10 +4531,10 @@ var MeetingRecordStore = class {
|
|
|
2960
4531
|
try {
|
|
2961
4532
|
rootReal = await this.io.realpath(this.memoryDir);
|
|
2962
4533
|
} catch {
|
|
2963
|
-
rootReal =
|
|
4534
|
+
rootReal = path8.resolve(this.memoryDir);
|
|
2964
4535
|
}
|
|
2965
4536
|
await this.assertNoEscape(rootReal, this.meetingsDir);
|
|
2966
|
-
await this.assertNoEscape(rootReal,
|
|
4537
|
+
await this.assertNoEscape(rootReal, path8.dirname(targetPath));
|
|
2967
4538
|
await this.assertNoEscape(rootReal, targetPath);
|
|
2968
4539
|
await this.assertNotSymlink(this.meetingsDir, "directory");
|
|
2969
4540
|
const dayDir = this.dayDirWithin(targetPath);
|
|
@@ -2973,11 +4544,11 @@ var MeetingRecordStore = class {
|
|
|
2973
4544
|
/** The `<meetings>/<date>` directory on the path to `targetPath`, or null when
|
|
2974
4545
|
* `targetPath` is the meetings root itself. */
|
|
2975
4546
|
dayDirWithin(targetPath) {
|
|
2976
|
-
const rel =
|
|
2977
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
2978
|
-
const first = rel.split(
|
|
4547
|
+
const rel = path8.relative(this.meetingsDir, targetPath);
|
|
4548
|
+
if (rel === "" || rel.startsWith("..") || path8.isAbsolute(rel)) return null;
|
|
4549
|
+
const first = rel.split(path8.sep)[0];
|
|
2979
4550
|
if (first === void 0 || first.length === 0) return null;
|
|
2980
|
-
return
|
|
4551
|
+
return path8.join(this.meetingsDir, first);
|
|
2981
4552
|
}
|
|
2982
4553
|
async assertNotSymlink(target, kind) {
|
|
2983
4554
|
let stat4;
|
|
@@ -2988,7 +4559,7 @@ var MeetingRecordStore = class {
|
|
|
2988
4559
|
throw err;
|
|
2989
4560
|
}
|
|
2990
4561
|
if (stat4.isSymbolicLink) {
|
|
2991
|
-
const label =
|
|
4562
|
+
const label = path8.relative(this.memoryDir, target) || MEETINGS_DIR_NAME;
|
|
2992
4563
|
throw new Error(
|
|
2993
4564
|
`meetings ${kind} '${label}' is a symbolic link \u2014 refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`
|
|
2994
4565
|
);
|
|
@@ -3406,7 +4977,7 @@ function isEntitySynthesisStale(entity) {
|
|
|
3406
4977
|
|
|
3407
4978
|
// src/storage/content-hash-index.ts
|
|
3408
4979
|
import { mkdir, stat as stat2 } from "fs/promises";
|
|
3409
|
-
import
|
|
4980
|
+
import path9 from "path";
|
|
3410
4981
|
var CONTENT_HASH_INDEX_LOCK_STALE_MS = 3e4;
|
|
3411
4982
|
var CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS = 5;
|
|
3412
4983
|
var CONTENT_HASH_INDEX_RETRY_BASE_MS = 500;
|
|
@@ -3466,8 +5037,8 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
3466
5037
|
* re-read on the hot path.
|
|
3467
5038
|
*/
|
|
3468
5039
|
lastSyncedFingerprint = null;
|
|
3469
|
-
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir =
|
|
3470
|
-
this.filePath =
|
|
5040
|
+
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path9.dirname(stateDir), lockOptions = {}) {
|
|
5041
|
+
this.filePath = path9.join(stateDir, "fact-hashes.txt");
|
|
3471
5042
|
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
3472
5043
|
this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
|
|
3473
5044
|
this.memoryDir = memoryDir;
|
|
@@ -3559,7 +5130,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
3559
5130
|
/** Persist index to disk if changed (plain whole-file overwrite). */
|
|
3560
5131
|
async save() {
|
|
3561
5132
|
if (!this.dirty) return;
|
|
3562
|
-
await mkdir(
|
|
5133
|
+
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
3563
5134
|
const publishedAdded = new Set(this.added);
|
|
3564
5135
|
const publishedRemoved = new Set(this.removed);
|
|
3565
5136
|
const serialized = [...this.hashes].join("\n") + "\n";
|
|
@@ -3606,7 +5177,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
3606
5177
|
*/
|
|
3607
5178
|
async saveMergingWithDisk() {
|
|
3608
5179
|
if (!this.dirty) return;
|
|
3609
|
-
await mkdir(
|
|
5180
|
+
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
3610
5181
|
const publish = async () => {
|
|
3611
5182
|
const publishedAdded = new Set(this.added);
|
|
3612
5183
|
const publishedRemoved = new Set(this.removed);
|
|
@@ -3625,7 +5196,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
3625
5196
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
3626
5197
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3627
5198
|
}
|
|
3628
|
-
await mkdir(
|
|
5199
|
+
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
3629
5200
|
await writeMaybeEncryptedFile(
|
|
3630
5201
|
this.filePath,
|
|
3631
5202
|
[...merged].join("\n") + "\n",
|
|
@@ -3692,7 +5263,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
3692
5263
|
* fired retry) simply yields `acquired=false` after the bounded wait.
|
|
3693
5264
|
*/
|
|
3694
5265
|
async rebuildUnderLock(populate) {
|
|
3695
|
-
await mkdir(
|
|
5266
|
+
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
3696
5267
|
let published = false;
|
|
3697
5268
|
await withHeldFileLock(
|
|
3698
5269
|
`${this.filePath}.lock`,
|
|
@@ -4493,17 +6064,17 @@ function inferEntityTypeFromContent(content) {
|
|
|
4493
6064
|
}
|
|
4494
6065
|
var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set(["company", "other", "person", "place", "project", "tool", "topic"]);
|
|
4495
6066
|
function inferEntityTypeFromFilename(pathRel) {
|
|
4496
|
-
const basename =
|
|
6067
|
+
const basename = path10.basename(pathRel, ".md").toLowerCase();
|
|
4497
6068
|
const separator = basename.indexOf("-");
|
|
4498
6069
|
if (separator <= 0) return void 0;
|
|
4499
6070
|
const candidate = basename.slice(0, separator);
|
|
4500
6071
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
4501
6072
|
}
|
|
4502
6073
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
4503
|
-
const normalizedPath = pathRel.split(
|
|
6074
|
+
const normalizedPath = pathRel.split(path10.sep).join("/");
|
|
4504
6075
|
let normalizedFrontmatter = frontmatter;
|
|
4505
6076
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
4506
|
-
const basename =
|
|
6077
|
+
const basename = path10.basename(pathRel, ".md");
|
|
4507
6078
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
4508
6079
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
4509
6080
|
normalizedFrontmatter = {
|
|
@@ -4524,27 +6095,6 @@ function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
|
4524
6095
|
function inferCurrentStateStatus(frontmatter, pathRel, fallbackStatus) {
|
|
4525
6096
|
return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
|
|
4526
6097
|
}
|
|
4527
|
-
var BUILTIN_ALIASES = {
|
|
4528
|
-
openclaw: "openclaw",
|
|
4529
|
-
"open-claw": "openclaw"
|
|
4530
|
-
};
|
|
4531
|
-
function normalizeEntityName(raw, type, aliases) {
|
|
4532
|
-
const rawStr = typeof raw === "string" ? raw : "";
|
|
4533
|
-
const typeStr = typeof type === "string" && type.trim().length > 0 ? type : "entity";
|
|
4534
|
-
let name = rawStr.toLowerCase().trim();
|
|
4535
|
-
const typePrefix = `${typeStr.toLowerCase()}-`;
|
|
4536
|
-
if (name.startsWith(typePrefix)) {
|
|
4537
|
-
name = name.slice(typePrefix.length);
|
|
4538
|
-
}
|
|
4539
|
-
let normalized = name.replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
4540
|
-
const userAlias = aliases !== void 0 && Object.hasOwn(aliases, normalized) ? aliases[normalized] : void 0;
|
|
4541
|
-
if (typeof userAlias === "string" && userAlias.length > 0) {
|
|
4542
|
-
normalized = userAlias;
|
|
4543
|
-
} else if (Object.hasOwn(BUILTIN_ALIASES, normalized)) {
|
|
4544
|
-
normalized = BUILTIN_ALIASES[normalized];
|
|
4545
|
-
}
|
|
4546
|
-
return `${typeStr.toLowerCase()}-${normalized}`;
|
|
4547
|
-
}
|
|
4548
6098
|
function levenshtein(a, b) {
|
|
4549
6099
|
const m = a.length;
|
|
4550
6100
|
const n = b.length;
|
|
@@ -4990,6 +6540,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
4990
6540
|
this.entitySchemas = entitySchemas;
|
|
4991
6541
|
this.hotMemoriesCacheEnabled = hotMemoriesCacheEnabledOverride ?? _StorageManager.hotMemoriesCacheDefaultByDir.get(baseDir) ?? _StorageManager.hotMemoriesCacheDefault;
|
|
4992
6542
|
this.loadAliasesSync();
|
|
6543
|
+
this.loadHistoricalEntityCanonicalIdsSync();
|
|
4993
6544
|
}
|
|
4994
6545
|
baseDir;
|
|
4995
6546
|
entitySchemas;
|
|
@@ -5169,8 +6720,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
5169
6720
|
}
|
|
5170
6721
|
/** Whether `filePath` resolves to `stateDir` itself or a path contained in it. */
|
|
5171
6722
|
isStateFilePath(filePath) {
|
|
5172
|
-
const rel =
|
|
5173
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6723
|
+
const rel = path10.relative(this.stateDir, path10.resolve(filePath));
|
|
6724
|
+
return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
|
|
5174
6725
|
}
|
|
5175
6726
|
// ── Tombstone store (issue #1579) ───────────────────────────────────────
|
|
5176
6727
|
// Instance-scoped (rule 11) + namespace-scoped (rule 42) tombstone index.
|
|
@@ -5213,17 +6764,34 @@ var StorageManager3 = class _StorageManager {
|
|
|
5213
6764
|
* Set or clear the at-rest encryption key.
|
|
5214
6765
|
*
|
|
5215
6766
|
* Pass a 32-byte Buffer to enable encryption; pass null to clear
|
|
5216
|
-
* (lock) the store.
|
|
6767
|
+
* (lock) the store. The caller is responsible for key lifecycle —
|
|
5217
6768
|
* this method does not zero the buffer on replacement; the keyring
|
|
5218
|
-
* module (`keyring.ts`) owns zeroization.
|
|
6769
|
+
* module (`keyring.ts`) owns zeroization. The setter remains synchronous
|
|
6770
|
+
* for existing consumers; call `setSecureStoreKeyAndWait` when unlock must
|
|
6771
|
+
* await migration.
|
|
5219
6772
|
*/
|
|
5220
6773
|
setSecureStoreKey(key, encryptOnWrite = true) {
|
|
6774
|
+
if (!this.applySecureStoreKey(key, encryptOnWrite) || key === null) return;
|
|
6775
|
+
void this.entityCanonicalIdMigration.triggerAfterUnlock().catch((error) => {
|
|
6776
|
+
log.warn(
|
|
6777
|
+
`secure-store unlock migration failed: ${error instanceof Error ? error.message : String(error)}`
|
|
6778
|
+
);
|
|
6779
|
+
});
|
|
6780
|
+
}
|
|
6781
|
+
async setSecureStoreKeyAndWait(key, encryptOnWrite = true) {
|
|
6782
|
+
if (!this.applySecureStoreKey(key, encryptOnWrite) || key === null) return;
|
|
6783
|
+
await this.entityCanonicalIdMigration.triggerAfterUnlock();
|
|
6784
|
+
}
|
|
6785
|
+
applySecureStoreKey(key, encryptOnWrite) {
|
|
6786
|
+
const sameKey = this._secureStoreKey === key || this._secureStoreKey !== null && key !== null && this._secureStoreKey.equals(key);
|
|
6787
|
+
if (sameKey && this._secureStoreEncryptOnWrite === encryptOnWrite) return false;
|
|
5221
6788
|
this._secureStoreKey = key;
|
|
5222
6789
|
this._secureStoreEncryptOnWrite = encryptOnWrite;
|
|
5223
6790
|
invalidateAllForDir(this.baseDir);
|
|
5224
6791
|
this.invalidateKnowledgeIndexCache();
|
|
5225
6792
|
this.secureFileEncryptionSniffCache.clear();
|
|
5226
6793
|
this.behaviorSignalsKeyCache = null;
|
|
6794
|
+
return true;
|
|
5227
6795
|
}
|
|
5228
6796
|
getEntityCacheSecureStoreKey() {
|
|
5229
6797
|
if (!this._secureStoreKey) return "secure-store:locked";
|
|
@@ -5270,8 +6838,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
5270
6838
|
* /srv/cold/remnic), skipping the patch and stranding a stale hot entry.
|
|
5271
6839
|
*/
|
|
5272
6840
|
isColdOrArchiveTierPath(p) {
|
|
5273
|
-
const rel =
|
|
5274
|
-
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${
|
|
6841
|
+
const rel = path10.relative(this.baseDir, p);
|
|
6842
|
+
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${path10.sep}`) || rel.startsWith(`archive${path10.sep}`);
|
|
5275
6843
|
}
|
|
5276
6844
|
/**
|
|
5277
6845
|
* Mark the secure-store as required for this storage instance.
|
|
@@ -5358,7 +6926,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5358
6926
|
void 0,
|
|
5359
6927
|
this.baseDir
|
|
5360
6928
|
);
|
|
5361
|
-
const rel =
|
|
6929
|
+
const rel = path10.relative(this.baseDir, filePath).split(path10.sep).join("/");
|
|
5362
6930
|
return `${rel}:${version.versionId}`;
|
|
5363
6931
|
} catch (err) {
|
|
5364
6932
|
log.warn(
|
|
@@ -5438,13 +7006,13 @@ var StorageManager3 = class _StorageManager {
|
|
|
5438
7006
|
}
|
|
5439
7007
|
identityFilePath(workspaceDir, namespace) {
|
|
5440
7008
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
5441
|
-
if (!rawNamespace) return
|
|
7009
|
+
if (!rawNamespace) return path10.join(workspaceDir, "IDENTITY.md");
|
|
5442
7010
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
5443
|
-
return
|
|
7011
|
+
return path10.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
5444
7012
|
}
|
|
5445
7013
|
versionFilePath(kind) {
|
|
5446
7014
|
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : kind === "cold-write" ? ".cold-write-version.log" : ".memory-corpus-version.log";
|
|
5447
|
-
return
|
|
7015
|
+
return path10.join(this.stateDir, fileName);
|
|
5448
7016
|
}
|
|
5449
7017
|
bumpSharedVersion(kind, fallbackMap) {
|
|
5450
7018
|
const filePath = this.versionFilePath(kind);
|
|
@@ -5536,7 +7104,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5536
7104
|
// memories.
|
|
5537
7105
|
// -------------------------------------------------------------------------
|
|
5538
7106
|
get wearablesDir() {
|
|
5539
|
-
return
|
|
7107
|
+
return path10.join(this.baseDir, WEARABLES_DIR_NAME);
|
|
5540
7108
|
}
|
|
5541
7109
|
/**
|
|
5542
7110
|
* Resolve the on-disk path for a source/day transcript. Throws on
|
|
@@ -5552,7 +7120,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5552
7120
|
if (!isValidTranscriptDate(date)) {
|
|
5553
7121
|
throw new Error(`invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`);
|
|
5554
7122
|
}
|
|
5555
|
-
return
|
|
7123
|
+
return path10.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
5556
7124
|
}
|
|
5557
7125
|
async writeWearableDayTranscript(sourceId, date, serialized) {
|
|
5558
7126
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
@@ -5578,10 +7146,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
5578
7146
|
return this._fusionStore = new FusionArtifactStore(this.wearablesDir, this.baseDir, {
|
|
5579
7147
|
writeFile: (p, c) => this.writeStorageSecureFile(p, c),
|
|
5580
7148
|
readFile: (p) => readMaybeEncryptedFile(p, this._secureStoreKey, this.baseDir),
|
|
5581
|
-
readDir: (d) =>
|
|
5582
|
-
deleteFile: (p) =>
|
|
5583
|
-
realpath: (p) =>
|
|
5584
|
-
lstat: (p) =>
|
|
7149
|
+
readDir: (d) => readdir5(d),
|
|
7150
|
+
deleteFile: (p) => unlink3(p),
|
|
7151
|
+
realpath: (p) => realpath3(p),
|
|
7152
|
+
lstat: (p) => lstat3(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
|
|
5585
7153
|
});
|
|
5586
7154
|
}
|
|
5587
7155
|
_meetingRecordStore;
|
|
@@ -5596,10 +7164,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
5596
7164
|
return this._meetingRecordStore = new MeetingRecordStore(this.baseDir, {
|
|
5597
7165
|
writeFile: (p, c) => this.writeStorageSecureFile(p, c),
|
|
5598
7166
|
readFile: (p) => readMaybeEncryptedFile(p, this._secureStoreKey, this.baseDir),
|
|
5599
|
-
readDir: (d) =>
|
|
5600
|
-
deleteFile: (p) =>
|
|
5601
|
-
realpath: (p) =>
|
|
5602
|
-
lstat: (p) =>
|
|
7167
|
+
readDir: (d) => readdir5(d),
|
|
7168
|
+
deleteFile: (p) => unlink3(p),
|
|
7169
|
+
realpath: (p) => realpath3(p),
|
|
7170
|
+
lstat: (p) => lstat3(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
|
|
5603
7171
|
});
|
|
5604
7172
|
}
|
|
5605
7173
|
/**
|
|
@@ -5668,25 +7236,25 @@ var StorageManager3 = class _StorageManager {
|
|
|
5668
7236
|
});
|
|
5669
7237
|
}
|
|
5670
7238
|
get factsDir() {
|
|
5671
|
-
return
|
|
7239
|
+
return path10.join(this.baseDir, "facts");
|
|
5672
7240
|
}
|
|
5673
7241
|
get correctionsDir() {
|
|
5674
|
-
return
|
|
7242
|
+
return path10.join(this.baseDir, "corrections");
|
|
5675
7243
|
}
|
|
5676
7244
|
get proceduresDir() {
|
|
5677
|
-
return
|
|
7245
|
+
return path10.join(this.baseDir, "procedures");
|
|
5678
7246
|
}
|
|
5679
7247
|
get reasoningTracesDir() {
|
|
5680
|
-
return
|
|
7248
|
+
return path10.join(this.baseDir, "reasoning-traces");
|
|
5681
7249
|
}
|
|
5682
7250
|
get entitiesDir() {
|
|
5683
|
-
return
|
|
7251
|
+
return path10.join(this.baseDir, "entities");
|
|
5684
7252
|
}
|
|
5685
7253
|
resolveEntityFilePath(name) {
|
|
5686
7254
|
if (typeof name !== "string") return null;
|
|
5687
|
-
const filePath =
|
|
5688
|
-
const relative2 =
|
|
5689
|
-
if (relative2 === "" || relative2.startsWith("..") ||
|
|
7255
|
+
const filePath = path10.resolve(this.entitiesDir, `${name}.md`);
|
|
7256
|
+
const relative2 = path10.relative(this.entitiesDir, filePath);
|
|
7257
|
+
if (relative2 === "" || relative2.startsWith("..") || path10.isAbsolute(relative2)) {
|
|
5690
7258
|
return null;
|
|
5691
7259
|
}
|
|
5692
7260
|
return filePath;
|
|
@@ -5707,10 +7275,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
5707
7275
|
});
|
|
5708
7276
|
}
|
|
5709
7277
|
assertManagedStoragePath(filePath, method) {
|
|
5710
|
-
const resolved =
|
|
5711
|
-
const base =
|
|
5712
|
-
const rel =
|
|
5713
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
7278
|
+
const resolved = path10.resolve(filePath);
|
|
7279
|
+
const base = path10.resolve(this.baseDir);
|
|
7280
|
+
const rel = path10.relative(base, resolved);
|
|
7281
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path10.sep}`) || path10.isAbsolute(rel)) {
|
|
5714
7282
|
throw new Error(`${method}: file path escapes memory dir`);
|
|
5715
7283
|
}
|
|
5716
7284
|
return resolved;
|
|
@@ -5722,7 +7290,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5722
7290
|
async digestOfflineSyncFile(filePath) {
|
|
5723
7291
|
const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
|
|
5724
7292
|
const st = await stat3(target);
|
|
5725
|
-
const relPath =
|
|
7293
|
+
const relPath = path10.relative(this.baseDir, target).split(path10.sep).join("/");
|
|
5726
7294
|
const cache = await this.loadOfflineSyncDigestCache();
|
|
5727
7295
|
const cached = cache.get(relPath);
|
|
5728
7296
|
if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
|
|
@@ -5768,7 +7336,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5768
7336
|
async readOfflineSyncDigestCache() {
|
|
5769
7337
|
const cache = /* @__PURE__ */ new Map();
|
|
5770
7338
|
try {
|
|
5771
|
-
const raw = await
|
|
7339
|
+
const raw = await readFile2(this.offlineSyncDigestCachePath, "utf-8");
|
|
5772
7340
|
const parsed = JSON.parse(raw);
|
|
5773
7341
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return cache;
|
|
5774
7342
|
const entries = parsed.entries;
|
|
@@ -5783,7 +7351,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5783
7351
|
const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
|
|
5784
7352
|
const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
|
|
5785
7353
|
const encrypted = record.encrypted === true;
|
|
5786
|
-
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") ||
|
|
7354
|
+
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path10.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
|
|
5787
7355
|
continue;
|
|
5788
7356
|
}
|
|
5789
7357
|
cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
|
|
@@ -5825,8 +7393,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
5825
7393
|
const cache = this.offlineSyncDigestCache;
|
|
5826
7394
|
if (!cache) return;
|
|
5827
7395
|
const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
5828
|
-
await mkdir2(
|
|
5829
|
-
await
|
|
7396
|
+
await mkdir2(path10.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
7397
|
+
await writeFile2(this.offlineSyncDigestCachePath, `${JSON.stringify({ version: 1, entries })}
|
|
5830
7398
|
`, "utf-8");
|
|
5831
7399
|
}).catch((err) => {
|
|
5832
7400
|
log.warn(
|
|
@@ -5860,7 +7428,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5860
7428
|
}
|
|
5861
7429
|
async deleteOfflineSyncFile(filePath) {
|
|
5862
7430
|
const target = this.assertManagedStoragePath(filePath, "storage.deleteOfflineSyncFile");
|
|
5863
|
-
await
|
|
7431
|
+
await unlink3(target).catch((error) => {
|
|
5864
7432
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
5865
7433
|
throw error;
|
|
5866
7434
|
});
|
|
@@ -5870,10 +7438,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
5870
7438
|
this.invalidateAllMemoriesCache();
|
|
5871
7439
|
this.invalidateKnowledgeIndexCache();
|
|
5872
7440
|
this.factHashIndexAuthoritative = false;
|
|
5873
|
-
if (filePath.includes(`${
|
|
7441
|
+
if (filePath.includes(`${path10.sep}cold${path10.sep}`)) {
|
|
5874
7442
|
this.invalidateColdMemoriesCache();
|
|
5875
7443
|
}
|
|
5876
|
-
if (filePath.includes(`${
|
|
7444
|
+
if (filePath.includes(`${path10.sep}artifacts${path10.sep}`)) {
|
|
5877
7445
|
this.bumpArtifactWriteVersion();
|
|
5878
7446
|
}
|
|
5879
7447
|
this.bumpMemoryStatusVersion();
|
|
@@ -5944,7 +7512,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5944
7512
|
}
|
|
5945
7513
|
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
5946
7514
|
const writeKey = this.resolveWriteKey();
|
|
5947
|
-
await mkdir2(
|
|
7515
|
+
await mkdir2(path10.dirname(filePath), { recursive: true });
|
|
5948
7516
|
if (writeKey === null) {
|
|
5949
7517
|
try {
|
|
5950
7518
|
if (await this.isEncryptedFileHeader(filePath)) {
|
|
@@ -5970,13 +7538,13 @@ var StorageManager3 = class _StorageManager {
|
|
|
5970
7538
|
this.notifyCatalogWriteForPath(filePath);
|
|
5971
7539
|
}
|
|
5972
7540
|
get stateDir() {
|
|
5973
|
-
return
|
|
7541
|
+
return path10.join(this.baseDir, "state");
|
|
5974
7542
|
}
|
|
5975
7543
|
get offlineSyncDigestCachePath() {
|
|
5976
|
-
return
|
|
7544
|
+
return path10.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
|
|
5977
7545
|
}
|
|
5978
7546
|
get entitySynthesisQueuePath() {
|
|
5979
|
-
return
|
|
7547
|
+
return path10.join(this.stateDir, "entity-synthesis-queue.json");
|
|
5980
7548
|
}
|
|
5981
7549
|
// ── Tombstone store access (issue #1579) ─────────────────────────────────
|
|
5982
7550
|
/**
|
|
@@ -5986,7 +7554,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
5986
7554
|
* helpers are injected into the store).
|
|
5987
7555
|
*/
|
|
5988
7556
|
get tombstonesPath() {
|
|
5989
|
-
return
|
|
7557
|
+
return path10.join(this.stateDir, "tombstones.jsonl");
|
|
5990
7558
|
}
|
|
5991
7559
|
/**
|
|
5992
7560
|
* Configure the tombstone invariant for this storage instance. Installed
|
|
@@ -6259,55 +7827,55 @@ var StorageManager3 = class _StorageManager {
|
|
|
6259
7827
|
return null;
|
|
6260
7828
|
}
|
|
6261
7829
|
get questionsDir() {
|
|
6262
|
-
return
|
|
7830
|
+
return path10.join(this.baseDir, "questions");
|
|
6263
7831
|
}
|
|
6264
7832
|
get artifactsDir() {
|
|
6265
|
-
return
|
|
7833
|
+
return path10.join(this.baseDir, "artifacts");
|
|
6266
7834
|
}
|
|
6267
7835
|
get identityDir() {
|
|
6268
|
-
return
|
|
7836
|
+
return path10.join(this.baseDir, "identity");
|
|
6269
7837
|
}
|
|
6270
7838
|
get identityAnchorPath() {
|
|
6271
|
-
return
|
|
7839
|
+
return path10.join(this.identityDir, "identity-anchor.md");
|
|
6272
7840
|
}
|
|
6273
7841
|
get identityIncidentsDir() {
|
|
6274
|
-
return
|
|
7842
|
+
return path10.join(this.identityDir, "incidents");
|
|
6275
7843
|
}
|
|
6276
7844
|
get identityAuditsWeeklyDir() {
|
|
6277
|
-
return
|
|
7845
|
+
return path10.join(this.identityDir, "audits", "weekly");
|
|
6278
7846
|
}
|
|
6279
7847
|
get identityAuditsMonthlyDir() {
|
|
6280
|
-
return
|
|
7848
|
+
return path10.join(this.identityDir, "audits", "monthly");
|
|
6281
7849
|
}
|
|
6282
7850
|
get identityImprovementLoopsPath() {
|
|
6283
|
-
return
|
|
7851
|
+
return path10.join(this.identityDir, "improvement-loops.md");
|
|
6284
7852
|
}
|
|
6285
7853
|
get identityReflectionsPath() {
|
|
6286
|
-
return
|
|
7854
|
+
return path10.join(this.identityDir, "reflections.md");
|
|
6287
7855
|
}
|
|
6288
7856
|
get profilePath() {
|
|
6289
|
-
return
|
|
7857
|
+
return path10.join(this.baseDir, "profile.md");
|
|
6290
7858
|
}
|
|
6291
7859
|
get memoryActionsPath() {
|
|
6292
|
-
return
|
|
7860
|
+
return path10.join(this.stateDir, "memory-actions.jsonl");
|
|
6293
7861
|
}
|
|
6294
7862
|
get memoryLifecycleLedgerPath() {
|
|
6295
|
-
return
|
|
7863
|
+
return path10.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
6296
7864
|
}
|
|
6297
7865
|
get compressionGuidelinesPath() {
|
|
6298
|
-
return
|
|
7866
|
+
return path10.join(this.stateDir, "compression-guidelines.md");
|
|
6299
7867
|
}
|
|
6300
7868
|
get compressionGuidelineDraftPath() {
|
|
6301
|
-
return
|
|
7869
|
+
return path10.join(this.stateDir, "compression-guidelines.draft.md");
|
|
6302
7870
|
}
|
|
6303
7871
|
get compressionGuidelineStatePath() {
|
|
6304
|
-
return
|
|
7872
|
+
return path10.join(this.stateDir, "compression-guideline-state.json");
|
|
6305
7873
|
}
|
|
6306
7874
|
get compressionGuidelineDraftStatePath() {
|
|
6307
|
-
return
|
|
7875
|
+
return path10.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
6308
7876
|
}
|
|
6309
7877
|
get behaviorSignalsPath() {
|
|
6310
|
-
return
|
|
7878
|
+
return path10.join(this.stateDir, "behavior-signals.jsonl");
|
|
6311
7879
|
}
|
|
6312
7880
|
/**
|
|
6313
7881
|
* In-memory dedup key set for behavior-signals appends (issue #1909),
|
|
@@ -6339,7 +7907,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
6339
7907
|
* governance sweeps can treat it uniformly.
|
|
6340
7908
|
*/
|
|
6341
7909
|
get bufferSurpriseLedgerPath() {
|
|
6342
|
-
return
|
|
7910
|
+
return path10.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
6343
7911
|
}
|
|
6344
7912
|
/**
|
|
6345
7913
|
* Entity alias table loaded from THIS store's config/aliases.json.
|
|
@@ -6349,8 +7917,17 @@ var StorageManager3 = class _StorageManager {
|
|
|
6349
7917
|
* store loaded last rewrite every other store's canonical entity ids.
|
|
6350
7918
|
*/
|
|
6351
7919
|
userAliases = {};
|
|
7920
|
+
historicalEntityCanonicalIds = {};
|
|
7921
|
+
entityCanonicalIdMigration = new EntityCanonicalIdMigrationRunner(
|
|
7922
|
+
() => !(this._secureStoreRequired && !this.isSecureStoreUnlocked()),
|
|
7923
|
+
() => this.runLegacyEntityCanonicalIdMigration(),
|
|
7924
|
+
() => getFingerprint(this.baseDir, this.entitiesDir, () => this.getCorpusScanVersion())
|
|
7925
|
+
);
|
|
6352
7926
|
normalizeEntityName(raw, type) {
|
|
6353
|
-
return
|
|
7927
|
+
return resolveHistoricalEntityCanonicalId(
|
|
7928
|
+
this.entityStore.normalizeEntityName(raw, type),
|
|
7929
|
+
this.historicalEntityCanonicalIds
|
|
7930
|
+
);
|
|
6354
7931
|
}
|
|
6355
7932
|
/**
|
|
6356
7933
|
* Read-only view of this store's user alias table, for call sites that
|
|
@@ -6370,10 +7947,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
6370
7947
|
this.loadAliasesSync();
|
|
6371
7948
|
}
|
|
6372
7949
|
loadAliasesSync() {
|
|
6373
|
-
const aliasPath =
|
|
7950
|
+
const aliasPath = path10.join(this.baseDir, "config", "aliases.json");
|
|
6374
7951
|
this.userAliases = {};
|
|
6375
7952
|
try {
|
|
6376
|
-
const raw =
|
|
7953
|
+
const raw = readFileSync2(aliasPath, "utf-8");
|
|
6377
7954
|
const parsed = JSON.parse(raw);
|
|
6378
7955
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
6379
7956
|
const cleaned = {};
|
|
@@ -6391,11 +7968,26 @@ var StorageManager3 = class _StorageManager {
|
|
|
6391
7968
|
log.debug("no config/aliases.json found \u2014 using built-in aliases only");
|
|
6392
7969
|
}
|
|
6393
7970
|
}
|
|
7971
|
+
loadHistoricalEntityCanonicalIdsSync() {
|
|
7972
|
+
this.historicalEntityCanonicalIds = loadHistoricalEntityCanonicalIds(this.stateDir);
|
|
7973
|
+
}
|
|
7974
|
+
async runLegacyEntityCanonicalIdMigration() {
|
|
7975
|
+
const completionFingerprint = await runLegacyEntityCanonicalIdMigration(
|
|
7976
|
+
this,
|
|
7977
|
+
(content) => parseEntityFile(content, this.entitySchemas),
|
|
7978
|
+
(entity) => serializeEntityFile(entity, this.entitySchemas),
|
|
7979
|
+
createMemoryEntityRefSerializer(serializeFrontmatter)
|
|
7980
|
+
);
|
|
7981
|
+
this.loadHistoricalEntityCanonicalIdsSync();
|
|
7982
|
+
return completionFingerprint;
|
|
7983
|
+
}
|
|
6394
7984
|
async ensureDirectories() {
|
|
7985
|
+
await mkdir2(this.baseDir, { recursive: true });
|
|
7986
|
+
await validateRoots(this.baseDir, this.entitiesDir, this.stateDir, this.archiveDir);
|
|
6395
7987
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6396
|
-
await mkdir2(
|
|
6397
|
-
await mkdir2(
|
|
6398
|
-
await mkdir2(
|
|
7988
|
+
await mkdir2(path10.join(this.factsDir, today), { recursive: true });
|
|
7989
|
+
await mkdir2(path10.join(this.proceduresDir, today), { recursive: true });
|
|
7990
|
+
await mkdir2(path10.join(this.reasoningTracesDir, today), { recursive: true });
|
|
6399
7991
|
await mkdir2(this.correctionsDir, { recursive: true });
|
|
6400
7992
|
await mkdir2(this.entitiesDir, { recursive: true });
|
|
6401
7993
|
await mkdir2(this.stateDir, { recursive: true });
|
|
@@ -6405,10 +7997,12 @@ var StorageManager3 = class _StorageManager {
|
|
|
6405
7997
|
await mkdir2(this.identityIncidentsDir, { recursive: true });
|
|
6406
7998
|
await mkdir2(this.identityAuditsWeeklyDir, { recursive: true });
|
|
6407
7999
|
await mkdir2(this.identityAuditsMonthlyDir, { recursive: true });
|
|
6408
|
-
await mkdir2(
|
|
8000
|
+
await mkdir2(path10.join(this.baseDir, "config"), { recursive: true });
|
|
8001
|
+
await this.entityCanonicalIdMigration.ensure();
|
|
6409
8002
|
if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
|
|
6410
8003
|
this.bumpMemoryCorpusVersion();
|
|
6411
8004
|
}
|
|
8005
|
+
await this.entityCanonicalIdMigration.markDirectoriesInitialized();
|
|
6412
8006
|
}
|
|
6413
8007
|
/**
|
|
6414
8008
|
* Resolve the on-disk write path for a memory of the given category, creating
|
|
@@ -6425,11 +8019,11 @@ var StorageManager3 = class _StorageManager {
|
|
|
6425
8019
|
async resolveCategoryWritePath(category, id, today) {
|
|
6426
8020
|
if (category === "correction") {
|
|
6427
8021
|
await mkdir2(this.correctionsDir, { recursive: true });
|
|
6428
|
-
return
|
|
8022
|
+
return path10.join(this.correctionsDir, `${id}.md`);
|
|
6429
8023
|
}
|
|
6430
|
-
const datedDir =
|
|
8024
|
+
const datedDir = path10.join(getCategoryDir(this.baseDir, category), today);
|
|
6431
8025
|
await mkdir2(datedDir, { recursive: true });
|
|
6432
|
-
return
|
|
8026
|
+
return path10.join(datedDir, `${id}.md`);
|
|
6433
8027
|
}
|
|
6434
8028
|
async writeMemory(category, content, options = {}) {
|
|
6435
8029
|
await this.ensureDirectories();
|
|
@@ -6734,7 +8328,7 @@ ${sanitized.text}
|
|
|
6734
8328
|
await this.ensureDirectories();
|
|
6735
8329
|
const now = /* @__PURE__ */ new Date();
|
|
6736
8330
|
const day = now.toISOString().slice(0, 10);
|
|
6737
|
-
const dir =
|
|
8331
|
+
const dir = path10.join(this.artifactsDir, day);
|
|
6738
8332
|
await mkdir2(dir, { recursive: true });
|
|
6739
8333
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
6740
8334
|
const fm = {
|
|
@@ -6759,7 +8353,7 @@ ${sanitized.text}
|
|
|
6759
8353
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
6760
8354
|
return "";
|
|
6761
8355
|
}
|
|
6762
|
-
const filePath =
|
|
8356
|
+
const filePath = path10.join(dir, `${id}.md`);
|
|
6763
8357
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
|
|
6764
8358
|
|
|
6765
8359
|
${sanitized.text}
|
|
@@ -6812,14 +8406,12 @@ ${sanitized.text}
|
|
|
6812
8406
|
}
|
|
6813
8407
|
}
|
|
6814
8408
|
async writeProfile(content) {
|
|
8409
|
+
const stampedContent = renderProfileWithLastUpdated(content, (/* @__PURE__ */ new Date()).toISOString());
|
|
6815
8410
|
await this.ensureDirectories();
|
|
6816
8411
|
await this.snapshotBeforeWrite(this.profilePath, "consolidation");
|
|
6817
|
-
await this.writeStorageSecureFile(this.profilePath,
|
|
8412
|
+
await this.writeStorageSecureFile(this.profilePath, stampedContent);
|
|
6818
8413
|
log.debug("updated profile.md");
|
|
6819
8414
|
}
|
|
6820
|
-
/**
|
|
6821
|
-
* Normalize a string for fuzzy profile dedup: lowercase, strip punctuation, collapse whitespace.
|
|
6822
|
-
*/
|
|
6823
8415
|
static normalizeForDedup(s) {
|
|
6824
8416
|
if (typeof s !== "string") return "";
|
|
6825
8417
|
return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -6855,15 +8447,12 @@ ${sanitized.text}
|
|
|
6855
8447
|
const content = [
|
|
6856
8448
|
"# Behavioral Profile",
|
|
6857
8449
|
"",
|
|
6858
|
-
`*Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}*`,
|
|
6859
|
-
"",
|
|
6860
8450
|
...newBullets.map((b) => `- ${b}`),
|
|
6861
8451
|
""
|
|
6862
8452
|
].join("\n");
|
|
6863
8453
|
await this.writeProfile(content);
|
|
6864
8454
|
} else {
|
|
6865
|
-
const
|
|
6866
|
-
const withBullets = updatedTimestamp.trimEnd() + "\n" + newBullets.map((b) => `- ${b}`).join("\n") + "\n";
|
|
8455
|
+
const withBullets = existing.trimEnd() + "\n" + newBullets.map((b) => `- ${b}`).join("\n") + "\n";
|
|
6867
8456
|
await this.writeProfile(withBullets);
|
|
6868
8457
|
}
|
|
6869
8458
|
}
|
|
@@ -7035,7 +8624,7 @@ ${sanitized.text}
|
|
|
7035
8624
|
* per-process in-memory cache safe across process boundaries.
|
|
7036
8625
|
*/
|
|
7037
8626
|
invalidateColdMemoriesCache() {
|
|
7038
|
-
const coldRoot =
|
|
8627
|
+
const coldRoot = path10.join(this.baseDir, "cold");
|
|
7039
8628
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
7040
8629
|
this.bumpColdWriteVersion();
|
|
7041
8630
|
invalidateAllForDir(this.baseDir);
|
|
@@ -7095,7 +8684,7 @@ ${sanitized.text}
|
|
|
7095
8684
|
const raw = await readMaybeEncryptedFile(fullPath, this._secureStoreKey, this.baseDir);
|
|
7096
8685
|
const parsed = parseFrontmatter(raw);
|
|
7097
8686
|
if (!parsed) return null;
|
|
7098
|
-
return {
|
|
8687
|
+
return rememberRawFrontmatter({
|
|
7099
8688
|
path: fullPath,
|
|
7100
8689
|
frontmatter: normalizeFrontmatterForPath(
|
|
7101
8690
|
parsed.frontmatter,
|
|
@@ -7103,7 +8692,7 @@ ${sanitized.text}
|
|
|
7103
8692
|
parsed.content
|
|
7104
8693
|
),
|
|
7105
8694
|
content: parsed.content
|
|
7106
|
-
};
|
|
8695
|
+
}, raw);
|
|
7107
8696
|
} catch (err) {
|
|
7108
8697
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
7109
8698
|
return null;
|
|
@@ -7150,7 +8739,7 @@ ${sanitized.text}
|
|
|
7150
8739
|
const correctionPaths = [];
|
|
7151
8740
|
const factPaths = [];
|
|
7152
8741
|
for (const filePath of filePaths) {
|
|
7153
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
8742
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path10.sep}`)) {
|
|
7154
8743
|
correctionPaths.push(filePath);
|
|
7155
8744
|
} else {
|
|
7156
8745
|
factPaths.push(filePath);
|
|
@@ -7211,9 +8800,9 @@ ${sanitized.text}
|
|
|
7211
8800
|
const root = this.archiveDir;
|
|
7212
8801
|
const readDir = async (dir) => {
|
|
7213
8802
|
try {
|
|
7214
|
-
const entries = await
|
|
8803
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
7215
8804
|
for (const entry of entries) {
|
|
7216
|
-
const fullPath =
|
|
8805
|
+
const fullPath = path10.join(dir, entry.name);
|
|
7217
8806
|
if (entry.isDirectory()) {
|
|
7218
8807
|
await readDir(fullPath);
|
|
7219
8808
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7221,7 +8810,7 @@ ${sanitized.text}
|
|
|
7221
8810
|
const raw = await readMaybeEncryptedFile(fullPath, this._secureStoreKey, this.baseDir);
|
|
7222
8811
|
const parsed = parseFrontmatter(raw);
|
|
7223
8812
|
if (parsed) {
|
|
7224
|
-
memories.push({
|
|
8813
|
+
memories.push(rememberRawFrontmatter({
|
|
7225
8814
|
path: fullPath,
|
|
7226
8815
|
frontmatter: normalizeFrontmatterForPath(
|
|
7227
8816
|
parsed.frontmatter,
|
|
@@ -7229,7 +8818,7 @@ ${sanitized.text}
|
|
|
7229
8818
|
parsed.content
|
|
7230
8819
|
),
|
|
7231
8820
|
content: parsed.content
|
|
7232
|
-
});
|
|
8821
|
+
}, raw));
|
|
7233
8822
|
}
|
|
7234
8823
|
} catch (err) {
|
|
7235
8824
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
@@ -7246,7 +8835,7 @@ ${sanitized.text}
|
|
|
7246
8835
|
return this.memoryReadStore.readMemoryByPath(filePath);
|
|
7247
8836
|
}
|
|
7248
8837
|
resolveTierRootDir(tier) {
|
|
7249
|
-
return tier === "cold" ?
|
|
8838
|
+
return tier === "cold" ? path10.join(this.baseDir, "cold") : this.baseDir;
|
|
7250
8839
|
}
|
|
7251
8840
|
resolveMemoryDateDir(memory) {
|
|
7252
8841
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -7261,13 +8850,13 @@ ${sanitized.text}
|
|
|
7261
8850
|
buildTierMemoryPath(memory, tier) {
|
|
7262
8851
|
const root = this.resolveTierRootDir(tier);
|
|
7263
8852
|
if (this.isArtifactMemory(memory)) {
|
|
7264
|
-
return
|
|
8853
|
+
return path10.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
7265
8854
|
}
|
|
7266
8855
|
if (memory.frontmatter.category === "correction") {
|
|
7267
|
-
return
|
|
8856
|
+
return path10.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
7268
8857
|
}
|
|
7269
8858
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
7270
|
-
return
|
|
8859
|
+
return path10.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
7271
8860
|
}
|
|
7272
8861
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
7273
8862
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}
|
|
@@ -7280,11 +8869,11 @@ ${memory.content}
|
|
|
7280
8869
|
}
|
|
7281
8870
|
async moveMemoryToPath(memory, targetPath) {
|
|
7282
8871
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
7283
|
-
const sourcePath =
|
|
7284
|
-
const destPath =
|
|
8872
|
+
const sourcePath = path10.resolve(memory.path);
|
|
8873
|
+
const destPath = path10.resolve(targetPath);
|
|
7285
8874
|
if (sourcePath !== destPath) {
|
|
7286
8875
|
try {
|
|
7287
|
-
await
|
|
8876
|
+
await unlink3(memory.path);
|
|
7288
8877
|
} catch (err) {
|
|
7289
8878
|
const message = err instanceof Error ? err.message : String(err);
|
|
7290
8879
|
if (!message.includes("ENOENT")) {
|
|
@@ -7297,15 +8886,15 @@ ${memory.content}
|
|
|
7297
8886
|
}
|
|
7298
8887
|
async migrateMemoryToTier(memory, targetTier) {
|
|
7299
8888
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
7300
|
-
const sourcePath =
|
|
7301
|
-
const destPath =
|
|
8889
|
+
const sourcePath = path10.resolve(memory.path);
|
|
8890
|
+
const destPath = path10.resolve(targetPath);
|
|
7302
8891
|
if (sourcePath === destPath) {
|
|
7303
8892
|
return { changed: false, targetPath };
|
|
7304
8893
|
}
|
|
7305
8894
|
const existing = await this.readMemoryByPath(targetPath);
|
|
7306
8895
|
if (existing?.frontmatter.id === memory.frontmatter.id) {
|
|
7307
8896
|
try {
|
|
7308
|
-
await
|
|
8897
|
+
await unlink3(memory.path);
|
|
7309
8898
|
} catch (err) {
|
|
7310
8899
|
const message = err instanceof Error ? err.message : String(err);
|
|
7311
8900
|
if (!message.includes("ENOENT")) {
|
|
@@ -7325,7 +8914,7 @@ ${memory.content}
|
|
|
7325
8914
|
return { changed: true, targetPath };
|
|
7326
8915
|
}
|
|
7327
8916
|
get archiveDir() {
|
|
7328
|
-
return
|
|
8917
|
+
return path10.join(this.baseDir, "archive");
|
|
7329
8918
|
}
|
|
7330
8919
|
/**
|
|
7331
8920
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -7336,7 +8925,7 @@ ${memory.content}
|
|
|
7336
8925
|
try {
|
|
7337
8926
|
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
7338
8927
|
const today = now.toISOString().slice(0, 10);
|
|
7339
|
-
const destDir =
|
|
8928
|
+
const destDir = path10.join(this.archiveDir, today);
|
|
7340
8929
|
await mkdir2(destDir, { recursive: true });
|
|
7341
8930
|
const updatedFm = {
|
|
7342
8931
|
...memory.frontmatter,
|
|
@@ -7348,9 +8937,9 @@ ${memory.content}
|
|
|
7348
8937
|
|
|
7349
8938
|
${memory.content}
|
|
7350
8939
|
`;
|
|
7351
|
-
const destPath =
|
|
8940
|
+
const destPath = path10.join(destDir, path10.basename(memory.path));
|
|
7352
8941
|
await this.writeStorageSecureFile(destPath, fileContent);
|
|
7353
|
-
await
|
|
8942
|
+
await unlink3(memory.path);
|
|
7354
8943
|
markProjectedMemoryPathInvalid(this.baseDir, memory.frontmatter.id);
|
|
7355
8944
|
this.invalidateAllMemoriesCache();
|
|
7356
8945
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
@@ -7378,7 +8967,7 @@ ${memory.content}
|
|
|
7378
8967
|
}
|
|
7379
8968
|
async readEntities() {
|
|
7380
8969
|
try {
|
|
7381
|
-
const entries = await
|
|
8970
|
+
const entries = await readdir5(this.entitiesDir);
|
|
7382
8971
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", ""));
|
|
7383
8972
|
} catch {
|
|
7384
8973
|
return [];
|
|
@@ -7430,7 +9019,7 @@ ${memory.content}
|
|
|
7430
9019
|
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
7431
9020
|
if (!memory) return false;
|
|
7432
9021
|
try {
|
|
7433
|
-
await
|
|
9022
|
+
await unlink3(memory.path);
|
|
7434
9023
|
markProjectedMemoryPathInvalid(this.baseDir, id);
|
|
7435
9024
|
this.invalidateAllMemoriesCache();
|
|
7436
9025
|
this.bumpMemoryStatusVersion();
|
|
@@ -7496,7 +9085,7 @@ ${memory.content}
|
|
|
7496
9085
|
`;
|
|
7497
9086
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
7498
9087
|
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
7499
|
-
if (memory.path.includes(`${
|
|
9088
|
+
if (memory.path.includes(`${path10.sep}cold${path10.sep}`)) {
|
|
7500
9089
|
this.invalidateColdMemoriesCache();
|
|
7501
9090
|
}
|
|
7502
9091
|
try {
|
|
@@ -7551,7 +9140,7 @@ ${memory.content}
|
|
|
7551
9140
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
7552
9141
|
if (expiresAt < now) {
|
|
7553
9142
|
try {
|
|
7554
|
-
await
|
|
9143
|
+
await unlink3(m.path);
|
|
7555
9144
|
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
7556
9145
|
this.bumpMemoryCorpusVersion();
|
|
7557
9146
|
deleted.push(m);
|
|
@@ -7567,7 +9156,7 @@ ${memory.content}
|
|
|
7567
9156
|
return deleted;
|
|
7568
9157
|
}
|
|
7569
9158
|
async loadBuffer() {
|
|
7570
|
-
const bufferPath =
|
|
9159
|
+
const bufferPath = path10.join(this.stateDir, "buffer.json");
|
|
7571
9160
|
try {
|
|
7572
9161
|
const raw = await this.readStorageSecureFile(bufferPath);
|
|
7573
9162
|
return JSON.parse(raw);
|
|
@@ -7579,11 +9168,11 @@ ${memory.content}
|
|
|
7579
9168
|
}
|
|
7580
9169
|
async saveBuffer(state) {
|
|
7581
9170
|
await this.ensureDirectories();
|
|
7582
|
-
const bufferPath =
|
|
9171
|
+
const bufferPath = path10.join(this.stateDir, "buffer.json");
|
|
7583
9172
|
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
7584
9173
|
}
|
|
7585
9174
|
async loadMeta() {
|
|
7586
|
-
const metaPath =
|
|
9175
|
+
const metaPath = path10.join(this.stateDir, "meta.json");
|
|
7587
9176
|
try {
|
|
7588
9177
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
7589
9178
|
const parsed = JSON.parse(raw);
|
|
@@ -7616,7 +9205,7 @@ ${memory.content}
|
|
|
7616
9205
|
}
|
|
7617
9206
|
async saveMeta(state) {
|
|
7618
9207
|
await this.ensureDirectories();
|
|
7619
|
-
const metaPath =
|
|
9208
|
+
const metaPath = path10.join(this.stateDir, "meta.json");
|
|
7620
9209
|
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
7621
9210
|
}
|
|
7622
9211
|
async appendMemoryActionEvents(events) {
|
|
@@ -7683,7 +9272,7 @@ ${memory.content}
|
|
|
7683
9272
|
},
|
|
7684
9273
|
(payload) => this.appendStorageSecureFile(target, payload),
|
|
7685
9274
|
async () => {
|
|
7686
|
-
await mkdir2(
|
|
9275
|
+
await mkdir2(path10.dirname(target), { recursive: true });
|
|
7687
9276
|
}
|
|
7688
9277
|
);
|
|
7689
9278
|
}
|
|
@@ -7781,7 +9370,7 @@ ${memory.content}
|
|
|
7781
9370
|
async appendReextractJobs(events) {
|
|
7782
9371
|
if (events.length === 0) return 0;
|
|
7783
9372
|
await this.ensureDirectories();
|
|
7784
|
-
const filePath =
|
|
9373
|
+
const filePath = path10.join(this.stateDir, "reextract-jobs.jsonl");
|
|
7785
9374
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
7786
9375
|
try {
|
|
7787
9376
|
await this.appendStorageSecureFile(filePath, lines);
|
|
@@ -7794,7 +9383,7 @@ ${memory.content}
|
|
|
7794
9383
|
}
|
|
7795
9384
|
async readReextractJobs(limit = 200) {
|
|
7796
9385
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
7797
|
-
const filePath =
|
|
9386
|
+
const filePath = path10.join(this.stateDir, "reextract-jobs.jsonl");
|
|
7798
9387
|
try {
|
|
7799
9388
|
const raw = await this.readStorageSecureFile(filePath);
|
|
7800
9389
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
@@ -7955,8 +9544,8 @@ ${memory.content}
|
|
|
7955
9544
|
activationState: "active"
|
|
7956
9545
|
});
|
|
7957
9546
|
await Promise.all([
|
|
7958
|
-
|
|
7959
|
-
|
|
9547
|
+
unlink3(this.compressionGuidelineDraftPath).catch(() => void 0),
|
|
9548
|
+
unlink3(this.compressionGuidelineDraftStatePath).catch(() => void 0)
|
|
7960
9549
|
]);
|
|
7961
9550
|
return true;
|
|
7962
9551
|
}
|
|
@@ -8024,7 +9613,7 @@ ${question}
|
|
|
8024
9613
|
|
|
8025
9614
|
**Context:** ${context}
|
|
8026
9615
|
`;
|
|
8027
|
-
const filePath =
|
|
9616
|
+
const filePath = path10.join(this.questionsDir, `${id}.md`);
|
|
8028
9617
|
await this.writeStorageSecureFile(filePath, content);
|
|
8029
9618
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
8030
9619
|
this.invalidateQuestionsCache();
|
|
@@ -8042,7 +9631,7 @@ ${question}
|
|
|
8042
9631
|
if (!match) return null;
|
|
8043
9632
|
const frontmatterStr = match[1];
|
|
8044
9633
|
const body = match[2].trim();
|
|
8045
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
9634
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path10.basename(filePath, ".md");
|
|
8046
9635
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
8047
9636
|
const priority = parseFloat(this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5");
|
|
8048
9637
|
const resolved = this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
|
|
@@ -8076,14 +9665,14 @@ ${question}
|
|
|
8076
9665
|
async readIdentity(workspaceDir, namespace) {
|
|
8077
9666
|
const identityPath = this.identityFilePath(workspaceDir, namespace);
|
|
8078
9667
|
try {
|
|
8079
|
-
return await
|
|
9668
|
+
return await readFile2(identityPath, "utf-8");
|
|
8080
9669
|
} catch {
|
|
8081
9670
|
return "";
|
|
8082
9671
|
}
|
|
8083
9672
|
}
|
|
8084
9673
|
async writeIdentity(workspaceDir, content, namespace) {
|
|
8085
9674
|
const identityPath = this.identityFilePath(workspaceDir, namespace);
|
|
8086
|
-
await
|
|
9675
|
+
await writeFile2(identityPath, content, "utf-8");
|
|
8087
9676
|
log.debug(`wrote consolidated IDENTITY.md (${content.length} chars)`);
|
|
8088
9677
|
}
|
|
8089
9678
|
/** Max size for IDENTITY.md before we stop appending reflections (15KB leaves room under 20KB gateway limit) */
|
|
@@ -8094,22 +9683,22 @@ ${question}
|
|
|
8094
9683
|
const identityPath = this.identityFilePath(workspaceDir, opts?.namespace);
|
|
8095
9684
|
let existing = "";
|
|
8096
9685
|
try {
|
|
8097
|
-
existing = await
|
|
9686
|
+
existing = await readFile2(identityPath, "utf-8");
|
|
8098
9687
|
} catch {
|
|
8099
9688
|
}
|
|
8100
9689
|
const hygiene = opts?.hygiene;
|
|
8101
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
9690
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path10.basename(identityPath));
|
|
8102
9691
|
if (rotateEnabled) {
|
|
8103
9692
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
8104
9693
|
if (existing.length > maxBytes) {
|
|
8105
|
-
const archiveDir =
|
|
9694
|
+
const archiveDir = path10.join(workspaceDir, hygiene.archiveDir);
|
|
8106
9695
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
8107
9696
|
filePath: identityPath,
|
|
8108
9697
|
archiveDir,
|
|
8109
9698
|
archivePrefix: "IDENTITY",
|
|
8110
9699
|
keepTailChars: hygiene.rotateKeepTailChars
|
|
8111
9700
|
});
|
|
8112
|
-
await
|
|
9701
|
+
await writeFile2(identityPath, newContent, "utf-8");
|
|
8113
9702
|
existing = newContent;
|
|
8114
9703
|
log.info(`rotated IDENTITY.md to archive (size=${existing.length} chars, maxBytes=${maxBytes})`);
|
|
8115
9704
|
}
|
|
@@ -8142,7 +9731,7 @@ ${question}
|
|
|
8142
9731
|
|
|
8143
9732
|
${reflection}
|
|
8144
9733
|
`;
|
|
8145
|
-
await
|
|
9734
|
+
await writeFile2(identityPath, existing + section, "utf-8");
|
|
8146
9735
|
log.debug(`appended reflection to ${identityPath}`);
|
|
8147
9736
|
}
|
|
8148
9737
|
async readIdentityReflections() {
|
|
@@ -8198,7 +9787,7 @@ ${reflection}
|
|
|
8198
9787
|
* Deduplicates by target+label.
|
|
8199
9788
|
*/
|
|
8200
9789
|
async addEntityRelationship(name, rel) {
|
|
8201
|
-
const filePath =
|
|
9790
|
+
const filePath = path10.join(this.entitiesDir, `${name}.md`);
|
|
8202
9791
|
let entity;
|
|
8203
9792
|
try {
|
|
8204
9793
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -8221,7 +9810,7 @@ ${reflection}
|
|
|
8221
9810
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
8222
9811
|
*/
|
|
8223
9812
|
async addEntityActivity(name, entry, maxEntries) {
|
|
8224
|
-
const filePath =
|
|
9813
|
+
const filePath = path10.join(this.entitiesDir, `${name}.md`);
|
|
8225
9814
|
let entity;
|
|
8226
9815
|
try {
|
|
8227
9816
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -8240,11 +9829,8 @@ ${reflection}
|
|
|
8240
9829
|
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
8241
9830
|
this.invalidateKnowledgeIndexCache();
|
|
8242
9831
|
}
|
|
8243
|
-
/**
|
|
8244
|
-
* Add an alias to an entity file. Deduplicates.
|
|
8245
|
-
*/
|
|
8246
9832
|
async addEntityAlias(name, alias) {
|
|
8247
|
-
const filePath =
|
|
9833
|
+
const filePath = path10.join(this.entitiesDir, `${name}.md`);
|
|
8248
9834
|
let entity;
|
|
8249
9835
|
try {
|
|
8250
9836
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -8260,6 +9846,7 @@ ${reflection}
|
|
|
8260
9846
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
8261
9847
|
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
8262
9848
|
this.invalidateKnowledgeIndexCache();
|
|
9849
|
+
this.bumpMemoryStatusVersion();
|
|
8263
9850
|
}
|
|
8264
9851
|
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
8265
9852
|
return this.entityStore.updateEntitySynthesis(name, synthesis, options);
|
|
@@ -8271,7 +9858,7 @@ ${reflection}
|
|
|
8271
9858
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8272
9859
|
let synthesisTimelineCount;
|
|
8273
9860
|
try {
|
|
8274
|
-
const filePath =
|
|
9861
|
+
const filePath = path10.join(this.entitiesDir, `${name}.md`);
|
|
8275
9862
|
const content = await this.readStorageSecureFile(filePath);
|
|
8276
9863
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
8277
9864
|
} catch (err) {
|
|
@@ -8354,7 +9941,7 @@ ${reflection}
|
|
|
8354
9941
|
if (!raw) continue;
|
|
8355
9942
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
8356
9943
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
8357
|
-
await this.writeStorageSecureFile(
|
|
9944
|
+
await this.writeStorageSecureFile(path10.join(this.entitiesDir, `${entityName}.md`), serialized);
|
|
8358
9945
|
migrated += 1;
|
|
8359
9946
|
}
|
|
8360
9947
|
if (migrated > 0) {
|
|
@@ -8422,7 +10009,7 @@ ${reflection}
|
|
|
8422
10009
|
const updatedAt = new Date(m.frontmatter.updated).getTime();
|
|
8423
10010
|
if (updatedAt < cutoff) {
|
|
8424
10011
|
try {
|
|
8425
|
-
await
|
|
10012
|
+
await unlink3(m.path);
|
|
8426
10013
|
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
8427
10014
|
this.bumpMemoryCorpusVersion();
|
|
8428
10015
|
deleted.push(m);
|
|
@@ -8447,14 +10034,14 @@ ${reflection}
|
|
|
8447
10034
|
if (entries.length === 0) return 0;
|
|
8448
10035
|
const memories = await this.readAllMemories();
|
|
8449
10036
|
const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
|
|
8450
|
-
const memoryPathMap = new Map(memories.map((m) => [
|
|
10037
|
+
const memoryPathMap = new Map(memories.map((m) => [path10.resolve(m.path), m]));
|
|
8451
10038
|
let updated = 0;
|
|
8452
10039
|
const prevVersion = this.getMemoryCorpusVersion();
|
|
8453
10040
|
const keyId = this.hotCacheKeyId();
|
|
8454
10041
|
const warm = this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
|
|
8455
10042
|
const appliedPatches = /* @__PURE__ */ new Map();
|
|
8456
10043
|
for (const entry of entries) {
|
|
8457
|
-
const memory = entry.memoryPath ? memoryPathMap.get(
|
|
10044
|
+
const memory = entry.memoryPath ? memoryPathMap.get(path10.resolve(entry.memoryPath)) : memoryMap.get(entry.memoryId);
|
|
8458
10045
|
if (!memory) continue;
|
|
8459
10046
|
const newFm = {
|
|
8460
10047
|
...memory.frontmatter,
|
|
@@ -8470,7 +10057,7 @@ ${memory.content}
|
|
|
8470
10057
|
if (warm) {
|
|
8471
10058
|
updateCacheOnWrite(this.baseDir, { ...memory, frontmatter: newFm }, keyId);
|
|
8472
10059
|
}
|
|
8473
|
-
appliedPatches.set(
|
|
10060
|
+
appliedPatches.set(path10.resolve(memory.path), {
|
|
8474
10061
|
accessCount: entry.newCount,
|
|
8475
10062
|
lastAccessed: entry.lastAccessed
|
|
8476
10063
|
});
|
|
@@ -8486,7 +10073,7 @@ ${memory.content}
|
|
|
8486
10073
|
const cur = getCachedMemories(this.baseDir, prevVersion, keyId);
|
|
8487
10074
|
if (cur) {
|
|
8488
10075
|
const reapplied = cur.map((m) => {
|
|
8489
|
-
const patch = appliedPatches.get(
|
|
10076
|
+
const patch = appliedPatches.get(path10.resolve(m.path));
|
|
8490
10077
|
return patch ? { ...m, frontmatter: { ...m.frontmatter, accessCount: patch.accessCount, lastAccessed: patch.lastAccessed } } : m;
|
|
8491
10078
|
});
|
|
8492
10079
|
setCachedMemories(this.baseDir, reapplied, produced, keyId, this.hotCacheTtlMs());
|
|
@@ -8518,7 +10105,7 @@ ${memory.content}
|
|
|
8518
10105
|
const pathsById = /* @__PURE__ */ new Map();
|
|
8519
10106
|
const filePathsById = /* @__PURE__ */ new Map();
|
|
8520
10107
|
for (const filePath of filePaths) {
|
|
8521
|
-
const memoryId =
|
|
10108
|
+
const memoryId = path10.basename(filePath, ".md");
|
|
8522
10109
|
if (!wantedIds.has(memoryId)) continue;
|
|
8523
10110
|
const paths = filePathsById.get(memoryId) ?? [];
|
|
8524
10111
|
paths.push(filePath);
|
|
@@ -8533,7 +10120,7 @@ ${memory.content}
|
|
|
8533
10120
|
qmdResultPathCandidates(this.baseDir, preferredPath)
|
|
8534
10121
|
);
|
|
8535
10122
|
const directMatch = existingPaths.find(
|
|
8536
|
-
(filePath) => directCandidates.has(
|
|
10123
|
+
(filePath) => directCandidates.has(path10.resolve(filePath))
|
|
8537
10124
|
);
|
|
8538
10125
|
if (directMatch) {
|
|
8539
10126
|
preferredMatches.push(directMatch);
|
|
@@ -8546,7 +10133,7 @@ ${memory.content}
|
|
|
8546
10133
|
candidates.add(candidate);
|
|
8547
10134
|
}
|
|
8548
10135
|
}
|
|
8549
|
-
const match = existingPaths.find((filePath) => candidates.has(
|
|
10136
|
+
const match = existingPaths.find((filePath) => candidates.has(path10.resolve(filePath)));
|
|
8550
10137
|
if (match) preferredMatches.push(match);
|
|
8551
10138
|
}
|
|
8552
10139
|
if (preferredMatches.length > 0) {
|
|
@@ -8765,14 +10352,14 @@ Reason: ${reason}`;
|
|
|
8765
10352
|
// Memory Summarization (Phase 4A)
|
|
8766
10353
|
// ---------------------------------------------------------------------------
|
|
8767
10354
|
get summariesDir() {
|
|
8768
|
-
return
|
|
10355
|
+
return path10.join(this.baseDir, "summaries");
|
|
8769
10356
|
}
|
|
8770
10357
|
/**
|
|
8771
10358
|
* Write a memory summary.
|
|
8772
10359
|
*/
|
|
8773
10360
|
async writeSummary(summary) {
|
|
8774
10361
|
await mkdir2(this.summariesDir, { recursive: true });
|
|
8775
|
-
const filePath =
|
|
10362
|
+
const filePath = path10.join(this.summariesDir, `${summary.id}.json`);
|
|
8776
10363
|
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
8777
10364
|
log.debug(`wrote summary ${summary.id}`);
|
|
8778
10365
|
}
|
|
@@ -8781,11 +10368,11 @@ Reason: ${reason}`;
|
|
|
8781
10368
|
*/
|
|
8782
10369
|
async readSummaries() {
|
|
8783
10370
|
try {
|
|
8784
|
-
const files = await
|
|
10371
|
+
const files = await readdir5(this.summariesDir);
|
|
8785
10372
|
const summaries = [];
|
|
8786
10373
|
for (const file of files) {
|
|
8787
10374
|
if (!file.endsWith(".json")) continue;
|
|
8788
|
-
const filePath =
|
|
10375
|
+
const filePath = path10.join(this.summariesDir, file);
|
|
8789
10376
|
const raw = await this.readStorageSecureFile(filePath);
|
|
8790
10377
|
summaries.push(JSON.parse(raw));
|
|
8791
10378
|
}
|
|
@@ -8847,7 +10434,7 @@ ${memory.content}
|
|
|
8847
10434
|
* Save topic scores to meta.json.
|
|
8848
10435
|
*/
|
|
8849
10436
|
async saveTopics(topics) {
|
|
8850
|
-
const metaPath =
|
|
10437
|
+
const metaPath = path10.join(this.stateDir, "topics.json");
|
|
8851
10438
|
await mkdir2(this.stateDir, { recursive: true });
|
|
8852
10439
|
await this.writeStorageSecureFile(
|
|
8853
10440
|
metaPath,
|
|
@@ -8859,7 +10446,7 @@ ${memory.content}
|
|
|
8859
10446
|
* Load topic scores from meta.json.
|
|
8860
10447
|
*/
|
|
8861
10448
|
async loadTopics() {
|
|
8862
|
-
const metaPath =
|
|
10449
|
+
const metaPath = path10.join(this.stateDir, "topics.json");
|
|
8863
10450
|
try {
|
|
8864
10451
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
8865
10452
|
return JSON.parse(raw);
|
|
@@ -8975,11 +10562,10 @@ export {
|
|
|
8975
10562
|
parseFrontmatterStringValue,
|
|
8976
10563
|
parseFrontmatter,
|
|
8977
10564
|
normalizeFrontmatterForPath,
|
|
8978
|
-
normalizeEntityName,
|
|
8979
10565
|
parseEntityFile,
|
|
8980
10566
|
serializeEntityFile,
|
|
8981
10567
|
buildEntitySchemaCacheKey,
|
|
8982
10568
|
isValidBufferSurpriseEvent,
|
|
8983
10569
|
StorageManager3 as StorageManager
|
|
8984
10570
|
};
|
|
8985
|
-
//# sourceMappingURL=chunk-
|
|
10571
|
+
//# sourceMappingURL=chunk-Q5VJUNXA.js.map
|