@remnic/core 9.6.0 → 9.6.2
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 +7 -8
- package/dist/access-admin-ops-surface.js +8 -8
- package/dist/access-boundary.d.ts +9 -8
- package/dist/access-boundary.js +9 -9
- package/dist/access-cli.js +40 -40
- package/dist/access-http.d.ts +47 -16
- package/dist/access-http.js +13 -13
- package/dist/access-identity-continuity-surface.d.ts +6 -7
- package/dist/access-identity-continuity-surface.js +8 -8
- package/dist/access-lcm-surface.d.ts +7 -8
- package/dist/access-lcm-surface.js +8 -8
- package/dist/access-mcp.d.ts +13 -8
- package/dist/access-mcp.js +12 -12
- package/dist/access-observe-write-surface.d.ts +7 -8
- package/dist/access-observe-write-surface.js +8 -8
- package/dist/access-operations-batch.js +10 -10
- package/dist/access-operations.d.ts +8 -9
- package/dist/access-operations.js +11 -11
- package/dist/access-recall-surface.d.ts +7 -8
- package/dist/access-recall-surface.js +8 -8
- package/dist/access-schema.d.ts +6 -6
- package/dist/{access-service-CXJaql3A.d.ts → access-service-B_sc1w_k.d.ts} +9 -9
- package/dist/access-service.d.ts +7 -8
- package/dist/access-service.js +8 -8
- package/dist/access-surface-catalog.d.ts +7 -8
- package/dist/action-confidence.d.ts +1 -2
- package/dist/active-memory-bridge.d.ts +1 -2
- package/dist/active-recall.d.ts +1 -2
- package/dist/behavior-learner.d.ts +1 -2
- package/dist/behavior-signals.d.ts +1 -2
- package/dist/bootstrap.d.ts +6 -7
- package/dist/briefing.d.ts +2 -3
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -2
- package/dist/buffer.d.ts +2 -3
- package/dist/buffer.js +1 -1
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/bulk-import/index.js +2 -2
- package/dist/calibration.d.ts +1 -2
- package/dist/capabilities.d.ts +1 -2
- package/dist/{catalog-C9cu67Ig.d.ts → catalog-9WanSGtt.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -2
- package/dist/causal-consolidation.d.ts +1 -2
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -2
- package/dist/{chunk-PNLFN2PZ.js → chunk-23G3T2Q5.js} +2 -2
- package/dist/{chunk-SIRPLF54.js → chunk-2K3OSLHE.js} +2 -2
- package/dist/{chunk-JS2HKKOK.js → chunk-3AC5UYKI.js} +1 -1
- package/dist/{chunk-JS2HKKOK.js.map → chunk-3AC5UYKI.js.map} +1 -1
- package/dist/{chunk-FKCRGLRD.js → chunk-4WRKKRTD.js} +266 -160
- package/dist/chunk-4WRKKRTD.js.map +1 -0
- package/dist/{chunk-LQHDIS7L.js → chunk-54BGIBCR.js} +2 -2
- package/dist/{chunk-PVUJ6WVV.js → chunk-5MH5NF3U.js} +5 -5
- package/dist/{chunk-PCLIWBGD.js → chunk-7ARV32PI.js} +8 -6
- package/dist/chunk-7ARV32PI.js.map +1 -0
- package/dist/{chunk-Y7UNNAZD.js → chunk-7ZYBDQ7V.js} +2 -2
- package/dist/{chunk-2PRQG7PV.js → chunk-AJVLDJU6.js} +1 -1
- package/dist/chunk-AJVLDJU6.js.map +1 -0
- package/dist/{chunk-LR5DZ56F.js → chunk-AXALUFAH.js} +2 -2
- package/dist/{chunk-AYZJID4S.js → chunk-B5GBWBCZ.js} +2 -2
- package/dist/{chunk-TLUDFPZL.js → chunk-CAZ3Z5M6.js} +2 -2
- package/dist/{chunk-RGK27ELN.js → chunk-DEKIXXIG.js} +2 -2
- package/dist/{chunk-2KK5WNQB.js → chunk-DQ42IQSK.js} +2 -2
- package/dist/{chunk-UFUKHGZD.js → chunk-F4I5NYBI.js} +89 -33
- package/dist/chunk-F4I5NYBI.js.map +1 -0
- package/dist/{chunk-HOJZMQ4J.js → chunk-FGDKJJZS.js} +2 -2
- package/dist/{chunk-ABP6QMVM.js → chunk-FQ7HAWWC.js} +2 -2
- package/dist/{chunk-CYWA2CXR.js → chunk-FVTLSFWU.js} +2 -2
- package/dist/{chunk-JJEJJ7RK.js → chunk-FXZTL7F7.js} +2 -2
- package/dist/{chunk-LADFWYHY.js → chunk-GOEV7CYF.js} +6 -6
- package/dist/chunk-GOEV7CYF.js.map +1 -0
- package/dist/{chunk-DANV2HTH.js → chunk-GQGDRLDM.js} +3 -3
- package/dist/{chunk-7F7Z6MOS.js → chunk-H4CKS5B2.js} +2 -2
- package/dist/{chunk-SVOHGUMI.js → chunk-H4FHR4UM.js} +174 -197
- package/dist/chunk-H4FHR4UM.js.map +1 -0
- package/dist/{chunk-CKITWVWJ.js → chunk-HSSERO3E.js} +2 -2
- package/dist/{chunk-23JFRB73.js → chunk-JODHPNLN.js} +2 -2
- package/dist/{chunk-DS7MM2ES.js → chunk-JZKR6ZAV.js} +2 -2
- package/dist/{chunk-BEM4D2QG.js → chunk-KJIJNLXT.js} +3 -3
- package/dist/{chunk-JSNVWNGX.js → chunk-KVSVVKHA.js} +2 -2
- package/dist/{chunk-4GNEDXDI.js → chunk-M67TLMP6.js} +2 -2
- package/dist/chunk-M67TLMP6.js.map +1 -0
- package/dist/{chunk-MC4FJXPA.js → chunk-MMACM5G2.js} +2 -2
- package/dist/{chunk-OUELRE5E.js → chunk-NOUJNHWG.js} +1 -1
- package/dist/chunk-NOUJNHWG.js.map +1 -0
- package/dist/{chunk-3XIY7MDQ.js → chunk-O6YI4CVI.js} +2 -2
- package/dist/{chunk-F53UF7CI.js → chunk-QLVLW7UD.js} +6 -2
- package/dist/{chunk-F53UF7CI.js.map → chunk-QLVLW7UD.js.map} +1 -1
- package/dist/{chunk-XCL57M6Q.js → chunk-RRKPLLCC.js} +21 -20
- package/dist/chunk-RRKPLLCC.js.map +1 -0
- package/dist/{chunk-72WG5QKN.js → chunk-SDAXPXER.js} +2 -2
- package/dist/{chunk-4LV23CHK.js → chunk-VHJARF5R.js} +2 -2
- package/dist/{chunk-H2DXDQCQ.js → chunk-VRJG3HIZ.js} +46 -42
- package/dist/chunk-VRJG3HIZ.js.map +1 -0
- package/dist/{chunk-6MMONPHM.js → chunk-W2AHSCKY.js} +2 -2
- package/dist/{chunk-P454KXD6.js → chunk-YAWJMR3Y.js} +21 -21
- package/dist/{chunk-K5LLP4CY.js → chunk-ZGPDSEKG.js} +20 -9
- package/dist/chunk-ZGPDSEKG.js.map +1 -0
- package/dist/{cli-CqdPA6z9.d.ts → cli-CnRsWREp.d.ts} +4 -5
- package/dist/cli.d.ts +9 -10
- package/dist/cli.js +32 -32
- package/dist/compounding/engine.d.ts +2 -3
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -2
- package/dist/compression-optimizer.d.ts +1 -2
- package/dist/config.d.ts +1 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -2
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -2
- package/dist/connectors/index.d.ts +1 -2
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +2 -3
- package/dist/consolidation-undo.d.ts +2 -3
- package/dist/contradiction/index.d.ts +3 -4
- package/dist/conversation-index/backend.d.ts +1 -2
- package/dist/conversation-index/chunker.d.ts +1 -2
- package/dist/conversation-index/faiss-adapter.d.ts +1 -2
- package/dist/conversation-index/indexer.d.ts +1 -2
- package/dist/conversation-index/search.d.ts +1 -2
- package/dist/day-summary.d.ts +1 -2
- package/dist/delinearize.d.ts +1 -2
- package/dist/direct-answer-wiring.d.ts +1 -2
- package/dist/direct-answer.d.ts +1 -2
- package/dist/embedding-fallback.d.ts +1 -2
- package/dist/enrichment/index.d.ts +1 -2
- package/dist/entity-retrieval.d.ts +2 -3
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -2
- package/dist/explicit-capture.d.ts +8 -7
- package/dist/explicit-capture.js +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -2
- package/dist/extraction-judge-training.d.ts +1 -2
- package/dist/extraction-judge.d.ts +1 -2
- package/dist/extraction.d.ts +1 -2
- package/dist/fallback-llm.d.ts +1 -2
- package/dist/graph-dashboard-diff.d.ts +1 -2
- package/dist/graph-dashboard-key.d.ts +1 -2
- package/dist/graph-dashboard-parser.d.ts +1 -2
- package/dist/graph-edge-reinforcement.d.ts +1 -2
- package/dist/graph-snapshot.d.ts +1 -2
- package/dist/graph.d.ts +1 -2
- package/dist/identity-continuity.d.ts +1 -2
- package/dist/importance.d.ts +1 -2
- package/dist/importers/index.d.ts +1 -1
- package/dist/importers/index.js +2 -2
- package/dist/index.d.ts +14 -15
- package/dist/index.js +40 -40
- package/dist/intent.d.ts +1 -2
- package/dist/lcm/engine.d.ts +1 -2
- package/dist/lcm/index.d.ts +1 -2
- package/dist/lcm/tools.d.ts +1 -2
- package/dist/lifecycle.d.ts +1 -2
- package/dist/live-connectors-runner.d.ts +1 -2
- package/dist/local-llm.d.ts +1 -2
- package/dist/local-model-endpoint.d.ts +1 -2
- package/dist/maintenance/memory-governance.d.ts +1 -2
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +7 -8
- package/dist/memory-action-policy.d.ts +1 -2
- package/dist/memory-cache.d.ts +1 -2
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -2
- package/dist/memory-projection-store.d.ts +1 -2
- package/dist/memory-provenance.d.ts +1 -2
- package/dist/memory-worth-outcomes.d.ts +2 -3
- package/dist/models-json.d.ts +1 -2
- package/dist/namespaces/migrate.d.ts +3 -4
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -2
- package/dist/namespaces/search.d.ts +1 -2
- package/dist/namespaces/storage.d.ts +3 -4
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -2
- package/dist/operator-toolkit.d.ts +2 -3
- package/dist/operator-toolkit.js +7 -7
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -3
- package/dist/orchestration/maintenance.d.ts +2 -3
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-CWFrT8AF.d.ts → orchestrator-NRLVXPpH.d.ts} +40 -15
- package/dist/orchestrator.d.ts +6 -7
- package/dist/orchestrator.js +40 -40
- package/dist/patterns-cli.d.ts +1 -2
- package/dist/{pipeline-DmqtvION.d.ts → pipeline-CiD86zIk.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -2
- package/dist/provenance.d.ts +1 -2
- package/dist/{qmd--fnPMK60.d.ts → qmd-Bef7DDuQ.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -3
- package/dist/qmd-recall-cache.d.ts +1 -2
- package/dist/qmd.d.ts +2 -3
- package/dist/recall-disclosure-escalation.d.ts +1 -2
- package/dist/recall-explain-renderer.d.ts +1 -2
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -2
- package/dist/recall-state.d.ts +1 -2
- package/dist/recall-tag-filter.d.ts +1 -2
- package/dist/recall-timings.d.ts +1 -2
- package/dist/recall-xray-cli.d.ts +1 -2
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -2
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -2
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -0
- package/dist/replay/normalizers/chatgpt.js +3 -3
- package/dist/replay/normalizers/claude.d.ts +1 -0
- package/dist/replay/normalizers/claude.js +3 -3
- package/dist/replay/normalizers/openclaw.d.ts +1 -0
- package/dist/replay/normalizers/openclaw.js +3 -3
- package/dist/replay/normalizers/shared.d.ts +1 -0
- package/dist/replay/normalizers/shared.js +2 -2
- package/dist/replay/runner.d.ts +1 -0
- package/dist/replay/runner.js +2 -2
- package/dist/replay/types.d.ts +2 -1
- package/dist/replay/types.js +1 -1
- package/dist/resolve-auth-token.d.ts +1 -2
- package/dist/retrieval-agents.d.ts +2 -3
- package/dist/retrieval-tiers.d.ts +1 -2
- package/dist/routing/engine.d.ts +1 -2
- package/dist/routing/store.d.ts +1 -2
- package/dist/schemas.d.ts +44 -44
- package/dist/search/embed-helper.d.ts +1 -2
- package/dist/search/factory.d.ts +1 -2
- package/dist/search/index.d.ts +1 -2
- package/dist/search/lancedb-backend.d.ts +1 -2
- package/dist/search/meilisearch-backend.d.ts +1 -2
- package/dist/search/noop-backend.d.ts +1 -2
- package/dist/search/orama-backend.d.ts +1 -2
- package/dist/search/port.d.ts +1 -2
- package/dist/search/remote-backend.d.ts +1 -2
- package/dist/{semantic-DJR8_DMQ.d.ts → semantic-TddhF0y7.d.ts} +17 -1
- package/dist/{semantic-consolidation-O5lMZ4LG.d.ts → semantic-consolidation-BTShF2Fo.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -3
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -2
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -2
- package/dist/session-observer-state.d.ts +1 -2
- package/dist/shared-context/manager.d.ts +3 -4
- package/dist/signal.d.ts +1 -2
- package/dist/{storage-C1maCNbc.d.ts → storage-DxPDAiU9.d.ts} +5 -1
- package/dist/storage.d.ts +2 -3
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -2
- package/dist/summary-snapshot.d.ts +1 -2
- package/dist/temporal-supersession.d.ts +2 -3
- package/dist/temporal-validity.d.ts +1 -2
- package/dist/threading.d.ts +1 -2
- package/dist/tier-migration.d.ts +2 -3
- package/dist/tier-routing.d.ts +1 -2
- package/dist/topics.d.ts +1 -2
- package/dist/transcript.d.ts +1 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -2
- package/dist/trust-score.d.ts +1 -2
- package/dist/{types-CGgcNWCG.d.ts → types-DDUDgd7T.d.ts} +93 -4
- package/dist/types.d.ts +1 -2
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -2
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-admin-ops-surface.ts +1 -0
- package/src/access-boundary.ts +2 -0
- package/src/access-http.test.ts +129 -0
- package/src/access-http.ts +92 -23
- package/src/access-mcp.ts +11 -2
- package/src/access-observe-write-surface.ts +87 -113
- package/src/access-operations-batch.ts +2 -2
- package/src/access-operations.ts +17 -16
- package/src/access-service.ts +11 -3
- package/src/admin/admin-surfaces.ts +2 -0
- package/src/buffer.ts +6 -1
- package/src/bulk-import/types.ts +2 -1
- package/src/coding/architecture-surfaces.test.ts +79 -5
- package/src/coding/architecture-surfaces.ts +7 -1
- package/src/coding/decision-surfaces.test.ts +1 -0
- package/src/coding/decision-surfaces.ts +4 -0
- package/src/connector-provenance-lifecycle.test.ts +618 -0
- package/src/connector-provenance.test.ts +219 -0
- package/src/dedup/semantic.test.ts +105 -0
- package/src/dedup/semantic.ts +69 -1
- package/src/explicit-capture.ts +21 -4
- package/src/orchestration/extraction-persist.ts +154 -6
- package/src/orchestration/extraction-run.ts +138 -228
- package/src/orchestration/persistence-index.ts +40 -5
- package/src/orchestration/turn-ingestion.ts +16 -9
- package/src/orchestrator-connector-dedup.test.ts +1082 -0
- package/src/orchestrator.ts +7 -11
- package/src/replay/types.ts +2 -1
- package/src/storage.ts +426 -553
- package/src/types.ts +11 -2
- package/src/utils/yaml-scalar.ts +20 -0
- package/dist/chunk-2PRQG7PV.js.map +0 -1
- package/dist/chunk-4GNEDXDI.js.map +0 -1
- package/dist/chunk-FKCRGLRD.js.map +0 -1
- package/dist/chunk-H2DXDQCQ.js.map +0 -1
- package/dist/chunk-K5LLP4CY.js.map +0 -1
- package/dist/chunk-LADFWYHY.js.map +0 -1
- package/dist/chunk-OUELRE5E.js.map +0 -1
- package/dist/chunk-PCLIWBGD.js.map +0 -1
- package/dist/chunk-SVOHGUMI.js.map +0 -1
- package/dist/chunk-UFUKHGZD.js.map +0 -1
- package/dist/chunk-XCL57M6Q.js.map +0 -1
- package/dist/types-gb_SNtjR.d.ts +0 -85
- /package/dist/{chunk-PNLFN2PZ.js.map → chunk-23G3T2Q5.js.map} +0 -0
- /package/dist/{chunk-SIRPLF54.js.map → chunk-2K3OSLHE.js.map} +0 -0
- /package/dist/{chunk-LQHDIS7L.js.map → chunk-54BGIBCR.js.map} +0 -0
- /package/dist/{chunk-PVUJ6WVV.js.map → chunk-5MH5NF3U.js.map} +0 -0
- /package/dist/{chunk-Y7UNNAZD.js.map → chunk-7ZYBDQ7V.js.map} +0 -0
- /package/dist/{chunk-LR5DZ56F.js.map → chunk-AXALUFAH.js.map} +0 -0
- /package/dist/{chunk-AYZJID4S.js.map → chunk-B5GBWBCZ.js.map} +0 -0
- /package/dist/{chunk-TLUDFPZL.js.map → chunk-CAZ3Z5M6.js.map} +0 -0
- /package/dist/{chunk-RGK27ELN.js.map → chunk-DEKIXXIG.js.map} +0 -0
- /package/dist/{chunk-2KK5WNQB.js.map → chunk-DQ42IQSK.js.map} +0 -0
- /package/dist/{chunk-HOJZMQ4J.js.map → chunk-FGDKJJZS.js.map} +0 -0
- /package/dist/{chunk-ABP6QMVM.js.map → chunk-FQ7HAWWC.js.map} +0 -0
- /package/dist/{chunk-CYWA2CXR.js.map → chunk-FVTLSFWU.js.map} +0 -0
- /package/dist/{chunk-JJEJJ7RK.js.map → chunk-FXZTL7F7.js.map} +0 -0
- /package/dist/{chunk-DANV2HTH.js.map → chunk-GQGDRLDM.js.map} +0 -0
- /package/dist/{chunk-7F7Z6MOS.js.map → chunk-H4CKS5B2.js.map} +0 -0
- /package/dist/{chunk-CKITWVWJ.js.map → chunk-HSSERO3E.js.map} +0 -0
- /package/dist/{chunk-23JFRB73.js.map → chunk-JODHPNLN.js.map} +0 -0
- /package/dist/{chunk-DS7MM2ES.js.map → chunk-JZKR6ZAV.js.map} +0 -0
- /package/dist/{chunk-BEM4D2QG.js.map → chunk-KJIJNLXT.js.map} +0 -0
- /package/dist/{chunk-JSNVWNGX.js.map → chunk-KVSVVKHA.js.map} +0 -0
- /package/dist/{chunk-MC4FJXPA.js.map → chunk-MMACM5G2.js.map} +0 -0
- /package/dist/{chunk-3XIY7MDQ.js.map → chunk-O6YI4CVI.js.map} +0 -0
- /package/dist/{chunk-72WG5QKN.js.map → chunk-SDAXPXER.js.map} +0 -0
- /package/dist/{chunk-4LV23CHK.js.map → chunk-VHJARF5R.js.map} +0 -0
- /package/dist/{chunk-6MMONPHM.js.map → chunk-W2AHSCKY.js.map} +0 -0
- /package/dist/{chunk-P454KXD6.js.map → chunk-YAWJMR3Y.js.map} +0 -0
package/src/storage.ts
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
access,
|
|
3
|
+
lstat,
|
|
4
|
+
readdir,
|
|
5
|
+
readFile,
|
|
6
|
+
realpath,
|
|
7
|
+
stat,
|
|
8
|
+
writeFile,
|
|
9
|
+
mkdir,
|
|
10
|
+
unlink,
|
|
11
|
+
rename,
|
|
12
|
+
appendFile,
|
|
13
|
+
open,
|
|
14
|
+
} from "node:fs/promises";
|
|
2
15
|
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync, type Dirent } from "node:fs";
|
|
3
|
-
import { createHash } from "node:crypto"
|
|
4
|
-
import { normalizeContent, computeContentHash } from "./content-hash.js"
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { normalizeContent, computeContentHash } from "./content-hash.js";
|
|
5
18
|
import path from "node:path";
|
|
6
19
|
import { log } from "./logger.js";
|
|
7
20
|
import { assertMemoryFrontmatterId, warnProjectionFallback } from "./storage-guards.js";
|
|
8
21
|
import { MemoryReadStore } from "./storage/memory-read-store.js";
|
|
9
|
-
import {
|
|
10
|
-
readMaybeEncryptedLines,
|
|
11
|
-
readMemoryActionEventRowsFromLines,
|
|
12
|
-
} from "./storage/secure-line-reader.js";
|
|
22
|
+
import { readMaybeEncryptedLines, readMemoryActionEventRowsFromLines } from "./storage/secure-line-reader.js";
|
|
13
23
|
import { selfDeps } from "./orchestration/self-deps.js";
|
|
14
24
|
import { EntityStore } from "./storage/entity-store.js";
|
|
15
25
|
import { IdentityContinuityStore } from "./storage/identity-continuity-store.js";
|
|
16
26
|
import { isErrnoCode } from "./utils/errno.js";
|
|
17
27
|
import { RECALL_FALLBACK_DIRS, getCategoryDir, categoryDirName } from "./utils/category-dir.js";
|
|
18
28
|
import { assertPathInsideRoot } from "./utils/path-containment.js";
|
|
29
|
+
import { decodeYamlScalar } from "./utils/yaml-scalar.js";
|
|
19
30
|
import { getCachedEntities, invalidateAllForDir, setCachedEntities } from "./memory-cache.js";
|
|
20
31
|
import { rotateMarkdownFileToArchive } from "./hygiene.js";
|
|
21
32
|
import { sanitizeMemoryContent } from "./sanitize.js";
|
|
22
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
serializeProvenanceFields,
|
|
35
|
+
parseProvenanceSources,
|
|
36
|
+
parseProvenanceTag,
|
|
37
|
+
reconcileProvenanceRead,
|
|
38
|
+
} from "./provenance.js";
|
|
23
39
|
import { serializeFaithfulnessFields, parseFaithfulnessField } from "./extraction-faithfulness.js";
|
|
24
40
|
import { createVersion as createPageVersion, type VersioningConfig, type VersionTrigger } from "./page-versioning.js";
|
|
25
41
|
import { isValidTranscriptDate, WEARABLES_DIR_NAME } from "./wearables/day-store.js";
|
|
@@ -104,11 +120,13 @@ import type {
|
|
|
104
120
|
import { confidenceTier, SPECULATIVE_TTL_DAYS } from "./types.js";
|
|
105
121
|
import {
|
|
106
122
|
type ProjectedMemoryBrowseOptions,
|
|
107
|
-
type ProjectedMemoryBrowsePage,
|
|
123
|
+
type ProjectedMemoryBrowsePage,
|
|
124
|
+
markProjectedMemoryPathInvalid,
|
|
108
125
|
readProjectedMemoryState,
|
|
109
126
|
readProjectedMemoryBrowse,
|
|
110
127
|
readProjectedGovernanceRecord,
|
|
111
|
-
readProjectedMemoryTimeline,
|
|
128
|
+
readProjectedMemoryTimeline,
|
|
129
|
+
updateProjectedMemoryPath,
|
|
112
130
|
} from "./memory-projection-store.js";
|
|
113
131
|
import {
|
|
114
132
|
inferMemoryStatus,
|
|
@@ -116,10 +134,7 @@ import {
|
|
|
116
134
|
sortMemoryLifecycleEvents,
|
|
117
135
|
toMemoryPathRel,
|
|
118
136
|
} from "./memory-lifecycle-ledger-utils.js";
|
|
119
|
-
import {
|
|
120
|
-
normalizeProjectionPreview,
|
|
121
|
-
normalizeProjectionTags,
|
|
122
|
-
} from "./memory-projection-format.js";
|
|
137
|
+
import { normalizeProjectionPreview, normalizeProjectionTags } from "./memory-projection-format.js";
|
|
123
138
|
import { parseFlexibleIsoTimestamp } from "./utils/iso-timestamp.js";
|
|
124
139
|
// stripCitation import removed: legacy rebuild fallback was replaced by a
|
|
125
140
|
// skip-with-warning strategy (Finding 1 — Uhol). See ensureFactHashIndexAuthoritative.
|
|
@@ -207,10 +222,7 @@ function assertMemoryWorthCounter(field: "mw_success" | "mw_fail", value: number
|
|
|
207
222
|
}
|
|
208
223
|
}
|
|
209
224
|
|
|
210
|
-
function normalizeMemoryWriteTimestamp(
|
|
211
|
-
field: string,
|
|
212
|
-
value: string | undefined,
|
|
213
|
-
): string | undefined {
|
|
225
|
+
function normalizeMemoryWriteTimestamp(field: string, value: string | undefined): string | undefined {
|
|
214
226
|
if (value === undefined) return undefined;
|
|
215
227
|
if (typeof value !== "string") {
|
|
216
228
|
throw new Error(`${field} must be an ISO timestamp string, got ${String(value)}`);
|
|
@@ -323,6 +335,14 @@ function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
|
323
335
|
`tags: [${fm.tags.map((t) => `"${t}"`).join(", ")}]`,
|
|
324
336
|
];
|
|
325
337
|
if (fm.entityRef) lines.push(`entityRef: ${fm.entityRef}`);
|
|
338
|
+
if (fm.sourceConnector) {
|
|
339
|
+
// YAML-injection guard (review thread QMPsP): emit unquoted for
|
|
340
|
+
// safe connector IDs (matching the connectors/index.ts validator),
|
|
341
|
+
// or JSON.stringify for values with special characters so newlines,
|
|
342
|
+
// colons, and spaces cannot inject extra frontmatter lines.
|
|
343
|
+
const safe = /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(fm.sourceConnector);
|
|
344
|
+
lines.push(`sourceConnector: ${safe ? fm.sourceConnector : JSON.stringify(fm.sourceConnector)}`);
|
|
345
|
+
}
|
|
326
346
|
if (fm.supersedes) lines.push(`supersedes: ${fm.supersedes}`);
|
|
327
347
|
if (fm.expiresAt) lines.push(`expiresAt: ${fm.expiresAt}`);
|
|
328
348
|
if (fm.lineage && fm.lineage.length > 0) {
|
|
@@ -353,7 +373,7 @@ function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
|
353
373
|
if (fm.eventTimeSource) {
|
|
354
374
|
if (fm.eventTimeSource !== "extracted" && fm.eventTimeSource !== "assumed") {
|
|
355
375
|
throw new Error(
|
|
356
|
-
`serializeFrontmatter: invalid eventTimeSource ${JSON.stringify(fm.eventTimeSource)} — expected "extracted" | "assumed"
|
|
376
|
+
`serializeFrontmatter: invalid eventTimeSource ${JSON.stringify(fm.eventTimeSource)} — expected "extracted" | "assumed"`
|
|
357
377
|
);
|
|
358
378
|
}
|
|
359
379
|
lines.push(`eventTimeSource: ${fm.eventTimeSource}`);
|
|
@@ -392,7 +412,7 @@ function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
|
392
412
|
lines.push(
|
|
393
413
|
`importanceReasons: [${fm.importance.reasons
|
|
394
414
|
.map((r) => `"${r.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`)
|
|
395
|
-
.join(", ")}]
|
|
415
|
+
.join(", ")}]`
|
|
396
416
|
);
|
|
397
417
|
}
|
|
398
418
|
if (fm.importance.keywords.length > 0) {
|
|
@@ -434,29 +454,25 @@ function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
|
434
454
|
// permissive; only writes go through the validator.
|
|
435
455
|
if (fm.derived_from !== undefined) {
|
|
436
456
|
if (!Array.isArray(fm.derived_from)) {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`serializeFrontmatter: derived_from must be an array of "<path>:<version>" strings`,
|
|
439
|
-
);
|
|
457
|
+
throw new Error(`serializeFrontmatter: derived_from must be an array of "<path>:<version>" strings`);
|
|
440
458
|
}
|
|
441
459
|
for (const entry of fm.derived_from) {
|
|
442
460
|
if (!isValidDerivedFromEntry(entry)) {
|
|
443
461
|
throw new Error(
|
|
444
|
-
`serializeFrontmatter: invalid derived_from entry ${JSON.stringify(entry)} — expected "<path>:<version>" with version >= 0
|
|
462
|
+
`serializeFrontmatter: invalid derived_from entry ${JSON.stringify(entry)} — expected "<path>:<version>" with version >= 0`
|
|
445
463
|
);
|
|
446
464
|
}
|
|
447
465
|
}
|
|
448
466
|
if (fm.derived_from.length > 0) {
|
|
449
467
|
lines.push(
|
|
450
|
-
`derived_from: [${fm.derived_from
|
|
451
|
-
.map((e) => `"${e.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`)
|
|
452
|
-
.join(", ")}]`,
|
|
468
|
+
`derived_from: [${fm.derived_from.map((e) => `"${e.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ")}]`
|
|
453
469
|
);
|
|
454
470
|
}
|
|
455
471
|
}
|
|
456
472
|
if (fm.derived_via !== undefined) {
|
|
457
473
|
if (!isConsolidationOperator(fm.derived_via)) {
|
|
458
474
|
throw new Error(
|
|
459
|
-
`serializeFrontmatter: invalid derived_via ${JSON.stringify(fm.derived_via)} — expected one of "split" | "merge" | "update" | "pattern-reinforcement"
|
|
475
|
+
`serializeFrontmatter: invalid derived_via ${JSON.stringify(fm.derived_via)} — expected one of "split" | "merge" | "update" | "pattern-reinforcement"`
|
|
460
476
|
);
|
|
461
477
|
}
|
|
462
478
|
lines.push(`derived_via: ${fm.derived_via}`);
|
|
@@ -465,12 +481,9 @@ function serializeFrontmatter(fm: MemoryFrontmatter): string {
|
|
|
465
481
|
// present so memories never touched by reinforcement round-trip
|
|
466
482
|
// unchanged; matches the `archivedAt` / `forgottenAt` precedent.
|
|
467
483
|
if (fm.reinforcement_count !== undefined) {
|
|
468
|
-
if (
|
|
469
|
-
!Number.isInteger(fm.reinforcement_count) ||
|
|
470
|
-
fm.reinforcement_count <= 0
|
|
471
|
-
) {
|
|
484
|
+
if (!Number.isInteger(fm.reinforcement_count) || fm.reinforcement_count <= 0) {
|
|
472
485
|
throw new Error(
|
|
473
|
-
`serializeFrontmatter: reinforcement_count must be a positive integer (got ${JSON.stringify(fm.reinforcement_count)})
|
|
486
|
+
`serializeFrontmatter: reinforcement_count must be a positive integer (got ${JSON.stringify(fm.reinforcement_count)})`
|
|
474
487
|
);
|
|
475
488
|
}
|
|
476
489
|
lines.push(`reinforcement_count: ${fm.reinforcement_count}`);
|
|
@@ -542,6 +555,32 @@ export function parseFrontmatterStringValue(rawValue: string | undefined): strin
|
|
|
542
555
|
* corrupt stored counter fails safely rather than poisoning downstream
|
|
543
556
|
* scoring. Pair with `assertMemoryWorthCounter` on the write path.
|
|
544
557
|
*/
|
|
558
|
+
function bracketListInner(raw: string): string | undefined {
|
|
559
|
+
const trimmed = raw.trim();
|
|
560
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return undefined;
|
|
561
|
+
return trimmed.slice(1, -1);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function quotedArrayValues(raw: string): string[] {
|
|
565
|
+
const inner = bracketListInner(raw);
|
|
566
|
+
if (inner === undefined) return [];
|
|
567
|
+
const values: string[] = [];
|
|
568
|
+
let i = 0;
|
|
569
|
+
while (i < inner.length) {
|
|
570
|
+
while (i < inner.length && inner[i] !== '"') i++;
|
|
571
|
+
if (i >= inner.length) break;
|
|
572
|
+
i++;
|
|
573
|
+
let value = "";
|
|
574
|
+
while (i < inner.length) {
|
|
575
|
+
const ch = inner[i++];
|
|
576
|
+
if (ch === "\\" && i < inner.length) value += ch + inner[i++];
|
|
577
|
+
else if (ch === '"') { values.push(value); break; }
|
|
578
|
+
else value += ch;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return values;
|
|
582
|
+
}
|
|
583
|
+
|
|
545
584
|
function parseMemoryWorthCounterField(raw: string | undefined): number | undefined {
|
|
546
585
|
if (raw === undefined) return undefined;
|
|
547
586
|
const trimmed = raw.trim();
|
|
@@ -567,9 +606,7 @@ function parseReinforcementCountField(raw: string | undefined): number | undefin
|
|
|
567
606
|
return n;
|
|
568
607
|
}
|
|
569
608
|
|
|
570
|
-
export function parseFrontmatter(
|
|
571
|
-
raw: string,
|
|
572
|
-
): { frontmatter: MemoryFrontmatter; content: string } | null {
|
|
609
|
+
export function parseFrontmatter(raw: string): { frontmatter: MemoryFrontmatter; content: string } | null {
|
|
573
610
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
574
611
|
if (!match) return null;
|
|
575
612
|
|
|
@@ -608,10 +645,7 @@ export function parseFrontmatter(
|
|
|
608
645
|
// single-quoted: `''` → `'` (YAML's native escape)
|
|
609
646
|
let item = m[2].trim();
|
|
610
647
|
if (item.startsWith('"') && item.endsWith('"') && item.length >= 2) {
|
|
611
|
-
item = item
|
|
612
|
-
.slice(1, -1)
|
|
613
|
-
.replace(/\\"/g, '"')
|
|
614
|
-
.replace(/\\\\/g, "\\");
|
|
648
|
+
item = item.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
615
649
|
} else if (item.startsWith("'") && item.endsWith("'") && item.length >= 2) {
|
|
616
650
|
item = item.slice(1, -1).replace(/''/g, "'");
|
|
617
651
|
}
|
|
@@ -619,9 +653,7 @@ export function parseFrontmatter(
|
|
|
619
653
|
j++;
|
|
620
654
|
}
|
|
621
655
|
if (items.length > 0) {
|
|
622
|
-
const inline = items
|
|
623
|
-
.map((v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`)
|
|
624
|
-
.join(", ");
|
|
656
|
+
const inline = items.map((v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`).join(", ");
|
|
625
657
|
lines.push(`${line.slice(0, colonIdx + 1)} [${inline}]`);
|
|
626
658
|
i = j;
|
|
627
659
|
continue;
|
|
@@ -641,9 +673,9 @@ export function parseFrontmatter(
|
|
|
641
673
|
|
|
642
674
|
let tags: string[] = [];
|
|
643
675
|
const tagsStr = fm.tags ?? "";
|
|
644
|
-
const
|
|
645
|
-
if (
|
|
646
|
-
tags =
|
|
676
|
+
const tagInner = bracketListInner(tagsStr);
|
|
677
|
+
if (tagInner !== undefined) {
|
|
678
|
+
tags = tagInner
|
|
647
679
|
.split(",")
|
|
648
680
|
.map((t) => t.trim().replace(/^"|"$/g, ""))
|
|
649
681
|
.filter(Boolean);
|
|
@@ -651,9 +683,9 @@ export function parseFrontmatter(
|
|
|
651
683
|
|
|
652
684
|
let intentEntityTypes: string[] | undefined;
|
|
653
685
|
const intentEntityTypesStr = fm.intentEntityTypes ?? "";
|
|
654
|
-
const
|
|
655
|
-
if (
|
|
656
|
-
intentEntityTypes =
|
|
686
|
+
const intentEntityTypesInner = bracketListInner(intentEntityTypesStr);
|
|
687
|
+
if (intentEntityTypesInner !== undefined) {
|
|
688
|
+
intentEntityTypes = intentEntityTypesInner
|
|
657
689
|
.split(",")
|
|
658
690
|
.map((t) => t.trim().replace(/^"|"$/g, ""))
|
|
659
691
|
.filter(Boolean);
|
|
@@ -664,9 +696,9 @@ export function parseFrontmatter(
|
|
|
664
696
|
// Parse lineage array if present
|
|
665
697
|
let lineage: string[] | undefined;
|
|
666
698
|
const lineageStr = fm.lineage ?? "";
|
|
667
|
-
const
|
|
668
|
-
if (
|
|
669
|
-
lineage =
|
|
699
|
+
const lineageInner = bracketListInner(lineageStr);
|
|
700
|
+
if (lineageInner !== undefined) {
|
|
701
|
+
lineage = lineageInner
|
|
670
702
|
.split(",")
|
|
671
703
|
.map((l) => l.trim().replace(/^"|"$/g, ""))
|
|
672
704
|
.filter(Boolean);
|
|
@@ -801,21 +833,18 @@ export function parseFrontmatter(
|
|
|
801
833
|
let reasons: string[] = [];
|
|
802
834
|
const reasonsStr = fm.importanceReasons ?? "";
|
|
803
835
|
if (reasonsStr.trim().startsWith("[") && reasonsStr.trim().endsWith("]")) {
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (reason.length > 0) {
|
|
808
|
-
reasons.push(reason);
|
|
809
|
-
}
|
|
836
|
+
for (const rawReason of quotedArrayValues(reasonsStr)) {
|
|
837
|
+
const reason = parseLinkReasonValue(rawReason);
|
|
838
|
+
if (reason.length > 0) reasons.push(reason);
|
|
810
839
|
}
|
|
811
840
|
}
|
|
812
841
|
|
|
813
842
|
// Parse importance keywords array
|
|
814
843
|
let keywords: string[] = [];
|
|
815
844
|
const keywordsStr = fm.importanceKeywords ?? "";
|
|
816
|
-
const
|
|
817
|
-
if (
|
|
818
|
-
keywords =
|
|
845
|
+
const keywordsInner = bracketListInner(keywordsStr);
|
|
846
|
+
if (keywordsInner !== undefined) {
|
|
847
|
+
keywords = keywordsInner
|
|
819
848
|
.split(",")
|
|
820
849
|
.map((k) => k.trim().replace(/^"|"$/g, ""))
|
|
821
850
|
.filter(Boolean);
|
|
@@ -835,6 +864,7 @@ export function parseFrontmatter(
|
|
|
835
864
|
confidenceTier: (fm.confidenceTier as ConfidenceTier) || confidenceTier(conf),
|
|
836
865
|
tags,
|
|
837
866
|
entityRef: fm.entityRef || undefined,
|
|
867
|
+
sourceConnector: fm.sourceConnector ? decodeYamlScalar(fm.sourceConnector) || undefined : undefined,
|
|
838
868
|
supersedes: fm.supersedes || undefined,
|
|
839
869
|
expiresAt: fm.expiresAt || undefined,
|
|
840
870
|
lineage: lineage && lineage.length > 0 ? lineage : undefined,
|
|
@@ -858,9 +888,7 @@ export function parseFrontmatter(
|
|
|
858
888
|
// Issue #1578 — bi-temporal ingestion provenance round-trip.
|
|
859
889
|
observedAt: fm.observedAt || undefined,
|
|
860
890
|
eventTimeSource:
|
|
861
|
-
fm.eventTimeSource === "extracted" || fm.eventTimeSource === "assumed"
|
|
862
|
-
? fm.eventTimeSource
|
|
863
|
-
: undefined,
|
|
891
|
+
fm.eventTimeSource === "extracted" || fm.eventTimeSource === "assumed" ? fm.eventTimeSource : undefined,
|
|
864
892
|
forgottenAt: fm.forgottenAt || undefined,
|
|
865
893
|
forgottenReason: parseFrontmatterStringValue(fm.forgottenReason),
|
|
866
894
|
lifecycleState: (fm.lifecycleState as LifecycleState) || undefined,
|
|
@@ -913,7 +941,7 @@ export function parseFrontmatter(
|
|
|
913
941
|
if (fmBlock.includes("links:")) {
|
|
914
942
|
const links: MemoryLink[] = [];
|
|
915
943
|
const linkMatches = fmBlock.matchAll(
|
|
916
|
-
/- targetId: (\S+)\s+linkType: (\S+)\s+strength: ([\d.]+)(?:\s+reason: "((?:\\.|[^"\\])*)")?/g
|
|
944
|
+
/- targetId: (\S+)\s+linkType: (\S+)\s+strength: ([\d.]+)(?:\s+reason: "((?:\\.|[^"\\])*)")?/g
|
|
917
945
|
);
|
|
918
946
|
for (const match of linkMatches) {
|
|
919
947
|
links.push({
|
|
@@ -932,19 +960,14 @@ export function parseFrontmatter(
|
|
|
932
960
|
}
|
|
933
961
|
|
|
934
962
|
function inferEntityTypeFromContent(content: string): string | undefined {
|
|
935
|
-
const typeMatch = content
|
|
963
|
+
const typeMatch = content
|
|
964
|
+
.match(/^\*\*Type:\*\*\s*([^\n]+)/m)?.[1]
|
|
965
|
+
?.trim()
|
|
966
|
+
.toLowerCase();
|
|
936
967
|
return typeMatch || undefined;
|
|
937
968
|
}
|
|
938
969
|
|
|
939
|
-
const KNOWN_ENTITY_FILENAME_PREFIXES = new Set([
|
|
940
|
-
"company",
|
|
941
|
-
"other",
|
|
942
|
-
"person",
|
|
943
|
-
"place",
|
|
944
|
-
"project",
|
|
945
|
-
"tool",
|
|
946
|
-
"topic",
|
|
947
|
-
]);
|
|
970
|
+
const KNOWN_ENTITY_FILENAME_PREFIXES = new Set(["company", "other", "person", "place", "project", "tool", "topic"]);
|
|
948
971
|
|
|
949
972
|
function inferEntityTypeFromFilename(pathRel: string): string | undefined {
|
|
950
973
|
const basename = path.basename(pathRel, ".md").toLowerCase();
|
|
@@ -954,19 +977,28 @@ function inferEntityTypeFromFilename(pathRel: string): string | undefined {
|
|
|
954
977
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : undefined;
|
|
955
978
|
}
|
|
956
979
|
|
|
957
|
-
export function normalizeFrontmatterForPath(
|
|
980
|
+
export function normalizeFrontmatterForPath(
|
|
981
|
+
frontmatter: MemoryFrontmatter,
|
|
982
|
+
pathRel: string,
|
|
983
|
+
content: string = ""
|
|
984
|
+
): MemoryFrontmatter {
|
|
958
985
|
const normalizedPath = pathRel.split(path.sep).join("/");
|
|
959
986
|
let normalizedFrontmatter = frontmatter;
|
|
960
987
|
|
|
961
|
-
if (
|
|
988
|
+
if (
|
|
989
|
+
normalizedPath === "entities" ||
|
|
990
|
+
normalizedPath.startsWith("entities/") ||
|
|
991
|
+
normalizedPath.includes("/entities/")
|
|
992
|
+
) {
|
|
962
993
|
const basename = path.basename(pathRel, ".md");
|
|
963
994
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
964
995
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
965
996
|
normalizedFrontmatter = {
|
|
966
997
|
...normalizedFrontmatter,
|
|
967
|
-
id:
|
|
968
|
-
|
|
969
|
-
|
|
998
|
+
id:
|
|
999
|
+
typeof normalizedFrontmatter.id === "string" && normalizedFrontmatter.id.trim().length > 0
|
|
1000
|
+
? normalizedFrontmatter.id
|
|
1001
|
+
: basename,
|
|
970
1002
|
category: "entity",
|
|
971
1003
|
tags: existingTags.includes(inferredType) ? existingTags : [...existingTags, inferredType],
|
|
972
1004
|
};
|
|
@@ -985,7 +1017,7 @@ export function normalizeFrontmatterForPath(frontmatter: MemoryFrontmatter, path
|
|
|
985
1017
|
function inferCurrentStateStatus(
|
|
986
1018
|
frontmatter: MemoryFrontmatter,
|
|
987
1019
|
pathRel: string,
|
|
988
|
-
fallbackStatus: MemoryStatus
|
|
1020
|
+
fallbackStatus: MemoryStatus
|
|
989
1021
|
): MemoryStatus {
|
|
990
1022
|
return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
|
|
991
1023
|
}
|
|
@@ -1007,11 +1039,7 @@ const BUILTIN_ALIASES: Record<string, string> = {
|
|
|
1007
1039
|
* store, or pass `storageManager.entityAliases` explicitly. Without an
|
|
1008
1040
|
* aliases argument only the built-in structural aliases apply.
|
|
1009
1041
|
*/
|
|
1010
|
-
export function normalizeEntityName(
|
|
1011
|
-
raw: string,
|
|
1012
|
-
type: string,
|
|
1013
|
-
aliases?: Readonly<Record<string, string>>,
|
|
1014
|
-
): string {
|
|
1042
|
+
export function normalizeEntityName(raw: string, type: string, aliases?: Readonly<Record<string, string>>): string {
|
|
1015
1043
|
// Strip type prefix if present (e.g. name="person-jane-doe", type="person")
|
|
1016
1044
|
const rawStr = typeof raw === "string" ? raw : "";
|
|
1017
1045
|
const typeStr = typeof type === "string" && type.trim().length > 0 ? type : "entity";
|
|
@@ -1031,8 +1059,7 @@ export function normalizeEntityName(
|
|
|
1031
1059
|
// Check caller-provided user aliases first, then built-in. Own-property and
|
|
1032
1060
|
// string guards keep inherited object keys (e.g. an entity literally named
|
|
1033
1061
|
// "constructor") and malformed alias values from corrupting canonical ids.
|
|
1034
|
-
const userAlias =
|
|
1035
|
-
aliases !== undefined && Object.hasOwn(aliases, normalized) ? aliases[normalized] : undefined;
|
|
1062
|
+
const userAlias = aliases !== undefined && Object.hasOwn(aliases, normalized) ? aliases[normalized] : undefined;
|
|
1036
1063
|
if (typeof userAlias === "string" && userAlias.length > 0) {
|
|
1037
1064
|
normalized = userAlias;
|
|
1038
1065
|
} else if (Object.hasOwn(BUILTIN_ALIASES, normalized)) {
|
|
@@ -1057,9 +1084,7 @@ function levenshtein(a: string, b: string): number {
|
|
|
1057
1084
|
|
|
1058
1085
|
for (let i = 1; i <= m; i++) {
|
|
1059
1086
|
for (let j = 1; j <= n; j++) {
|
|
1060
|
-
dp[i][j] = a[i - 1] === b[j - 1]
|
|
1061
|
-
? dp[i - 1][j - 1]
|
|
1062
|
-
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1087
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1063
1088
|
}
|
|
1064
1089
|
}
|
|
1065
1090
|
return dp[m][n];
|
|
@@ -1088,7 +1113,7 @@ export class ContentHashIndex {
|
|
|
1088
1113
|
stateDir: string,
|
|
1089
1114
|
secureStoreKeyProvider: () => Buffer | null = () => null,
|
|
1090
1115
|
secureStoreWriteKeyProvider: () => Buffer | null = secureStoreKeyProvider,
|
|
1091
|
-
memoryDir: string = path.dirname(stateDir)
|
|
1116
|
+
memoryDir: string = path.dirname(stateDir)
|
|
1092
1117
|
) {
|
|
1093
1118
|
this.filePath = path.join(stateDir, "fact-hashes.txt");
|
|
1094
1119
|
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
@@ -1149,7 +1174,7 @@ export class ContentHashIndex {
|
|
|
1149
1174
|
[...this.hashes].join("\n") + "\n",
|
|
1150
1175
|
this.secureStoreWriteKeyProvider(),
|
|
1151
1176
|
{},
|
|
1152
|
-
this.memoryDir
|
|
1177
|
+
this.memoryDir
|
|
1153
1178
|
);
|
|
1154
1179
|
this.dirty = false;
|
|
1155
1180
|
log.debug(`content-hash index: saved ${this.hashes.size} hashes`);
|
|
@@ -1243,9 +1268,7 @@ export function normalizeAttributePairs(pairs: Record<string, string>): string {
|
|
|
1243
1268
|
// Entity file parsing / serialization (Knowledge Graph v7.0)
|
|
1244
1269
|
// ---------------------------------------------------------------------------
|
|
1245
1270
|
|
|
1246
|
-
function parseEntityFrontmatter(
|
|
1247
|
-
raw: string,
|
|
1248
|
-
): {
|
|
1271
|
+
function parseEntityFrontmatter(raw: string): {
|
|
1249
1272
|
frontmatter: {
|
|
1250
1273
|
created?: string;
|
|
1251
1274
|
updated?: string;
|
|
@@ -1302,7 +1325,9 @@ function parseEntityFrontmatter(
|
|
|
1302
1325
|
updated: values.updated || undefined,
|
|
1303
1326
|
synthesisUpdatedAt: values.synthesis_updated_at || undefined,
|
|
1304
1327
|
synthesisTimelineCount: Number.isFinite(synthesisTimelineCount) ? synthesisTimelineCount : undefined,
|
|
1305
|
-
synthesisStructuredFactCount: Number.isFinite(synthesisStructuredFactCount)
|
|
1328
|
+
synthesisStructuredFactCount: Number.isFinite(synthesisStructuredFactCount)
|
|
1329
|
+
? synthesisStructuredFactCount
|
|
1330
|
+
: undefined,
|
|
1306
1331
|
synthesisStructuredFactDigest: values.synthesis_structured_fact_digest || undefined,
|
|
1307
1332
|
synthesisVersion: Number.isFinite(synthesisVersion) ? synthesisVersion : undefined,
|
|
1308
1333
|
extraLines,
|
|
@@ -1347,7 +1372,7 @@ function readEntitySectionText(
|
|
|
1347
1372
|
options: {
|
|
1348
1373
|
preserveBullets?: boolean;
|
|
1349
1374
|
skipTimelineBullets?: boolean;
|
|
1350
|
-
} = {}
|
|
1375
|
+
} = {}
|
|
1351
1376
|
): string | undefined {
|
|
1352
1377
|
const normalizedSections = new Set(sectionNames.map((name) => name.toLowerCase()));
|
|
1353
1378
|
let section = "";
|
|
@@ -1368,9 +1393,9 @@ function readEntitySectionText(
|
|
|
1368
1393
|
continue;
|
|
1369
1394
|
}
|
|
1370
1395
|
if (
|
|
1371
|
-
options.skipTimelineBullets === true
|
|
1372
|
-
|
|
1373
|
-
|
|
1396
|
+
options.skipTimelineBullets === true &&
|
|
1397
|
+
trimmed.startsWith("- ") &&
|
|
1398
|
+
isEntitySynthesisTimelinePromotionBullet(trimmed.slice(2))
|
|
1374
1399
|
) {
|
|
1375
1400
|
continue;
|
|
1376
1401
|
}
|
|
@@ -1384,10 +1409,7 @@ function readEntitySectionText(
|
|
|
1384
1409
|
return sectionLines.join(options.preserveBullets === true ? "\n" : " ");
|
|
1385
1410
|
}
|
|
1386
1411
|
|
|
1387
|
-
function parseEntityTimelineBullet(
|
|
1388
|
-
bullet: string,
|
|
1389
|
-
fallbackTimestamp: string,
|
|
1390
|
-
): EntityTimelineEntry | null {
|
|
1412
|
+
function parseEntityTimelineBullet(bullet: string, fallbackTimestamp: string): EntityTimelineEntry | null {
|
|
1391
1413
|
const trimmed = bullet.trim();
|
|
1392
1414
|
if (!trimmed) return null;
|
|
1393
1415
|
|
|
@@ -1440,10 +1462,10 @@ function parseEntityTimelineBullet(
|
|
|
1440
1462
|
break;
|
|
1441
1463
|
case "source":
|
|
1442
1464
|
if (
|
|
1443
|
-
consumedMetadataSegments.length === 0
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1465
|
+
consumedMetadataSegments.length === 0 &&
|
|
1466
|
+
!nextRest.startsWith("[") &&
|
|
1467
|
+
nextRest.length > 0 &&
|
|
1468
|
+
!isManagedEntityTimelineSource(value)
|
|
1447
1469
|
) {
|
|
1448
1470
|
literalSingleSourceSegment = rawSegment;
|
|
1449
1471
|
rest = nextRest;
|
|
@@ -1642,11 +1664,7 @@ function serializeEntityTimelineEntry(entry: EntityTimelineEntry): string {
|
|
|
1642
1664
|
}
|
|
1643
1665
|
|
|
1644
1666
|
function dedupeEntityTimelineFacts(timeline: EntityTimelineEntry[]): string[] {
|
|
1645
|
-
return [...new Set(
|
|
1646
|
-
timeline
|
|
1647
|
-
.map((entry) => entry.text.trim())
|
|
1648
|
-
.filter((entry) => entry.length > 0),
|
|
1649
|
-
)];
|
|
1667
|
+
return [...new Set(timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0))];
|
|
1650
1668
|
}
|
|
1651
1669
|
|
|
1652
1670
|
function normalizeEntitySectionFact(value: string): string {
|
|
@@ -1654,11 +1672,7 @@ function normalizeEntitySectionFact(value: string): string {
|
|
|
1654
1672
|
}
|
|
1655
1673
|
|
|
1656
1674
|
export function normalizeStructuredSectionFacts(facts: string[]): string[] {
|
|
1657
|
-
return [...new Set(
|
|
1658
|
-
facts
|
|
1659
|
-
.map((fact) => normalizeEntitySectionFact(fact))
|
|
1660
|
-
.filter((fact) => fact.length > 0),
|
|
1661
|
-
)];
|
|
1675
|
+
return [...new Set(facts.map((fact) => normalizeEntitySectionFact(fact)).filter((fact) => fact.length > 0))];
|
|
1662
1676
|
}
|
|
1663
1677
|
|
|
1664
1678
|
function collectStructuredSectionFacts(structuredSections: EntityStructuredSection[]): string[] {
|
|
@@ -1675,7 +1689,7 @@ function collectStructuredSectionFacts(structuredSections: EntityStructuredSecti
|
|
|
1675
1689
|
|
|
1676
1690
|
export function compileEntityFacts(
|
|
1677
1691
|
timeline: EntityTimelineEntry[],
|
|
1678
|
-
structuredSections: EntityStructuredSection[]
|
|
1692
|
+
structuredSections: EntityStructuredSection[]
|
|
1679
1693
|
): string[] {
|
|
1680
1694
|
const facts: string[] = [];
|
|
1681
1695
|
const seen = new Set<string>();
|
|
@@ -1728,7 +1742,7 @@ function looksLikeStructuredSectionFactList(lines: string[]): boolean {
|
|
|
1728
1742
|
function partitionEntityStructuredSections(
|
|
1729
1743
|
entityType: string,
|
|
1730
1744
|
extraSections: Array<{ title: string; lines: string[] }>,
|
|
1731
|
-
entitySchemas?: PluginConfig["entitySchemas"]
|
|
1745
|
+
entitySchemas?: PluginConfig["entitySchemas"]
|
|
1732
1746
|
): {
|
|
1733
1747
|
structuredSections: EntityStructuredSection[];
|
|
1734
1748
|
remainingExtraSections: Array<{ title: string; lines: string[] }>;
|
|
@@ -1750,11 +1764,7 @@ function partitionEntityStructuredSections(
|
|
|
1750
1764
|
}
|
|
1751
1765
|
const normalizedSection = matchedSection
|
|
1752
1766
|
? { key: matchedSection.key, title: matchedSection.title }
|
|
1753
|
-
: normalizeEntityStructuredSection(
|
|
1754
|
-
entityType,
|
|
1755
|
-
{ key: section.title, title: section.title },
|
|
1756
|
-
entitySchemas,
|
|
1757
|
-
);
|
|
1767
|
+
: normalizeEntityStructuredSection(entityType, { key: section.title, title: section.title }, entitySchemas);
|
|
1758
1768
|
if (facts.length === 0) {
|
|
1759
1769
|
remainingExtraSections.push(section);
|
|
1760
1770
|
continue;
|
|
@@ -1822,13 +1832,17 @@ export function fingerprintEntityStructuredFacts(entity: Pick<EntityFile, "struc
|
|
|
1822
1832
|
.map((section) => ({
|
|
1823
1833
|
key: section.key.trim().toLowerCase(),
|
|
1824
1834
|
title: section.title.replace(/\s+/g, " ").trim(),
|
|
1825
|
-
facts: normalizeStructuredSectionFacts(section.facts)
|
|
1835
|
+
facts: normalizeStructuredSectionFacts(section.facts)
|
|
1836
|
+
.slice()
|
|
1837
|
+
.sort((left, right) => left.localeCompare(right)),
|
|
1826
1838
|
}))
|
|
1827
1839
|
.filter((section) => section.facts.length > 0)
|
|
1828
|
-
.sort(
|
|
1829
|
-
left
|
|
1830
|
-
|
|
1831
|
-
|
|
1840
|
+
.sort(
|
|
1841
|
+
(left, right) =>
|
|
1842
|
+
left.key.localeCompare(right.key) ||
|
|
1843
|
+
left.title.localeCompare(right.title) ||
|
|
1844
|
+
left.facts.join("\n").localeCompare(right.facts.join("\n"))
|
|
1845
|
+
);
|
|
1832
1846
|
if (normalizedSections.length === 0) return undefined;
|
|
1833
1847
|
return createHash("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
|
|
1834
1848
|
}
|
|
@@ -1844,26 +1858,27 @@ export function isEntitySynthesisStale(entity: EntityFile): boolean {
|
|
|
1844
1858
|
if (structuredFactCount > 0 && !storedStructuredFactDigest) return true;
|
|
1845
1859
|
const latestTimelineTimestamp = latestEntityTimelineTimestamp(entity);
|
|
1846
1860
|
if (!latestTimelineTimestamp) {
|
|
1847
|
-
return
|
|
1848
|
-
|
|
1849
|
-
|
|
1861
|
+
return (
|
|
1862
|
+
entity.timeline.length > entity.synthesisTimelineCount ||
|
|
1863
|
+
structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) ||
|
|
1864
|
+
structuredFactDigest !== storedStructuredFactDigest
|
|
1865
|
+
);
|
|
1850
1866
|
}
|
|
1851
1867
|
if (!entity.synthesisUpdatedAt?.trim()) return true;
|
|
1852
1868
|
const timelineFreshness = compareEntityTimestamps(latestTimelineTimestamp, entity.synthesisUpdatedAt);
|
|
1853
1869
|
if (timelineFreshness > 0) return true;
|
|
1854
|
-
return
|
|
1855
|
-
|
|
1856
|
-
|
|
1870
|
+
return (
|
|
1871
|
+
entity.timeline.length > entity.synthesisTimelineCount ||
|
|
1872
|
+
structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) ||
|
|
1873
|
+
structuredFactDigest !== storedStructuredFactDigest
|
|
1874
|
+
);
|
|
1857
1875
|
}
|
|
1858
1876
|
|
|
1859
1877
|
/**
|
|
1860
1878
|
* Parse an entity markdown file into a structured EntityFile.
|
|
1861
1879
|
* Backward compatible: old files without new sections get empty arrays.
|
|
1862
1880
|
*/
|
|
1863
|
-
export function parseEntityFile(
|
|
1864
|
-
content: string,
|
|
1865
|
-
entitySchemas?: PluginConfig["entitySchemas"],
|
|
1866
|
-
): EntityFile {
|
|
1881
|
+
export function parseEntityFile(content: string, entitySchemas?: PluginConfig["entitySchemas"]): EntityFile {
|
|
1867
1882
|
const { frontmatter, body } = parseEntityFrontmatter(content);
|
|
1868
1883
|
const lines = body.split("\n");
|
|
1869
1884
|
const recognizedSections = new Set([
|
|
@@ -1905,11 +1920,10 @@ export function parseEntityFile(
|
|
|
1905
1920
|
const headingLineIndex = lines.findIndex((l) => l.startsWith("# "));
|
|
1906
1921
|
const firstSectionIndex = lines.findIndex((l) => l.startsWith("## "));
|
|
1907
1922
|
const preSectionStartIndex = headingLineIndex > -1 ? headingLineIndex + 1 : 0;
|
|
1908
|
-
const preSectionCandidates =
|
|
1909
|
-
? lines.slice(preSectionStartIndex, firstSectionIndex)
|
|
1910
|
-
: lines.slice(preSectionStartIndex);
|
|
1923
|
+
const preSectionCandidates =
|
|
1924
|
+
firstSectionIndex > -1 ? lines.slice(preSectionStartIndex, firstSectionIndex) : lines.slice(preSectionStartIndex);
|
|
1911
1925
|
const preSectionLines = preSectionCandidates.filter(
|
|
1912
|
-
(line) => !line.startsWith("**Type:**") && !line.startsWith("**Updated:**")
|
|
1926
|
+
(line) => !line.startsWith("**Type:**") && !line.startsWith("**Updated:**")
|
|
1913
1927
|
);
|
|
1914
1928
|
const normalizedPreSectionLines = [...preSectionLines];
|
|
1915
1929
|
while (normalizedPreSectionLines[0] === "") {
|
|
@@ -1949,20 +1963,14 @@ export function parseEntityFile(
|
|
|
1949
1963
|
legacyFacts.push(bullet);
|
|
1950
1964
|
break;
|
|
1951
1965
|
case "timeline": {
|
|
1952
|
-
const parsed = parseEntityTimelineBullet(
|
|
1953
|
-
bullet,
|
|
1954
|
-
fallbackTimestamp,
|
|
1955
|
-
);
|
|
1966
|
+
const parsed = parseEntityTimelineBullet(bullet, fallbackTimestamp);
|
|
1956
1967
|
if (parsed) timeline.push(parsed);
|
|
1957
1968
|
break;
|
|
1958
1969
|
}
|
|
1959
1970
|
case "summary":
|
|
1960
1971
|
case "synthesis":
|
|
1961
1972
|
if (isEntitySynthesisTimelinePromotionBullet(bullet)) {
|
|
1962
|
-
const parsed = parseEntityTimelineBullet(
|
|
1963
|
-
bullet,
|
|
1964
|
-
fallbackTimestamp,
|
|
1965
|
-
);
|
|
1973
|
+
const parsed = parseEntityTimelineBullet(bullet, fallbackTimestamp);
|
|
1966
1974
|
if (parsed) timeline.push(parsed);
|
|
1967
1975
|
}
|
|
1968
1976
|
// Summary/synthesis is typically a paragraph after the heading, not a bullet.
|
|
@@ -2006,9 +2014,7 @@ export function parseEntityFile(
|
|
|
2006
2014
|
|
|
2007
2015
|
if (legacyFactTimelineEntries.length > 0) {
|
|
2008
2016
|
const existingTimelineFacts = new Set(
|
|
2009
|
-
timeline
|
|
2010
|
-
.map((entry) => entry.text.trim())
|
|
2011
|
-
.filter((entry) => entry.length > 0),
|
|
2017
|
+
timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0)
|
|
2012
2018
|
);
|
|
2013
2019
|
for (const fact of legacyFactTimelineEntries) {
|
|
2014
2020
|
const normalizedFact = fact.text.trim();
|
|
@@ -2019,8 +2025,8 @@ export function parseEntityFile(
|
|
|
2019
2025
|
}
|
|
2020
2026
|
|
|
2021
2027
|
const synthesis =
|
|
2022
|
-
readEntitySectionText(lines, ["Synthesis"], { preserveBullets: true, skipTimelineBullets: true })
|
|
2023
|
-
|
|
2028
|
+
readEntitySectionText(lines, ["Synthesis"], { preserveBullets: true, skipTimelineBullets: true }) ??
|
|
2029
|
+
readEntitySectionText(lines, ["Summary"], { preserveBullets: true, skipTimelineBullets: true });
|
|
2024
2030
|
const synthesisUpdatedAt = frontmatter.synthesisUpdatedAt || undefined;
|
|
2025
2031
|
const synthesisTimelineCount = frontmatter.synthesisTimelineCount;
|
|
2026
2032
|
const synthesisStructuredFactCount = frontmatter.synthesisStructuredFactCount;
|
|
@@ -2028,7 +2034,7 @@ export function parseEntityFile(
|
|
|
2028
2034
|
const { structuredSections, remainingExtraSections } = partitionEntityStructuredSections(
|
|
2029
2035
|
type,
|
|
2030
2036
|
extraSections,
|
|
2031
|
-
entitySchemas
|
|
2037
|
+
entitySchemas
|
|
2032
2038
|
);
|
|
2033
2039
|
const facts = compileEntityFacts(timeline, structuredSections);
|
|
2034
2040
|
|
|
@@ -2061,30 +2067,32 @@ export function parseEntityFile(
|
|
|
2061
2067
|
* Writes the compiled-truth + timeline format while remaining parse-compatible
|
|
2062
2068
|
* with the legacy in-memory `summary` and `facts` fields.
|
|
2063
2069
|
*/
|
|
2064
|
-
export function serializeEntityFile(
|
|
2065
|
-
entity: EntityFile,
|
|
2066
|
-
entitySchemas?: PluginConfig["entitySchemas"],
|
|
2067
|
-
): string {
|
|
2070
|
+
export function serializeEntityFile(entity: EntityFile, entitySchemas?: PluginConfig["entitySchemas"]): string {
|
|
2068
2071
|
const synthesis = entity.synthesis || entity.summary || "";
|
|
2069
2072
|
const created = entity.created?.trim() || entity.updated || new Date().toISOString();
|
|
2070
2073
|
const updated = entity.updated || created;
|
|
2071
2074
|
const timeline = entity.timeline;
|
|
2072
2075
|
const structuredSections = sortStructuredSectionsBySchema(
|
|
2073
2076
|
entity.type,
|
|
2074
|
-
(entity.structuredSections ?? [])
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2077
|
+
(entity.structuredSections ?? [])
|
|
2078
|
+
.map((section) => ({
|
|
2079
|
+
...section,
|
|
2080
|
+
facts: normalizeStructuredSectionFacts(section.facts),
|
|
2081
|
+
}))
|
|
2082
|
+
.filter((section) => section.facts.length > 0),
|
|
2083
|
+
entitySchemas
|
|
2079
2084
|
);
|
|
2080
2085
|
const sectionFacts = new Set(collectStructuredSectionFacts(structuredSections));
|
|
2081
|
-
const legacyFacts =
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2086
|
+
const legacyFacts =
|
|
2087
|
+
timeline.length === 0
|
|
2088
|
+
? [
|
|
2089
|
+
...new Set(
|
|
2090
|
+
entity.facts
|
|
2091
|
+
.map((fact) => normalizeEntitySectionFact(fact))
|
|
2092
|
+
.filter((fact) => fact.length > 0 && !sectionFacts.has(fact))
|
|
2093
|
+
),
|
|
2094
|
+
]
|
|
2095
|
+
: [];
|
|
2088
2096
|
const synthesisUpdatedAt = entity.synthesisUpdatedAt?.trim() || "";
|
|
2089
2097
|
const synthesisTimelineCount = entity.synthesisTimelineCount;
|
|
2090
2098
|
const synthesisStructuredFactCount = entity.synthesisStructuredFactCount;
|
|
@@ -2096,15 +2104,11 @@ export function serializeEntityFile(
|
|
|
2096
2104
|
`created: ${created}`,
|
|
2097
2105
|
`updated: ${updated}`,
|
|
2098
2106
|
`synthesis_updated_at: "${synthesisUpdatedAt}"`,
|
|
2099
|
-
...(synthesisTimelineCount === undefined
|
|
2100
|
-
? []
|
|
2101
|
-
: [`synthesis_timeline_count: ${synthesisTimelineCount}`]),
|
|
2107
|
+
...(synthesisTimelineCount === undefined ? [] : [`synthesis_timeline_count: ${synthesisTimelineCount}`]),
|
|
2102
2108
|
...(synthesisStructuredFactCount === undefined
|
|
2103
2109
|
? []
|
|
2104
2110
|
: [`synthesis_structured_fact_count: ${synthesisStructuredFactCount}`]),
|
|
2105
|
-
...(synthesisStructuredFactDigest
|
|
2106
|
-
? [`synthesis_structured_fact_digest: "${synthesisStructuredFactDigest}"`]
|
|
2107
|
-
: []),
|
|
2111
|
+
...(synthesisStructuredFactDigest ? [`synthesis_structured_fact_digest: "${synthesisStructuredFactDigest}"`] : []),
|
|
2108
2112
|
`synthesis_version: ${synthesisVersion}`,
|
|
2109
2113
|
...(entity.extraFrontmatterLines ?? []),
|
|
2110
2114
|
"---",
|
|
@@ -2297,7 +2301,10 @@ export class StorageManager {
|
|
|
2297
2301
|
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2298
2302
|
static readonly COLD_SCAN_CACHE_TTL_MS = 30_000; // 30 seconds
|
|
2299
2303
|
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2300
|
-
static readonly coldMemoriesCache = new Map<
|
|
2304
|
+
static readonly coldMemoriesCache = new Map<
|
|
2305
|
+
string,
|
|
2306
|
+
{ memories: MemoryFile[]; loadedAt: number; coldVersion: number }
|
|
2307
|
+
>();
|
|
2301
2308
|
|
|
2302
2309
|
// Cache for readQuestions() — avoids serially re-reading tens of thousands of
|
|
2303
2310
|
// question files on every recall. 60-second TTL is intentionally short so that
|
|
@@ -2334,7 +2341,11 @@ export class StorageManager {
|
|
|
2334
2341
|
/** Post-write catalog hook (#1522). Installed by the namespace router; fire-and-forget. */
|
|
2335
2342
|
onCatalogWrite?: () => void;
|
|
2336
2343
|
private notifyCatalogWrite(): void {
|
|
2337
|
-
try {
|
|
2344
|
+
try {
|
|
2345
|
+
this.onCatalogWrite?.();
|
|
2346
|
+
} catch {
|
|
2347
|
+
/* gotcha #13 */
|
|
2348
|
+
}
|
|
2338
2349
|
}
|
|
2339
2350
|
|
|
2340
2351
|
// ── Tombstone store (issue #1579) ───────────────────────────────────────
|
|
@@ -2445,7 +2456,7 @@ export class StorageManager {
|
|
|
2445
2456
|
if (this._secureStoreRequired) {
|
|
2446
2457
|
throw new SecureStoreLockedError(
|
|
2447
2458
|
"secure-store is locked — cannot write memory file. " +
|
|
2448
|
-
"Run `remnic secure-store unlock` to decrypt, or restart the daemon after unlocking."
|
|
2459
|
+
"Run `remnic secure-store unlock` to decrypt, or restart the daemon after unlocking."
|
|
2449
2460
|
);
|
|
2450
2461
|
}
|
|
2451
2462
|
return null;
|
|
@@ -2497,13 +2508,13 @@ export class StorageManager {
|
|
|
2497
2508
|
this._versioningConfig,
|
|
2498
2509
|
log,
|
|
2499
2510
|
undefined,
|
|
2500
|
-
this.baseDir
|
|
2511
|
+
this.baseDir
|
|
2501
2512
|
);
|
|
2502
2513
|
const rel = path.relative(this.baseDir, filePath).split(path.sep).join("/");
|
|
2503
2514
|
return `${rel}:${version.versionId}`;
|
|
2504
2515
|
} catch (err) {
|
|
2505
2516
|
log.warn(
|
|
2506
|
-
`storage.snapshotForProvenance: failed to snapshot ${filePath}: ${err instanceof Error ? err.message : String(err)}
|
|
2517
|
+
`storage.snapshotForProvenance: failed to snapshot ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2507
2518
|
);
|
|
2508
2519
|
return null;
|
|
2509
2520
|
}
|
|
@@ -2511,7 +2522,7 @@ export class StorageManager {
|
|
|
2511
2522
|
|
|
2512
2523
|
constructor(
|
|
2513
2524
|
private readonly baseDir: string,
|
|
2514
|
-
private readonly entitySchemas?: PluginConfig["entitySchemas"]
|
|
2525
|
+
private readonly entitySchemas?: PluginConfig["entitySchemas"]
|
|
2515
2526
|
) {
|
|
2516
2527
|
// Load this store's alias table at construction (#1534): StorageManager
|
|
2517
2528
|
// is created in a dozen places (namespace router, operator toolkit,
|
|
@@ -2540,9 +2551,7 @@ export class StorageManager {
|
|
|
2540
2551
|
|
|
2541
2552
|
private get memoryReadStore(): MemoryReadStore {
|
|
2542
2553
|
if (!this._memoryReadStore) {
|
|
2543
|
-
this._memoryReadStore = new MemoryReadStore(
|
|
2544
|
-
selfDeps<ConstructorParameters<typeof MemoryReadStore>[0]>(this),
|
|
2545
|
-
);
|
|
2554
|
+
this._memoryReadStore = new MemoryReadStore(selfDeps<ConstructorParameters<typeof MemoryReadStore>[0]>(this));
|
|
2546
2555
|
}
|
|
2547
2556
|
return this._memoryReadStore;
|
|
2548
2557
|
}
|
|
@@ -2552,9 +2561,7 @@ export class StorageManager {
|
|
|
2552
2561
|
|
|
2553
2562
|
private get entityStore(): EntityStore {
|
|
2554
2563
|
if (!this._entityStore) {
|
|
2555
|
-
this._entityStore = new EntityStore(
|
|
2556
|
-
selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this),
|
|
2557
|
-
);
|
|
2564
|
+
this._entityStore = new EntityStore(selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this));
|
|
2558
2565
|
}
|
|
2559
2566
|
return this._entityStore;
|
|
2560
2567
|
}
|
|
@@ -2565,7 +2572,7 @@ export class StorageManager {
|
|
|
2565
2572
|
private get identityContinuityStore(): IdentityContinuityStore {
|
|
2566
2573
|
if (!this._identityContinuityStore) {
|
|
2567
2574
|
this._identityContinuityStore = new IdentityContinuityStore(
|
|
2568
|
-
selfDeps<ConstructorParameters<typeof IdentityContinuityStore>[0]>(this)
|
|
2575
|
+
selfDeps<ConstructorParameters<typeof IdentityContinuityStore>[0]>(this)
|
|
2569
2576
|
);
|
|
2570
2577
|
}
|
|
2571
2578
|
return this._identityContinuityStore;
|
|
@@ -2594,7 +2601,7 @@ export class StorageManager {
|
|
|
2594
2601
|
|
|
2595
2602
|
private bumpSharedVersion(
|
|
2596
2603
|
kind: "memory-status" | "artifact-write" | "cold-write",
|
|
2597
|
-
fallbackMap: Map<string, number
|
|
2604
|
+
fallbackMap: Map<string, number>
|
|
2598
2605
|
): number {
|
|
2599
2606
|
const filePath = this.versionFilePath(kind);
|
|
2600
2607
|
try {
|
|
@@ -2612,7 +2619,7 @@ export class StorageManager {
|
|
|
2612
2619
|
|
|
2613
2620
|
private readSharedVersion(
|
|
2614
2621
|
kind: "memory-status" | "artifact-write" | "cold-write",
|
|
2615
|
-
fallbackMap: Map<string, number
|
|
2622
|
+
fallbackMap: Map<string, number>
|
|
2616
2623
|
): number {
|
|
2617
2624
|
const filePath = this.versionFilePath(kind);
|
|
2618
2625
|
try {
|
|
@@ -2667,32 +2674,23 @@ export class StorageManager {
|
|
|
2667
2674
|
wearableTranscriptPath(sourceId: string, date: string): string {
|
|
2668
2675
|
if (typeof sourceId !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(sourceId)) {
|
|
2669
2676
|
throw new Error(
|
|
2670
|
-
`invalid wearable source id '${String(sourceId)}' — expected lowercase letters, digits, and dashes
|
|
2677
|
+
`invalid wearable source id '${String(sourceId)}' — expected lowercase letters, digits, and dashes`
|
|
2671
2678
|
);
|
|
2672
2679
|
}
|
|
2673
2680
|
if (!isValidTranscriptDate(date)) {
|
|
2674
|
-
throw new Error(
|
|
2675
|
-
`invalid wearable transcript date '${String(date)}' — expected YYYY-MM-DD`,
|
|
2676
|
-
);
|
|
2681
|
+
throw new Error(`invalid wearable transcript date '${String(date)}' — expected YYYY-MM-DD`);
|
|
2677
2682
|
}
|
|
2678
2683
|
return path.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
2679
2684
|
}
|
|
2680
2685
|
|
|
2681
|
-
async writeWearableDayTranscript(
|
|
2682
|
-
sourceId: string,
|
|
2683
|
-
date: string,
|
|
2684
|
-
serialized: string,
|
|
2685
|
-
): Promise<void> {
|
|
2686
|
+
async writeWearableDayTranscript(sourceId: string, date: string, serialized: string): Promise<void> {
|
|
2686
2687
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
2687
2688
|
// writeMaybeEncryptedFile handles mkdir + atomic temp→rename.
|
|
2688
2689
|
await this.writeStorageSecureFile(targetPath, serialized);
|
|
2689
2690
|
}
|
|
2690
2691
|
|
|
2691
2692
|
/** Read a stored day transcript; null when the day has no file. */
|
|
2692
|
-
async readWearableDayTranscript(
|
|
2693
|
-
sourceId: string,
|
|
2694
|
-
date: string,
|
|
2695
|
-
): Promise<string | null> {
|
|
2693
|
+
async readWearableDayTranscript(sourceId: string, date: string): Promise<string | null> {
|
|
2696
2694
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
2697
2695
|
try {
|
|
2698
2696
|
return await readMaybeEncryptedFile(targetPath, this._secureStoreKey, this.baseDir);
|
|
@@ -2702,12 +2700,8 @@ export class StorageManager {
|
|
|
2702
2700
|
}
|
|
2703
2701
|
}
|
|
2704
2702
|
|
|
2705
|
-
async listWearableTranscriptDays(
|
|
2706
|
-
sourceId
|
|
2707
|
-
): Promise<Array<{ source: string; date: string }>> {
|
|
2708
|
-
return this.memoryReadStore.listWearableTranscriptDays(
|
|
2709
|
-
sourceId,
|
|
2710
|
-
);
|
|
2703
|
+
async listWearableTranscriptDays(sourceId?: string): Promise<Array<{ source: string; date: string }>> {
|
|
2704
|
+
return this.memoryReadStore.listWearableTranscriptDays(sourceId);
|
|
2711
2705
|
}
|
|
2712
2706
|
|
|
2713
2707
|
private _fusionStore?: FusionArtifactStore;
|
|
@@ -2732,9 +2726,7 @@ export class StorageManager {
|
|
|
2732
2726
|
* smart trust pipeline to find an earlier borderline write when the
|
|
2733
2727
|
* same fact re-extracts with stronger evidence.
|
|
2734
2728
|
*/
|
|
2735
|
-
async findWearableMemoryByContent(
|
|
2736
|
-
content: string,
|
|
2737
|
-
): Promise<{ id: string; status: MemoryStatus | undefined } | null> {
|
|
2729
|
+
async findWearableMemoryByContent(content: string): Promise<{ id: string; status: MemoryStatus | undefined } | null> {
|
|
2738
2730
|
const needle = stripAttributesSuffix(content);
|
|
2739
2731
|
const memories = await this.readAllMemories();
|
|
2740
2732
|
for (const memory of memories) {
|
|
@@ -2761,7 +2753,7 @@ export class StorageManager {
|
|
|
2761
2753
|
async promoteWearableMemory(
|
|
2762
2754
|
id: string,
|
|
2763
2755
|
attributeUpdates: Record<string, string>,
|
|
2764
|
-
confidence?: number
|
|
2756
|
+
confidence?: number
|
|
2765
2757
|
): Promise<boolean> {
|
|
2766
2758
|
const memories = await this.readAllMemories();
|
|
2767
2759
|
const memory = memories.find((entry) => entry.frontmatter.id === id);
|
|
@@ -2792,10 +2784,7 @@ export class StorageManager {
|
|
|
2792
2784
|
* approvals and accrued recall signals win; contradiction scans and
|
|
2793
2785
|
* supersession own active-row retirement).
|
|
2794
2786
|
*/
|
|
2795
|
-
async demoteWearableMemory(
|
|
2796
|
-
id: string,
|
|
2797
|
-
attributeUpdates: Record<string, string>,
|
|
2798
|
-
): Promise<boolean> {
|
|
2787
|
+
async demoteWearableMemory(id: string, attributeUpdates: Record<string, string>): Promise<boolean> {
|
|
2799
2788
|
const memories = await this.readAllMemories();
|
|
2800
2789
|
const memory = memories.find((entry) => entry.frontmatter.id === id);
|
|
2801
2790
|
if (!memory) return false;
|
|
@@ -2837,12 +2826,14 @@ export class StorageManager {
|
|
|
2837
2826
|
return readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
2838
2827
|
}
|
|
2839
2828
|
private writeStorageSecureFile(filePath: string, content: string | Buffer): Promise<void> {
|
|
2840
|
-
return writeMaybeEncryptedFile(filePath, content, this.resolveWriteKey(), {}, this.baseDir)
|
|
2841
|
-
|
|
2829
|
+
return writeMaybeEncryptedFile(filePath, content, this.resolveWriteKey(), {}, this.baseDir).then(() =>
|
|
2830
|
+
this.notifyCatalogWrite()
|
|
2831
|
+
);
|
|
2842
2832
|
}
|
|
2843
2833
|
private writeStorageSecureFileChunks(filePath: string, chunks: AsyncIterable<Buffer>): Promise<void> {
|
|
2844
|
-
return writeMaybeEncryptedFileFromChunks(filePath, chunks, this.resolveWriteKey(), {}, this.baseDir)
|
|
2845
|
-
|
|
2834
|
+
return writeMaybeEncryptedFileFromChunks(filePath, chunks, this.resolveWriteKey(), {}, this.baseDir).then(() =>
|
|
2835
|
+
this.notifyCatalogWrite()
|
|
2836
|
+
);
|
|
2846
2837
|
}
|
|
2847
2838
|
|
|
2848
2839
|
private assertManagedStoragePath(filePath: string, method: string): string {
|
|
@@ -2951,7 +2942,7 @@ export class StorageManager {
|
|
|
2951
2942
|
} catch (err) {
|
|
2952
2943
|
if (!isErrnoCode(err, "ENOENT")) {
|
|
2953
2944
|
log.warn(
|
|
2954
|
-
`storage.offlineSyncDigestCache: ignoring unreadable cache: ${err instanceof Error ? err.message : String(err)}
|
|
2945
|
+
`storage.offlineSyncDigestCache: ignoring unreadable cache: ${err instanceof Error ? err.message : String(err)}`
|
|
2955
2946
|
);
|
|
2956
2947
|
}
|
|
2957
2948
|
}
|
|
@@ -2961,7 +2952,7 @@ export class StorageManager {
|
|
|
2961
2952
|
private rememberOfflineSyncDigest(
|
|
2962
2953
|
relPath: string,
|
|
2963
2954
|
st: { size: number; mtimeMs: number; ctimeMs: number },
|
|
2964
|
-
digest: { sha256: string; bytes: number }
|
|
2955
|
+
digest: { sha256: string; bytes: number }
|
|
2965
2956
|
): void {
|
|
2966
2957
|
const cache = this.offlineSyncDigestCache;
|
|
2967
2958
|
if (!cache) return;
|
|
@@ -2997,15 +2988,11 @@ export class StorageManager {
|
|
|
2997
2988
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
2998
2989
|
.map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
2999
2990
|
await mkdir(path.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
3000
|
-
await writeFile(
|
|
3001
|
-
this.offlineSyncDigestCachePath,
|
|
3002
|
-
`${JSON.stringify({ version: 1, entries })}\n`,
|
|
3003
|
-
"utf-8",
|
|
3004
|
-
);
|
|
2991
|
+
await writeFile(this.offlineSyncDigestCachePath, `${JSON.stringify({ version: 1, entries })}\n`, "utf-8");
|
|
3005
2992
|
})
|
|
3006
2993
|
.catch((err) => {
|
|
3007
2994
|
log.warn(
|
|
3008
|
-
`storage.offlineSyncDigestCache: failed to write cache: ${err instanceof Error ? err.message : String(err)}
|
|
2995
|
+
`storage.offlineSyncDigestCache: failed to write cache: ${err instanceof Error ? err.message : String(err)}`
|
|
3009
2996
|
);
|
|
3010
2997
|
});
|
|
3011
2998
|
}
|
|
@@ -3071,15 +3058,13 @@ export class StorageManager {
|
|
|
3071
3058
|
this.stateDir,
|
|
3072
3059
|
() => this._secureStoreKey,
|
|
3073
3060
|
() => this.resolveWriteKey(),
|
|
3074
|
-
this.baseDir
|
|
3061
|
+
this.baseDir
|
|
3075
3062
|
);
|
|
3076
3063
|
}
|
|
3077
3064
|
|
|
3078
3065
|
private async appendStorageSecureFile(filePath: string, content: string): Promise<void> {
|
|
3079
3066
|
const previous = this.secureAppendChains.get(filePath) ?? Promise.resolve();
|
|
3080
|
-
const current = previous
|
|
3081
|
-
.catch(() => undefined)
|
|
3082
|
-
.then(() => this.appendStorageSecureFileUnlocked(filePath, content));
|
|
3067
|
+
const current = previous.catch(() => undefined).then(() => this.appendStorageSecureFileUnlocked(filePath, content));
|
|
3083
3068
|
const next = current.catch(() => undefined);
|
|
3084
3069
|
this.secureAppendChains.set(filePath, next);
|
|
3085
3070
|
try {
|
|
@@ -3184,12 +3169,7 @@ export class StorageManager {
|
|
|
3184
3169
|
// stat lets the store own its cross-process staleness probe (#1579).
|
|
3185
3170
|
stat: (filePath) => statSync(filePath),
|
|
3186
3171
|
};
|
|
3187
|
-
return new TombstoneStore(
|
|
3188
|
-
this.tombstonesPath,
|
|
3189
|
-
this.tombstonesConfig.namespace,
|
|
3190
|
-
options,
|
|
3191
|
-
io,
|
|
3192
|
-
);
|
|
3172
|
+
return new TombstoneStore(this.tombstonesPath, this.tombstonesConfig.namespace, options, io);
|
|
3193
3173
|
}
|
|
3194
3174
|
|
|
3195
3175
|
/**
|
|
@@ -3397,9 +3377,7 @@ export class StorageManager {
|
|
|
3397
3377
|
const stripped = stripCitationForTemplate(content, this.citationTemplate);
|
|
3398
3378
|
if (stripped !== content) {
|
|
3399
3379
|
// Citation was stripped — index the bare body.
|
|
3400
|
-
factHashIndex.addByHash(
|
|
3401
|
-
ContentHashIndex.computeHash(sanitizeMemoryContent(stripped).text),
|
|
3402
|
-
);
|
|
3380
|
+
factHashIndex.addByHash(ContentHashIndex.computeHash(sanitizeMemoryContent(stripped).text));
|
|
3403
3381
|
continue;
|
|
3404
3382
|
}
|
|
3405
3383
|
// No citation was removed. Decide whether to index or skip.
|
|
@@ -3410,9 +3388,7 @@ export class StorageManager {
|
|
|
3410
3388
|
// means the citation is from an unknown/custom template we cannot strip.
|
|
3411
3389
|
if (!hasCitation(content)) {
|
|
3412
3390
|
// Content has no recognisable citation marker — index raw body.
|
|
3413
|
-
factHashIndex.addByHash(
|
|
3414
|
-
ContentHashIndex.computeHash(sanitizeMemoryContent(content).text),
|
|
3415
|
-
);
|
|
3391
|
+
factHashIndex.addByHash(ContentHashIndex.computeHash(sanitizeMemoryContent(content).text));
|
|
3416
3392
|
continue;
|
|
3417
3393
|
}
|
|
3418
3394
|
// Content carries a citation from an unknown/custom template
|
|
@@ -3422,7 +3398,7 @@ export class StorageManager {
|
|
|
3422
3398
|
}
|
|
3423
3399
|
if (legacyRecovered > 0) {
|
|
3424
3400
|
log.info(
|
|
3425
|
-
`ensureFactHashIndexAuthoritative: skipped ${legacyRecovered} legacy fact(s) with no contentHash in frontmatter
|
|
3401
|
+
`ensureFactHashIndexAuthoritative: skipped ${legacyRecovered} legacy fact(s) with no contentHash in frontmatter`
|
|
3426
3402
|
);
|
|
3427
3403
|
}
|
|
3428
3404
|
await factHashIndex.save();
|
|
@@ -3509,10 +3485,7 @@ export class StorageManager {
|
|
|
3509
3485
|
private userAliases: Record<string, string> = {};
|
|
3510
3486
|
|
|
3511
3487
|
normalizeEntityName(raw: string, type: string): string {
|
|
3512
|
-
return this.entityStore.normalizeEntityName(
|
|
3513
|
-
raw,
|
|
3514
|
-
type,
|
|
3515
|
-
);
|
|
3488
|
+
return this.entityStore.normalizeEntityName(raw, type);
|
|
3516
3489
|
}
|
|
3517
3490
|
|
|
3518
3491
|
/**
|
|
@@ -3552,9 +3525,7 @@ export class StorageManager {
|
|
|
3552
3525
|
this.userAliases = cleaned;
|
|
3553
3526
|
log.debug(`loaded ${Object.keys(cleaned).length} entity aliases from ${aliasPath}`);
|
|
3554
3527
|
} else {
|
|
3555
|
-
log.warn(
|
|
3556
|
-
`ignoring ${aliasPath}: payload must be a JSON object mapping variant → canonical strings`,
|
|
3557
|
-
);
|
|
3528
|
+
log.warn(`ignoring ${aliasPath}: payload must be a JSON object mapping variant → canonical strings`);
|
|
3558
3529
|
}
|
|
3559
3530
|
} catch {
|
|
3560
3531
|
// No aliases file — that's fine, use built-in only
|
|
@@ -3591,11 +3562,7 @@ export class StorageManager {
|
|
|
3591
3562
|
* as `<dir>/<date>/`. Read/scan/reindex already iterate every category dir
|
|
3592
3563
|
* (RECALL_FALLBACK_DIRS; QMD scans baseDir recursively), so writes stay found.
|
|
3593
3564
|
*/
|
|
3594
|
-
private async resolveCategoryWritePath(
|
|
3595
|
-
category: MemoryCategory,
|
|
3596
|
-
id: string,
|
|
3597
|
-
today: string,
|
|
3598
|
-
): Promise<string> {
|
|
3565
|
+
private async resolveCategoryWritePath(category: MemoryCategory, id: string, today: string): Promise<string> {
|
|
3599
3566
|
if (category === "correction") {
|
|
3600
3567
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
3601
3568
|
return path.join(this.correctionsDir, `${id}.md`);
|
|
@@ -3671,7 +3638,8 @@ export class StorageManager {
|
|
|
3671
3638
|
*/
|
|
3672
3639
|
sources?: ProvenanceSource[];
|
|
3673
3640
|
provenance?: "verified" | "unverified" | "none";
|
|
3674
|
-
|
|
3641
|
+
sourceConnector?: string;
|
|
3642
|
+
} = {}
|
|
3675
3643
|
): Promise<MemoryWriteResult> {
|
|
3676
3644
|
await this.ensureDirectories();
|
|
3677
3645
|
const now = new Date();
|
|
@@ -3680,10 +3648,7 @@ export class StorageManager {
|
|
|
3680
3648
|
const conf = options.confidence ?? 0.8;
|
|
3681
3649
|
const tier = confidenceTier(conf);
|
|
3682
3650
|
const validAt = normalizeMemoryWriteTimestamp("validAt", options.validAt);
|
|
3683
|
-
const observedAt = normalizeMemoryWriteTimestamp(
|
|
3684
|
-
"observedAt",
|
|
3685
|
-
options.observedAt,
|
|
3686
|
-
);
|
|
3651
|
+
const observedAt = normalizeMemoryWriteTimestamp("observedAt", options.observedAt);
|
|
3687
3652
|
|
|
3688
3653
|
// Auto-set TTL for speculative memories
|
|
3689
3654
|
let expiresAt: string | undefined;
|
|
@@ -3753,6 +3718,9 @@ export class StorageManager {
|
|
|
3753
3718
|
if (options.provenance !== undefined) {
|
|
3754
3719
|
fm.provenance = options.provenance;
|
|
3755
3720
|
}
|
|
3721
|
+
if (options.sourceConnector !== undefined) {
|
|
3722
|
+
fm.sourceConnector = options.sourceConnector;
|
|
3723
|
+
}
|
|
3756
3724
|
|
|
3757
3725
|
// Append structured attributes as searchable suffix so QMD indexes them.
|
|
3758
3726
|
// normalizeAttributePairs sorts and lowercases keys so the enriched content
|
|
@@ -3830,7 +3798,7 @@ export class StorageManager {
|
|
|
3830
3798
|
fm.blockedBy = match.tombstoneId;
|
|
3831
3799
|
fm.tombstoneBlockTier = match.matchedTier;
|
|
3832
3800
|
log.info(
|
|
3833
|
-
`tombstone: blocked resurrection of fact ${id} (tier=${match.matchedTier}, tombstone=${match.tombstoneId}, reason=${match.reason})
|
|
3801
|
+
`tombstone: blocked resurrection of fact ${id} (tier=${match.matchedTier}, tombstone=${match.tombstoneId}, reason=${match.reason})`
|
|
3834
3802
|
);
|
|
3835
3803
|
}
|
|
3836
3804
|
} catch (err) {
|
|
@@ -3856,7 +3824,7 @@ export class StorageManager {
|
|
|
3856
3824
|
after: this.summarizeLifecycleState(fm, filePath),
|
|
3857
3825
|
relatedMemoryIds: [
|
|
3858
3826
|
...(options.supersedes ? [options.supersedes] : []),
|
|
3859
|
-
...(
|
|
3827
|
+
...(options.lineage ?? []).filter(Boolean),
|
|
3860
3828
|
],
|
|
3861
3829
|
});
|
|
3862
3830
|
if (category === "fact" && !tombstoneBlocked) {
|
|
@@ -3957,7 +3925,7 @@ export class StorageManager {
|
|
|
3957
3925
|
const removedIds = new Set(
|
|
3958
3926
|
memories
|
|
3959
3927
|
.map((memory) => memory.frontmatter.id)
|
|
3960
|
-
.filter((id): id is string => typeof id === "string" && id.length > 0)
|
|
3928
|
+
.filter((id): id is string => typeof id === "string" && id.length > 0)
|
|
3961
3929
|
);
|
|
3962
3930
|
const removedHashes = new Map<MemoryFile, string>();
|
|
3963
3931
|
for (const memory of memories) {
|
|
@@ -3969,10 +3937,7 @@ export class StorageManager {
|
|
|
3969
3937
|
if (removedHashes.size === 0) return;
|
|
3970
3938
|
|
|
3971
3939
|
const remainingActiveHashes = new Set<string>();
|
|
3972
|
-
const remainingMemories = [
|
|
3973
|
-
...await this.readAllMemories(),
|
|
3974
|
-
...await this.readAllColdMemories(),
|
|
3975
|
-
];
|
|
3940
|
+
const remainingMemories = [...(await this.readAllMemories()), ...(await this.readAllColdMemories())];
|
|
3976
3941
|
for (const memory of remainingMemories) {
|
|
3977
3942
|
if (memory.frontmatter.category !== "fact") continue;
|
|
3978
3943
|
if (removedIds.has(memory.frontmatter.id)) continue;
|
|
@@ -4008,7 +3973,8 @@ export class StorageManager {
|
|
|
4008
3973
|
intentGoal?: string;
|
|
4009
3974
|
intentActionType?: string;
|
|
4010
3975
|
intentEntityTypes?: string[];
|
|
4011
|
-
|
|
3976
|
+
sourceConnector?: string;
|
|
3977
|
+
} = {}
|
|
4012
3978
|
): Promise<string> {
|
|
4013
3979
|
await this.ensureDirectories();
|
|
4014
3980
|
const now = new Date();
|
|
@@ -4032,6 +3998,7 @@ export class StorageManager {
|
|
|
4032
3998
|
intentGoal: options.intentGoal,
|
|
4033
3999
|
intentActionType: options.intentActionType,
|
|
4034
4000
|
intentEntityTypes: options.intentEntityTypes,
|
|
4001
|
+
...(options.sourceConnector ? { sourceConnector: options.sourceConnector } : {}),
|
|
4035
4002
|
};
|
|
4036
4003
|
|
|
4037
4004
|
const sanitized = sanitizeMemoryContent(quote);
|
|
@@ -4042,9 +4009,7 @@ export class StorageManager {
|
|
|
4042
4009
|
const filePath = path.join(dir, `${id}.md`);
|
|
4043
4010
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}\n\n${sanitized.text}\n`);
|
|
4044
4011
|
const actor =
|
|
4045
|
-
typeof options.actor === "string" && options.actor.length > 0
|
|
4046
|
-
? options.actor
|
|
4047
|
-
: "storage.writeArtifact";
|
|
4012
|
+
typeof options.actor === "string" && options.actor.length > 0 ? options.actor : "storage.writeArtifact";
|
|
4048
4013
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeArtifact", {
|
|
4049
4014
|
memoryId: id,
|
|
4050
4015
|
eventType: "created",
|
|
@@ -4061,8 +4026,7 @@ export class StorageManager {
|
|
|
4061
4026
|
}
|
|
4062
4027
|
|
|
4063
4028
|
private async readAllArtifactsCached(): Promise<MemoryFile[]> {
|
|
4064
|
-
return this.memoryReadStore.readAllArtifactsCached(
|
|
4065
|
-
);
|
|
4029
|
+
return this.memoryReadStore.readAllArtifactsCached();
|
|
4066
4030
|
}
|
|
4067
4031
|
|
|
4068
4032
|
async searchArtifacts(query: string, maxResults: number): Promise<MemoryFile[]> {
|
|
@@ -4073,7 +4037,7 @@ export class StorageManager {
|
|
|
4073
4037
|
const hits: Array<{ score: number; memory: MemoryFile }> = [];
|
|
4074
4038
|
for (const memory of artifacts) {
|
|
4075
4039
|
const indexedTokens = new Set(
|
|
4076
|
-
tokenizeArtifactSearchText(`${memory.content} ${(memory.frontmatter.tags ?? []).join(" ")}`)
|
|
4040
|
+
tokenizeArtifactSearchText(`${memory.content} ${(memory.frontmatter.tags ?? []).join(" ")}`)
|
|
4077
4041
|
);
|
|
4078
4042
|
const score = tokens.reduce((sum, t) => sum + (indexedTokens.has(t) ? 1 : 0), 0);
|
|
4079
4043
|
if (score > 0) {
|
|
@@ -4094,14 +4058,9 @@ export class StorageManager {
|
|
|
4094
4058
|
sessionKey?: string;
|
|
4095
4059
|
principal?: string;
|
|
4096
4060
|
structuredSections?: EntityStructuredSection[];
|
|
4097
|
-
} = {}
|
|
4061
|
+
} = {}
|
|
4098
4062
|
): Promise<string> {
|
|
4099
|
-
return this.entityStore.writeEntity(
|
|
4100
|
-
name,
|
|
4101
|
-
type,
|
|
4102
|
-
facts,
|
|
4103
|
-
options,
|
|
4104
|
-
);
|
|
4063
|
+
return this.entityStore.writeEntity(name, type, facts, options);
|
|
4105
4064
|
}
|
|
4106
4065
|
|
|
4107
4066
|
async readProfile(): Promise<string> {
|
|
@@ -4161,9 +4120,7 @@ export class StorageManager {
|
|
|
4161
4120
|
const existing = await this.readProfile();
|
|
4162
4121
|
|
|
4163
4122
|
const lines = existing ? existing.split("\n") : [];
|
|
4164
|
-
const existingBulletRaw = lines
|
|
4165
|
-
.filter((l) => l.startsWith("- "))
|
|
4166
|
-
.map((l) => l.slice(2).trim());
|
|
4123
|
+
const existingBulletRaw = lines.filter((l) => l.startsWith("- ")).map((l) => l.slice(2).trim());
|
|
4167
4124
|
const existingNorms = existingBulletRaw.map(StorageManager.normalizeForDedup);
|
|
4168
4125
|
|
|
4169
4126
|
const newBullets = updates.filter((u) => {
|
|
@@ -4183,10 +4140,7 @@ export class StorageManager {
|
|
|
4183
4140
|
].join("\n");
|
|
4184
4141
|
await this.writeProfile(content);
|
|
4185
4142
|
} else {
|
|
4186
|
-
const updatedTimestamp = existing.replace(
|
|
4187
|
-
/\*Last updated:.*\*/,
|
|
4188
|
-
`*Last updated: ${new Date().toISOString()}*`,
|
|
4189
|
-
);
|
|
4143
|
+
const updatedTimestamp = existing.replace(/\*Last updated:.*\*/, `*Last updated: ${new Date().toISOString()}*`);
|
|
4190
4144
|
const withBullets = updatedTimestamp.trimEnd() + "\n" + newBullets.map((b) => `- ${b}`).join("\n") + "\n";
|
|
4191
4145
|
await this.writeProfile(withBullets);
|
|
4192
4146
|
}
|
|
@@ -4197,9 +4151,8 @@ export class StorageManager {
|
|
|
4197
4151
|
const profile = await this.readProfile();
|
|
4198
4152
|
if (!profile) return false;
|
|
4199
4153
|
const lineCount = profile.split("\n").length;
|
|
4200
|
-
const threshold =
|
|
4201
|
-
? Math.max(0, Math.floor(triggerLines))
|
|
4202
|
-
: StorageManager.PROFILE_MAX_LINES;
|
|
4154
|
+
const threshold =
|
|
4155
|
+
typeof triggerLines === "number" ? Math.max(0, Math.floor(triggerLines)) : StorageManager.PROFILE_MAX_LINES;
|
|
4203
4156
|
return lineCount > threshold;
|
|
4204
4157
|
}
|
|
4205
4158
|
|
|
@@ -4333,14 +4286,10 @@ export class StorageManager {
|
|
|
4333
4286
|
}
|
|
4334
4287
|
|
|
4335
4288
|
private async collectActiveMemoryPaths(): Promise<string[]> {
|
|
4336
|
-
return this.memoryReadStore.collectActiveMemoryPaths(
|
|
4337
|
-
);
|
|
4289
|
+
return this.memoryReadStore.collectActiveMemoryPaths();
|
|
4338
4290
|
}
|
|
4339
4291
|
|
|
4340
|
-
private async readParsedMemoriesFromPaths(
|
|
4341
|
-
filePaths: string[],
|
|
4342
|
-
batchSize?: number,
|
|
4343
|
-
): Promise<MemoryFile[]> {
|
|
4292
|
+
private async readParsedMemoriesFromPaths(filePaths: string[], batchSize?: number): Promise<MemoryFile[]> {
|
|
4344
4293
|
if (filePaths.length === 0) return [];
|
|
4345
4294
|
|
|
4346
4295
|
const normalizedBatchSize = this.normalizeMemoryReadBatchSize(batchSize);
|
|
@@ -4358,7 +4307,7 @@ export class StorageManager {
|
|
|
4358
4307
|
frontmatter: normalizeFrontmatterForPath(
|
|
4359
4308
|
parsed.frontmatter,
|
|
4360
4309
|
toMemoryPathRel(this.baseDir, fullPath),
|
|
4361
|
-
parsed.content
|
|
4310
|
+
parsed.content
|
|
4362
4311
|
),
|
|
4363
4312
|
content: parsed.content,
|
|
4364
4313
|
} satisfies MemoryFile;
|
|
@@ -4369,7 +4318,7 @@ export class StorageManager {
|
|
|
4369
4318
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
4370
4319
|
return null;
|
|
4371
4320
|
}
|
|
4372
|
-
})
|
|
4321
|
+
})
|
|
4373
4322
|
);
|
|
4374
4323
|
for (const memory of results) {
|
|
4375
4324
|
if (memory !== null) memories.push(memory);
|
|
@@ -4385,9 +4334,9 @@ export class StorageManager {
|
|
|
4385
4334
|
if (!match) return null;
|
|
4386
4335
|
const frontmatterBlock = match[1];
|
|
4387
4336
|
const rawUpdated =
|
|
4388
|
-
frontmatterBlock.match(/^updated:\s*"?([^"\n]*)"?/m)?.[1]
|
|
4389
|
-
|
|
4390
|
-
|
|
4337
|
+
frontmatterBlock.match(/^updated:\s*"?([^"\n]*)"?/m)?.[1] ??
|
|
4338
|
+
frontmatterBlock.match(/^created:\s*"?([^"\n]*)"?/m)?.[1] ??
|
|
4339
|
+
null;
|
|
4391
4340
|
const updatedMs = rawUpdated ? Date.parse(rawUpdated) : Number.NaN;
|
|
4392
4341
|
return Number.isFinite(updatedMs) ? updatedMs : null;
|
|
4393
4342
|
} catch {
|
|
@@ -4396,18 +4345,20 @@ export class StorageManager {
|
|
|
4396
4345
|
}
|
|
4397
4346
|
|
|
4398
4347
|
private async filterWindowPathsByUpdatedAfter(filePaths: string[], updatedAfterMs: number): Promise<string[]> {
|
|
4399
|
-
const results = await Promise.all(
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4348
|
+
const results = await Promise.all(
|
|
4349
|
+
filePaths.map(async (filePath) => {
|
|
4350
|
+
const updatedMs = await this.readWindowUpdatedMs(filePath);
|
|
4351
|
+
if (updatedMs !== null) {
|
|
4352
|
+
return updatedMs >= updatedAfterMs ? filePath : null;
|
|
4353
|
+
}
|
|
4354
|
+
try {
|
|
4355
|
+
const fileStat = await stat(filePath);
|
|
4356
|
+
return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
|
|
4357
|
+
} catch {
|
|
4358
|
+
return filePath;
|
|
4359
|
+
}
|
|
4360
|
+
})
|
|
4361
|
+
);
|
|
4411
4362
|
return results.filter((filePath): filePath is string => filePath !== null);
|
|
4412
4363
|
}
|
|
4413
4364
|
|
|
@@ -4444,7 +4395,7 @@ export class StorageManager {
|
|
|
4444
4395
|
candidateBatchPaths: string[],
|
|
4445
4396
|
remainingSlots: number,
|
|
4446
4397
|
remainingInspectionBudget: number,
|
|
4447
|
-
readBatchSize: number
|
|
4398
|
+
readBatchSize: number
|
|
4448
4399
|
): Promise<{ memories: MemoryFile[]; filePaths: string[] }> {
|
|
4449
4400
|
const memories: MemoryFile[] = [];
|
|
4450
4401
|
const filePaths: string[] = [];
|
|
@@ -4455,12 +4406,10 @@ export class StorageManager {
|
|
|
4455
4406
|
const availableSlots = remainingSlots - memories.length;
|
|
4456
4407
|
const availableInspectionBudget = remainingInspectionBudget - filePaths.length;
|
|
4457
4408
|
const parallelWindow =
|
|
4458
|
-
availableSlots >= 4 && availableInspectionBudget >= 4
|
|
4459
|
-
? Math.min(normalizedReadBatchSize, 4)
|
|
4460
|
-
: 1;
|
|
4409
|
+
availableSlots >= 4 && availableInspectionBudget >= 4 ? Math.min(normalizedReadBatchSize, 4) : 1;
|
|
4461
4410
|
const candidatePaths = candidateBatchPaths.slice(
|
|
4462
4411
|
index,
|
|
4463
|
-
index + Math.min(parallelWindow, availableInspectionBudget)
|
|
4412
|
+
index + Math.min(parallelWindow, availableInspectionBudget)
|
|
4464
4413
|
);
|
|
4465
4414
|
index += candidatePaths.length;
|
|
4466
4415
|
if (candidatePaths.length === 0) break;
|
|
@@ -4473,14 +4422,14 @@ export class StorageManager {
|
|
|
4473
4422
|
return { memories, filePaths };
|
|
4474
4423
|
}
|
|
4475
4424
|
|
|
4476
|
-
async readMemoriesWindow(
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
);
|
|
4425
|
+
async readMemoriesWindow(
|
|
4426
|
+
options: {
|
|
4427
|
+
maxMemories?: number;
|
|
4428
|
+
batchSize?: number;
|
|
4429
|
+
updatedAfter?: Date;
|
|
4430
|
+
} = {}
|
|
4431
|
+
): Promise<{ memories: MemoryFile[]; filePaths: string[] }> {
|
|
4432
|
+
return this.memoryReadStore.readMemoriesWindow(options);
|
|
4484
4433
|
}
|
|
4485
4434
|
|
|
4486
4435
|
private async _readAllMemoriesFromDisk(): Promise<MemoryFile[]> {
|
|
@@ -4489,8 +4438,7 @@ export class StorageManager {
|
|
|
4489
4438
|
}
|
|
4490
4439
|
|
|
4491
4440
|
async readAllColdMemories(): Promise<MemoryFile[]> {
|
|
4492
|
-
return this.memoryReadStore.readAllColdMemories(
|
|
4493
|
-
);
|
|
4441
|
+
return this.memoryReadStore.readAllColdMemories();
|
|
4494
4442
|
}
|
|
4495
4443
|
|
|
4496
4444
|
/**
|
|
@@ -4518,7 +4466,7 @@ export class StorageManager {
|
|
|
4518
4466
|
frontmatter: normalizeFrontmatterForPath(
|
|
4519
4467
|
parsed.frontmatter,
|
|
4520
4468
|
toMemoryPathRel(this.baseDir, fullPath),
|
|
4521
|
-
parsed.content
|
|
4469
|
+
parsed.content
|
|
4522
4470
|
),
|
|
4523
4471
|
content: parsed.content,
|
|
4524
4472
|
});
|
|
@@ -4541,9 +4489,7 @@ export class StorageManager {
|
|
|
4541
4489
|
}
|
|
4542
4490
|
|
|
4543
4491
|
async readMemoryByPath(filePath: string): Promise<MemoryFile | null> {
|
|
4544
|
-
return this.memoryReadStore.readMemoryByPath(
|
|
4545
|
-
filePath,
|
|
4546
|
-
);
|
|
4492
|
+
return this.memoryReadStore.readMemoryByPath(filePath);
|
|
4547
4493
|
}
|
|
4548
4494
|
|
|
4549
4495
|
private resolveTierRootDir(tier: "hot" | "cold"): string {
|
|
@@ -4553,9 +4499,7 @@ export class StorageManager {
|
|
|
4553
4499
|
private resolveMemoryDateDir(memory: MemoryFile): string {
|
|
4554
4500
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
4555
4501
|
const dateToken = (preferred ?? "").slice(0, 10);
|
|
4556
|
-
return /^\d{4}-\d{2}-\d{2}$/.test(dateToken)
|
|
4557
|
-
? dateToken
|
|
4558
|
-
: new Date().toISOString().slice(0, 10);
|
|
4502
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(dateToken) ? dateToken : new Date().toISOString().slice(0, 10);
|
|
4559
4503
|
}
|
|
4560
4504
|
|
|
4561
4505
|
private isArtifactMemory(memory: MemoryFile): boolean {
|
|
@@ -4611,7 +4555,7 @@ export class StorageManager {
|
|
|
4611
4555
|
|
|
4612
4556
|
async migrateMemoryToTier(
|
|
4613
4557
|
memory: MemoryFile,
|
|
4614
|
-
targetTier: "hot" | "cold"
|
|
4558
|
+
targetTier: "hot" | "cold"
|
|
4615
4559
|
): Promise<{ changed: boolean; targetPath: string }> {
|
|
4616
4560
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
4617
4561
|
const sourcePath = path.resolve(memory.path);
|
|
@@ -4655,10 +4599,7 @@ export class StorageManager {
|
|
|
4655
4599
|
* Updates frontmatter with archived status before moving.
|
|
4656
4600
|
* Returns the new file path on success, null on failure.
|
|
4657
4601
|
*/
|
|
4658
|
-
async archiveMemory(
|
|
4659
|
-
memory: MemoryFile,
|
|
4660
|
-
lifecycle?: MemoryLifecycleEventWriteOptions,
|
|
4661
|
-
): Promise<string | null> {
|
|
4602
|
+
async archiveMemory(memory: MemoryFile, lifecycle?: MemoryLifecycleEventWriteOptions): Promise<string | null> {
|
|
4662
4603
|
try {
|
|
4663
4604
|
const now = lifecycle?.at ?? new Date();
|
|
4664
4605
|
const today = now.toISOString().slice(0, 10);
|
|
@@ -4678,7 +4619,8 @@ export class StorageManager {
|
|
|
4678
4619
|
|
|
4679
4620
|
// Write to archive location first (encrypted if applicable), then remove original.
|
|
4680
4621
|
await this.writeStorageSecureFile(destPath, fileContent);
|
|
4681
|
-
await unlink(memory.path);
|
|
4622
|
+
await unlink(memory.path);
|
|
4623
|
+
markProjectedMemoryPathInvalid(this.baseDir, memory.frontmatter.id);
|
|
4682
4624
|
this.invalidateAllMemoriesCache();
|
|
4683
4625
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
4684
4626
|
"storage.archiveMemory",
|
|
@@ -4693,7 +4635,7 @@ export class StorageManager {
|
|
|
4693
4635
|
relatedMemoryIds: lifecycle?.relatedMemoryIds,
|
|
4694
4636
|
correlationId: lifecycle?.correlationId,
|
|
4695
4637
|
},
|
|
4696
|
-
lifecycle?.ruleVersion
|
|
4638
|
+
lifecycle?.ruleVersion
|
|
4697
4639
|
);
|
|
4698
4640
|
this.bumpMemoryStatusVersion();
|
|
4699
4641
|
|
|
@@ -4715,14 +4657,11 @@ export class StorageManager {
|
|
|
4715
4657
|
}
|
|
4716
4658
|
|
|
4717
4659
|
async readEntity(name: string): Promise<string> {
|
|
4718
|
-
return this.entityStore.readEntity(
|
|
4719
|
-
name,
|
|
4720
|
-
);
|
|
4660
|
+
return this.entityStore.readEntity(name);
|
|
4721
4661
|
}
|
|
4722
4662
|
|
|
4723
4663
|
async listEntityNames(): Promise<string[]> {
|
|
4724
|
-
return this.entityStore.listEntityNames(
|
|
4725
|
-
);
|
|
4664
|
+
return this.entityStore.listEntityNames();
|
|
4726
4665
|
}
|
|
4727
4666
|
|
|
4728
4667
|
/**
|
|
@@ -4742,9 +4681,7 @@ export class StorageManager {
|
|
|
4742
4681
|
const typePrefix = `${type.toLowerCase()}-`;
|
|
4743
4682
|
// Extract the name part from the proposed normalized name
|
|
4744
4683
|
const proposedFull = this.normalizeEntityName(proposedName, type);
|
|
4745
|
-
const proposedNamePart = proposedFull.startsWith(typePrefix)
|
|
4746
|
-
? proposedFull.slice(typePrefix.length)
|
|
4747
|
-
: proposedFull;
|
|
4684
|
+
const proposedNamePart = proposedFull.startsWith(typePrefix) ? proposedFull.slice(typePrefix.length) : proposedFull;
|
|
4748
4685
|
const proposedDehyph = dehyphenate(proposedNamePart);
|
|
4749
4686
|
|
|
4750
4687
|
// Only compare against entities of the same type
|
|
@@ -4783,7 +4720,8 @@ export class StorageManager {
|
|
|
4783
4720
|
if (!memory) return false;
|
|
4784
4721
|
|
|
4785
4722
|
try {
|
|
4786
|
-
await unlink(memory.path);
|
|
4723
|
+
await unlink(memory.path);
|
|
4724
|
+
markProjectedMemoryPathInvalid(this.baseDir, id);
|
|
4787
4725
|
this.invalidateAllMemoriesCache();
|
|
4788
4726
|
this.bumpMemoryStatusVersion();
|
|
4789
4727
|
log.debug(`invalidated memory ${id}`);
|
|
@@ -4796,22 +4734,24 @@ export class StorageManager {
|
|
|
4796
4734
|
async updateMemory(
|
|
4797
4735
|
id: string,
|
|
4798
4736
|
newContent: string,
|
|
4799
|
-
options?: { supersedes?: string; lineage?: string[]; actor?: string }
|
|
4737
|
+
options?: { supersedes?: string; lineage?: string[]; actor?: string; sourceConnector?: string }
|
|
4800
4738
|
): Promise<boolean> {
|
|
4801
4739
|
const memories = await this.readAllMemories();
|
|
4802
4740
|
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
4803
4741
|
if (!memory) return false;
|
|
4804
4742
|
|
|
4805
|
-
const mergedLineage = [
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
].filter((v, i, a) => a.indexOf(v) === i); // dedupe
|
|
4743
|
+
const mergedLineage = [...(memory.frontmatter.lineage ?? []), ...(options?.lineage ?? [])].filter(
|
|
4744
|
+
(v, i, a) => a.indexOf(v) === i
|
|
4745
|
+
); // dedupe
|
|
4809
4746
|
|
|
4810
4747
|
const updated: MemoryFrontmatter = {
|
|
4811
4748
|
...memory.frontmatter,
|
|
4812
4749
|
updated: new Date().toISOString(),
|
|
4813
4750
|
supersedes: options?.supersedes ?? memory.frontmatter.supersedes,
|
|
4814
4751
|
lineage: mergedLineage.length > 0 ? mergedLineage : undefined,
|
|
4752
|
+
...(memory.frontmatter.sourceConnector === undefined && options?.sourceConnector
|
|
4753
|
+
? { sourceConnector: options.sourceConnector }
|
|
4754
|
+
: {}),
|
|
4815
4755
|
};
|
|
4816
4756
|
const sanitized = sanitizeMemoryContent(newContent);
|
|
4817
4757
|
if (!sanitized.clean) {
|
|
@@ -4829,7 +4769,7 @@ export class StorageManager {
|
|
|
4829
4769
|
after: this.summarizeLifecycleState(updated, memory.path),
|
|
4830
4770
|
relatedMemoryIds: [
|
|
4831
4771
|
...(updated.supersedes ? [updated.supersedes] : []),
|
|
4832
|
-
...(
|
|
4772
|
+
...(updated.lineage ?? []).filter(Boolean),
|
|
4833
4773
|
],
|
|
4834
4774
|
});
|
|
4835
4775
|
log.debug(`updated memory ${id}`);
|
|
@@ -4843,7 +4783,7 @@ export class StorageManager {
|
|
|
4843
4783
|
async writeMemoryFrontmatter(
|
|
4844
4784
|
memory: MemoryFile,
|
|
4845
4785
|
patch: Partial<MemoryFrontmatter>,
|
|
4846
|
-
lifecycle?: MemoryLifecycleEventWriteOptions
|
|
4786
|
+
lifecycle?: MemoryLifecycleEventWriteOptions
|
|
4847
4787
|
): Promise<boolean> {
|
|
4848
4788
|
const beforeStatus = memory.frontmatter.status ?? "active";
|
|
4849
4789
|
const updated: MemoryFrontmatter = {
|
|
@@ -4862,13 +4802,10 @@ export class StorageManager {
|
|
|
4862
4802
|
this.invalidateColdMemoriesCache();
|
|
4863
4803
|
}
|
|
4864
4804
|
try {
|
|
4865
|
-
await this.syncFactHashIndexAfterRewrite(
|
|
4866
|
-
memory,
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
frontmatter: updated,
|
|
4870
|
-
},
|
|
4871
|
-
);
|
|
4805
|
+
await this.syncFactHashIndexAfterRewrite(memory, {
|
|
4806
|
+
...memory,
|
|
4807
|
+
frontmatter: updated,
|
|
4808
|
+
});
|
|
4872
4809
|
} catch (err) {
|
|
4873
4810
|
log.warn(`storage.writeMemoryFrontmatter completed but failed to update fact hash index: ${err}`);
|
|
4874
4811
|
}
|
|
@@ -4889,7 +4826,7 @@ export class StorageManager {
|
|
|
4889
4826
|
],
|
|
4890
4827
|
correlationId: lifecycle?.correlationId,
|
|
4891
4828
|
},
|
|
4892
|
-
lifecycle?.ruleVersion
|
|
4829
|
+
lifecycle?.ruleVersion
|
|
4893
4830
|
);
|
|
4894
4831
|
if (beforeStatus !== afterStatus) {
|
|
4895
4832
|
this.bumpMemoryStatusVersion();
|
|
@@ -4901,10 +4838,7 @@ export class StorageManager {
|
|
|
4901
4838
|
* Update frontmatter by memory ID.
|
|
4902
4839
|
* Prefer writeMemoryFrontmatter(memory, patch) in batch loops to avoid full-corpus rescans.
|
|
4903
4840
|
*/
|
|
4904
|
-
async updateMemoryFrontmatter(
|
|
4905
|
-
id: string,
|
|
4906
|
-
patch: Partial<MemoryFrontmatter>,
|
|
4907
|
-
): Promise<boolean> {
|
|
4841
|
+
async updateMemoryFrontmatter(id: string, patch: Partial<MemoryFrontmatter>): Promise<boolean> {
|
|
4908
4842
|
const memories = await this.readAllMemories();
|
|
4909
4843
|
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
4910
4844
|
if (!memory) return false;
|
|
@@ -4922,7 +4856,8 @@ export class StorageManager {
|
|
|
4922
4856
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
4923
4857
|
if (expiresAt < now) {
|
|
4924
4858
|
try {
|
|
4925
|
-
await unlink(m.path);
|
|
4859
|
+
await unlink(m.path);
|
|
4860
|
+
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
4926
4861
|
deleted.push(m);
|
|
4927
4862
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
4928
4863
|
} catch {
|
|
@@ -4963,26 +4898,19 @@ export class StorageManager {
|
|
|
4963
4898
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
4964
4899
|
const parsed = JSON.parse(raw) as MetaState;
|
|
4965
4900
|
return {
|
|
4966
|
-
extractionCount:
|
|
4967
|
-
typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
|
|
4901
|
+
extractionCount: typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
|
|
4968
4902
|
lastExtractionAt: parsed.lastExtractionAt ?? null,
|
|
4969
4903
|
lastConsolidationAt: parsed.lastConsolidationAt ?? null,
|
|
4970
|
-
totalMemories:
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
typeof parsed.totalEntities === "number" ? parsed.totalEntities : 0,
|
|
4974
|
-
processedExtractionFingerprints: Array.isArray(
|
|
4975
|
-
parsed.processedExtractionFingerprints,
|
|
4976
|
-
)
|
|
4904
|
+
totalMemories: typeof parsed.totalMemories === "number" ? parsed.totalMemories : 0,
|
|
4905
|
+
totalEntities: typeof parsed.totalEntities === "number" ? parsed.totalEntities : 0,
|
|
4906
|
+
processedExtractionFingerprints: Array.isArray(parsed.processedExtractionFingerprints)
|
|
4977
4907
|
? parsed.processedExtractionFingerprints
|
|
4978
4908
|
.filter(
|
|
4979
4909
|
(entry) =>
|
|
4980
4910
|
entry &&
|
|
4981
4911
|
typeof entry === "object" &&
|
|
4982
|
-
typeof (entry as { fingerprint?: unknown }).fingerprint ===
|
|
4983
|
-
|
|
4984
|
-
typeof (entry as { observedAt?: unknown }).observedAt ===
|
|
4985
|
-
"string",
|
|
4912
|
+
typeof (entry as { fingerprint?: unknown }).fingerprint === "string" &&
|
|
4913
|
+
typeof (entry as { observedAt?: unknown }).observedAt === "string"
|
|
4986
4914
|
)
|
|
4987
4915
|
.map((entry) => ({
|
|
4988
4916
|
fingerprint: (entry as { fingerprint: string }).fingerprint,
|
|
@@ -5015,13 +4943,15 @@ export class StorageManager {
|
|
|
5015
4943
|
await this.ensureDirectories();
|
|
5016
4944
|
|
|
5017
4945
|
const nowIso = new Date().toISOString();
|
|
5018
|
-
const payload = events
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
4946
|
+
const payload = events
|
|
4947
|
+
.map((event) => {
|
|
4948
|
+
const normalized: MemoryActionEvent = {
|
|
4949
|
+
...event,
|
|
4950
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso,
|
|
4951
|
+
};
|
|
4952
|
+
return `${JSON.stringify(normalized)}\n`;
|
|
4953
|
+
})
|
|
4954
|
+
.join("");
|
|
5025
4955
|
|
|
5026
4956
|
await this.appendStorageSecureFile(this.memoryActionsPath, payload);
|
|
5027
4957
|
return events.length;
|
|
@@ -5032,32 +4962,26 @@ export class StorageManager {
|
|
|
5032
4962
|
await this.ensureDirectories();
|
|
5033
4963
|
|
|
5034
4964
|
const nowIso = new Date().toISOString();
|
|
5035
|
-
const payload = events
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
4965
|
+
const payload = events
|
|
4966
|
+
.map((event) => {
|
|
4967
|
+
const normalized: MemoryLifecycleEvent = {
|
|
4968
|
+
...event,
|
|
4969
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso,
|
|
4970
|
+
};
|
|
4971
|
+
return `${JSON.stringify(normalized)}\n`;
|
|
4972
|
+
})
|
|
4973
|
+
.join("");
|
|
5042
4974
|
|
|
5043
4975
|
await this.appendStorageSecureFile(this.memoryLifecycleLedgerPath, payload);
|
|
5044
4976
|
return events.length;
|
|
5045
4977
|
}
|
|
5046
4978
|
|
|
5047
|
-
async appendBufferSurpriseEvents(
|
|
5048
|
-
events
|
|
5049
|
-
): Promise<number> {
|
|
5050
|
-
return this.memoryReadStore.appendBufferSurpriseEvents(
|
|
5051
|
-
events,
|
|
5052
|
-
);
|
|
4979
|
+
async appendBufferSurpriseEvents(events: BufferSurpriseEvent[]): Promise<number> {
|
|
4980
|
+
return this.memoryReadStore.appendBufferSurpriseEvents(events);
|
|
5053
4981
|
}
|
|
5054
4982
|
|
|
5055
|
-
async readBufferSurpriseEvents(
|
|
5056
|
-
options
|
|
5057
|
-
): Promise<BufferSurpriseEvent[]> {
|
|
5058
|
-
return this.memoryReadStore.readBufferSurpriseEvents(
|
|
5059
|
-
options,
|
|
5060
|
-
);
|
|
4983
|
+
async readBufferSurpriseEvents(options: { limit?: number } = {}): Promise<BufferSurpriseEvent[]> {
|
|
4984
|
+
return this.memoryReadStore.readBufferSurpriseEvents(options);
|
|
5061
4985
|
}
|
|
5062
4986
|
|
|
5063
4987
|
async appendBehaviorSignals(events: BehaviorSignalEvent[]): Promise<number> {
|
|
@@ -5206,11 +5130,8 @@ export class StorageManager {
|
|
|
5206
5130
|
|
|
5207
5131
|
try {
|
|
5208
5132
|
return await readMemoryActionEventRowsFromLines(
|
|
5209
|
-
readMaybeEncryptedLines(
|
|
5210
|
-
|
|
5211
|
-
() => this.readStorageSecureFile(this.memoryActionsPath),
|
|
5212
|
-
),
|
|
5213
|
-
cappedLimit,
|
|
5133
|
+
readMaybeEncryptedLines(this.memoryActionsPath, () => this.readStorageSecureFile(this.memoryActionsPath)),
|
|
5134
|
+
cappedLimit
|
|
5214
5135
|
);
|
|
5215
5136
|
} catch (err) {
|
|
5216
5137
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
@@ -5222,9 +5143,8 @@ export class StorageManager {
|
|
|
5222
5143
|
async readAllMemoryLifecycleEvents(): Promise<MemoryLifecycleEvent[]> {
|
|
5223
5144
|
try {
|
|
5224
5145
|
const out: MemoryLifecycleEvent[] = [];
|
|
5225
|
-
for await (const line of readMaybeEncryptedLines(
|
|
5226
|
-
this.memoryLifecycleLedgerPath
|
|
5227
|
-
() => this.readStorageSecureFile(this.memoryLifecycleLedgerPath),
|
|
5146
|
+
for await (const line of readMaybeEncryptedLines(this.memoryLifecycleLedgerPath, () =>
|
|
5147
|
+
this.readStorageSecureFile(this.memoryLifecycleLedgerPath)
|
|
5228
5148
|
)) {
|
|
5229
5149
|
const row = line.trim();
|
|
5230
5150
|
if (!row) continue;
|
|
@@ -5289,16 +5209,12 @@ export class StorageManager {
|
|
|
5289
5209
|
}
|
|
5290
5210
|
}
|
|
5291
5211
|
|
|
5292
|
-
async writeCompressionGuidelineOptimizerState(
|
|
5293
|
-
state: CompressionGuidelineOptimizerState,
|
|
5294
|
-
): Promise<void> {
|
|
5212
|
+
async writeCompressionGuidelineOptimizerState(state: CompressionGuidelineOptimizerState): Promise<void> {
|
|
5295
5213
|
await this.ensureDirectories();
|
|
5296
5214
|
await this.writeStorageSecureFile(this.compressionGuidelineStatePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
5297
5215
|
}
|
|
5298
5216
|
|
|
5299
|
-
async writeCompressionGuidelineDraftState(
|
|
5300
|
-
state: CompressionGuidelineOptimizerState,
|
|
5301
|
-
): Promise<void> {
|
|
5217
|
+
async writeCompressionGuidelineDraftState(state: CompressionGuidelineOptimizerState): Promise<void> {
|
|
5302
5218
|
await this.ensureDirectories();
|
|
5303
5219
|
await this.writeStorageSecureFile(this.compressionGuidelineDraftStatePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
5304
5220
|
}
|
|
@@ -5352,11 +5268,9 @@ export class StorageManager {
|
|
|
5352
5268
|
}
|
|
5353
5269
|
|
|
5354
5270
|
private async readCompressionGuidelineStateFile(
|
|
5355
|
-
filePath: string
|
|
5271
|
+
filePath: string
|
|
5356
5272
|
): Promise<CompressionGuidelineOptimizerState | null> {
|
|
5357
|
-
return this.memoryReadStore.readCompressionGuidelineStateFile(
|
|
5358
|
-
filePath,
|
|
5359
|
-
);
|
|
5273
|
+
return this.memoryReadStore.readCompressionGuidelineStateFile(filePath);
|
|
5360
5274
|
}
|
|
5361
5275
|
|
|
5362
5276
|
async writeIdentityAnchor(content: string): Promise<void> {
|
|
@@ -5373,14 +5287,14 @@ export class StorageManager {
|
|
|
5373
5287
|
|
|
5374
5288
|
async readContinuityIncidents(
|
|
5375
5289
|
limit: number = 200,
|
|
5376
|
-
state: "open" | "closed" | "all" = "all"
|
|
5290
|
+
state: "open" | "closed" | "all" = "all"
|
|
5377
5291
|
): Promise<ContinuityIncidentRecord[]> {
|
|
5378
5292
|
return this.identityContinuityStore.readContinuityIncidents(limit, state);
|
|
5379
5293
|
}
|
|
5380
5294
|
|
|
5381
5295
|
async closeContinuityIncident(
|
|
5382
5296
|
id: string,
|
|
5383
|
-
closure: ContinuityIncidentCloseInput
|
|
5297
|
+
closure: ContinuityIncidentCloseInput
|
|
5384
5298
|
): Promise<ContinuityIncidentRecord | null> {
|
|
5385
5299
|
return this.identityContinuityStore.closeContinuityIncident(id, closure);
|
|
5386
5300
|
}
|
|
@@ -5411,7 +5325,7 @@ export class StorageManager {
|
|
|
5411
5325
|
|
|
5412
5326
|
async reviewIdentityImprovementLoop(
|
|
5413
5327
|
id: string,
|
|
5414
|
-
input: ContinuityLoopReviewInput
|
|
5328
|
+
input: ContinuityLoopReviewInput
|
|
5415
5329
|
): Promise<ContinuityImprovementLoop | null> {
|
|
5416
5330
|
return this.identityContinuityStore.reviewIdentityImprovementLoop(id, input);
|
|
5417
5331
|
}
|
|
@@ -5426,11 +5340,7 @@ export class StorageManager {
|
|
|
5426
5340
|
return `${prefix}-${ts}-${rand}`;
|
|
5427
5341
|
}
|
|
5428
5342
|
|
|
5429
|
-
async writeQuestion(
|
|
5430
|
-
question: string,
|
|
5431
|
-
context: string,
|
|
5432
|
-
priority: number,
|
|
5433
|
-
): Promise<string> {
|
|
5343
|
+
async writeQuestion(question: string, context: string, priority: number): Promise<string> {
|
|
5434
5344
|
await mkdir(this.questionsDir, { recursive: true });
|
|
5435
5345
|
|
|
5436
5346
|
const id = this.generateId("q");
|
|
@@ -5441,7 +5351,9 @@ export class StorageManager {
|
|
|
5441
5351
|
resolved: false,
|
|
5442
5352
|
};
|
|
5443
5353
|
|
|
5444
|
-
const content = `---\n${Object.entries(frontmatter)
|
|
5354
|
+
const content = `---\n${Object.entries(frontmatter)
|
|
5355
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
5356
|
+
.join("\n")}\n---\n\n${question}\n\n**Context:** ${context}\n`;
|
|
5445
5357
|
|
|
5446
5358
|
const filePath = path.join(this.questionsDir, `${id}.md`);
|
|
5447
5359
|
await this.writeStorageSecureFile(filePath, content);
|
|
@@ -5451,9 +5363,7 @@ export class StorageManager {
|
|
|
5451
5363
|
return id;
|
|
5452
5364
|
}
|
|
5453
5365
|
|
|
5454
|
-
async readQuestions(
|
|
5455
|
-
opts?: { unresolvedOnly?: boolean },
|
|
5456
|
-
): Promise<
|
|
5366
|
+
async readQuestions(opts?: { unresolvedOnly?: boolean }): Promise<
|
|
5457
5367
|
Array<{
|
|
5458
5368
|
id: string;
|
|
5459
5369
|
question: string;
|
|
@@ -5474,7 +5384,7 @@ export class StorageManager {
|
|
|
5474
5384
|
|
|
5475
5385
|
private parseQuestionFile(
|
|
5476
5386
|
raw: string,
|
|
5477
|
-
filePath: string
|
|
5387
|
+
filePath: string
|
|
5478
5388
|
): {
|
|
5479
5389
|
id: string;
|
|
5480
5390
|
question: string;
|
|
@@ -5491,34 +5401,21 @@ export class StorageManager {
|
|
|
5491
5401
|
const body = match[2].trim();
|
|
5492
5402
|
|
|
5493
5403
|
// Parse frontmatter
|
|
5494
|
-
const id =
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
const
|
|
5498
|
-
this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
5499
|
-
const priority = parseFloat(
|
|
5500
|
-
this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5",
|
|
5501
|
-
);
|
|
5502
|
-
const resolved =
|
|
5503
|
-
this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
|
|
5404
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path.basename(filePath, ".md");
|
|
5405
|
+
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
5406
|
+
const priority = parseFloat(this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5");
|
|
5407
|
+
const resolved = this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
|
|
5504
5408
|
|
|
5505
5409
|
// Extract question and context from body
|
|
5506
5410
|
const contextMatch = body.match(/\*\*Context:\*\*\s*(.*)/);
|
|
5507
|
-
const question = contextMatch
|
|
5508
|
-
? body.slice(0, contextMatch.index).trim()
|
|
5509
|
-
: body;
|
|
5411
|
+
const question = contextMatch ? body.slice(0, contextMatch.index).trim() : body;
|
|
5510
5412
|
const context = contextMatch ? contextMatch[1].trim() : "";
|
|
5511
5413
|
|
|
5512
5414
|
return { id, question, context, priority, resolved, created, filePath };
|
|
5513
5415
|
}
|
|
5514
5416
|
|
|
5515
|
-
private extractFrontmatterValue(
|
|
5516
|
-
frontmatter
|
|
5517
|
-
key: string,
|
|
5518
|
-
): string | null {
|
|
5519
|
-
const match = frontmatter.match(
|
|
5520
|
-
new RegExp(`^${key}:\\s*"?([^"\\n]*)"?`, "m"),
|
|
5521
|
-
);
|
|
5417
|
+
private extractFrontmatterValue(frontmatter: string, key: string): string | null {
|
|
5418
|
+
const match = frontmatter.match(new RegExp(`^${key}:\\s*"?([^"\\n]*)"?`, "m"));
|
|
5522
5419
|
return match ? match[1] : null;
|
|
5523
5420
|
}
|
|
5524
5421
|
|
|
@@ -5529,10 +5426,7 @@ export class StorageManager {
|
|
|
5529
5426
|
|
|
5530
5427
|
let raw = await this.readStorageSecureFile(q.filePath);
|
|
5531
5428
|
raw = raw.replace(/resolved: false/, "resolved: true");
|
|
5532
|
-
raw = raw.replace(
|
|
5533
|
-
/---\n\n/,
|
|
5534
|
-
`resolvedAt: "${new Date().toISOString()}"\n---\n\n`,
|
|
5535
|
-
);
|
|
5429
|
+
raw = raw.replace(/---\n\n/, `resolvedAt: "${new Date().toISOString()}"\n---\n\n`);
|
|
5536
5430
|
await this.writeStorageSecureFile(q.filePath, raw);
|
|
5537
5431
|
this.invalidateQuestionsCache();
|
|
5538
5432
|
log.debug(`resolved question ${id}`);
|
|
@@ -5566,7 +5460,7 @@ export class StorageManager {
|
|
|
5566
5460
|
async appendToIdentity(
|
|
5567
5461
|
workspaceDir: string,
|
|
5568
5462
|
reflection: string,
|
|
5569
|
-
opts?: { hygiene?: FileHygieneConfig; namespace?: string }
|
|
5463
|
+
opts?: { hygiene?: FileHygieneConfig; namespace?: string }
|
|
5570
5464
|
): Promise<void> {
|
|
5571
5465
|
const identityPath = this.identityFilePath(workspaceDir, opts?.namespace);
|
|
5572
5466
|
|
|
@@ -5597,14 +5491,14 @@ export class StorageManager {
|
|
|
5597
5491
|
});
|
|
5598
5492
|
await writeFile(identityPath, newContent, "utf-8");
|
|
5599
5493
|
existing = newContent;
|
|
5600
|
-
log.info(
|
|
5601
|
-
`rotated IDENTITY.md to archive (size=${existing.length} chars, maxBytes=${maxBytes})`,
|
|
5602
|
-
);
|
|
5494
|
+
log.info(`rotated IDENTITY.md to archive (size=${existing.length} chars, maxBytes=${maxBytes})`);
|
|
5603
5495
|
}
|
|
5604
5496
|
} else {
|
|
5605
5497
|
// Legacy behavior: skip if file is too large
|
|
5606
5498
|
if (existing.length > StorageManager.IDENTITY_MAX_BYTES) {
|
|
5607
|
-
log.debug(
|
|
5499
|
+
log.debug(
|
|
5500
|
+
`IDENTITY.md is ${existing.length} chars (limit ${StorageManager.IDENTITY_MAX_BYTES}); skipping reflection`
|
|
5501
|
+
);
|
|
5608
5502
|
return;
|
|
5609
5503
|
}
|
|
5610
5504
|
}
|
|
@@ -5618,7 +5512,9 @@ export class StorageManager {
|
|
|
5618
5512
|
const lastTimestamp = allMatches[allMatches.length - 1][1];
|
|
5619
5513
|
const elapsed = Date.now() - new Date(lastTimestamp).getTime();
|
|
5620
5514
|
if (elapsed < StorageManager.REFLECTION_COOLDOWN_MS) {
|
|
5621
|
-
log.debug(
|
|
5515
|
+
log.debug(
|
|
5516
|
+
`reflection cooldown: ${Math.round(elapsed / 1000)}s since last (need ${StorageManager.REFLECTION_COOLDOWN_MS / 1000}s)`
|
|
5517
|
+
);
|
|
5622
5518
|
return;
|
|
5623
5519
|
}
|
|
5624
5520
|
}
|
|
@@ -5656,7 +5552,7 @@ export class StorageManager {
|
|
|
5656
5552
|
|
|
5657
5553
|
if (existing.length > StorageManager.IDENTITY_MAX_BYTES) {
|
|
5658
5554
|
log.debug(
|
|
5659
|
-
`identity/reflections.md is ${existing.length} chars (limit ${StorageManager.IDENTITY_MAX_BYTES}); skipping reflection
|
|
5555
|
+
`identity/reflections.md is ${existing.length} chars (limit ${StorageManager.IDENTITY_MAX_BYTES}); skipping reflection`
|
|
5660
5556
|
);
|
|
5661
5557
|
return;
|
|
5662
5558
|
}
|
|
@@ -5667,7 +5563,7 @@ export class StorageManager {
|
|
|
5667
5563
|
const elapsed = Date.now() - new Date(lastTimestamp).getTime();
|
|
5668
5564
|
if (elapsed < StorageManager.REFLECTION_COOLDOWN_MS) {
|
|
5669
5565
|
log.debug(
|
|
5670
|
-
`reflection cooldown: ${Math.round(elapsed / 1000)}s since last (need ${StorageManager.REFLECTION_COOLDOWN_MS / 1000}s)
|
|
5566
|
+
`reflection cooldown: ${Math.round(elapsed / 1000)}s since last (need ${StorageManager.REFLECTION_COOLDOWN_MS / 1000}s)`
|
|
5671
5567
|
);
|
|
5672
5568
|
return;
|
|
5673
5569
|
}
|
|
@@ -5702,9 +5598,7 @@ export class StorageManager {
|
|
|
5702
5598
|
}
|
|
5703
5599
|
|
|
5704
5600
|
// Dedupe by target+label
|
|
5705
|
-
const exists = entity.relationships.some(
|
|
5706
|
-
(r) => r.target === rel.target && r.label === rel.label,
|
|
5707
|
-
);
|
|
5601
|
+
const exists = entity.relationships.some((r) => r.target === rel.target && r.label === rel.label);
|
|
5708
5602
|
if (exists) return;
|
|
5709
5603
|
|
|
5710
5604
|
entity.relationships.push(rel);
|
|
@@ -5717,11 +5611,7 @@ export class StorageManager {
|
|
|
5717
5611
|
* Add an activity entry to an entity file.
|
|
5718
5612
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
5719
5613
|
*/
|
|
5720
|
-
async addEntityActivity(
|
|
5721
|
-
name: string,
|
|
5722
|
-
entry: EntityActivityEntry,
|
|
5723
|
-
maxEntries: number,
|
|
5724
|
-
): Promise<void> {
|
|
5614
|
+
async addEntityActivity(name: string, entry: EntityActivityEntry, maxEntries: number): Promise<void> {
|
|
5725
5615
|
const filePath = path.join(this.entitiesDir, `${name}.md`);
|
|
5726
5616
|
let entity: EntityFile;
|
|
5727
5617
|
try {
|
|
@@ -5776,13 +5666,9 @@ export class StorageManager {
|
|
|
5776
5666
|
synthesisTimelineCount?: number;
|
|
5777
5667
|
updatedAt?: string;
|
|
5778
5668
|
incrementVersion?: boolean;
|
|
5779
|
-
} = {}
|
|
5669
|
+
} = {}
|
|
5780
5670
|
): Promise<void> {
|
|
5781
|
-
return this.entityStore.updateEntitySynthesis(
|
|
5782
|
-
name,
|
|
5783
|
-
synthesis,
|
|
5784
|
-
options,
|
|
5785
|
-
);
|
|
5671
|
+
return this.entityStore.updateEntitySynthesis(name, synthesis, options);
|
|
5786
5672
|
}
|
|
5787
5673
|
|
|
5788
5674
|
/**
|
|
@@ -5831,7 +5717,7 @@ export class StorageManager {
|
|
|
5831
5717
|
entityName,
|
|
5832
5718
|
entity: parseEntityFile(raw, this.entitySchemas),
|
|
5833
5719
|
};
|
|
5834
|
-
})
|
|
5720
|
+
})
|
|
5835
5721
|
);
|
|
5836
5722
|
const staleEntityNames = entityQueueEntries
|
|
5837
5723
|
.filter((entry): entry is { entityName: string; entity: EntityFile } => entry !== null)
|
|
@@ -5852,8 +5738,8 @@ export class StorageManager {
|
|
|
5852
5738
|
entityNames: staleEntityNames,
|
|
5853
5739
|
},
|
|
5854
5740
|
null,
|
|
5855
|
-
2
|
|
5856
|
-
) + "\n"
|
|
5741
|
+
2
|
|
5742
|
+
) + "\n"
|
|
5857
5743
|
);
|
|
5858
5744
|
return staleEntityNames;
|
|
5859
5745
|
}
|
|
@@ -5873,8 +5759,8 @@ export class StorageManager {
|
|
|
5873
5759
|
entityNames: nextQueue,
|
|
5874
5760
|
},
|
|
5875
5761
|
null,
|
|
5876
|
-
2
|
|
5877
|
-
) + "\n"
|
|
5762
|
+
2
|
|
5763
|
+
) + "\n"
|
|
5878
5764
|
);
|
|
5879
5765
|
}
|
|
5880
5766
|
|
|
@@ -5907,8 +5793,7 @@ export class StorageManager {
|
|
|
5907
5793
|
// ---------------------------------------------------------------------------
|
|
5908
5794
|
|
|
5909
5795
|
async readAllEntityFiles(): Promise<EntityFile[]> {
|
|
5910
|
-
return this.entityStore.readAllEntityFiles(
|
|
5911
|
-
);
|
|
5796
|
+
return this.entityStore.readAllEntityFiles();
|
|
5912
5797
|
}
|
|
5913
5798
|
|
|
5914
5799
|
/**
|
|
@@ -5943,23 +5828,14 @@ export class StorageManager {
|
|
|
5943
5828
|
// Relationship density: min(relationships.length / 8, 1.0)
|
|
5944
5829
|
const relDensity = Math.min(entity.relationships.length / 8, 1.0);
|
|
5945
5830
|
|
|
5946
|
-
return
|
|
5947
|
-
recency * 0.40 +
|
|
5948
|
-
frequency * 0.25 +
|
|
5949
|
-
activityScore * 0.15 +
|
|
5950
|
-
typePriority * 0.10 +
|
|
5951
|
-
relDensity * 0.10
|
|
5952
|
-
);
|
|
5831
|
+
return recency * 0.4 + frequency * 0.25 + activityScore * 0.15 + typePriority * 0.1 + relDensity * 0.1;
|
|
5953
5832
|
}
|
|
5954
5833
|
|
|
5955
5834
|
async buildKnowledgeIndex(
|
|
5956
5835
|
config: PluginConfig,
|
|
5957
|
-
overrides?: { maxEntities?: number; maxChars?: number }
|
|
5836
|
+
overrides?: { maxEntities?: number; maxChars?: number }
|
|
5958
5837
|
): Promise<{ result: string; cached: boolean }> {
|
|
5959
|
-
return this.entityStore.buildKnowledgeIndex(
|
|
5960
|
-
config,
|
|
5961
|
-
overrides,
|
|
5962
|
-
);
|
|
5838
|
+
return this.entityStore.buildKnowledgeIndex(config, overrides);
|
|
5963
5839
|
}
|
|
5964
5840
|
|
|
5965
5841
|
/** Invalidate the Knowledge Index cache (call after entity mutations). */
|
|
@@ -5975,8 +5851,7 @@ export class StorageManager {
|
|
|
5975
5851
|
private static readonly PROFILE_MAX_LINES = 300;
|
|
5976
5852
|
|
|
5977
5853
|
async mergeFragmentedEntities(): Promise<number> {
|
|
5978
|
-
return this.entityStore.mergeFragmentedEntities(
|
|
5979
|
-
);
|
|
5854
|
+
return this.entityStore.mergeFragmentedEntities();
|
|
5980
5855
|
}
|
|
5981
5856
|
|
|
5982
5857
|
async cleanExpiredCommitments(decayDays: number): Promise<MemoryFile[]> {
|
|
@@ -5988,16 +5863,15 @@ export class StorageManager {
|
|
|
5988
5863
|
if (m.frontmatter.category !== "commitment") continue;
|
|
5989
5864
|
// Only decay commitments that have been marked as resolved/expired
|
|
5990
5865
|
// (indicated by tags containing "fulfilled" or "expired")
|
|
5991
|
-
const isResolved = m.frontmatter.tags.some(
|
|
5992
|
-
(t) => t === "fulfilled" || t === "expired",
|
|
5993
|
-
);
|
|
5866
|
+
const isResolved = m.frontmatter.tags.some((t) => t === "fulfilled" || t === "expired");
|
|
5994
5867
|
if (!isResolved) continue;
|
|
5995
5868
|
|
|
5996
5869
|
const updatedAt = new Date(m.frontmatter.updated).getTime();
|
|
5997
5870
|
if (updatedAt < cutoff) {
|
|
5998
5871
|
// Remove the file
|
|
5999
5872
|
try {
|
|
6000
|
-
await unlink(m.path);
|
|
5873
|
+
await unlink(m.path);
|
|
5874
|
+
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
6001
5875
|
deleted.push(m);
|
|
6002
5876
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
6003
5877
|
} catch {
|
|
@@ -6088,7 +5962,8 @@ export class StorageManager {
|
|
|
6088
5962
|
|
|
6089
5963
|
async getProjectedMemoryState(id: string): Promise<MemoryProjectionCurrentState | null> {
|
|
6090
5964
|
const projected = readProjectedMemoryState(this.baseDir, id);
|
|
6091
|
-
if (projected) return projected;
|
|
5965
|
+
if (projected) return projected;
|
|
5966
|
+
warnProjectionFallback(this.baseDir, "getProjectedMemoryState");
|
|
6092
5967
|
const active = await this.getMemoryById(id);
|
|
6093
5968
|
if (active) return this.toProjectedCurrentState(active, "active");
|
|
6094
5969
|
|
|
@@ -6098,21 +5973,18 @@ export class StorageManager {
|
|
|
6098
5973
|
return this.toProjectedCurrentState(archived, "archived");
|
|
6099
5974
|
}
|
|
6100
5975
|
|
|
6101
|
-
async browseProjectedMemories(
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
5976
|
+
async browseProjectedMemories(options: ProjectedMemoryBrowseOptions): Promise<ProjectedMemoryBrowsePage | null> {
|
|
5977
|
+
return (
|
|
5978
|
+
readProjectedMemoryBrowse(this.baseDir, options) ??
|
|
5979
|
+
warnProjectionFallback(this.baseDir, "browseProjectedMemories")
|
|
5980
|
+
);
|
|
6106
5981
|
}
|
|
6107
5982
|
|
|
6108
5983
|
async getProjectedGovernanceRecord(): Promise<ReturnType<typeof readProjectedGovernanceRecord>> {
|
|
6109
5984
|
return readProjectedGovernanceRecord(this.baseDir);
|
|
6110
5985
|
}
|
|
6111
5986
|
|
|
6112
|
-
private toProjectedCurrentState(
|
|
6113
|
-
memory: MemoryFile,
|
|
6114
|
-
fallbackStatus: MemoryStatus,
|
|
6115
|
-
): MemoryProjectionCurrentState {
|
|
5987
|
+
private toProjectedCurrentState(memory: MemoryFile, fallbackStatus: MemoryStatus): MemoryProjectionCurrentState {
|
|
6116
5988
|
const pathRel = toMemoryPathRel(this.baseDir, memory.path);
|
|
6117
5989
|
return {
|
|
6118
5990
|
memoryId: memory.frontmatter.id,
|
|
@@ -6142,7 +6014,8 @@ export class StorageManager {
|
|
|
6142
6014
|
if (cappedLimit === 0) return [];
|
|
6143
6015
|
|
|
6144
6016
|
const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
|
|
6145
|
-
if (projected && projected.length > 0) return projected;
|
|
6017
|
+
if (projected && projected.length > 0) return projected;
|
|
6018
|
+
warnProjectionFallback(this.baseDir, "getMemoryTimeline");
|
|
6146
6019
|
const events = await this.readAllMemoryLifecycleEvents();
|
|
6147
6020
|
return events.filter((event) => event.memoryId === memoryId).slice(-cappedLimit);
|
|
6148
6021
|
}
|
|
@@ -6193,7 +6066,8 @@ export class StorageManager {
|
|
|
6193
6066
|
invalidAt?: string;
|
|
6194
6067
|
observedAt?: string;
|
|
6195
6068
|
eventTimeSource?: "extracted" | "assumed";
|
|
6196
|
-
|
|
6069
|
+
sourceConnector?: string;
|
|
6070
|
+
} = {}
|
|
6197
6071
|
): Promise<string> {
|
|
6198
6072
|
await this.ensureDirectories();
|
|
6199
6073
|
const now = new Date();
|
|
@@ -6232,6 +6106,7 @@ export class StorageManager {
|
|
|
6232
6106
|
...(options.faithfulness ? { faithfulness: options.faithfulness } : {}),
|
|
6233
6107
|
...(options.sources ? { sources: options.sources } : {}),
|
|
6234
6108
|
...(options.provenance ? { provenance: options.provenance } : {}),
|
|
6109
|
+
...(options.sourceConnector ? { sourceConnector: options.sourceConnector } : {}),
|
|
6235
6110
|
};
|
|
6236
6111
|
|
|
6237
6112
|
const sanitized = sanitizeMemoryContent(content);
|
|
@@ -6266,11 +6141,7 @@ export class StorageManager {
|
|
|
6266
6141
|
* Mark a memory as superseded by another.
|
|
6267
6142
|
* Updates the old memory's status and adds the supersededBy link.
|
|
6268
6143
|
*/
|
|
6269
|
-
async supersedeMemory(
|
|
6270
|
-
oldMemoryId: string,
|
|
6271
|
-
newMemoryId: string,
|
|
6272
|
-
reason: string,
|
|
6273
|
-
): Promise<boolean> {
|
|
6144
|
+
async supersedeMemory(oldMemoryId: string, newMemoryId: string, reason: string): Promise<boolean> {
|
|
6274
6145
|
const memories = await this.readAllMemories();
|
|
6275
6146
|
const oldMemory = memories.find((m) => m.frontmatter.id === oldMemoryId);
|
|
6276
6147
|
if (!oldMemory) return false;
|
|
@@ -6317,7 +6188,12 @@ export class StorageManager {
|
|
|
6317
6188
|
entityRef: oldMemory.frontmatter.entityRef,
|
|
6318
6189
|
structuredAttributes: oldMemory.frontmatter.structuredAttributes,
|
|
6319
6190
|
},
|
|
6320
|
-
{
|
|
6191
|
+
{
|
|
6192
|
+
reason: "contradiction_resolution",
|
|
6193
|
+
createdBy: "contradiction_resolution",
|
|
6194
|
+
createdAt: now,
|
|
6195
|
+
supersessionKeysForFact,
|
|
6196
|
+
}
|
|
6321
6197
|
)) {
|
|
6322
6198
|
await this.appendTombstone(input);
|
|
6323
6199
|
}
|
|
@@ -6435,7 +6311,10 @@ export class StorageManager {
|
|
|
6435
6311
|
async saveTopics(topics: TopicScore[]): Promise<void> {
|
|
6436
6312
|
const metaPath = path.join(this.stateDir, "topics.json");
|
|
6437
6313
|
await mkdir(this.stateDir, { recursive: true });
|
|
6438
|
-
await this.writeStorageSecureFile(
|
|
6314
|
+
await this.writeStorageSecureFile(
|
|
6315
|
+
metaPath,
|
|
6316
|
+
JSON.stringify({ topics, updatedAt: new Date().toISOString() }, null, 2)
|
|
6317
|
+
);
|
|
6439
6318
|
log.debug(`saved ${topics.length} topic scores`);
|
|
6440
6319
|
}
|
|
6441
6320
|
|
|
@@ -6460,7 +6339,7 @@ export class StorageManager {
|
|
|
6460
6339
|
async addLinksToMemory(
|
|
6461
6340
|
memoryId: string,
|
|
6462
6341
|
links: MemoryLink[],
|
|
6463
|
-
lifecycle?: MemoryLifecycleEventWriteOptions
|
|
6342
|
+
lifecycle?: MemoryLifecycleEventWriteOptions
|
|
6464
6343
|
): Promise<boolean> {
|
|
6465
6344
|
const memories = await this.readAllMemories();
|
|
6466
6345
|
const memory = memories.find((m) => m.frontmatter.id === memoryId);
|
|
@@ -6483,7 +6362,7 @@ export class StorageManager {
|
|
|
6483
6362
|
links: mergedLinks,
|
|
6484
6363
|
updated: new Date().toISOString(),
|
|
6485
6364
|
},
|
|
6486
|
-
lifecycle
|
|
6365
|
+
lifecycle
|
|
6487
6366
|
);
|
|
6488
6367
|
log.debug(`added ${links.length} links to memory ${memoryId}`);
|
|
6489
6368
|
return true;
|
|
@@ -6493,10 +6372,7 @@ export class StorageManager {
|
|
|
6493
6372
|
}
|
|
6494
6373
|
}
|
|
6495
6374
|
|
|
6496
|
-
private summarizeLifecycleState(
|
|
6497
|
-
frontmatter: MemoryFrontmatter,
|
|
6498
|
-
filePath: string,
|
|
6499
|
-
): MemoryLifecycleStateSummary {
|
|
6375
|
+
private summarizeLifecycleState(frontmatter: MemoryFrontmatter, filePath: string): MemoryLifecycleStateSummary {
|
|
6500
6376
|
return {
|
|
6501
6377
|
category: frontmatter.category,
|
|
6502
6378
|
path: filePath,
|
|
@@ -6505,10 +6381,7 @@ export class StorageManager {
|
|
|
6505
6381
|
};
|
|
6506
6382
|
}
|
|
6507
6383
|
|
|
6508
|
-
private frontmatterPatchEventType(
|
|
6509
|
-
before: MemoryFrontmatter,
|
|
6510
|
-
after: MemoryFrontmatter,
|
|
6511
|
-
): MemoryLifecycleEventType {
|
|
6384
|
+
private frontmatterPatchEventType(before: MemoryFrontmatter, after: MemoryFrontmatter): MemoryLifecycleEventType {
|
|
6512
6385
|
const beforeStatus = before.status ?? "active";
|
|
6513
6386
|
const afterStatus = after.status ?? "active";
|
|
6514
6387
|
if (beforeStatus !== "archived" && afterStatus === "archived") return "archived";
|
|
@@ -6522,7 +6395,7 @@ export class StorageManager {
|
|
|
6522
6395
|
|
|
6523
6396
|
private async appendGeneratedMemoryLifecycleEvent(
|
|
6524
6397
|
input: Omit<MemoryLifecycleEvent, "eventId" | "ruleVersion">,
|
|
6525
|
-
ruleVersion = "memory-lifecycle-ledger.v1"
|
|
6398
|
+
ruleVersion = "memory-lifecycle-ledger.v1"
|
|
6526
6399
|
): Promise<void> {
|
|
6527
6400
|
await this.appendMemoryLifecycleEvents([
|
|
6528
6401
|
{
|
|
@@ -6536,7 +6409,7 @@ export class StorageManager {
|
|
|
6536
6409
|
private async appendGeneratedMemoryLifecycleEventFailOpen(
|
|
6537
6410
|
operation: string,
|
|
6538
6411
|
input: Omit<MemoryLifecycleEvent, "eventId" | "ruleVersion">,
|
|
6539
|
-
ruleVersion?: string
|
|
6412
|
+
ruleVersion?: string
|
|
6540
6413
|
): Promise<void> {
|
|
6541
6414
|
try {
|
|
6542
6415
|
await this.appendGeneratedMemoryLifecycleEvent(input, ruleVersion);
|