@remnic/core 9.3.754 → 9.3.756
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 +5 -5
- package/dist/access-admin-ops-surface.js +34 -34
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +35 -35
- package/dist/access-cli.js +83 -83
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +44 -44
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +34 -34
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +41 -41
- package/dist/access-observe-write-surface.d.ts +225 -0
- package/dist/access-observe-write-surface.js +128 -0
- package/dist/access-operations-batch.js +38 -38
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +40 -40
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +34 -34
- package/dist/access-schema.d.ts +10 -10
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DKRV1zxh.d.ts → access-service-CJwcDoQp.d.ts} +7 -8
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +34 -34
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-G4YKBPOU.js → auto-sync-RZO5ZPHR.js} +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-FFWJ4X4A.js} +2 -2
- package/dist/{catalog-BEmNVRN0.d.ts → catalog-DaKAFbSJ.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +15 -15
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-M5QKGHCR.js → chunk-3I2IASNK.js} +2 -2
- package/dist/{chunk-JBO6UBIE.js → chunk-4AC57RCM.js} +4 -4
- package/dist/{chunk-3DOD6ICJ.js → chunk-4I45UZ4V.js} +5 -5
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-CXCZ4LAI.js → chunk-5VWRYFQE.js} +7 -7
- package/dist/{chunk-TXALJRYO.js → chunk-5ZKT7WTR.js} +7 -7
- package/dist/{chunk-PYVZKEZZ.js → chunk-6DA5BBLS.js} +120 -120
- package/dist/{chunk-WS6GLJ7P.js → chunk-6DXXIXED.js} +4 -4
- package/dist/{chunk-THZ4WR5L.js → chunk-7APVFQ34.js} +1182 -1009
- package/dist/chunk-7APVFQ34.js.map +1 -0
- package/dist/{chunk-OH66EBCS.js → chunk-7CFGXWYX.js} +2 -2
- package/dist/{chunk-TJATYB3F.js → chunk-A6XCHI53.js} +5 -5
- package/dist/{chunk-2MHCWXX6.js → chunk-BTH6MQJT.js} +7 -7
- package/dist/{chunk-G7LCG56O.js → chunk-CXW6Y7SI.js} +4 -4
- package/dist/{chunk-L4AS7TIG.js → chunk-DWDDQLSZ.js} +2300 -2239
- package/dist/chunk-DWDDQLSZ.js.map +1 -0
- package/dist/{chunk-DS4QM6I7.js → chunk-EEKDRAWX.js} +4 -4
- package/dist/{chunk-GAH5755F.js → chunk-EF3VFQC4.js} +2 -2
- package/dist/{chunk-437S3G37.js → chunk-EJJQDP3H.js} +1 -1
- package/dist/{chunk-V4C4SFZB.js → chunk-EVVLGYHR.js} +3 -3
- package/dist/{chunk-ZJMSTFOR.js → chunk-GBXP62CL.js} +10 -10
- package/dist/{chunk-7AL5WNPW.js → chunk-GG6G2RYR.js} +9 -9
- package/dist/{chunk-BERWPOXU.js → chunk-H2T4OJNI.js} +4 -4
- package/dist/{chunk-2MR3MFQB.js → chunk-JTYM4HPA.js} +2 -2
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/{chunk-GPFXPC2V.js → chunk-NG2NHB4C.js} +7 -7
- package/dist/{chunk-COZEVVXZ.js → chunk-NLXKSM46.js} +2 -2
- package/dist/{chunk-VSU2NZGC.js → chunk-OLAVQS6S.js} +6 -6
- package/dist/{chunk-ER3CCWK2.js → chunk-ORAK3LNZ.js} +2 -2
- package/dist/{chunk-WUVUTOUV.js → chunk-PBZQVVLG.js} +5 -5
- package/dist/{chunk-PJQMISPD.js → chunk-PPAEJXVF.js} +5 -5
- package/dist/{chunk-KURUULFF.js → chunk-PRX77NPC.js} +5 -5
- package/dist/{chunk-24463PIV.js → chunk-PS572XYO.js} +2 -2
- package/dist/{chunk-U2SMR57B.js → chunk-REZ4TUD2.js} +10 -10
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-V5UV7BOA.js → chunk-SQNED75R.js} +1 -1
- package/dist/{chunk-FLVL4L3L.js → chunk-TCKP4EHY.js} +2 -2
- package/dist/{chunk-YRBXBHU7.js → chunk-TXV3I2O6.js} +7 -7
- package/dist/{chunk-CJ276MDI.js → chunk-UNV6ZGTS.js} +2 -2
- package/dist/{chunk-2BFLDH5F.js → chunk-WEPWY4HB.js} +2 -2
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-7CAJLUNW.js → chunk-Y4TAFMUE.js} +6 -6
- package/dist/{chunk-TIFF32QK.js → chunk-YMGQXBPS.js} +2 -2
- package/dist/{chunk-T422SYM6.js → chunk-ZUDM75KG.js} +5 -5
- package/dist/{chunk-4DVYRPLL.js → chunk-ZUHD6XZZ.js} +43 -43
- package/dist/{chunk-BQ6J6P3K.js → chunk-ZW5SZ3TH.js} +12 -12
- package/dist/{cli-D-xw8Sp1.d.ts → cli-cZyANcmY.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +59 -59
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +8 -8
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +8 -8
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -9
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-2SODRNUF.js → contradiction-scan-3FJYWS3G.js} +3 -3
- package/dist/contradiction-scan-3FJYWS3G.js.map +1 -0
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +8 -8
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +2 -2
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +126 -126
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +2 -2
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +18 -18
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +9 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +24 -24
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +12 -12
- package/dist/{orchestrator-D7ZGOQLX.d.ts → orchestrator-Xm6sK5jw.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +82 -82
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +8 -8
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-7Sndms2M.d.ts → semantic-consolidation-bfXJNKVf.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +11 -11
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +8 -8
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +3 -3
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +69 -97
- package/dist/storage.js +22 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +2 -2
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DTEFxFuL.d.ts → types-D1iBqhJA.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +8 -8
- package/package.json +2 -2
- package/src/access-observe-write-surface.ts +1007 -0
- package/src/access-service.ts +48 -794
- package/src/orchestration/self-deps.ts +9 -1
- package/src/storage/entity-store.ts +674 -0
- package/src/storage/memory-read-store.ts +756 -0
- package/src/storage.ts +111 -1191
- package/dist/chunk-L4AS7TIG.js.map +0 -1
- package/dist/chunk-THZ4WR5L.js.map +0 -1
- /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-observe-write-surface.js.map} +0 -0
- /package/dist/{auto-sync-G4YKBPOU.js.map → auto-sync-RZO5ZPHR.js.map} +0 -0
- /package/dist/{contradiction-scan-2SODRNUF.js.map → capsule-crypto-FFWJ4X4A.js.map} +0 -0
- /package/dist/{chunk-M5QKGHCR.js.map → chunk-3I2IASNK.js.map} +0 -0
- /package/dist/{chunk-JBO6UBIE.js.map → chunk-4AC57RCM.js.map} +0 -0
- /package/dist/{chunk-3DOD6ICJ.js.map → chunk-4I45UZ4V.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-CXCZ4LAI.js.map → chunk-5VWRYFQE.js.map} +0 -0
- /package/dist/{chunk-TXALJRYO.js.map → chunk-5ZKT7WTR.js.map} +0 -0
- /package/dist/{chunk-PYVZKEZZ.js.map → chunk-6DA5BBLS.js.map} +0 -0
- /package/dist/{chunk-WS6GLJ7P.js.map → chunk-6DXXIXED.js.map} +0 -0
- /package/dist/{chunk-OH66EBCS.js.map → chunk-7CFGXWYX.js.map} +0 -0
- /package/dist/{chunk-TJATYB3F.js.map → chunk-A6XCHI53.js.map} +0 -0
- /package/dist/{chunk-2MHCWXX6.js.map → chunk-BTH6MQJT.js.map} +0 -0
- /package/dist/{chunk-G7LCG56O.js.map → chunk-CXW6Y7SI.js.map} +0 -0
- /package/dist/{chunk-DS4QM6I7.js.map → chunk-EEKDRAWX.js.map} +0 -0
- /package/dist/{chunk-GAH5755F.js.map → chunk-EF3VFQC4.js.map} +0 -0
- /package/dist/{chunk-437S3G37.js.map → chunk-EJJQDP3H.js.map} +0 -0
- /package/dist/{chunk-V4C4SFZB.js.map → chunk-EVVLGYHR.js.map} +0 -0
- /package/dist/{chunk-ZJMSTFOR.js.map → chunk-GBXP62CL.js.map} +0 -0
- /package/dist/{chunk-7AL5WNPW.js.map → chunk-GG6G2RYR.js.map} +0 -0
- /package/dist/{chunk-BERWPOXU.js.map → chunk-H2T4OJNI.js.map} +0 -0
- /package/dist/{chunk-2MR3MFQB.js.map → chunk-JTYM4HPA.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-GPFXPC2V.js.map → chunk-NG2NHB4C.js.map} +0 -0
- /package/dist/{chunk-COZEVVXZ.js.map → chunk-NLXKSM46.js.map} +0 -0
- /package/dist/{chunk-VSU2NZGC.js.map → chunk-OLAVQS6S.js.map} +0 -0
- /package/dist/{chunk-ER3CCWK2.js.map → chunk-ORAK3LNZ.js.map} +0 -0
- /package/dist/{chunk-WUVUTOUV.js.map → chunk-PBZQVVLG.js.map} +0 -0
- /package/dist/{chunk-PJQMISPD.js.map → chunk-PPAEJXVF.js.map} +0 -0
- /package/dist/{chunk-KURUULFF.js.map → chunk-PRX77NPC.js.map} +0 -0
- /package/dist/{chunk-24463PIV.js.map → chunk-PS572XYO.js.map} +0 -0
- /package/dist/{chunk-U2SMR57B.js.map → chunk-REZ4TUD2.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-V5UV7BOA.js.map → chunk-SQNED75R.js.map} +0 -0
- /package/dist/{chunk-FLVL4L3L.js.map → chunk-TCKP4EHY.js.map} +0 -0
- /package/dist/{chunk-YRBXBHU7.js.map → chunk-TXV3I2O6.js.map} +0 -0
- /package/dist/{chunk-CJ276MDI.js.map → chunk-UNV6ZGTS.js.map} +0 -0
- /package/dist/{chunk-2BFLDH5F.js.map → chunk-WEPWY4HB.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-7CAJLUNW.js.map → chunk-Y4TAFMUE.js.map} +0 -0
- /package/dist/{chunk-TIFF32QK.js.map → chunk-YMGQXBPS.js.map} +0 -0
- /package/dist/{chunk-T422SYM6.js.map → chunk-ZUDM75KG.js.map} +0 -0
- /package/dist/{chunk-4DVYRPLL.js.map → chunk-ZUHD6XZZ.js.map} +0 -0
- /package/dist/{chunk-BQ6J6P3K.js.map → chunk-ZW5SZ3TH.js.map} +0 -0
|
@@ -16,18 +16,24 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
supersessionKeysForFact
|
|
18
18
|
} from "./chunk-TKZ3AL5V.js";
|
|
19
|
-
import {
|
|
20
|
-
stripAttributesSuffix
|
|
21
|
-
} from "./chunk-SVOZFLIQ.js";
|
|
22
19
|
import {
|
|
23
20
|
DEFAULT_CITATION_FORMAT,
|
|
24
21
|
hasCitation,
|
|
25
22
|
stripCitationForTemplate
|
|
26
23
|
} from "./chunk-J6A3CX5N.js";
|
|
24
|
+
import {
|
|
25
|
+
stripAttributesSuffix
|
|
26
|
+
} from "./chunk-SVOZFLIQ.js";
|
|
27
27
|
import {
|
|
28
28
|
SPECULATIVE_TTL_DAYS,
|
|
29
29
|
confidenceTier
|
|
30
30
|
} from "./chunk-JS2HKKOK.js";
|
|
31
|
+
import {
|
|
32
|
+
inferMemoryStatus,
|
|
33
|
+
isArchivedMemoryPath,
|
|
34
|
+
sortMemoryLifecycleEvents,
|
|
35
|
+
toMemoryPathRel
|
|
36
|
+
} from "./chunk-RULE4VG5.js";
|
|
31
37
|
import {
|
|
32
38
|
normalizeProjectionPreview,
|
|
33
39
|
normalizeProjectionTags
|
|
@@ -43,15 +49,6 @@ import {
|
|
|
43
49
|
invalidateAllForDir,
|
|
44
50
|
setCachedEntities
|
|
45
51
|
} from "./chunk-AWJ2FHCF.js";
|
|
46
|
-
import {
|
|
47
|
-
inferMemoryStatus,
|
|
48
|
-
isArchivedMemoryPath,
|
|
49
|
-
sortMemoryLifecycleEvents,
|
|
50
|
-
toMemoryPathRel
|
|
51
|
-
} from "./chunk-RULE4VG5.js";
|
|
52
|
-
import {
|
|
53
|
-
rotateMarkdownFileToArchive
|
|
54
|
-
} from "./chunk-DM2T26WE.js";
|
|
55
52
|
import {
|
|
56
53
|
closeContinuityIncidentRecord,
|
|
57
54
|
createContinuityIncidentRecord,
|
|
@@ -61,10 +58,17 @@ import {
|
|
|
61
58
|
serializeContinuityIncident,
|
|
62
59
|
upsertContinuityLoopInMarkdown
|
|
63
60
|
} from "./chunk-LDXUBPMO.js";
|
|
61
|
+
import {
|
|
62
|
+
rotateMarkdownFileToArchive
|
|
63
|
+
} from "./chunk-DM2T26WE.js";
|
|
64
64
|
import {
|
|
65
65
|
parseFaithfulnessField,
|
|
66
66
|
serializeFaithfulnessFields
|
|
67
67
|
} from "./chunk-2OZ2JEUP.js";
|
|
68
|
+
import {
|
|
69
|
+
computeContentHash,
|
|
70
|
+
normalizeContent
|
|
71
|
+
} from "./chunk-W5H6ZRPT.js";
|
|
68
72
|
import {
|
|
69
73
|
isConsolidationOperator,
|
|
70
74
|
isValidDerivedFromEntry
|
|
@@ -72,24 +76,20 @@ import {
|
|
|
72
76
|
import {
|
|
73
77
|
createVersion
|
|
74
78
|
} from "./chunk-VF4XKTX3.js";
|
|
75
|
-
import {
|
|
76
|
-
computeContentHash,
|
|
77
|
-
normalizeContent
|
|
78
|
-
} from "./chunk-W5H6ZRPT.js";
|
|
79
79
|
import {
|
|
80
80
|
parseProvenanceSources,
|
|
81
81
|
parseProvenanceTag,
|
|
82
82
|
reconcileProvenanceRead,
|
|
83
83
|
serializeProvenanceFields
|
|
84
84
|
} from "./chunk-OEHCMALU.js";
|
|
85
|
+
import {
|
|
86
|
+
sanitizeMemoryContent
|
|
87
|
+
} from "./chunk-5GKPFL3S.js";
|
|
85
88
|
import {
|
|
86
89
|
matchEntitySchemaSection,
|
|
87
90
|
normalizeEntityStructuredSection,
|
|
88
91
|
sortStructuredSectionsBySchema
|
|
89
92
|
} from "./chunk-4DJQYKMN.js";
|
|
90
|
-
import {
|
|
91
|
-
sanitizeMemoryContent
|
|
92
|
-
} from "./chunk-5GKPFL3S.js";
|
|
93
93
|
import {
|
|
94
94
|
log
|
|
95
95
|
} from "./chunk-NU3CSQ4H.js";
|
|
@@ -112,10 +112,935 @@ import {
|
|
|
112
112
|
} from "./chunk-VS2IYZRU.js";
|
|
113
113
|
|
|
114
114
|
// src/storage.ts
|
|
115
|
-
import { access,
|
|
116
|
-
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
|
|
117
|
-
import { createHash } from "crypto";
|
|
118
|
-
import
|
|
115
|
+
import { access, readdir as readdir3, readFile, stat as stat2, writeFile, mkdir, unlink as unlink2, appendFile, open } from "fs/promises";
|
|
116
|
+
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
|
|
117
|
+
import { createHash } from "crypto";
|
|
118
|
+
import path3 from "path";
|
|
119
|
+
|
|
120
|
+
// src/storage/memory-read-store.ts
|
|
121
|
+
import { lstat, readdir, realpath, stat } from "fs/promises";
|
|
122
|
+
import path from "path";
|
|
123
|
+
var MemoryReadStore = class {
|
|
124
|
+
constructor(deps) {
|
|
125
|
+
this.deps = deps;
|
|
126
|
+
}
|
|
127
|
+
deps;
|
|
128
|
+
/**
|
|
129
|
+
* List stored transcript days, newest first, optionally scoped to one
|
|
130
|
+
* source. Non-transcript files in the tree are ignored.
|
|
131
|
+
*/
|
|
132
|
+
async listWearableTranscriptDays(sourceId) {
|
|
133
|
+
const days = [];
|
|
134
|
+
let sources;
|
|
135
|
+
if (sourceId !== void 0) {
|
|
136
|
+
sources = [sourceId];
|
|
137
|
+
} else {
|
|
138
|
+
try {
|
|
139
|
+
const entries = await readdir(this.deps.wearablesDir, { withFileTypes: true });
|
|
140
|
+
sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err.code === "ENOENT") return [];
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const source of sources) {
|
|
147
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = await readdir(path.join(this.deps.wearablesDir, source));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (err.code === "ENOENT") continue;
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.endsWith(".md")) continue;
|
|
157
|
+
const date = entry.slice(0, -3);
|
|
158
|
+
if (!isValidTranscriptDate(date)) continue;
|
|
159
|
+
days.push({ source, date });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
days.sort((a, b) => {
|
|
163
|
+
if (a.date > b.date) return -1;
|
|
164
|
+
if (a.date < b.date) return 1;
|
|
165
|
+
if (a.source < b.source) return -1;
|
|
166
|
+
if (a.source > b.source) return 1;
|
|
167
|
+
return 0;
|
|
168
|
+
});
|
|
169
|
+
return days;
|
|
170
|
+
}
|
|
171
|
+
async readAllArtifactsCached() {
|
|
172
|
+
if (this.deps.artifactIndexCache && Date.now() - this.deps.artifactIndexCache.loadedAtMs <= this.deps.storageManagerClass.ARTIFACT_INDEX_CACHE_TTL_MS && this.deps.artifactIndexCache.writeVersion === this.deps.getArtifactWriteVersion()) {
|
|
173
|
+
return this.deps.artifactIndexCache.memories;
|
|
174
|
+
}
|
|
175
|
+
const scanArtifacts = async () => {
|
|
176
|
+
const artifacts = [];
|
|
177
|
+
const readDir = async (dir) => {
|
|
178
|
+
try {
|
|
179
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const fullPath = path.join(dir, entry.name);
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
await readDir(fullPath);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
187
|
+
const memory = await this.deps.readMemoryByPath(fullPath);
|
|
188
|
+
if (!memory) continue;
|
|
189
|
+
artifacts.push(memory);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
await readDir(this.deps.artifactsDir);
|
|
195
|
+
return artifacts;
|
|
196
|
+
};
|
|
197
|
+
const MAX_REBUILD_RETRIES = 2;
|
|
198
|
+
let latestArtifacts = [];
|
|
199
|
+
for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
|
|
200
|
+
const versionBefore = this.deps.getArtifactWriteVersion();
|
|
201
|
+
const artifacts = await scanArtifacts();
|
|
202
|
+
const versionAfter = this.deps.getArtifactWriteVersion();
|
|
203
|
+
latestArtifacts = artifacts;
|
|
204
|
+
if (versionAfter === versionBefore) {
|
|
205
|
+
this.deps.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
|
|
206
|
+
return artifacts;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.deps.artifactIndexCache = null;
|
|
210
|
+
return latestArtifacts;
|
|
211
|
+
}
|
|
212
|
+
async collectActiveMemoryPaths() {
|
|
213
|
+
const filePaths = [];
|
|
214
|
+
let memoryRootReal;
|
|
215
|
+
try {
|
|
216
|
+
memoryRootReal = await realpath(this.deps.baseDir);
|
|
217
|
+
} catch {
|
|
218
|
+
return filePaths;
|
|
219
|
+
}
|
|
220
|
+
const collectPaths = async (dir) => {
|
|
221
|
+
let entries;
|
|
222
|
+
try {
|
|
223
|
+
const dirStat = await lstat(dir);
|
|
224
|
+
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
|
|
225
|
+
assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
|
|
226
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
227
|
+
} catch {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const subdirs = [];
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
if (entry.isSymbolicLink()) continue;
|
|
233
|
+
const fullPath = path.join(dir, entry.name);
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
subdirs.push(fullPath);
|
|
236
|
+
} else if (entry.name.endsWith(".md")) {
|
|
237
|
+
try {
|
|
238
|
+
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
239
|
+
filePaths.push(fullPath);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const subdir of subdirs) {
|
|
245
|
+
await collectPaths(subdir);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
for (const dir of RECALL_FALLBACK_DIRS) {
|
|
249
|
+
await collectPaths(path.join(this.deps.baseDir, dir));
|
|
250
|
+
}
|
|
251
|
+
return filePaths;
|
|
252
|
+
}
|
|
253
|
+
async readMemoriesWindow(options = {}) {
|
|
254
|
+
const allPaths = await this.deps.collectActiveMemoryPaths();
|
|
255
|
+
const sortedPaths = this.deps.orderWindowPaths(allPaths);
|
|
256
|
+
const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
|
|
257
|
+
const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
|
|
258
|
+
const updatedAfterMs = options.updatedAfter?.getTime();
|
|
259
|
+
const normalizedBatchSize = this.deps.normalizeMemoryReadBatchSize(options.batchSize);
|
|
260
|
+
const memories = [];
|
|
261
|
+
const selectedPaths = [];
|
|
262
|
+
for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
|
|
263
|
+
if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
|
|
264
|
+
return { memories, filePaths: selectedPaths };
|
|
265
|
+
}
|
|
266
|
+
const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
|
|
267
|
+
const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.deps.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
|
|
268
|
+
const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
|
|
269
|
+
const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
|
|
270
|
+
const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
|
|
271
|
+
memories: await this.deps.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
|
|
272
|
+
filePaths: candidateBatchPaths
|
|
273
|
+
} : await this.deps.readWindowBoundedBatch(
|
|
274
|
+
candidateBatchPaths,
|
|
275
|
+
remainingSlots,
|
|
276
|
+
remainingInspectionBudget ?? remainingSlots,
|
|
277
|
+
normalizedBatchSize
|
|
278
|
+
);
|
|
279
|
+
selectedPaths.push(...parsedCandidatePaths);
|
|
280
|
+
for (const memory of batchMemories) {
|
|
281
|
+
memories.push(memory);
|
|
282
|
+
if (maxMemories !== void 0 && memories.length >= maxMemories) {
|
|
283
|
+
return { memories, filePaths: selectedPaths };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { memories, filePaths: selectedPaths };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Read all memories from the cold tier by scanning the entire cold/ root
|
|
291
|
+
* tree. Previously this only scanned cold/facts/ and cold/corrections/, but
|
|
292
|
+
* structuredAttributes can appear on any MemoryCategory (preference, decision,
|
|
293
|
+
* entity, etc.). buildTierMemoryPath now routes each category to its own
|
|
294
|
+
* cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
|
|
295
|
+
* #1546), so cold decisions/preferences/... live outside cold/facts/.
|
|
296
|
+
* Scanning the full coldRoot covers every category dir and guards against
|
|
297
|
+
* files placed in unexpected subdirectories during manual operations or future
|
|
298
|
+
* refactors.
|
|
299
|
+
*
|
|
300
|
+
* Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
|
|
301
|
+
* corrections/ was a narrower-than-necessary subset of the cold directory
|
|
302
|
+
* tree. Correctness trumps the minor performance difference — cold scans
|
|
303
|
+
* already happen at most once per supersession write.
|
|
304
|
+
*
|
|
305
|
+
* Used by applyTemporalSupersession so that memories already demoted to
|
|
306
|
+
* cold/ can still be marked superseded when a newer hot fact arrives.
|
|
307
|
+
*
|
|
308
|
+
* Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
|
|
309
|
+
* structured-attribute writes in the same burst reuse the cached result
|
|
310
|
+
* instead of re-scanning the cold tree on every call. The cache is
|
|
311
|
+
* invalidated whenever a write calls invalidateAllMemoriesCache() (which
|
|
312
|
+
* covers any hot→cold demotion that changes cold-tier contents) and
|
|
313
|
+
* expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
|
|
314
|
+
*/
|
|
315
|
+
async readAllColdMemories() {
|
|
316
|
+
const coldRoot = this.deps.resolveTierRootDir("cold");
|
|
317
|
+
const currentColdVersion = this.deps.readColdWriteVersion();
|
|
318
|
+
const cached = this.deps.storageManagerClass.coldMemoriesCache.get(coldRoot);
|
|
319
|
+
if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
|
|
320
|
+
return cached.memories;
|
|
321
|
+
}
|
|
322
|
+
const filePaths = [];
|
|
323
|
+
const collectPaths = async (dir) => {
|
|
324
|
+
try {
|
|
325
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
326
|
+
const subdirs = [];
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
const fullPath = path.join(dir, entry.name);
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
subdirs.push(fullPath);
|
|
331
|
+
} else if (entry.name.endsWith(".md")) {
|
|
332
|
+
filePaths.push(fullPath);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const subdir of subdirs) {
|
|
336
|
+
await collectPaths(subdir);
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
await collectPaths(coldRoot);
|
|
342
|
+
const memories = await this.deps.readParsedMemoriesFromPaths(filePaths, 50);
|
|
343
|
+
this.deps.storageManagerClass.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
|
|
344
|
+
return memories;
|
|
345
|
+
}
|
|
346
|
+
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
347
|
+
async readMemoryByPath(filePath) {
|
|
348
|
+
try {
|
|
349
|
+
const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
|
|
350
|
+
const parsed = parseFrontmatter(raw);
|
|
351
|
+
if (parsed) {
|
|
352
|
+
return {
|
|
353
|
+
path: filePath,
|
|
354
|
+
frontmatter: normalizeFrontmatterForPath(
|
|
355
|
+
parsed.frontmatter,
|
|
356
|
+
toMemoryPathRel(this.deps.baseDir, filePath),
|
|
357
|
+
parsed.content
|
|
358
|
+
),
|
|
359
|
+
content: parsed.content
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const normalizedPath = filePath.split(path.sep).join("/");
|
|
363
|
+
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
364
|
+
const entity = parseEntityFile(raw, this.deps.entitySchemas);
|
|
365
|
+
if (!entity.name) return null;
|
|
366
|
+
const nameWithoutExt = path.basename(filePath, ".md");
|
|
367
|
+
const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
368
|
+
return {
|
|
369
|
+
path: filePath,
|
|
370
|
+
frontmatter: {
|
|
371
|
+
id: nameWithoutExt,
|
|
372
|
+
category: "entity",
|
|
373
|
+
created: fileMtime,
|
|
374
|
+
updated: fileMtime,
|
|
375
|
+
source: "entity_extraction",
|
|
376
|
+
confidence: 0.9,
|
|
377
|
+
confidenceTier: confidenceTier(0.9),
|
|
378
|
+
tags: entity.type ? [entity.type] : []
|
|
379
|
+
},
|
|
380
|
+
content: raw
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
} catch (err) {
|
|
385
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
|
|
391
|
+
*
|
|
392
|
+
* Each event records a single buffer flush decision driven by the
|
|
393
|
+
* surprise gate. The ledger is consumed by
|
|
394
|
+
* `reportBufferSurpriseDistribution` (Doctor report) and by downstream
|
|
395
|
+
* benchmark analysis. This method is fire-and-forget by contract:
|
|
396
|
+
* callers log but do not fail the hot path if the append throws.
|
|
397
|
+
*/
|
|
398
|
+
async appendBufferSurpriseEvents(events) {
|
|
399
|
+
if (events.length === 0) return 0;
|
|
400
|
+
await this.deps.ensureDirectories();
|
|
401
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
402
|
+
const payload = events.map((event) => {
|
|
403
|
+
const normalized = {
|
|
404
|
+
...event,
|
|
405
|
+
event: "BUFFER_SURPRISE",
|
|
406
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
407
|
+
};
|
|
408
|
+
return `${JSON.stringify(normalized)}
|
|
409
|
+
`;
|
|
410
|
+
}).join("");
|
|
411
|
+
await this.deps.appendStorageSecureFile(this.deps.bufferSurpriseLedgerPath, payload);
|
|
412
|
+
return events.length;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Read the buffer-surprise ledger, most recent rows last.
|
|
416
|
+
*
|
|
417
|
+
* `limit` bounds the number of **valid rows** returned (not the
|
|
418
|
+
* number of raw lines parsed). We parse every row, discard malformed
|
|
419
|
+
* ones, then take the tail — so a partial/truncated trailing line
|
|
420
|
+
* (the common failure mode after an interrupted append) cannot hide
|
|
421
|
+
* otherwise-valid recent data above it.
|
|
422
|
+
*
|
|
423
|
+
* Non-positive / non-integer / non-finite limits return `[]` rather
|
|
424
|
+
* than the entire file, matching the other ledger readers in this
|
|
425
|
+
* class and protecting against `slice(-0.5)` → `slice(-0)` silently
|
|
426
|
+
* devolving into an unbounded parse.
|
|
427
|
+
*
|
|
428
|
+
* # Performance note
|
|
429
|
+
*
|
|
430
|
+
* For very large ledgers (issue #563 follow-up), a tail-first reader
|
|
431
|
+
* would avoid parsing the full file when only a recent window is
|
|
432
|
+
* needed. We keep the full-scan implementation here because:
|
|
433
|
+
*
|
|
434
|
+
* - the ledger is opt-in (flag off by default), so early deployments
|
|
435
|
+
* accumulate rows slowly;
|
|
436
|
+
* - telemetry rows are small (~200 bytes), so even 100k rows parse
|
|
437
|
+
* in well under a second;
|
|
438
|
+
* - the governance archive/cleanup flow can trim the ledger when
|
|
439
|
+
* size becomes a concern, reusing the existing maintenance hooks.
|
|
440
|
+
*
|
|
441
|
+
* Swap to a chunked tail-reader if production logs show this is a
|
|
442
|
+
* hot path — leaving that work for a follow-up keeps this PR scoped
|
|
443
|
+
* to correctness, not optimization.
|
|
444
|
+
*/
|
|
445
|
+
async readBufferSurpriseEvents(options = {}) {
|
|
446
|
+
let raw;
|
|
447
|
+
try {
|
|
448
|
+
raw = await this.deps.readStorageSecureFile(this.deps.bufferSurpriseLedgerPath);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
451
|
+
const code = err.code;
|
|
452
|
+
if (code === "ENOENT") return [];
|
|
453
|
+
throw err;
|
|
454
|
+
}
|
|
455
|
+
let effectiveLimit = null;
|
|
456
|
+
if (options.limit !== void 0) {
|
|
457
|
+
if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
const floored = Math.floor(options.limit);
|
|
461
|
+
if (floored <= 0) return [];
|
|
462
|
+
effectiveLimit = floored;
|
|
463
|
+
}
|
|
464
|
+
const lines = raw.split("\n");
|
|
465
|
+
const events = [];
|
|
466
|
+
for (const line of lines) {
|
|
467
|
+
const trimmed = line.trim();
|
|
468
|
+
if (trimmed.length === 0) continue;
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(trimmed);
|
|
471
|
+
if (isValidBufferSurpriseEvent(parsed)) {
|
|
472
|
+
events.push(parsed);
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
events.sort(
|
|
478
|
+
(left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
|
|
479
|
+
);
|
|
480
|
+
if (effectiveLimit === null) return events;
|
|
481
|
+
return events.slice(-effectiveLimit);
|
|
482
|
+
}
|
|
483
|
+
async readCompressionGuidelineStateFile(filePath) {
|
|
484
|
+
const isFiniteNonNegativeInteger = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
485
|
+
const isValidActionSummary = (value) => {
|
|
486
|
+
if (!value || typeof value !== "object") return false;
|
|
487
|
+
const summary = value;
|
|
488
|
+
return typeof summary.action === "string" && isFiniteNonNegativeInteger(summary.total) && summary.outcomes !== null && typeof summary.outcomes === "object" && isFiniteNonNegativeInteger(summary.outcomes.applied) && isFiniteNonNegativeInteger(summary.outcomes.skipped) && isFiniteNonNegativeInteger(summary.outcomes.failed) && summary.quality !== null && typeof summary.quality === "object" && isFiniteNonNegativeInteger(summary.quality.good) && isFiniteNonNegativeInteger(summary.quality.poor) && isFiniteNonNegativeInteger(summary.quality.unknown);
|
|
489
|
+
};
|
|
490
|
+
const isValidRuleUpdate = (value) => {
|
|
491
|
+
if (!value || typeof value !== "object") return false;
|
|
492
|
+
const rule = value;
|
|
493
|
+
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
494
|
+
};
|
|
495
|
+
try {
|
|
496
|
+
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
497
|
+
const parsed = JSON.parse(raw);
|
|
498
|
+
const sourceWindow = parsed?.sourceWindow;
|
|
499
|
+
const eventCounts = parsed?.eventCounts;
|
|
500
|
+
const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
|
|
501
|
+
const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
|
|
502
|
+
const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
|
|
503
|
+
const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
|
|
504
|
+
if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
version: parsed.version,
|
|
509
|
+
updatedAt: parsed.updatedAt,
|
|
510
|
+
sourceWindow: {
|
|
511
|
+
from: sourceWindow.from,
|
|
512
|
+
to: sourceWindow.to
|
|
513
|
+
},
|
|
514
|
+
eventCounts: {
|
|
515
|
+
total: eventCounts.total,
|
|
516
|
+
applied: eventCounts.applied,
|
|
517
|
+
skipped: eventCounts.skipped,
|
|
518
|
+
failed: eventCounts.failed
|
|
519
|
+
},
|
|
520
|
+
guidelineVersion: parsed.guidelineVersion,
|
|
521
|
+
...contentHash ? { contentHash } : {},
|
|
522
|
+
...activationState ? { activationState } : {},
|
|
523
|
+
...actionSummaries ? { actionSummaries } : {},
|
|
524
|
+
...ruleUpdates ? { ruleUpdates } : {}
|
|
525
|
+
};
|
|
526
|
+
} catch (err) {
|
|
527
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
528
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async readQuestions(opts) {
|
|
533
|
+
const cacheKey = this.deps.questionsDir;
|
|
534
|
+
const cached = this.deps.storageManagerClass.questionsCache.get(cacheKey);
|
|
535
|
+
if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.QUESTIONS_CACHE_TTL_MS) {
|
|
536
|
+
try {
|
|
537
|
+
const dirStat = await stat(this.deps.questionsDir);
|
|
538
|
+
if (dirStat.mtimeMs <= cached.loadedAt) {
|
|
539
|
+
const all = cached.questions;
|
|
540
|
+
return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const files = await readdir(this.deps.questionsDir);
|
|
547
|
+
const questions = [];
|
|
548
|
+
for (const file of files) {
|
|
549
|
+
if (!file.endsWith(".md")) continue;
|
|
550
|
+
const filePath = path.join(this.deps.questionsDir, file);
|
|
551
|
+
const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
|
|
552
|
+
const parsed = this.deps.parseQuestionFile(raw, filePath);
|
|
553
|
+
if (parsed) {
|
|
554
|
+
questions.push(parsed);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const sorted = questions.sort((a, b) => b.priority - a.priority);
|
|
558
|
+
this.deps.storageManagerClass.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
|
|
559
|
+
return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
|
|
560
|
+
} catch {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// src/orchestration/self-deps.ts
|
|
567
|
+
function selfDeps(self) {
|
|
568
|
+
const source = self;
|
|
569
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
570
|
+
get(_target, prop) {
|
|
571
|
+
const value = source[prop];
|
|
572
|
+
return typeof value === "function" && !Object.prototype.hasOwnProperty.call(value, "prototype") ? value.bind(self) : value;
|
|
573
|
+
},
|
|
574
|
+
set(_target, prop, value) {
|
|
575
|
+
if (!(prop in source)) return false;
|
|
576
|
+
source[prop] = value;
|
|
577
|
+
return true;
|
|
578
|
+
},
|
|
579
|
+
has(_target, prop) {
|
|
580
|
+
return prop in source;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/storage/entity-store.ts
|
|
586
|
+
import { readdir as readdir2, unlink } from "fs/promises";
|
|
587
|
+
import path2 from "path";
|
|
588
|
+
var EntityStore = class {
|
|
589
|
+
constructor(deps) {
|
|
590
|
+
this.deps = deps;
|
|
591
|
+
}
|
|
592
|
+
deps;
|
|
593
|
+
/** Normalize an entity name using this store's alias table. */
|
|
594
|
+
normalizeEntityName(raw, type) {
|
|
595
|
+
return normalizeEntityName(raw, type, this.deps.userAliases);
|
|
596
|
+
}
|
|
597
|
+
async writeEntity(name, type, facts, options = {}) {
|
|
598
|
+
await this.deps.ensureDirectories();
|
|
599
|
+
if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
|
|
600
|
+
log.warn("writeEntity: invalid entity payload, skipping", {
|
|
601
|
+
nameType: typeof name,
|
|
602
|
+
typeType: typeof type
|
|
603
|
+
});
|
|
604
|
+
return "";
|
|
605
|
+
}
|
|
606
|
+
const safeFacts = Array.isArray(facts) ? [...new Set(
|
|
607
|
+
facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
|
|
608
|
+
)] : [];
|
|
609
|
+
let normalized = this.deps.normalizeEntityName(name, type);
|
|
610
|
+
const match = await this.deps.findMatchingEntity(name, type);
|
|
611
|
+
if (match && match !== normalized) {
|
|
612
|
+
log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
|
|
613
|
+
normalized = match;
|
|
614
|
+
}
|
|
615
|
+
const filePath = path2.join(this.deps.entitiesDir, `${normalized}.md`);
|
|
616
|
+
let entity = {
|
|
617
|
+
name,
|
|
618
|
+
type,
|
|
619
|
+
created: "",
|
|
620
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
621
|
+
facts: [],
|
|
622
|
+
summary: void 0,
|
|
623
|
+
synthesis: void 0,
|
|
624
|
+
synthesisUpdatedAt: void 0,
|
|
625
|
+
synthesisVersion: void 0,
|
|
626
|
+
synthesisStructuredFactCount: void 0,
|
|
627
|
+
synthesisStructuredFactDigest: void 0,
|
|
628
|
+
timeline: [],
|
|
629
|
+
relationships: [],
|
|
630
|
+
activity: [],
|
|
631
|
+
aliases: []
|
|
632
|
+
};
|
|
633
|
+
try {
|
|
634
|
+
const existing = await this.deps.readStorageSecureFile(filePath);
|
|
635
|
+
entity = parseEntityFile(existing, this.deps.entitySchemas);
|
|
636
|
+
} catch (err) {
|
|
637
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
638
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
639
|
+
}
|
|
640
|
+
const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
|
|
641
|
+
const source = options.source?.trim() || void 0;
|
|
642
|
+
const sessionKey = options.sessionKey?.trim() || void 0;
|
|
643
|
+
const principal = options.principal?.trim() || void 0;
|
|
644
|
+
const structuredSectionMap = new Map(
|
|
645
|
+
(entity.structuredSections ?? []).map((section) => [section.key, {
|
|
646
|
+
...section,
|
|
647
|
+
facts: [...section.facts]
|
|
648
|
+
}])
|
|
649
|
+
);
|
|
650
|
+
for (const section of options.structuredSections ?? []) {
|
|
651
|
+
const normalizedSection = normalizeEntityStructuredSection(type, section, this.deps.entitySchemas);
|
|
652
|
+
const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
|
|
653
|
+
if (normalizedFacts.length === 0) continue;
|
|
654
|
+
const existingSection = structuredSectionMap.get(normalizedSection.key);
|
|
655
|
+
if (!existingSection) {
|
|
656
|
+
structuredSectionMap.set(normalizedSection.key, {
|
|
657
|
+
key: normalizedSection.key,
|
|
658
|
+
title: normalizedSection.title,
|
|
659
|
+
facts: normalizedFacts
|
|
660
|
+
});
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
|
|
664
|
+
if (!existingSection.title.trim() && normalizedSection.title.trim()) {
|
|
665
|
+
existingSection.title = normalizedSection.title;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
for (const fact of safeFacts) {
|
|
669
|
+
const nextEntry = {
|
|
670
|
+
timestamp,
|
|
671
|
+
text: fact,
|
|
672
|
+
...source ? { source } : {},
|
|
673
|
+
...sessionKey ? { sessionKey } : {},
|
|
674
|
+
...principal ? { principal } : {}
|
|
675
|
+
};
|
|
676
|
+
const alreadyPresent = entity.timeline.some(
|
|
677
|
+
(entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
|
|
678
|
+
);
|
|
679
|
+
if (alreadyPresent) continue;
|
|
680
|
+
entity.timeline.push(nextEntry);
|
|
681
|
+
}
|
|
682
|
+
entity.structuredSections = sortStructuredSectionsBySchema(
|
|
683
|
+
type,
|
|
684
|
+
Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
|
|
685
|
+
this.deps.entitySchemas
|
|
686
|
+
);
|
|
687
|
+
entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
|
|
688
|
+
entity.summary = entity.synthesis || entity.summary;
|
|
689
|
+
entity.name = name;
|
|
690
|
+
entity.type = type;
|
|
691
|
+
entity.created = entity.created || timestamp;
|
|
692
|
+
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
693
|
+
await this.deps.snapshotBeforeWrite(filePath, "write");
|
|
694
|
+
await this.deps.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.deps.entitySchemas));
|
|
695
|
+
this.deps.invalidateKnowledgeIndexCache();
|
|
696
|
+
this.deps.bumpMemoryStatusVersion();
|
|
697
|
+
log.debug(`wrote entity ${normalized}`);
|
|
698
|
+
return normalized;
|
|
699
|
+
}
|
|
700
|
+
async readEntity(name) {
|
|
701
|
+
const filePath = this.deps.resolveEntityFilePath(name);
|
|
702
|
+
if (filePath === null) {
|
|
703
|
+
return "";
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
return await this.deps.readStorageSecureFile(filePath);
|
|
707
|
+
} catch (err) {
|
|
708
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
709
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
710
|
+
return "";
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/** Return sorted list of entity filenames (without .md extension) */
|
|
714
|
+
async listEntityNames() {
|
|
715
|
+
try {
|
|
716
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
717
|
+
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
|
|
718
|
+
} catch {
|
|
719
|
+
return [];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Set or rewrite the synthesis layer of an entity file.
|
|
724
|
+
*/
|
|
725
|
+
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
726
|
+
const filePath = path2.join(this.deps.entitiesDir, `${name}.md`);
|
|
727
|
+
let entity;
|
|
728
|
+
try {
|
|
729
|
+
const content = await this.deps.readStorageSecureFile(filePath);
|
|
730
|
+
entity = parseEntityFile(content, this.deps.entitySchemas);
|
|
731
|
+
} catch (err) {
|
|
732
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
733
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
734
|
+
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
|
|
738
|
+
const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
739
|
+
const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
|
|
740
|
+
const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
|
|
741
|
+
const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
|
|
742
|
+
entity.synthesis = synthesis.trim();
|
|
743
|
+
entity.summary = entity.synthesis;
|
|
744
|
+
entity.synthesisUpdatedAt = updatedAt;
|
|
745
|
+
entity.synthesisTimelineCount = synthesisTimelineCount;
|
|
746
|
+
entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
|
|
747
|
+
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
748
|
+
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
|
|
749
|
+
entity.updated = entityUpdatedAt;
|
|
750
|
+
await this.deps.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.deps.entitySchemas));
|
|
751
|
+
await this.deps.removeEntitySynthesisQueueEntries([
|
|
752
|
+
.../* @__PURE__ */ new Set([name, this.deps.normalizeEntityName(entity.name, entity.type)])
|
|
753
|
+
]);
|
|
754
|
+
this.deps.invalidateKnowledgeIndexCache();
|
|
755
|
+
this.deps.bumpMemoryStatusVersion();
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Read all entity files and return lightweight EntityFile objects.
|
|
759
|
+
* Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
|
|
760
|
+
*/
|
|
761
|
+
async readAllEntityFiles() {
|
|
762
|
+
const currentVersion = this.deps.getMemoryStatusVersion();
|
|
763
|
+
const schemaCacheKey = buildEntitySchemaCacheKey(this.deps.entitySchemas);
|
|
764
|
+
const cacheKey = `${this.deps.getEntityCacheSecureStoreKey()}\0${schemaCacheKey}`;
|
|
765
|
+
const cached = getCachedEntities(this.deps.baseDir, currentVersion, cacheKey);
|
|
766
|
+
if (cached) return cached;
|
|
767
|
+
try {
|
|
768
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
769
|
+
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
770
|
+
if (mdFiles.length === 0) return [];
|
|
771
|
+
const BATCH_SIZE = 100;
|
|
772
|
+
const entities = [];
|
|
773
|
+
for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
|
|
774
|
+
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
775
|
+
const results = await Promise.all(
|
|
776
|
+
batch.map(async (entry) => {
|
|
777
|
+
try {
|
|
778
|
+
return await this.deps.readStorageSecureFile(path2.join(this.deps.entitiesDir, entry));
|
|
779
|
+
} catch (err) {
|
|
780
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
781
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
for (const content of results) {
|
|
787
|
+
if (content !== null) entities.push(parseEntityFile(content, this.deps.entitySchemas));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
setCachedEntities(this.deps.baseDir, entities, currentVersion, cacheKey);
|
|
791
|
+
return entities;
|
|
792
|
+
} catch (err) {
|
|
793
|
+
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
794
|
+
return [];
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Build the Knowledge Index: a compact markdown table of top-scored entities.
|
|
799
|
+
* Respects maxEntities and maxChars limits from config.
|
|
800
|
+
*/
|
|
801
|
+
async buildKnowledgeIndex(config, overrides) {
|
|
802
|
+
const useDefaultLimits = overrides?.maxEntities === void 0 && overrides?.maxChars === void 0;
|
|
803
|
+
if (useDefaultLimits && this.deps.knowledgeIndexCache && Date.now() - this.deps.knowledgeIndexCache.builtAt < this.deps.storageManagerClass.KNOWLEDGE_INDEX_CACHE_TTL_MS) {
|
|
804
|
+
return { result: this.deps.knowledgeIndexCache.result, cached: true };
|
|
805
|
+
}
|
|
806
|
+
const entities = await this.deps.readAllEntityFiles();
|
|
807
|
+
if (entities.length === 0) {
|
|
808
|
+
if (useDefaultLimits) this.deps.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
809
|
+
return { result: "", cached: false };
|
|
810
|
+
}
|
|
811
|
+
const now = /* @__PURE__ */ new Date();
|
|
812
|
+
const scored = entities.map((e) => ({
|
|
813
|
+
name: e.name,
|
|
814
|
+
type: e.type,
|
|
815
|
+
score: this.deps.storageManagerClass.scoreEntity(e, now),
|
|
816
|
+
factCount: e.facts.length,
|
|
817
|
+
summary: e.synthesis ?? e.summary,
|
|
818
|
+
topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
|
|
819
|
+
}));
|
|
820
|
+
scored.sort((a, b) => b.score - a.score);
|
|
821
|
+
const maxEntities = typeof overrides?.maxEntities === "number" ? Math.max(0, Math.floor(overrides.maxEntities)) : config.knowledgeIndexMaxEntities;
|
|
822
|
+
const topN = scored.slice(0, maxEntities);
|
|
823
|
+
if (topN.length === 0) {
|
|
824
|
+
if (useDefaultLimits) this.deps.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
825
|
+
return { result: "", cached: false };
|
|
826
|
+
}
|
|
827
|
+
const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
|
|
828
|
+
const rows = [];
|
|
829
|
+
let totalChars = header.length;
|
|
830
|
+
const maxChars = typeof overrides?.maxChars === "number" ? Math.max(0, Math.floor(overrides.maxChars)) : config.knowledgeIndexMaxChars;
|
|
831
|
+
for (const entity of topN) {
|
|
832
|
+
const summary = entity.summary || `${entity.factCount} facts`;
|
|
833
|
+
const connected = entity.topRelationships.length > 0 ? entity.topRelationships.join(", ") : "\u2014";
|
|
834
|
+
const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
|
|
835
|
+
if (totalChars + row.length + 1 > maxChars) break;
|
|
836
|
+
rows.push(row);
|
|
837
|
+
totalChars += row.length + 1;
|
|
838
|
+
}
|
|
839
|
+
const result = rows.length === 0 ? "" : `${header}
|
|
840
|
+
${rows.join("\n")}
|
|
841
|
+
`;
|
|
842
|
+
if (useDefaultLimits) this.deps.knowledgeIndexCache = { result, builtAt: Date.now() };
|
|
843
|
+
return { result, cached: false };
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Merge fragmented entity files that resolve to the same canonical name.
|
|
847
|
+
* Preserves relationships, activity, aliases, and summary from all fragments.
|
|
848
|
+
* Returns count of files merged.
|
|
849
|
+
*/
|
|
850
|
+
async mergeFragmentedEntities() {
|
|
851
|
+
let merged = 0;
|
|
852
|
+
try {
|
|
853
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
854
|
+
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
855
|
+
const groups = /* @__PURE__ */ new Map();
|
|
856
|
+
for (const file of mdFiles) {
|
|
857
|
+
const baseName = file.replace(".md", "");
|
|
858
|
+
const dashIdx = baseName.indexOf("-");
|
|
859
|
+
if (dashIdx === -1) continue;
|
|
860
|
+
const type = baseName.slice(0, dashIdx);
|
|
861
|
+
const restOfName = baseName.slice(dashIdx + 1);
|
|
862
|
+
const canonical = this.deps.normalizeEntityName(restOfName, type);
|
|
863
|
+
if (!groups.has(canonical)) groups.set(canonical, []);
|
|
864
|
+
groups.get(canonical).push(file);
|
|
865
|
+
}
|
|
866
|
+
for (const [canonical, files] of groups) {
|
|
867
|
+
if (files.length <= 1) continue;
|
|
868
|
+
const mergedEntity = {
|
|
869
|
+
name: "",
|
|
870
|
+
type: "other",
|
|
871
|
+
created: "",
|
|
872
|
+
updated: "",
|
|
873
|
+
extraFrontmatterLines: [],
|
|
874
|
+
preSectionLines: [],
|
|
875
|
+
facts: [],
|
|
876
|
+
summary: void 0,
|
|
877
|
+
synthesis: void 0,
|
|
878
|
+
synthesisUpdatedAt: void 0,
|
|
879
|
+
synthesisTimelineCount: void 0,
|
|
880
|
+
synthesisStructuredFactCount: void 0,
|
|
881
|
+
synthesisStructuredFactDigest: void 0,
|
|
882
|
+
synthesisVersion: void 0,
|
|
883
|
+
timeline: [],
|
|
884
|
+
relationships: [],
|
|
885
|
+
activity: [],
|
|
886
|
+
aliases: [],
|
|
887
|
+
structuredSections: [],
|
|
888
|
+
extraSections: []
|
|
889
|
+
};
|
|
890
|
+
for (const file of files) {
|
|
891
|
+
const filePath = path2.join(this.deps.entitiesDir, file);
|
|
892
|
+
try {
|
|
893
|
+
const content = await this.deps.readStorageSecureFile(filePath);
|
|
894
|
+
const parsed = parseEntityFile(content, this.deps.entitySchemas);
|
|
895
|
+
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
896
|
+
mergedEntity.type = parsed.type;
|
|
897
|
+
}
|
|
898
|
+
if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
|
|
899
|
+
mergedEntity.updated = parsed.updated;
|
|
900
|
+
}
|
|
901
|
+
const parsedCreated = parsed.created || parsed.updated;
|
|
902
|
+
const mergedCreated = mergedEntity.created?.trim() || "";
|
|
903
|
+
const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
|
|
904
|
+
const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
|
|
905
|
+
const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
|
|
906
|
+
const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
|
|
907
|
+
if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
|
|
908
|
+
mergedEntity.created = parsedCreated;
|
|
909
|
+
}
|
|
910
|
+
if (parsed.name.length > mergedEntity.name.length) {
|
|
911
|
+
mergedEntity.name = parsed.name;
|
|
912
|
+
}
|
|
913
|
+
const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
|
|
914
|
+
const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
|
|
915
|
+
if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
|
|
916
|
+
mergedEntity.synthesis = parsed.synthesis;
|
|
917
|
+
mergedEntity.summary = parsed.synthesis;
|
|
918
|
+
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
919
|
+
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
920
|
+
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
921
|
+
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
922
|
+
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
923
|
+
} else if (!mergedEntity.summary && parsed.summary) {
|
|
924
|
+
mergedEntity.summary = parsed.summary;
|
|
925
|
+
mergedEntity.synthesis = parsed.summary;
|
|
926
|
+
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
927
|
+
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
928
|
+
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
929
|
+
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
930
|
+
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
931
|
+
}
|
|
932
|
+
mergedEntity.timeline.push(...parsed.timeline);
|
|
933
|
+
mergedEntity.relationships.push(...parsed.relationships);
|
|
934
|
+
mergedEntity.activity.push(...parsed.activity);
|
|
935
|
+
mergedEntity.aliases.push(...parsed.aliases);
|
|
936
|
+
const mergedStructuredSectionMap = new Map(
|
|
937
|
+
(mergedEntity.structuredSections ?? []).map((section) => [section.key, {
|
|
938
|
+
...section,
|
|
939
|
+
facts: [...section.facts]
|
|
940
|
+
}])
|
|
941
|
+
);
|
|
942
|
+
for (const section of parsed.structuredSections ?? []) {
|
|
943
|
+
const existingSection = mergedStructuredSectionMap.get(section.key);
|
|
944
|
+
if (!existingSection) {
|
|
945
|
+
mergedStructuredSectionMap.set(section.key, {
|
|
946
|
+
key: section.key,
|
|
947
|
+
title: section.title,
|
|
948
|
+
facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
|
|
949
|
+
});
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
|
|
953
|
+
for (const fact of section.facts) {
|
|
954
|
+
const trimmed = fact.trim();
|
|
955
|
+
if (!trimmed) continue;
|
|
956
|
+
mergedFacts.add(trimmed);
|
|
957
|
+
}
|
|
958
|
+
existingSection.facts = Array.from(mergedFacts);
|
|
959
|
+
if (!existingSection.title.trim() && section.title.trim()) {
|
|
960
|
+
existingSection.title = section.title;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
|
|
964
|
+
mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
|
|
965
|
+
mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
|
|
966
|
+
mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
|
|
967
|
+
title: section.title,
|
|
968
|
+
lines: [...section.lines]
|
|
969
|
+
})));
|
|
970
|
+
} catch (err) {
|
|
971
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
972
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
const timelineKeys = /* @__PURE__ */ new Set();
|
|
976
|
+
mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
|
|
977
|
+
const key = JSON.stringify([
|
|
978
|
+
entry.timestamp,
|
|
979
|
+
entry.source ?? "",
|
|
980
|
+
entry.sessionKey ?? "",
|
|
981
|
+
entry.principal ?? "",
|
|
982
|
+
entry.text
|
|
983
|
+
]);
|
|
984
|
+
if (timelineKeys.has(key)) return false;
|
|
985
|
+
timelineKeys.add(key);
|
|
986
|
+
return true;
|
|
987
|
+
});
|
|
988
|
+
const relKeys = /* @__PURE__ */ new Set();
|
|
989
|
+
mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
|
|
990
|
+
const key = `${r.target}::${r.label}`;
|
|
991
|
+
if (relKeys.has(key)) return false;
|
|
992
|
+
relKeys.add(key);
|
|
993
|
+
return true;
|
|
994
|
+
});
|
|
995
|
+
const actKeys = /* @__PURE__ */ new Set();
|
|
996
|
+
mergedEntity.activity = mergedEntity.activity.filter((a) => {
|
|
997
|
+
const key = `${a.date}::${a.note}`;
|
|
998
|
+
if (actKeys.has(key)) return false;
|
|
999
|
+
actKeys.add(key);
|
|
1000
|
+
return true;
|
|
1001
|
+
}).sort((a, b) => b.date.localeCompare(a.date));
|
|
1002
|
+
mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
|
|
1003
|
+
mergedEntity.structuredSections = sortStructuredSectionsBySchema(
|
|
1004
|
+
mergedEntity.type,
|
|
1005
|
+
mergedEntity.structuredSections ?? [],
|
|
1006
|
+
this.deps.entitySchemas
|
|
1007
|
+
);
|
|
1008
|
+
mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
|
|
1009
|
+
const extraSectionKeys = /* @__PURE__ */ new Set();
|
|
1010
|
+
mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
|
|
1011
|
+
const key = `${section.title}::${section.lines.join("\n")}`;
|
|
1012
|
+
if (extraSectionKeys.has(key)) return false;
|
|
1013
|
+
extraSectionKeys.add(key);
|
|
1014
|
+
return true;
|
|
1015
|
+
});
|
|
1016
|
+
if (!mergedEntity.name) {
|
|
1017
|
+
const dashIdx = canonical.indexOf("-");
|
|
1018
|
+
mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
|
|
1019
|
+
}
|
|
1020
|
+
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
1021
|
+
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
1022
|
+
const canonicalPath = path2.join(this.deps.entitiesDir, `${canonical}.md`);
|
|
1023
|
+
await this.deps.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.deps.entitySchemas));
|
|
1024
|
+
for (const file of files) {
|
|
1025
|
+
const filePath = path2.join(this.deps.entitiesDir, file);
|
|
1026
|
+
if (filePath !== canonicalPath) {
|
|
1027
|
+
try {
|
|
1028
|
+
await unlink(filePath);
|
|
1029
|
+
merged++;
|
|
1030
|
+
log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
1038
|
+
}
|
|
1039
|
+
return merged;
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// src/storage.ts
|
|
119
1044
|
var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
|
|
120
1045
|
"a",
|
|
121
1046
|
"an",
|
|
@@ -720,17 +1645,17 @@ var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
|
|
|
720
1645
|
"topic"
|
|
721
1646
|
]);
|
|
722
1647
|
function inferEntityTypeFromFilename(pathRel) {
|
|
723
|
-
const basename =
|
|
1648
|
+
const basename = path3.basename(pathRel, ".md").toLowerCase();
|
|
724
1649
|
const separator = basename.indexOf("-");
|
|
725
1650
|
if (separator <= 0) return void 0;
|
|
726
1651
|
const candidate = basename.slice(0, separator);
|
|
727
1652
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
728
1653
|
}
|
|
729
1654
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
730
|
-
const normalizedPath = pathRel.split(
|
|
1655
|
+
const normalizedPath = pathRel.split(path3.sep).join("/");
|
|
731
1656
|
let normalizedFrontmatter = frontmatter;
|
|
732
1657
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
733
|
-
const basename =
|
|
1658
|
+
const basename = path3.basename(pathRel, ".md");
|
|
734
1659
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
735
1660
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
736
1661
|
normalizedFrontmatter = {
|
|
@@ -797,8 +1722,8 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
797
1722
|
secureStoreKeyProvider;
|
|
798
1723
|
secureStoreWriteKeyProvider;
|
|
799
1724
|
memoryDir;
|
|
800
|
-
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir =
|
|
801
|
-
this.filePath =
|
|
1725
|
+
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path3.dirname(stateDir)) {
|
|
1726
|
+
this.filePath = path3.join(stateDir, "fact-hashes.txt");
|
|
802
1727
|
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
803
1728
|
this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
|
|
804
1729
|
this.memoryDir = memoryDir;
|
|
@@ -845,7 +1770,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
845
1770
|
/** Persist index to disk if changed. */
|
|
846
1771
|
async save() {
|
|
847
1772
|
if (!this.dirty) return;
|
|
848
|
-
await mkdir(
|
|
1773
|
+
await mkdir(path3.dirname(this.filePath), { recursive: true });
|
|
849
1774
|
await writeMaybeEncryptedFile(
|
|
850
1775
|
this.filePath,
|
|
851
1776
|
[...this.hashes].join("\n") + "\n",
|
|
@@ -1699,7 +2624,7 @@ function isValidBufferSurpriseEvent(value) {
|
|
|
1699
2624
|
}
|
|
1700
2625
|
return true;
|
|
1701
2626
|
}
|
|
1702
|
-
var
|
|
2627
|
+
var StorageManager3 = class _StorageManager {
|
|
1703
2628
|
constructor(baseDir, entitySchemas) {
|
|
1704
2629
|
this.baseDir = baseDir;
|
|
1705
2630
|
this.entitySchemas = entitySchemas;
|
|
@@ -1708,9 +2633,11 @@ var StorageManager = class _StorageManager {
|
|
|
1708
2633
|
baseDir;
|
|
1709
2634
|
entitySchemas;
|
|
1710
2635
|
knowledgeIndexCache = null;
|
|
2636
|
+
artifactIndexCache = null;
|
|
2637
|
+
/** Read by storage/entity-store.ts (decomposition), hence not `private`. */
|
|
1711
2638
|
static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
|
|
1712
2639
|
// 10 minutes (entity mutations invalidate)
|
|
1713
|
-
|
|
2640
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
1714
2641
|
static ARTIFACT_INDEX_CACHE_TTL_MS = 6e4;
|
|
1715
2642
|
// 1 minute
|
|
1716
2643
|
static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
@@ -1739,14 +2666,18 @@ var StorageManager = class _StorageManager {
|
|
|
1739
2666
|
// on every cold-tier write, making the cache correct across process boundaries
|
|
1740
2667
|
// (gateway + CLI). After Finding UTsP broadened the scan to the entire cold/
|
|
1741
2668
|
// subtree, amortizing across back-to-back writes is even more important.
|
|
2669
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
1742
2670
|
static COLD_SCAN_CACHE_TTL_MS = 3e4;
|
|
1743
2671
|
// 30 seconds
|
|
2672
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
1744
2673
|
static coldMemoriesCache = /* @__PURE__ */ new Map();
|
|
1745
2674
|
// Cache for readQuestions() — avoids serially re-reading tens of thousands of
|
|
1746
2675
|
// question files on every recall. 60-second TTL is intentionally short so that
|
|
1747
2676
|
// newly written questions surface quickly.
|
|
2677
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
1748
2678
|
static QUESTIONS_CACHE_TTL_MS = 6e4;
|
|
1749
2679
|
// 1 minute
|
|
2680
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
1750
2681
|
static questionsCache = /* @__PURE__ */ new Map();
|
|
1751
2682
|
factHashIndex = null;
|
|
1752
2683
|
factHashIndexLoadPromise = null;
|
|
@@ -1905,7 +2836,7 @@ var StorageManager = class _StorageManager {
|
|
|
1905
2836
|
void 0,
|
|
1906
2837
|
this.baseDir
|
|
1907
2838
|
);
|
|
1908
|
-
const rel =
|
|
2839
|
+
const rel = path3.relative(this.baseDir, filePath).split(path3.sep).join("/");
|
|
1909
2840
|
return `${rel}:${version.versionId}`;
|
|
1910
2841
|
} catch (err) {
|
|
1911
2842
|
log.warn(
|
|
@@ -1914,19 +2845,51 @@ var StorageManager = class _StorageManager {
|
|
|
1914
2845
|
return null;
|
|
1915
2846
|
}
|
|
1916
2847
|
}
|
|
2848
|
+
/**
|
|
2849
|
+
* The LIVE class object of this instance (issue #1809 review of the
|
|
2850
|
+
* storage decomposition): extracted store modules must read/write the
|
|
2851
|
+
* shared static caches (questionsCache, coldMemoriesCache, ...) through
|
|
2852
|
+
* the SAME class binding the host methods use. Referencing the imported
|
|
2853
|
+
* `StorageManager` symbol from a split bundle chunk can yield a second
|
|
2854
|
+
* class copy with its own statics, split-braining the caches
|
|
2855
|
+
* (resolveQuestion clears copy A while readQuestions reads copy B).
|
|
2856
|
+
*/
|
|
2857
|
+
get storageManagerClass() {
|
|
2858
|
+
return this.constructor;
|
|
2859
|
+
}
|
|
1917
2860
|
/** The root directory of this storage instance. */
|
|
2861
|
+
/** MemoryReadStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
2862
|
+
_memoryReadStore;
|
|
2863
|
+
get memoryReadStore() {
|
|
2864
|
+
if (!this._memoryReadStore) {
|
|
2865
|
+
this._memoryReadStore = new MemoryReadStore(
|
|
2866
|
+
selfDeps(this)
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
return this._memoryReadStore;
|
|
2870
|
+
}
|
|
2871
|
+
/** EntityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
2872
|
+
_entityStore;
|
|
2873
|
+
get entityStore() {
|
|
2874
|
+
if (!this._entityStore) {
|
|
2875
|
+
this._entityStore = new EntityStore(
|
|
2876
|
+
selfDeps(this)
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
return this._entityStore;
|
|
2880
|
+
}
|
|
1918
2881
|
get dir() {
|
|
1919
2882
|
return this.baseDir;
|
|
1920
2883
|
}
|
|
1921
2884
|
identityFilePath(workspaceDir, namespace) {
|
|
1922
2885
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
1923
|
-
if (!rawNamespace) return
|
|
2886
|
+
if (!rawNamespace) return path3.join(workspaceDir, "IDENTITY.md");
|
|
1924
2887
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1925
|
-
return
|
|
2888
|
+
return path3.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
1926
2889
|
}
|
|
1927
2890
|
versionFilePath(kind) {
|
|
1928
2891
|
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
|
|
1929
|
-
return
|
|
2892
|
+
return path3.join(this.stateDir, fileName);
|
|
1930
2893
|
}
|
|
1931
2894
|
bumpSharedVersion(kind, fallbackMap) {
|
|
1932
2895
|
const filePath = this.versionFilePath(kind);
|
|
@@ -1973,7 +2936,7 @@ var StorageManager = class _StorageManager {
|
|
|
1973
2936
|
// memories.
|
|
1974
2937
|
// -------------------------------------------------------------------------
|
|
1975
2938
|
get wearablesDir() {
|
|
1976
|
-
return
|
|
2939
|
+
return path3.join(this.baseDir, WEARABLES_DIR_NAME);
|
|
1977
2940
|
}
|
|
1978
2941
|
/**
|
|
1979
2942
|
* Resolve the on-disk path for a source/day transcript. Throws on
|
|
@@ -1991,7 +2954,7 @@ var StorageManager = class _StorageManager {
|
|
|
1991
2954
|
`invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`
|
|
1992
2955
|
);
|
|
1993
2956
|
}
|
|
1994
|
-
return
|
|
2957
|
+
return path3.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
1995
2958
|
}
|
|
1996
2959
|
async writeWearableDayTranscript(sourceId, date, serialized) {
|
|
1997
2960
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
@@ -2000,55 +2963,17 @@ var StorageManager = class _StorageManager {
|
|
|
2000
2963
|
/** Read a stored day transcript; null when the day has no file. */
|
|
2001
2964
|
async readWearableDayTranscript(sourceId, date) {
|
|
2002
2965
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
2003
|
-
try {
|
|
2004
|
-
return await readMaybeEncryptedFile(targetPath, this._secureStoreKey, this.baseDir);
|
|
2005
|
-
} catch (err) {
|
|
2006
|
-
if (err.code === "ENOENT") return null;
|
|
2007
|
-
throw err;
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
async listWearableTranscriptDays(sourceId) {
|
|
2015
|
-
const days = [];
|
|
2016
|
-
let sources;
|
|
2017
|
-
if (sourceId !== void 0) {
|
|
2018
|
-
sources = [sourceId];
|
|
2019
|
-
} else {
|
|
2020
|
-
try {
|
|
2021
|
-
const entries = await readdir(this.wearablesDir, { withFileTypes: true });
|
|
2022
|
-
sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2023
|
-
} catch (err) {
|
|
2024
|
-
if (err.code === "ENOENT") return [];
|
|
2025
|
-
throw err;
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
for (const source of sources) {
|
|
2029
|
-
if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
|
|
2030
|
-
let entries;
|
|
2031
|
-
try {
|
|
2032
|
-
entries = await readdir(path.join(this.wearablesDir, source));
|
|
2033
|
-
} catch (err) {
|
|
2034
|
-
if (err.code === "ENOENT") continue;
|
|
2035
|
-
throw err;
|
|
2036
|
-
}
|
|
2037
|
-
for (const entry of entries) {
|
|
2038
|
-
if (!entry.endsWith(".md")) continue;
|
|
2039
|
-
const date = entry.slice(0, -3);
|
|
2040
|
-
if (!isValidTranscriptDate(date)) continue;
|
|
2041
|
-
days.push({ source, date });
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
days.sort((a, b) => {
|
|
2045
|
-
if (a.date > b.date) return -1;
|
|
2046
|
-
if (a.date < b.date) return 1;
|
|
2047
|
-
if (a.source < b.source) return -1;
|
|
2048
|
-
if (a.source > b.source) return 1;
|
|
2049
|
-
return 0;
|
|
2050
|
-
});
|
|
2051
|
-
return days;
|
|
2966
|
+
try {
|
|
2967
|
+
return await readMaybeEncryptedFile(targetPath, this._secureStoreKey, this.baseDir);
|
|
2968
|
+
} catch (err) {
|
|
2969
|
+
if (err.code === "ENOENT") return null;
|
|
2970
|
+
throw err;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
async listWearableTranscriptDays(sourceId) {
|
|
2974
|
+
return this.memoryReadStore.listWearableTranscriptDays(
|
|
2975
|
+
sourceId
|
|
2976
|
+
);
|
|
2052
2977
|
}
|
|
2053
2978
|
/**
|
|
2054
2979
|
* Locate a wearable-sourced memory by exact (trimmed) content,
|
|
@@ -2116,25 +3041,25 @@ var StorageManager = class _StorageManager {
|
|
|
2116
3041
|
});
|
|
2117
3042
|
}
|
|
2118
3043
|
get factsDir() {
|
|
2119
|
-
return
|
|
3044
|
+
return path3.join(this.baseDir, "facts");
|
|
2120
3045
|
}
|
|
2121
3046
|
get correctionsDir() {
|
|
2122
|
-
return
|
|
3047
|
+
return path3.join(this.baseDir, "corrections");
|
|
2123
3048
|
}
|
|
2124
3049
|
get proceduresDir() {
|
|
2125
|
-
return
|
|
3050
|
+
return path3.join(this.baseDir, "procedures");
|
|
2126
3051
|
}
|
|
2127
3052
|
get reasoningTracesDir() {
|
|
2128
|
-
return
|
|
3053
|
+
return path3.join(this.baseDir, "reasoning-traces");
|
|
2129
3054
|
}
|
|
2130
3055
|
get entitiesDir() {
|
|
2131
|
-
return
|
|
3056
|
+
return path3.join(this.baseDir, "entities");
|
|
2132
3057
|
}
|
|
2133
3058
|
resolveEntityFilePath(name) {
|
|
2134
3059
|
if (typeof name !== "string") return null;
|
|
2135
|
-
const filePath =
|
|
2136
|
-
const relative =
|
|
2137
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
3060
|
+
const filePath = path3.resolve(this.entitiesDir, `${name}.md`);
|
|
3061
|
+
const relative = path3.relative(this.entitiesDir, filePath);
|
|
3062
|
+
if (relative === "" || relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
2138
3063
|
return null;
|
|
2139
3064
|
}
|
|
2140
3065
|
return filePath;
|
|
@@ -2149,10 +3074,10 @@ var StorageManager = class _StorageManager {
|
|
|
2149
3074
|
return writeMaybeEncryptedFileFromChunks(filePath, chunks, this.resolveWriteKey(), {}, this.baseDir).then(() => this.notifyCatalogWrite());
|
|
2150
3075
|
}
|
|
2151
3076
|
assertManagedStoragePath(filePath, method) {
|
|
2152
|
-
const resolved =
|
|
2153
|
-
const base =
|
|
2154
|
-
const rel =
|
|
2155
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
3077
|
+
const resolved = path3.resolve(filePath);
|
|
3078
|
+
const base = path3.resolve(this.baseDir);
|
|
3079
|
+
const rel = path3.relative(base, resolved);
|
|
3080
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel)) {
|
|
2156
3081
|
throw new Error(`${method}: file path escapes memory dir`);
|
|
2157
3082
|
}
|
|
2158
3083
|
return resolved;
|
|
@@ -2163,8 +3088,8 @@ var StorageManager = class _StorageManager {
|
|
|
2163
3088
|
}
|
|
2164
3089
|
async digestOfflineSyncFile(filePath) {
|
|
2165
3090
|
const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
|
|
2166
|
-
const st = await
|
|
2167
|
-
const relPath =
|
|
3091
|
+
const st = await stat2(target);
|
|
3092
|
+
const relPath = path3.relative(this.baseDir, target).split(path3.sep).join("/");
|
|
2168
3093
|
const cache = await this.loadOfflineSyncDigestCache();
|
|
2169
3094
|
const cached = cache.get(relPath);
|
|
2170
3095
|
if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
|
|
@@ -2225,7 +3150,7 @@ var StorageManager = class _StorageManager {
|
|
|
2225
3150
|
const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
|
|
2226
3151
|
const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
|
|
2227
3152
|
const encrypted = record.encrypted === true;
|
|
2228
|
-
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") ||
|
|
3153
|
+
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path3.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
|
|
2229
3154
|
continue;
|
|
2230
3155
|
}
|
|
2231
3156
|
cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
|
|
@@ -2267,7 +3192,7 @@ var StorageManager = class _StorageManager {
|
|
|
2267
3192
|
const cache = this.offlineSyncDigestCache;
|
|
2268
3193
|
if (!cache) return;
|
|
2269
3194
|
const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
2270
|
-
await mkdir(
|
|
3195
|
+
await mkdir(path3.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
2271
3196
|
await writeFile(
|
|
2272
3197
|
this.offlineSyncDigestCachePath,
|
|
2273
3198
|
`${JSON.stringify({ version: 1, entries })}
|
|
@@ -2306,7 +3231,7 @@ var StorageManager = class _StorageManager {
|
|
|
2306
3231
|
}
|
|
2307
3232
|
async deleteOfflineSyncFile(filePath) {
|
|
2308
3233
|
const target = this.assertManagedStoragePath(filePath, "storage.deleteOfflineSyncFile");
|
|
2309
|
-
await
|
|
3234
|
+
await unlink2(target).catch((error) => {
|
|
2310
3235
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2311
3236
|
throw error;
|
|
2312
3237
|
});
|
|
@@ -2316,14 +3241,14 @@ var StorageManager = class _StorageManager {
|
|
|
2316
3241
|
this.invalidateAllMemoriesCache();
|
|
2317
3242
|
this.invalidateKnowledgeIndexCache();
|
|
2318
3243
|
this.factHashIndexAuthoritative = false;
|
|
2319
|
-
await
|
|
3244
|
+
await unlink2(this.factHashIndexReadyPath).catch((error) => {
|
|
2320
3245
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2321
3246
|
throw error;
|
|
2322
3247
|
});
|
|
2323
|
-
if (filePath.includes(`${
|
|
3248
|
+
if (filePath.includes(`${path3.sep}cold${path3.sep}`)) {
|
|
2324
3249
|
this.invalidateColdMemoriesCache();
|
|
2325
3250
|
}
|
|
2326
|
-
if (filePath.includes(`${
|
|
3251
|
+
if (filePath.includes(`${path3.sep}artifacts${path3.sep}`)) {
|
|
2327
3252
|
this.bumpArtifactWriteVersion();
|
|
2328
3253
|
}
|
|
2329
3254
|
this.bumpMemoryStatusVersion();
|
|
@@ -2351,7 +3276,7 @@ var StorageManager = class _StorageManager {
|
|
|
2351
3276
|
}
|
|
2352
3277
|
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
2353
3278
|
const writeKey = this.resolveWriteKey();
|
|
2354
|
-
await mkdir(
|
|
3279
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
2355
3280
|
if (writeKey === null) {
|
|
2356
3281
|
try {
|
|
2357
3282
|
if (isEncryptedFile(await readFile(filePath))) {
|
|
@@ -2377,16 +3302,16 @@ var StorageManager = class _StorageManager {
|
|
|
2377
3302
|
this.notifyCatalogWrite();
|
|
2378
3303
|
}
|
|
2379
3304
|
get stateDir() {
|
|
2380
|
-
return
|
|
3305
|
+
return path3.join(this.baseDir, "state");
|
|
2381
3306
|
}
|
|
2382
3307
|
get offlineSyncDigestCachePath() {
|
|
2383
|
-
return
|
|
3308
|
+
return path3.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
|
|
2384
3309
|
}
|
|
2385
3310
|
get entitySynthesisQueuePath() {
|
|
2386
|
-
return
|
|
3311
|
+
return path3.join(this.stateDir, "entity-synthesis-queue.json");
|
|
2387
3312
|
}
|
|
2388
3313
|
get factHashIndexReadyPath() {
|
|
2389
|
-
return
|
|
3314
|
+
return path3.join(this.stateDir, "fact-hashes.ready");
|
|
2390
3315
|
}
|
|
2391
3316
|
// ── Tombstone store access (issue #1579) ─────────────────────────────────
|
|
2392
3317
|
/**
|
|
@@ -2396,7 +3321,7 @@ var StorageManager = class _StorageManager {
|
|
|
2396
3321
|
* helpers are injected into the store).
|
|
2397
3322
|
*/
|
|
2398
3323
|
get tombstonesPath() {
|
|
2399
|
-
return
|
|
3324
|
+
return path3.join(this.stateDir, "tombstones.jsonl");
|
|
2400
3325
|
}
|
|
2401
3326
|
/**
|
|
2402
3327
|
* Configure the tombstone invariant for this storage instance. Installed
|
|
@@ -2595,7 +3520,7 @@ var StorageManager = class _StorageManager {
|
|
|
2595
3520
|
);
|
|
2596
3521
|
}
|
|
2597
3522
|
await factHashIndex.save();
|
|
2598
|
-
await mkdir(
|
|
3523
|
+
await mkdir(path3.dirname(this.factHashIndexReadyPath), { recursive: true });
|
|
2599
3524
|
await writeFile(this.factHashIndexReadyPath, "v1\n", "utf-8");
|
|
2600
3525
|
this.factHashIndexAuthoritative = true;
|
|
2601
3526
|
})().finally(() => {
|
|
@@ -2604,55 +3529,55 @@ var StorageManager = class _StorageManager {
|
|
|
2604
3529
|
await this.factHashIndexAuthoritativePromise;
|
|
2605
3530
|
}
|
|
2606
3531
|
get questionsDir() {
|
|
2607
|
-
return
|
|
3532
|
+
return path3.join(this.baseDir, "questions");
|
|
2608
3533
|
}
|
|
2609
3534
|
get artifactsDir() {
|
|
2610
|
-
return
|
|
3535
|
+
return path3.join(this.baseDir, "artifacts");
|
|
2611
3536
|
}
|
|
2612
3537
|
get identityDir() {
|
|
2613
|
-
return
|
|
3538
|
+
return path3.join(this.baseDir, "identity");
|
|
2614
3539
|
}
|
|
2615
3540
|
get identityAnchorPath() {
|
|
2616
|
-
return
|
|
3541
|
+
return path3.join(this.identityDir, "identity-anchor.md");
|
|
2617
3542
|
}
|
|
2618
3543
|
get identityIncidentsDir() {
|
|
2619
|
-
return
|
|
3544
|
+
return path3.join(this.identityDir, "incidents");
|
|
2620
3545
|
}
|
|
2621
3546
|
get identityAuditsWeeklyDir() {
|
|
2622
|
-
return
|
|
3547
|
+
return path3.join(this.identityDir, "audits", "weekly");
|
|
2623
3548
|
}
|
|
2624
3549
|
get identityAuditsMonthlyDir() {
|
|
2625
|
-
return
|
|
3550
|
+
return path3.join(this.identityDir, "audits", "monthly");
|
|
2626
3551
|
}
|
|
2627
3552
|
get identityImprovementLoopsPath() {
|
|
2628
|
-
return
|
|
3553
|
+
return path3.join(this.identityDir, "improvement-loops.md");
|
|
2629
3554
|
}
|
|
2630
3555
|
get identityReflectionsPath() {
|
|
2631
|
-
return
|
|
3556
|
+
return path3.join(this.identityDir, "reflections.md");
|
|
2632
3557
|
}
|
|
2633
3558
|
get profilePath() {
|
|
2634
|
-
return
|
|
3559
|
+
return path3.join(this.baseDir, "profile.md");
|
|
2635
3560
|
}
|
|
2636
3561
|
get memoryActionsPath() {
|
|
2637
|
-
return
|
|
3562
|
+
return path3.join(this.stateDir, "memory-actions.jsonl");
|
|
2638
3563
|
}
|
|
2639
3564
|
get memoryLifecycleLedgerPath() {
|
|
2640
|
-
return
|
|
3565
|
+
return path3.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
2641
3566
|
}
|
|
2642
3567
|
get compressionGuidelinesPath() {
|
|
2643
|
-
return
|
|
3568
|
+
return path3.join(this.stateDir, "compression-guidelines.md");
|
|
2644
3569
|
}
|
|
2645
3570
|
get compressionGuidelineDraftPath() {
|
|
2646
|
-
return
|
|
3571
|
+
return path3.join(this.stateDir, "compression-guidelines.draft.md");
|
|
2647
3572
|
}
|
|
2648
3573
|
get compressionGuidelineStatePath() {
|
|
2649
|
-
return
|
|
3574
|
+
return path3.join(this.stateDir, "compression-guideline-state.json");
|
|
2650
3575
|
}
|
|
2651
3576
|
get compressionGuidelineDraftStatePath() {
|
|
2652
|
-
return
|
|
3577
|
+
return path3.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
2653
3578
|
}
|
|
2654
3579
|
get behaviorSignalsPath() {
|
|
2655
|
-
return
|
|
3580
|
+
return path3.join(this.stateDir, "behavior-signals.jsonl");
|
|
2656
3581
|
}
|
|
2657
3582
|
/**
|
|
2658
3583
|
* Buffer surprise telemetry ledger (issue #563 PR 3).
|
|
@@ -2665,7 +3590,7 @@ var StorageManager = class _StorageManager {
|
|
|
2665
3590
|
* governance sweeps can treat it uniformly.
|
|
2666
3591
|
*/
|
|
2667
3592
|
get bufferSurpriseLedgerPath() {
|
|
2668
|
-
return
|
|
3593
|
+
return path3.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
2669
3594
|
}
|
|
2670
3595
|
/**
|
|
2671
3596
|
* Entity alias table loaded from THIS store's config/aliases.json.
|
|
@@ -2675,9 +3600,11 @@ var StorageManager = class _StorageManager {
|
|
|
2675
3600
|
* store loaded last rewrite every other store's canonical entity ids.
|
|
2676
3601
|
*/
|
|
2677
3602
|
userAliases = {};
|
|
2678
|
-
/** Normalize an entity name using this store's alias table. */
|
|
2679
3603
|
normalizeEntityName(raw, type) {
|
|
2680
|
-
return normalizeEntityName(
|
|
3604
|
+
return this.entityStore.normalizeEntityName(
|
|
3605
|
+
raw,
|
|
3606
|
+
type
|
|
3607
|
+
);
|
|
2681
3608
|
}
|
|
2682
3609
|
/**
|
|
2683
3610
|
* Read-only view of this store's user alias table, for call sites that
|
|
@@ -2697,7 +3624,7 @@ var StorageManager = class _StorageManager {
|
|
|
2697
3624
|
this.loadAliasesSync();
|
|
2698
3625
|
}
|
|
2699
3626
|
loadAliasesSync() {
|
|
2700
|
-
const aliasPath =
|
|
3627
|
+
const aliasPath = path3.join(this.baseDir, "config", "aliases.json");
|
|
2701
3628
|
this.userAliases = {};
|
|
2702
3629
|
try {
|
|
2703
3630
|
const raw = readFileSync(aliasPath, "utf-8");
|
|
@@ -2722,9 +3649,9 @@ var StorageManager = class _StorageManager {
|
|
|
2722
3649
|
}
|
|
2723
3650
|
async ensureDirectories() {
|
|
2724
3651
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2725
|
-
await mkdir(
|
|
2726
|
-
await mkdir(
|
|
2727
|
-
await mkdir(
|
|
3652
|
+
await mkdir(path3.join(this.factsDir, today), { recursive: true });
|
|
3653
|
+
await mkdir(path3.join(this.proceduresDir, today), { recursive: true });
|
|
3654
|
+
await mkdir(path3.join(this.reasoningTracesDir, today), { recursive: true });
|
|
2728
3655
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
2729
3656
|
await mkdir(this.entitiesDir, { recursive: true });
|
|
2730
3657
|
await mkdir(this.stateDir, { recursive: true });
|
|
@@ -2734,7 +3661,7 @@ var StorageManager = class _StorageManager {
|
|
|
2734
3661
|
await mkdir(this.identityIncidentsDir, { recursive: true });
|
|
2735
3662
|
await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
|
|
2736
3663
|
await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
|
|
2737
|
-
await mkdir(
|
|
3664
|
+
await mkdir(path3.join(this.baseDir, "config"), { recursive: true });
|
|
2738
3665
|
}
|
|
2739
3666
|
/**
|
|
2740
3667
|
* Resolve the on-disk write path for a memory of the given category, creating
|
|
@@ -2751,11 +3678,11 @@ var StorageManager = class _StorageManager {
|
|
|
2751
3678
|
async resolveCategoryWritePath(category, id, today) {
|
|
2752
3679
|
if (category === "correction") {
|
|
2753
3680
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
2754
|
-
return
|
|
3681
|
+
return path3.join(this.correctionsDir, `${id}.md`);
|
|
2755
3682
|
}
|
|
2756
|
-
const datedDir =
|
|
3683
|
+
const datedDir = path3.join(getCategoryDir(this.baseDir, category), today);
|
|
2757
3684
|
await mkdir(datedDir, { recursive: true });
|
|
2758
|
-
return
|
|
3685
|
+
return path3.join(datedDir, `${id}.md`);
|
|
2759
3686
|
}
|
|
2760
3687
|
async writeMemory(category, content, options = {}) {
|
|
2761
3688
|
await this.ensureDirectories();
|
|
@@ -3005,7 +3932,7 @@ ${sanitized.text}
|
|
|
3005
3932
|
await this.ensureDirectories();
|
|
3006
3933
|
const now = /* @__PURE__ */ new Date();
|
|
3007
3934
|
const day = now.toISOString().slice(0, 10);
|
|
3008
|
-
const dir =
|
|
3935
|
+
const dir = path3.join(this.artifactsDir, day);
|
|
3009
3936
|
await mkdir(dir, { recursive: true });
|
|
3010
3937
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
3011
3938
|
const fm = {
|
|
@@ -3029,7 +3956,7 @@ ${sanitized.text}
|
|
|
3029
3956
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
3030
3957
|
return "";
|
|
3031
3958
|
}
|
|
3032
|
-
const filePath =
|
|
3959
|
+
const filePath = path3.join(dir, `${id}.md`);
|
|
3033
3960
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
|
|
3034
3961
|
|
|
3035
3962
|
${sanitized.text}
|
|
@@ -3048,45 +3975,7 @@ ${sanitized.text}
|
|
|
3048
3975
|
return id;
|
|
3049
3976
|
}
|
|
3050
3977
|
async readAllArtifactsCached() {
|
|
3051
|
-
|
|
3052
|
-
return this.artifactIndexCache.memories;
|
|
3053
|
-
}
|
|
3054
|
-
const scanArtifacts = async () => {
|
|
3055
|
-
const artifacts = [];
|
|
3056
|
-
const readDir = async (dir) => {
|
|
3057
|
-
try {
|
|
3058
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
3059
|
-
for (const entry of entries) {
|
|
3060
|
-
const fullPath = path.join(dir, entry.name);
|
|
3061
|
-
if (entry.isDirectory()) {
|
|
3062
|
-
await readDir(fullPath);
|
|
3063
|
-
continue;
|
|
3064
|
-
}
|
|
3065
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
3066
|
-
const memory = await this.readMemoryByPath(fullPath);
|
|
3067
|
-
if (!memory) continue;
|
|
3068
|
-
artifacts.push(memory);
|
|
3069
|
-
}
|
|
3070
|
-
} catch {
|
|
3071
|
-
}
|
|
3072
|
-
};
|
|
3073
|
-
await readDir(this.artifactsDir);
|
|
3074
|
-
return artifacts;
|
|
3075
|
-
};
|
|
3076
|
-
const MAX_REBUILD_RETRIES = 2;
|
|
3077
|
-
let latestArtifacts = [];
|
|
3078
|
-
for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
|
|
3079
|
-
const versionBefore = this.getArtifactWriteVersion();
|
|
3080
|
-
const artifacts = await scanArtifacts();
|
|
3081
|
-
const versionAfter = this.getArtifactWriteVersion();
|
|
3082
|
-
latestArtifacts = artifacts;
|
|
3083
|
-
if (versionAfter === versionBefore) {
|
|
3084
|
-
this.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
|
|
3085
|
-
return artifacts;
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
this.artifactIndexCache = null;
|
|
3089
|
-
return latestArtifacts;
|
|
3978
|
+
return this.memoryReadStore.readAllArtifactsCached();
|
|
3090
3979
|
}
|
|
3091
3980
|
async searchArtifacts(query, maxResults) {
|
|
3092
3981
|
const tokens = tokenizeArtifactSearchText(query);
|
|
@@ -3106,107 +3995,12 @@ ${sanitized.text}
|
|
|
3106
3995
|
return hits.slice(0, maxResults).map((h) => h.memory);
|
|
3107
3996
|
}
|
|
3108
3997
|
async writeEntity(name, type, facts, options = {}) {
|
|
3109
|
-
|
|
3110
|
-
if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
|
|
3111
|
-
log.warn("writeEntity: invalid entity payload, skipping", {
|
|
3112
|
-
nameType: typeof name,
|
|
3113
|
-
typeType: typeof type
|
|
3114
|
-
});
|
|
3115
|
-
return "";
|
|
3116
|
-
}
|
|
3117
|
-
const safeFacts = Array.isArray(facts) ? [...new Set(
|
|
3118
|
-
facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
|
|
3119
|
-
)] : [];
|
|
3120
|
-
let normalized = this.normalizeEntityName(name, type);
|
|
3121
|
-
const match = await this.findMatchingEntity(name, type);
|
|
3122
|
-
if (match && match !== normalized) {
|
|
3123
|
-
log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
|
|
3124
|
-
normalized = match;
|
|
3125
|
-
}
|
|
3126
|
-
const filePath = path.join(this.entitiesDir, `${normalized}.md`);
|
|
3127
|
-
let entity = {
|
|
3998
|
+
return this.entityStore.writeEntity(
|
|
3128
3999
|
name,
|
|
3129
4000
|
type,
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
facts: [],
|
|
3133
|
-
summary: void 0,
|
|
3134
|
-
synthesis: void 0,
|
|
3135
|
-
synthesisUpdatedAt: void 0,
|
|
3136
|
-
synthesisVersion: void 0,
|
|
3137
|
-
synthesisStructuredFactCount: void 0,
|
|
3138
|
-
synthesisStructuredFactDigest: void 0,
|
|
3139
|
-
timeline: [],
|
|
3140
|
-
relationships: [],
|
|
3141
|
-
activity: [],
|
|
3142
|
-
aliases: []
|
|
3143
|
-
};
|
|
3144
|
-
try {
|
|
3145
|
-
const existing = await this.readStorageSecureFile(filePath);
|
|
3146
|
-
entity = parseEntityFile(existing, this.entitySchemas);
|
|
3147
|
-
} catch (err) {
|
|
3148
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
3149
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3150
|
-
}
|
|
3151
|
-
const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
|
|
3152
|
-
const source = options.source?.trim() || void 0;
|
|
3153
|
-
const sessionKey = options.sessionKey?.trim() || void 0;
|
|
3154
|
-
const principal = options.principal?.trim() || void 0;
|
|
3155
|
-
const structuredSectionMap = new Map(
|
|
3156
|
-
(entity.structuredSections ?? []).map((section) => [section.key, {
|
|
3157
|
-
...section,
|
|
3158
|
-
facts: [...section.facts]
|
|
3159
|
-
}])
|
|
3160
|
-
);
|
|
3161
|
-
for (const section of options.structuredSections ?? []) {
|
|
3162
|
-
const normalizedSection = normalizeEntityStructuredSection(type, section, this.entitySchemas);
|
|
3163
|
-
const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
|
|
3164
|
-
if (normalizedFacts.length === 0) continue;
|
|
3165
|
-
const existingSection = structuredSectionMap.get(normalizedSection.key);
|
|
3166
|
-
if (!existingSection) {
|
|
3167
|
-
structuredSectionMap.set(normalizedSection.key, {
|
|
3168
|
-
key: normalizedSection.key,
|
|
3169
|
-
title: normalizedSection.title,
|
|
3170
|
-
facts: normalizedFacts
|
|
3171
|
-
});
|
|
3172
|
-
continue;
|
|
3173
|
-
}
|
|
3174
|
-
existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
|
|
3175
|
-
if (!existingSection.title.trim() && normalizedSection.title.trim()) {
|
|
3176
|
-
existingSection.title = normalizedSection.title;
|
|
3177
|
-
}
|
|
3178
|
-
}
|
|
3179
|
-
for (const fact of safeFacts) {
|
|
3180
|
-
const nextEntry = {
|
|
3181
|
-
timestamp,
|
|
3182
|
-
text: fact,
|
|
3183
|
-
...source ? { source } : {},
|
|
3184
|
-
...sessionKey ? { sessionKey } : {},
|
|
3185
|
-
...principal ? { principal } : {}
|
|
3186
|
-
};
|
|
3187
|
-
const alreadyPresent = entity.timeline.some(
|
|
3188
|
-
(entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
|
|
3189
|
-
);
|
|
3190
|
-
if (alreadyPresent) continue;
|
|
3191
|
-
entity.timeline.push(nextEntry);
|
|
3192
|
-
}
|
|
3193
|
-
entity.structuredSections = sortStructuredSectionsBySchema(
|
|
3194
|
-
type,
|
|
3195
|
-
Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
|
|
3196
|
-
this.entitySchemas
|
|
4001
|
+
facts,
|
|
4002
|
+
options
|
|
3197
4003
|
);
|
|
3198
|
-
entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
|
|
3199
|
-
entity.summary = entity.synthesis || entity.summary;
|
|
3200
|
-
entity.name = name;
|
|
3201
|
-
entity.type = type;
|
|
3202
|
-
entity.created = entity.created || timestamp;
|
|
3203
|
-
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
3204
|
-
await this.snapshotBeforeWrite(filePath, "write");
|
|
3205
|
-
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
3206
|
-
this.invalidateKnowledgeIndexCache();
|
|
3207
|
-
this.bumpMemoryStatusVersion();
|
|
3208
|
-
log.debug(`wrote entity ${normalized}`);
|
|
3209
|
-
return normalized;
|
|
3210
4004
|
}
|
|
3211
4005
|
async readProfile() {
|
|
3212
4006
|
try {
|
|
@@ -3364,7 +4158,7 @@ ${sanitized.text}
|
|
|
3364
4158
|
* per-process in-memory cache safe across process boundaries.
|
|
3365
4159
|
*/
|
|
3366
4160
|
invalidateColdMemoriesCache() {
|
|
3367
|
-
const coldRoot =
|
|
4161
|
+
const coldRoot = path3.join(this.baseDir, "cold");
|
|
3368
4162
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
3369
4163
|
this.bumpColdWriteVersion();
|
|
3370
4164
|
invalidateAllForDir(this.baseDir);
|
|
@@ -3387,45 +4181,7 @@ ${sanitized.text}
|
|
|
3387
4181
|
return Math.max(1, Math.floor(batchSize));
|
|
3388
4182
|
}
|
|
3389
4183
|
async collectActiveMemoryPaths() {
|
|
3390
|
-
|
|
3391
|
-
let memoryRootReal;
|
|
3392
|
-
try {
|
|
3393
|
-
memoryRootReal = await realpath(this.baseDir);
|
|
3394
|
-
} catch {
|
|
3395
|
-
return filePaths;
|
|
3396
|
-
}
|
|
3397
|
-
const collectPaths = async (dir) => {
|
|
3398
|
-
let entries;
|
|
3399
|
-
try {
|
|
3400
|
-
const dirStat = await lstat(dir);
|
|
3401
|
-
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
|
|
3402
|
-
assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
|
|
3403
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
3404
|
-
} catch {
|
|
3405
|
-
return;
|
|
3406
|
-
}
|
|
3407
|
-
const subdirs = [];
|
|
3408
|
-
for (const entry of entries) {
|
|
3409
|
-
if (entry.isSymbolicLink()) continue;
|
|
3410
|
-
const fullPath = path.join(dir, entry.name);
|
|
3411
|
-
if (entry.isDirectory()) {
|
|
3412
|
-
subdirs.push(fullPath);
|
|
3413
|
-
} else if (entry.name.endsWith(".md")) {
|
|
3414
|
-
try {
|
|
3415
|
-
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
3416
|
-
filePaths.push(fullPath);
|
|
3417
|
-
} catch {
|
|
3418
|
-
}
|
|
3419
|
-
}
|
|
3420
|
-
}
|
|
3421
|
-
for (const subdir of subdirs) {
|
|
3422
|
-
await collectPaths(subdir);
|
|
3423
|
-
}
|
|
3424
|
-
};
|
|
3425
|
-
for (const dir of RECALL_FALLBACK_DIRS) {
|
|
3426
|
-
await collectPaths(path.join(this.baseDir, dir));
|
|
3427
|
-
}
|
|
3428
|
-
return filePaths;
|
|
4184
|
+
return this.memoryReadStore.collectActiveMemoryPaths();
|
|
3429
4185
|
}
|
|
3430
4186
|
async readParsedMemoriesFromPaths(filePaths, batchSize) {
|
|
3431
4187
|
if (filePaths.length === 0) return [];
|
|
@@ -3480,7 +4236,7 @@ ${sanitized.text}
|
|
|
3480
4236
|
return updatedMs >= updatedAfterMs ? filePath : null;
|
|
3481
4237
|
}
|
|
3482
4238
|
try {
|
|
3483
|
-
const fileStat = await
|
|
4239
|
+
const fileStat = await stat2(filePath);
|
|
3484
4240
|
return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
|
|
3485
4241
|
} catch {
|
|
3486
4242
|
return filePath;
|
|
@@ -3492,7 +4248,7 @@ ${sanitized.text}
|
|
|
3492
4248
|
const correctionPaths = [];
|
|
3493
4249
|
const factPaths = [];
|
|
3494
4250
|
for (const filePath of filePaths) {
|
|
3495
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
4251
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path3.sep}`)) {
|
|
3496
4252
|
correctionPaths.push(filePath);
|
|
3497
4253
|
} else {
|
|
3498
4254
|
factPaths.push(filePath);
|
|
@@ -3534,103 +4290,18 @@ ${sanitized.text}
|
|
|
3534
4290
|
}
|
|
3535
4291
|
return { memories, filePaths };
|
|
3536
4292
|
}
|
|
3537
|
-
async readMemoriesWindow(options = {}) {
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
const
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
}
|
|
3550
|
-
const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
|
|
3551
|
-
const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
|
|
3552
|
-
const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
|
|
3553
|
-
const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
|
|
3554
|
-
const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
|
|
3555
|
-
memories: await this.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
|
|
3556
|
-
filePaths: candidateBatchPaths
|
|
3557
|
-
} : await this.readWindowBoundedBatch(
|
|
3558
|
-
candidateBatchPaths,
|
|
3559
|
-
remainingSlots,
|
|
3560
|
-
remainingInspectionBudget ?? remainingSlots,
|
|
3561
|
-
normalizedBatchSize
|
|
3562
|
-
);
|
|
3563
|
-
selectedPaths.push(...parsedCandidatePaths);
|
|
3564
|
-
for (const memory of batchMemories) {
|
|
3565
|
-
memories.push(memory);
|
|
3566
|
-
if (maxMemories !== void 0 && memories.length >= maxMemories) {
|
|
3567
|
-
return { memories, filePaths: selectedPaths };
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
return { memories, filePaths: selectedPaths };
|
|
3572
|
-
}
|
|
3573
|
-
async _readAllMemoriesFromDisk() {
|
|
3574
|
-
const filePaths = await this.collectActiveMemoryPaths();
|
|
3575
|
-
return this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
3576
|
-
}
|
|
3577
|
-
/**
|
|
3578
|
-
* Read all memories from the cold tier by scanning the entire cold/ root
|
|
3579
|
-
* tree. Previously this only scanned cold/facts/ and cold/corrections/, but
|
|
3580
|
-
* structuredAttributes can appear on any MemoryCategory (preference, decision,
|
|
3581
|
-
* entity, etc.). buildTierMemoryPath now routes each category to its own
|
|
3582
|
-
* cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
|
|
3583
|
-
* #1546), so cold decisions/preferences/... live outside cold/facts/.
|
|
3584
|
-
* Scanning the full coldRoot covers every category dir and guards against
|
|
3585
|
-
* files placed in unexpected subdirectories during manual operations or future
|
|
3586
|
-
* refactors.
|
|
3587
|
-
*
|
|
3588
|
-
* Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
|
|
3589
|
-
* corrections/ was a narrower-than-necessary subset of the cold directory
|
|
3590
|
-
* tree. Correctness trumps the minor performance difference — cold scans
|
|
3591
|
-
* already happen at most once per supersession write.
|
|
3592
|
-
*
|
|
3593
|
-
* Used by applyTemporalSupersession so that memories already demoted to
|
|
3594
|
-
* cold/ can still be marked superseded when a newer hot fact arrives.
|
|
3595
|
-
*
|
|
3596
|
-
* Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
|
|
3597
|
-
* structured-attribute writes in the same burst reuse the cached result
|
|
3598
|
-
* instead of re-scanning the cold tree on every call. The cache is
|
|
3599
|
-
* invalidated whenever a write calls invalidateAllMemoriesCache() (which
|
|
3600
|
-
* covers any hot→cold demotion that changes cold-tier contents) and
|
|
3601
|
-
* expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
|
|
3602
|
-
*/
|
|
3603
|
-
async readAllColdMemories() {
|
|
3604
|
-
const coldRoot = this.resolveTierRootDir("cold");
|
|
3605
|
-
const currentColdVersion = this.readColdWriteVersion();
|
|
3606
|
-
const cached = _StorageManager.coldMemoriesCache.get(coldRoot);
|
|
3607
|
-
if (cached && Date.now() - cached.loadedAt < _StorageManager.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
|
|
3608
|
-
return cached.memories;
|
|
3609
|
-
}
|
|
3610
|
-
const filePaths = [];
|
|
3611
|
-
const collectPaths = async (dir) => {
|
|
3612
|
-
try {
|
|
3613
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
3614
|
-
const subdirs = [];
|
|
3615
|
-
for (const entry of entries) {
|
|
3616
|
-
const fullPath = path.join(dir, entry.name);
|
|
3617
|
-
if (entry.isDirectory()) {
|
|
3618
|
-
subdirs.push(fullPath);
|
|
3619
|
-
} else if (entry.name.endsWith(".md")) {
|
|
3620
|
-
filePaths.push(fullPath);
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
for (const subdir of subdirs) {
|
|
3624
|
-
await collectPaths(subdir);
|
|
3625
|
-
}
|
|
3626
|
-
} catch {
|
|
3627
|
-
}
|
|
3628
|
-
};
|
|
3629
|
-
await collectPaths(coldRoot);
|
|
3630
|
-
const memories = await this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
3631
|
-
_StorageManager.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
|
|
3632
|
-
return memories;
|
|
3633
|
-
}
|
|
4293
|
+
async readMemoriesWindow(options = {}) {
|
|
4294
|
+
return this.memoryReadStore.readMemoriesWindow(
|
|
4295
|
+
options
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
4298
|
+
async _readAllMemoriesFromDisk() {
|
|
4299
|
+
const filePaths = await this.collectActiveMemoryPaths();
|
|
4300
|
+
return this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
4301
|
+
}
|
|
4302
|
+
async readAllColdMemories() {
|
|
4303
|
+
return this.memoryReadStore.readAllColdMemories();
|
|
4304
|
+
}
|
|
3634
4305
|
/**
|
|
3635
4306
|
* Read archived memory markdown files under archive/.
|
|
3636
4307
|
* Used by long-term recall fallback when hot recall has no hits.
|
|
@@ -3640,9 +4311,9 @@ ${sanitized.text}
|
|
|
3640
4311
|
const root = this.archiveDir;
|
|
3641
4312
|
const readDir = async (dir) => {
|
|
3642
4313
|
try {
|
|
3643
|
-
const entries = await
|
|
4314
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3644
4315
|
for (const entry of entries) {
|
|
3645
|
-
const fullPath =
|
|
4316
|
+
const fullPath = path3.join(dir, entry.name);
|
|
3646
4317
|
if (entry.isDirectory()) {
|
|
3647
4318
|
await readDir(fullPath);
|
|
3648
4319
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -3671,51 +4342,13 @@ ${sanitized.text}
|
|
|
3671
4342
|
await readDir(root);
|
|
3672
4343
|
return memories;
|
|
3673
4344
|
}
|
|
3674
|
-
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
3675
4345
|
async readMemoryByPath(filePath) {
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
if (parsed) {
|
|
3680
|
-
return {
|
|
3681
|
-
path: filePath,
|
|
3682
|
-
frontmatter: normalizeFrontmatterForPath(
|
|
3683
|
-
parsed.frontmatter,
|
|
3684
|
-
toMemoryPathRel(this.baseDir, filePath),
|
|
3685
|
-
parsed.content
|
|
3686
|
-
),
|
|
3687
|
-
content: parsed.content
|
|
3688
|
-
};
|
|
3689
|
-
}
|
|
3690
|
-
const normalizedPath = filePath.split(path.sep).join("/");
|
|
3691
|
-
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
3692
|
-
const entity = parseEntityFile(raw, this.entitySchemas);
|
|
3693
|
-
if (!entity.name) return null;
|
|
3694
|
-
const nameWithoutExt = path.basename(filePath, ".md");
|
|
3695
|
-
const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3696
|
-
return {
|
|
3697
|
-
path: filePath,
|
|
3698
|
-
frontmatter: {
|
|
3699
|
-
id: nameWithoutExt,
|
|
3700
|
-
category: "entity",
|
|
3701
|
-
created: fileMtime,
|
|
3702
|
-
updated: fileMtime,
|
|
3703
|
-
source: "entity_extraction",
|
|
3704
|
-
confidence: 0.9,
|
|
3705
|
-
confidenceTier: confidenceTier(0.9),
|
|
3706
|
-
tags: entity.type ? [entity.type] : []
|
|
3707
|
-
},
|
|
3708
|
-
content: raw
|
|
3709
|
-
};
|
|
3710
|
-
}
|
|
3711
|
-
return null;
|
|
3712
|
-
} catch (err) {
|
|
3713
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
3714
|
-
return null;
|
|
3715
|
-
}
|
|
4346
|
+
return this.memoryReadStore.readMemoryByPath(
|
|
4347
|
+
filePath
|
|
4348
|
+
);
|
|
3716
4349
|
}
|
|
3717
4350
|
resolveTierRootDir(tier) {
|
|
3718
|
-
return tier === "cold" ?
|
|
4351
|
+
return tier === "cold" ? path3.join(this.baseDir, "cold") : this.baseDir;
|
|
3719
4352
|
}
|
|
3720
4353
|
resolveMemoryDateDir(memory) {
|
|
3721
4354
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -3730,13 +4363,13 @@ ${sanitized.text}
|
|
|
3730
4363
|
buildTierMemoryPath(memory, tier) {
|
|
3731
4364
|
const root = this.resolveTierRootDir(tier);
|
|
3732
4365
|
if (this.isArtifactMemory(memory)) {
|
|
3733
|
-
return
|
|
4366
|
+
return path3.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
3734
4367
|
}
|
|
3735
4368
|
if (memory.frontmatter.category === "correction") {
|
|
3736
|
-
return
|
|
4369
|
+
return path3.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
3737
4370
|
}
|
|
3738
4371
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
3739
|
-
return
|
|
4372
|
+
return path3.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
3740
4373
|
}
|
|
3741
4374
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
3742
4375
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}
|
|
@@ -3749,11 +4382,11 @@ ${memory.content}
|
|
|
3749
4382
|
}
|
|
3750
4383
|
async moveMemoryToPath(memory, targetPath) {
|
|
3751
4384
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
3752
|
-
const sourcePath =
|
|
3753
|
-
const destPath =
|
|
4385
|
+
const sourcePath = path3.resolve(memory.path);
|
|
4386
|
+
const destPath = path3.resolve(targetPath);
|
|
3754
4387
|
if (sourcePath !== destPath) {
|
|
3755
4388
|
try {
|
|
3756
|
-
await
|
|
4389
|
+
await unlink2(memory.path);
|
|
3757
4390
|
} catch (err) {
|
|
3758
4391
|
const message = err instanceof Error ? err.message : String(err);
|
|
3759
4392
|
if (!message.includes("ENOENT")) {
|
|
@@ -3765,15 +4398,15 @@ ${memory.content}
|
|
|
3765
4398
|
}
|
|
3766
4399
|
async migrateMemoryToTier(memory, targetTier) {
|
|
3767
4400
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
3768
|
-
const sourcePath =
|
|
3769
|
-
const destPath =
|
|
4401
|
+
const sourcePath = path3.resolve(memory.path);
|
|
4402
|
+
const destPath = path3.resolve(targetPath);
|
|
3770
4403
|
if (sourcePath === destPath) {
|
|
3771
4404
|
return { changed: false, targetPath };
|
|
3772
4405
|
}
|
|
3773
4406
|
const existing = await this.readMemoryByPath(targetPath);
|
|
3774
4407
|
if (existing?.frontmatter.id === memory.frontmatter.id) {
|
|
3775
4408
|
try {
|
|
3776
|
-
await
|
|
4409
|
+
await unlink2(memory.path);
|
|
3777
4410
|
} catch (err) {
|
|
3778
4411
|
const message = err instanceof Error ? err.message : String(err);
|
|
3779
4412
|
if (!message.includes("ENOENT")) {
|
|
@@ -3792,7 +4425,7 @@ ${memory.content}
|
|
|
3792
4425
|
return { changed: true, targetPath };
|
|
3793
4426
|
}
|
|
3794
4427
|
get archiveDir() {
|
|
3795
|
-
return
|
|
4428
|
+
return path3.join(this.baseDir, "archive");
|
|
3796
4429
|
}
|
|
3797
4430
|
/**
|
|
3798
4431
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -3803,7 +4436,7 @@ ${memory.content}
|
|
|
3803
4436
|
try {
|
|
3804
4437
|
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
3805
4438
|
const today = now.toISOString().slice(0, 10);
|
|
3806
|
-
const destDir =
|
|
4439
|
+
const destDir = path3.join(this.archiveDir, today);
|
|
3807
4440
|
await mkdir(destDir, { recursive: true });
|
|
3808
4441
|
const updatedFm = {
|
|
3809
4442
|
...memory.frontmatter,
|
|
@@ -3815,9 +4448,9 @@ ${memory.content}
|
|
|
3815
4448
|
|
|
3816
4449
|
${memory.content}
|
|
3817
4450
|
`;
|
|
3818
|
-
const destPath =
|
|
4451
|
+
const destPath = path3.join(destDir, path3.basename(memory.path));
|
|
3819
4452
|
await this.writeStorageSecureFile(destPath, fileContent);
|
|
3820
|
-
await
|
|
4453
|
+
await unlink2(memory.path);
|
|
3821
4454
|
this.invalidateAllMemoriesCache();
|
|
3822
4455
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
3823
4456
|
"storage.archiveMemory",
|
|
@@ -3844,33 +4477,19 @@ ${memory.content}
|
|
|
3844
4477
|
}
|
|
3845
4478
|
async readEntities() {
|
|
3846
4479
|
try {
|
|
3847
|
-
const entries = await
|
|
4480
|
+
const entries = await readdir3(this.entitiesDir);
|
|
3848
4481
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", ""));
|
|
3849
4482
|
} catch {
|
|
3850
4483
|
return [];
|
|
3851
4484
|
}
|
|
3852
4485
|
}
|
|
3853
4486
|
async readEntity(name) {
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
}
|
|
3858
|
-
try {
|
|
3859
|
-
return await this.readStorageSecureFile(filePath);
|
|
3860
|
-
} catch (err) {
|
|
3861
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
3862
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
3863
|
-
return "";
|
|
3864
|
-
}
|
|
4487
|
+
return this.entityStore.readEntity(
|
|
4488
|
+
name
|
|
4489
|
+
);
|
|
3865
4490
|
}
|
|
3866
|
-
/** Return sorted list of entity filenames (without .md extension) */
|
|
3867
4491
|
async listEntityNames() {
|
|
3868
|
-
|
|
3869
|
-
const entries = await readdir(this.entitiesDir);
|
|
3870
|
-
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
|
|
3871
|
-
} catch {
|
|
3872
|
-
return [];
|
|
3873
|
-
}
|
|
4492
|
+
return this.entityStore.listEntityNames();
|
|
3874
4493
|
}
|
|
3875
4494
|
/**
|
|
3876
4495
|
* Find an existing entity that fuzzy-matches the proposed name.
|
|
@@ -3912,7 +4531,7 @@ ${memory.content}
|
|
|
3912
4531
|
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
3913
4532
|
if (!memory) return false;
|
|
3914
4533
|
try {
|
|
3915
|
-
await
|
|
4534
|
+
await unlink2(memory.path);
|
|
3916
4535
|
this.invalidateAllMemoriesCache();
|
|
3917
4536
|
this.bumpMemoryStatusVersion();
|
|
3918
4537
|
log.debug(`invalidated memory ${id}`);
|
|
@@ -3977,7 +4596,7 @@ ${memory.content}
|
|
|
3977
4596
|
`;
|
|
3978
4597
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
3979
4598
|
this.invalidateAllMemoriesCache();
|
|
3980
|
-
if (memory.path.includes(`${
|
|
4599
|
+
if (memory.path.includes(`${path3.sep}cold${path3.sep}`)) {
|
|
3981
4600
|
this.invalidateColdMemoriesCache();
|
|
3982
4601
|
}
|
|
3983
4602
|
try {
|
|
@@ -4035,7 +4654,7 @@ ${memory.content}
|
|
|
4035
4654
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
4036
4655
|
if (expiresAt < now) {
|
|
4037
4656
|
try {
|
|
4038
|
-
await
|
|
4657
|
+
await unlink2(m.path);
|
|
4039
4658
|
deleted.push(m);
|
|
4040
4659
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
4041
4660
|
} catch {
|
|
@@ -4049,7 +4668,7 @@ ${memory.content}
|
|
|
4049
4668
|
return deleted;
|
|
4050
4669
|
}
|
|
4051
4670
|
async loadBuffer() {
|
|
4052
|
-
const bufferPath =
|
|
4671
|
+
const bufferPath = path3.join(this.stateDir, "buffer.json");
|
|
4053
4672
|
try {
|
|
4054
4673
|
const raw = await this.readStorageSecureFile(bufferPath);
|
|
4055
4674
|
return JSON.parse(raw);
|
|
@@ -4061,11 +4680,11 @@ ${memory.content}
|
|
|
4061
4680
|
}
|
|
4062
4681
|
async saveBuffer(state) {
|
|
4063
4682
|
await this.ensureDirectories();
|
|
4064
|
-
const bufferPath =
|
|
4683
|
+
const bufferPath = path3.join(this.stateDir, "buffer.json");
|
|
4065
4684
|
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
4066
4685
|
}
|
|
4067
4686
|
async loadMeta() {
|
|
4068
|
-
const metaPath =
|
|
4687
|
+
const metaPath = path3.join(this.stateDir, "meta.json");
|
|
4069
4688
|
try {
|
|
4070
4689
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
4071
4690
|
const parsed = JSON.parse(raw);
|
|
@@ -4099,7 +4718,7 @@ ${memory.content}
|
|
|
4099
4718
|
}
|
|
4100
4719
|
async saveMeta(state) {
|
|
4101
4720
|
await this.ensureDirectories();
|
|
4102
|
-
const metaPath =
|
|
4721
|
+
const metaPath = path3.join(this.stateDir, "meta.json");
|
|
4103
4722
|
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
4104
4723
|
}
|
|
4105
4724
|
async appendMemoryActionEvents(events) {
|
|
@@ -4132,99 +4751,15 @@ ${memory.content}
|
|
|
4132
4751
|
await this.appendStorageSecureFile(this.memoryLifecycleLedgerPath, payload);
|
|
4133
4752
|
return events.length;
|
|
4134
4753
|
}
|
|
4135
|
-
/**
|
|
4136
|
-
* Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
|
|
4137
|
-
*
|
|
4138
|
-
* Each event records a single buffer flush decision driven by the
|
|
4139
|
-
* surprise gate. The ledger is consumed by
|
|
4140
|
-
* `reportBufferSurpriseDistribution` (Doctor report) and by downstream
|
|
4141
|
-
* benchmark analysis. This method is fire-and-forget by contract:
|
|
4142
|
-
* callers log but do not fail the hot path if the append throws.
|
|
4143
|
-
*/
|
|
4144
4754
|
async appendBufferSurpriseEvents(events) {
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
const payload = events.map((event) => {
|
|
4149
|
-
const normalized = {
|
|
4150
|
-
...event,
|
|
4151
|
-
event: "BUFFER_SURPRISE",
|
|
4152
|
-
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
4153
|
-
};
|
|
4154
|
-
return `${JSON.stringify(normalized)}
|
|
4155
|
-
`;
|
|
4156
|
-
}).join("");
|
|
4157
|
-
await this.appendStorageSecureFile(this.bufferSurpriseLedgerPath, payload);
|
|
4158
|
-
return events.length;
|
|
4755
|
+
return this.memoryReadStore.appendBufferSurpriseEvents(
|
|
4756
|
+
events
|
|
4757
|
+
);
|
|
4159
4758
|
}
|
|
4160
|
-
/**
|
|
4161
|
-
* Read the buffer-surprise ledger, most recent rows last.
|
|
4162
|
-
*
|
|
4163
|
-
* `limit` bounds the number of **valid rows** returned (not the
|
|
4164
|
-
* number of raw lines parsed). We parse every row, discard malformed
|
|
4165
|
-
* ones, then take the tail — so a partial/truncated trailing line
|
|
4166
|
-
* (the common failure mode after an interrupted append) cannot hide
|
|
4167
|
-
* otherwise-valid recent data above it.
|
|
4168
|
-
*
|
|
4169
|
-
* Non-positive / non-integer / non-finite limits return `[]` rather
|
|
4170
|
-
* than the entire file, matching the other ledger readers in this
|
|
4171
|
-
* class and protecting against `slice(-0.5)` → `slice(-0)` silently
|
|
4172
|
-
* devolving into an unbounded parse.
|
|
4173
|
-
*
|
|
4174
|
-
* # Performance note
|
|
4175
|
-
*
|
|
4176
|
-
* For very large ledgers (issue #563 follow-up), a tail-first reader
|
|
4177
|
-
* would avoid parsing the full file when only a recent window is
|
|
4178
|
-
* needed. We keep the full-scan implementation here because:
|
|
4179
|
-
*
|
|
4180
|
-
* - the ledger is opt-in (flag off by default), so early deployments
|
|
4181
|
-
* accumulate rows slowly;
|
|
4182
|
-
* - telemetry rows are small (~200 bytes), so even 100k rows parse
|
|
4183
|
-
* in well under a second;
|
|
4184
|
-
* - the governance archive/cleanup flow can trim the ledger when
|
|
4185
|
-
* size becomes a concern, reusing the existing maintenance hooks.
|
|
4186
|
-
*
|
|
4187
|
-
* Swap to a chunked tail-reader if production logs show this is a
|
|
4188
|
-
* hot path — leaving that work for a follow-up keeps this PR scoped
|
|
4189
|
-
* to correctness, not optimization.
|
|
4190
|
-
*/
|
|
4191
4759
|
async readBufferSurpriseEvents(options = {}) {
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
raw = await this.readStorageSecureFile(this.bufferSurpriseLedgerPath);
|
|
4195
|
-
} catch (err) {
|
|
4196
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4197
|
-
const code = err.code;
|
|
4198
|
-
if (code === "ENOENT") return [];
|
|
4199
|
-
throw err;
|
|
4200
|
-
}
|
|
4201
|
-
let effectiveLimit = null;
|
|
4202
|
-
if (options.limit !== void 0) {
|
|
4203
|
-
if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
4204
|
-
return [];
|
|
4205
|
-
}
|
|
4206
|
-
const floored = Math.floor(options.limit);
|
|
4207
|
-
if (floored <= 0) return [];
|
|
4208
|
-
effectiveLimit = floored;
|
|
4209
|
-
}
|
|
4210
|
-
const lines = raw.split("\n");
|
|
4211
|
-
const events = [];
|
|
4212
|
-
for (const line of lines) {
|
|
4213
|
-
const trimmed = line.trim();
|
|
4214
|
-
if (trimmed.length === 0) continue;
|
|
4215
|
-
try {
|
|
4216
|
-
const parsed = JSON.parse(trimmed);
|
|
4217
|
-
if (isValidBufferSurpriseEvent(parsed)) {
|
|
4218
|
-
events.push(parsed);
|
|
4219
|
-
}
|
|
4220
|
-
} catch {
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
events.sort(
|
|
4224
|
-
(left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
|
|
4760
|
+
return this.memoryReadStore.readBufferSurpriseEvents(
|
|
4761
|
+
options
|
|
4225
4762
|
);
|
|
4226
|
-
if (effectiveLimit === null) return events;
|
|
4227
|
-
return events.slice(-effectiveLimit);
|
|
4228
4763
|
}
|
|
4229
4764
|
async appendBehaviorSignals(events) {
|
|
4230
4765
|
if (events.length === 0) return 0;
|
|
@@ -4269,7 +4804,7 @@ ${memory.content}
|
|
|
4269
4804
|
async appendReextractJobs(events) {
|
|
4270
4805
|
if (events.length === 0) return 0;
|
|
4271
4806
|
await this.ensureDirectories();
|
|
4272
|
-
const filePath =
|
|
4807
|
+
const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4273
4808
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
4274
4809
|
try {
|
|
4275
4810
|
await this.appendStorageSecureFile(filePath, lines);
|
|
@@ -4282,7 +4817,7 @@ ${memory.content}
|
|
|
4282
4817
|
}
|
|
4283
4818
|
async readReextractJobs(limit = 200) {
|
|
4284
4819
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
4285
|
-
const filePath =
|
|
4820
|
+
const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4286
4821
|
try {
|
|
4287
4822
|
const raw = await this.readStorageSecureFile(filePath);
|
|
4288
4823
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
@@ -4463,59 +4998,15 @@ ${memory.content}
|
|
|
4463
4998
|
activationState: "active"
|
|
4464
4999
|
});
|
|
4465
5000
|
await Promise.all([
|
|
4466
|
-
|
|
4467
|
-
|
|
5001
|
+
unlink2(this.compressionGuidelineDraftPath).catch(() => void 0),
|
|
5002
|
+
unlink2(this.compressionGuidelineDraftStatePath).catch(() => void 0)
|
|
4468
5003
|
]);
|
|
4469
5004
|
return true;
|
|
4470
5005
|
}
|
|
4471
5006
|
async readCompressionGuidelineStateFile(filePath) {
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
const summary = value;
|
|
4476
|
-
return typeof summary.action === "string" && isFiniteNonNegativeInteger(summary.total) && summary.outcomes !== null && typeof summary.outcomes === "object" && isFiniteNonNegativeInteger(summary.outcomes.applied) && isFiniteNonNegativeInteger(summary.outcomes.skipped) && isFiniteNonNegativeInteger(summary.outcomes.failed) && summary.quality !== null && typeof summary.quality === "object" && isFiniteNonNegativeInteger(summary.quality.good) && isFiniteNonNegativeInteger(summary.quality.poor) && isFiniteNonNegativeInteger(summary.quality.unknown);
|
|
4477
|
-
};
|
|
4478
|
-
const isValidRuleUpdate = (value) => {
|
|
4479
|
-
if (!value || typeof value !== "object") return false;
|
|
4480
|
-
const rule = value;
|
|
4481
|
-
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
4482
|
-
};
|
|
4483
|
-
try {
|
|
4484
|
-
const raw = await this.readStorageSecureFile(filePath);
|
|
4485
|
-
const parsed = JSON.parse(raw);
|
|
4486
|
-
const sourceWindow = parsed?.sourceWindow;
|
|
4487
|
-
const eventCounts = parsed?.eventCounts;
|
|
4488
|
-
const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
|
|
4489
|
-
const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
|
|
4490
|
-
const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
|
|
4491
|
-
const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
|
|
4492
|
-
if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
|
|
4493
|
-
return null;
|
|
4494
|
-
}
|
|
4495
|
-
return {
|
|
4496
|
-
version: parsed.version,
|
|
4497
|
-
updatedAt: parsed.updatedAt,
|
|
4498
|
-
sourceWindow: {
|
|
4499
|
-
from: sourceWindow.from,
|
|
4500
|
-
to: sourceWindow.to
|
|
4501
|
-
},
|
|
4502
|
-
eventCounts: {
|
|
4503
|
-
total: eventCounts.total,
|
|
4504
|
-
applied: eventCounts.applied,
|
|
4505
|
-
skipped: eventCounts.skipped,
|
|
4506
|
-
failed: eventCounts.failed
|
|
4507
|
-
},
|
|
4508
|
-
guidelineVersion: parsed.guidelineVersion,
|
|
4509
|
-
...contentHash ? { contentHash } : {},
|
|
4510
|
-
...activationState ? { activationState } : {},
|
|
4511
|
-
...actionSummaries ? { actionSummaries } : {},
|
|
4512
|
-
...ruleUpdates ? { ruleUpdates } : {}
|
|
4513
|
-
};
|
|
4514
|
-
} catch (err) {
|
|
4515
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4516
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4517
|
-
return null;
|
|
4518
|
-
}
|
|
5007
|
+
return this.memoryReadStore.readCompressionGuidelineStateFile(
|
|
5008
|
+
filePath
|
|
5009
|
+
);
|
|
4519
5010
|
}
|
|
4520
5011
|
async writeIdentityAnchor(content) {
|
|
4521
5012
|
await this.ensureDirectories();
|
|
@@ -4536,7 +5027,7 @@ ${memory.content}
|
|
|
4536
5027
|
const date = nowIso.slice(0, 10);
|
|
4537
5028
|
const id = this.generateId("incident");
|
|
4538
5029
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
4539
|
-
const filePath =
|
|
5030
|
+
const filePath = path3.join(this.identityIncidentsDir, `${date}-${id}.md`);
|
|
4540
5031
|
await this.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
4541
5032
|
return { ...incident, filePath };
|
|
4542
5033
|
}
|
|
@@ -4549,7 +5040,7 @@ ${memory.content}
|
|
|
4549
5040
|
const incidents = [];
|
|
4550
5041
|
for (const file of candidates) {
|
|
4551
5042
|
if (incidents.length >= cappedLimit) break;
|
|
4552
|
-
const filePath =
|
|
5043
|
+
const filePath = path3.join(this.identityIncidentsDir, file);
|
|
4553
5044
|
try {
|
|
4554
5045
|
const raw = await this.readStorageSecureFile(filePath);
|
|
4555
5046
|
const parsed = parseContinuityIncident(raw);
|
|
@@ -4580,7 +5071,7 @@ ${memory.content}
|
|
|
4580
5071
|
await this.ensureDirectories();
|
|
4581
5072
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4582
5073
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4583
|
-
const filePath =
|
|
5074
|
+
const filePath = path3.join(dir, `${safeKey}.md`);
|
|
4584
5075
|
await this.writeStorageSecureFile(filePath, content);
|
|
4585
5076
|
return filePath;
|
|
4586
5077
|
}
|
|
@@ -4588,7 +5079,7 @@ ${memory.content}
|
|
|
4588
5079
|
try {
|
|
4589
5080
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4590
5081
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4591
|
-
return await this.readStorageSecureFile(
|
|
5082
|
+
return await this.readStorageSecureFile(path3.join(dir, `${safeKey}.md`));
|
|
4592
5083
|
} catch (err) {
|
|
4593
5084
|
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
4594
5085
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -4635,7 +5126,7 @@ ${memory.content}
|
|
|
4635
5126
|
return `${prefix}-${ts}-${rand}`;
|
|
4636
5127
|
}
|
|
4637
5128
|
async readContinuityIncidentFileNames() {
|
|
4638
|
-
const files = await
|
|
5129
|
+
const files = await readdir3(this.identityIncidentsDir);
|
|
4639
5130
|
return files.filter((file) => file.endsWith(".md")).sort().reverse();
|
|
4640
5131
|
}
|
|
4641
5132
|
async readContinuityIncidentFile(filePath) {
|
|
@@ -4652,12 +5143,12 @@ ${memory.content}
|
|
|
4652
5143
|
const fileNames = await this.readContinuityIncidentFileNames();
|
|
4653
5144
|
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
4654
5145
|
if (directMatch) {
|
|
4655
|
-
const directPath =
|
|
5146
|
+
const directPath = path3.join(this.identityIncidentsDir, directMatch);
|
|
4656
5147
|
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
4657
5148
|
if (parsed?.id === id) return directPath;
|
|
4658
5149
|
}
|
|
4659
5150
|
for (const fileName of fileNames) {
|
|
4660
|
-
const filePath =
|
|
5151
|
+
const filePath = path3.join(this.identityIncidentsDir, fileName);
|
|
4661
5152
|
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
4662
5153
|
if (parsed?.id === id) return filePath;
|
|
4663
5154
|
}
|
|
@@ -4687,43 +5178,16 @@ ${question}
|
|
|
4687
5178
|
|
|
4688
5179
|
**Context:** ${context}
|
|
4689
5180
|
`;
|
|
4690
|
-
const filePath =
|
|
5181
|
+
const filePath = path3.join(this.questionsDir, `${id}.md`);
|
|
4691
5182
|
await this.writeStorageSecureFile(filePath, content);
|
|
4692
5183
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
4693
5184
|
this.invalidateQuestionsCache();
|
|
4694
5185
|
return id;
|
|
4695
5186
|
}
|
|
4696
5187
|
async readQuestions(opts) {
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
try {
|
|
4701
|
-
const dirStat = await stat(this.questionsDir);
|
|
4702
|
-
if (dirStat.mtimeMs <= cached.loadedAt) {
|
|
4703
|
-
const all = cached.questions;
|
|
4704
|
-
return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
|
|
4705
|
-
}
|
|
4706
|
-
} catch {
|
|
4707
|
-
}
|
|
4708
|
-
}
|
|
4709
|
-
try {
|
|
4710
|
-
const files = await readdir(this.questionsDir);
|
|
4711
|
-
const questions = [];
|
|
4712
|
-
for (const file of files) {
|
|
4713
|
-
if (!file.endsWith(".md")) continue;
|
|
4714
|
-
const filePath = path.join(this.questionsDir, file);
|
|
4715
|
-
const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
4716
|
-
const parsed = this.parseQuestionFile(raw, filePath);
|
|
4717
|
-
if (parsed) {
|
|
4718
|
-
questions.push(parsed);
|
|
4719
|
-
}
|
|
4720
|
-
}
|
|
4721
|
-
const sorted = questions.sort((a, b) => b.priority - a.priority);
|
|
4722
|
-
_StorageManager.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
|
|
4723
|
-
return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
|
|
4724
|
-
} catch {
|
|
4725
|
-
return [];
|
|
4726
|
-
}
|
|
5188
|
+
return this.memoryReadStore.readQuestions(
|
|
5189
|
+
opts
|
|
5190
|
+
);
|
|
4727
5191
|
}
|
|
4728
5192
|
/** Invalidate the questions cache (call after writing a question). */
|
|
4729
5193
|
invalidateQuestionsCache() {
|
|
@@ -4734,7 +5198,7 @@ ${question}
|
|
|
4734
5198
|
if (!match) return null;
|
|
4735
5199
|
const frontmatterStr = match[1];
|
|
4736
5200
|
const body = match[2].trim();
|
|
4737
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
5201
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path3.basename(filePath, ".md");
|
|
4738
5202
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
4739
5203
|
const priority = parseFloat(
|
|
4740
5204
|
this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5"
|
|
@@ -4796,11 +5260,11 @@ ${question}
|
|
|
4796
5260
|
} catch {
|
|
4797
5261
|
}
|
|
4798
5262
|
const hygiene = opts?.hygiene;
|
|
4799
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
5263
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path3.basename(identityPath));
|
|
4800
5264
|
if (rotateEnabled) {
|
|
4801
5265
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
4802
5266
|
if (existing.length > maxBytes) {
|
|
4803
|
-
const archiveDir =
|
|
5267
|
+
const archiveDir = path3.join(workspaceDir, hygiene.archiveDir);
|
|
4804
5268
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
4805
5269
|
filePath: identityPath,
|
|
4806
5270
|
archiveDir,
|
|
@@ -4894,7 +5358,7 @@ ${reflection}
|
|
|
4894
5358
|
* Deduplicates by target+label.
|
|
4895
5359
|
*/
|
|
4896
5360
|
async addEntityRelationship(name, rel) {
|
|
4897
|
-
const filePath =
|
|
5361
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
4898
5362
|
let entity;
|
|
4899
5363
|
try {
|
|
4900
5364
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -4919,7 +5383,7 @@ ${reflection}
|
|
|
4919
5383
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
4920
5384
|
*/
|
|
4921
5385
|
async addEntityActivity(name, entry, maxEntries) {
|
|
4922
|
-
const filePath =
|
|
5386
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
4923
5387
|
let entity;
|
|
4924
5388
|
try {
|
|
4925
5389
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -4942,7 +5406,7 @@ ${reflection}
|
|
|
4942
5406
|
* Add an alias to an entity file. Deduplicates.
|
|
4943
5407
|
*/
|
|
4944
5408
|
async addEntityAlias(name, alias) {
|
|
4945
|
-
const filePath =
|
|
5409
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
4946
5410
|
let entity;
|
|
4947
5411
|
try {
|
|
4948
5412
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -4959,40 +5423,12 @@ ${reflection}
|
|
|
4959
5423
|
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
4960
5424
|
this.invalidateKnowledgeIndexCache();
|
|
4961
5425
|
}
|
|
4962
|
-
/**
|
|
4963
|
-
* Set or rewrite the synthesis layer of an entity file.
|
|
4964
|
-
*/
|
|
4965
5426
|
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
} catch (err) {
|
|
4972
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4973
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4974
|
-
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
4975
|
-
return;
|
|
4976
|
-
}
|
|
4977
|
-
const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
|
|
4978
|
-
const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
4979
|
-
const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
|
|
4980
|
-
const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
|
|
4981
|
-
const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
|
|
4982
|
-
entity.synthesis = synthesis.trim();
|
|
4983
|
-
entity.summary = entity.synthesis;
|
|
4984
|
-
entity.synthesisUpdatedAt = updatedAt;
|
|
4985
|
-
entity.synthesisTimelineCount = synthesisTimelineCount;
|
|
4986
|
-
entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
|
|
4987
|
-
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
4988
|
-
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
|
|
4989
|
-
entity.updated = entityUpdatedAt;
|
|
4990
|
-
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
4991
|
-
await this.removeEntitySynthesisQueueEntries([
|
|
4992
|
-
.../* @__PURE__ */ new Set([name, this.normalizeEntityName(entity.name, entity.type)])
|
|
4993
|
-
]);
|
|
4994
|
-
this.invalidateKnowledgeIndexCache();
|
|
4995
|
-
this.bumpMemoryStatusVersion();
|
|
5427
|
+
return this.entityStore.updateEntitySynthesis(
|
|
5428
|
+
name,
|
|
5429
|
+
synthesis,
|
|
5430
|
+
options
|
|
5431
|
+
);
|
|
4996
5432
|
}
|
|
4997
5433
|
/**
|
|
4998
5434
|
* Backward-compatible alias for legacy callers/tests.
|
|
@@ -5001,7 +5437,7 @@ ${reflection}
|
|
|
5001
5437
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5002
5438
|
let synthesisTimelineCount;
|
|
5003
5439
|
try {
|
|
5004
|
-
const filePath =
|
|
5440
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
5005
5441
|
const content = await this.readStorageSecureFile(filePath);
|
|
5006
5442
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
5007
5443
|
} catch (err) {
|
|
@@ -5084,7 +5520,7 @@ ${reflection}
|
|
|
5084
5520
|
if (!raw) continue;
|
|
5085
5521
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
5086
5522
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
5087
|
-
await this.writeStorageSecureFile(
|
|
5523
|
+
await this.writeStorageSecureFile(path3.join(this.entitiesDir, `${entityName}.md`), serialized);
|
|
5088
5524
|
migrated += 1;
|
|
5089
5525
|
}
|
|
5090
5526
|
if (migrated > 0) {
|
|
@@ -5099,45 +5535,8 @@ ${reflection}
|
|
|
5099
5535
|
// ---------------------------------------------------------------------------
|
|
5100
5536
|
// Scoring + Knowledge Index (Knowledge Graph v7.0)
|
|
5101
5537
|
// ---------------------------------------------------------------------------
|
|
5102
|
-
/**
|
|
5103
|
-
* Read all entity files and return lightweight EntityFile objects.
|
|
5104
|
-
* Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
|
|
5105
|
-
*/
|
|
5106
5538
|
async readAllEntityFiles() {
|
|
5107
|
-
|
|
5108
|
-
const schemaCacheKey = buildEntitySchemaCacheKey(this.entitySchemas);
|
|
5109
|
-
const cacheKey = `${this.getEntityCacheSecureStoreKey()}\0${schemaCacheKey}`;
|
|
5110
|
-
const cached = getCachedEntities(this.baseDir, currentVersion, cacheKey);
|
|
5111
|
-
if (cached) return cached;
|
|
5112
|
-
try {
|
|
5113
|
-
const entries = await readdir(this.entitiesDir);
|
|
5114
|
-
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
5115
|
-
if (mdFiles.length === 0) return [];
|
|
5116
|
-
const BATCH_SIZE = 100;
|
|
5117
|
-
const entities = [];
|
|
5118
|
-
for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
|
|
5119
|
-
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
5120
|
-
const results = await Promise.all(
|
|
5121
|
-
batch.map(async (entry) => {
|
|
5122
|
-
try {
|
|
5123
|
-
return await this.readStorageSecureFile(path.join(this.entitiesDir, entry));
|
|
5124
|
-
} catch (err) {
|
|
5125
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5126
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5127
|
-
return null;
|
|
5128
|
-
}
|
|
5129
|
-
})
|
|
5130
|
-
);
|
|
5131
|
-
for (const content of results) {
|
|
5132
|
-
if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
|
|
5133
|
-
}
|
|
5134
|
-
}
|
|
5135
|
-
setCachedEntities(this.baseDir, entities, currentVersion, cacheKey);
|
|
5136
|
-
return entities;
|
|
5137
|
-
} catch (err) {
|
|
5138
|
-
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
5139
|
-
return [];
|
|
5140
|
-
}
|
|
5539
|
+
return this.entityStore.readAllEntityFiles();
|
|
5141
5540
|
}
|
|
5142
5541
|
/**
|
|
5143
5542
|
* Score an entity based on recency, frequency, activity, type priority,
|
|
@@ -5163,53 +5562,11 @@ ${reflection}
|
|
|
5163
5562
|
const relDensity = Math.min(entity.relationships.length / 8, 1);
|
|
5164
5563
|
return recency * 0.4 + frequency * 0.25 + activityScore * 0.15 + typePriority * 0.1 + relDensity * 0.1;
|
|
5165
5564
|
}
|
|
5166
|
-
/**
|
|
5167
|
-
* Build the Knowledge Index: a compact markdown table of top-scored entities.
|
|
5168
|
-
* Respects maxEntities and maxChars limits from config.
|
|
5169
|
-
*/
|
|
5170
5565
|
async buildKnowledgeIndex(config, overrides) {
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
const entities = await this.readAllEntityFiles();
|
|
5176
|
-
if (entities.length === 0) {
|
|
5177
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
5178
|
-
return { result: "", cached: false };
|
|
5179
|
-
}
|
|
5180
|
-
const now = /* @__PURE__ */ new Date();
|
|
5181
|
-
const scored = entities.map((e) => ({
|
|
5182
|
-
name: e.name,
|
|
5183
|
-
type: e.type,
|
|
5184
|
-
score: _StorageManager.scoreEntity(e, now),
|
|
5185
|
-
factCount: e.facts.length,
|
|
5186
|
-
summary: e.synthesis ?? e.summary,
|
|
5187
|
-
topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
|
|
5188
|
-
}));
|
|
5189
|
-
scored.sort((a, b) => b.score - a.score);
|
|
5190
|
-
const maxEntities = typeof overrides?.maxEntities === "number" ? Math.max(0, Math.floor(overrides.maxEntities)) : config.knowledgeIndexMaxEntities;
|
|
5191
|
-
const topN = scored.slice(0, maxEntities);
|
|
5192
|
-
if (topN.length === 0) {
|
|
5193
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
5194
|
-
return { result: "", cached: false };
|
|
5195
|
-
}
|
|
5196
|
-
const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
|
|
5197
|
-
const rows = [];
|
|
5198
|
-
let totalChars = header.length;
|
|
5199
|
-
const maxChars = typeof overrides?.maxChars === "number" ? Math.max(0, Math.floor(overrides.maxChars)) : config.knowledgeIndexMaxChars;
|
|
5200
|
-
for (const entity of topN) {
|
|
5201
|
-
const summary = entity.summary || `${entity.factCount} facts`;
|
|
5202
|
-
const connected = entity.topRelationships.length > 0 ? entity.topRelationships.join(", ") : "\u2014";
|
|
5203
|
-
const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
|
|
5204
|
-
if (totalChars + row.length + 1 > maxChars) break;
|
|
5205
|
-
rows.push(row);
|
|
5206
|
-
totalChars += row.length + 1;
|
|
5207
|
-
}
|
|
5208
|
-
const result = rows.length === 0 ? "" : `${header}
|
|
5209
|
-
${rows.join("\n")}
|
|
5210
|
-
`;
|
|
5211
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result, builtAt: Date.now() };
|
|
5212
|
-
return { result, cached: false };
|
|
5566
|
+
return this.entityStore.buildKnowledgeIndex(
|
|
5567
|
+
config,
|
|
5568
|
+
overrides
|
|
5569
|
+
);
|
|
5213
5570
|
}
|
|
5214
5571
|
/** Invalidate the Knowledge Index cache (call after entity mutations). */
|
|
5215
5572
|
invalidateKnowledgeIndexCache() {
|
|
@@ -5220,201 +5577,8 @@ ${rows.join("\n")}
|
|
|
5220
5577
|
// ---------------------------------------------------------------------------
|
|
5221
5578
|
/** Max lines for profile.md before LLM consolidation triggers */
|
|
5222
5579
|
static PROFILE_MAX_LINES = 300;
|
|
5223
|
-
/**
|
|
5224
|
-
* Merge fragmented entity files that resolve to the same canonical name.
|
|
5225
|
-
* Preserves relationships, activity, aliases, and summary from all fragments.
|
|
5226
|
-
* Returns count of files merged.
|
|
5227
|
-
*/
|
|
5228
5580
|
async mergeFragmentedEntities() {
|
|
5229
|
-
|
|
5230
|
-
try {
|
|
5231
|
-
const entries = await readdir(this.entitiesDir);
|
|
5232
|
-
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
5233
|
-
const groups = /* @__PURE__ */ new Map();
|
|
5234
|
-
for (const file of mdFiles) {
|
|
5235
|
-
const baseName = file.replace(".md", "");
|
|
5236
|
-
const dashIdx = baseName.indexOf("-");
|
|
5237
|
-
if (dashIdx === -1) continue;
|
|
5238
|
-
const type = baseName.slice(0, dashIdx);
|
|
5239
|
-
const restOfName = baseName.slice(dashIdx + 1);
|
|
5240
|
-
const canonical = this.normalizeEntityName(restOfName, type);
|
|
5241
|
-
if (!groups.has(canonical)) groups.set(canonical, []);
|
|
5242
|
-
groups.get(canonical).push(file);
|
|
5243
|
-
}
|
|
5244
|
-
for (const [canonical, files] of groups) {
|
|
5245
|
-
if (files.length <= 1) continue;
|
|
5246
|
-
const mergedEntity = {
|
|
5247
|
-
name: "",
|
|
5248
|
-
type: "other",
|
|
5249
|
-
created: "",
|
|
5250
|
-
updated: "",
|
|
5251
|
-
extraFrontmatterLines: [],
|
|
5252
|
-
preSectionLines: [],
|
|
5253
|
-
facts: [],
|
|
5254
|
-
summary: void 0,
|
|
5255
|
-
synthesis: void 0,
|
|
5256
|
-
synthesisUpdatedAt: void 0,
|
|
5257
|
-
synthesisTimelineCount: void 0,
|
|
5258
|
-
synthesisStructuredFactCount: void 0,
|
|
5259
|
-
synthesisStructuredFactDigest: void 0,
|
|
5260
|
-
synthesisVersion: void 0,
|
|
5261
|
-
timeline: [],
|
|
5262
|
-
relationships: [],
|
|
5263
|
-
activity: [],
|
|
5264
|
-
aliases: [],
|
|
5265
|
-
structuredSections: [],
|
|
5266
|
-
extraSections: []
|
|
5267
|
-
};
|
|
5268
|
-
for (const file of files) {
|
|
5269
|
-
const filePath = path.join(this.entitiesDir, file);
|
|
5270
|
-
try {
|
|
5271
|
-
const content = await this.readStorageSecureFile(filePath);
|
|
5272
|
-
const parsed = parseEntityFile(content, this.entitySchemas);
|
|
5273
|
-
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
5274
|
-
mergedEntity.type = parsed.type;
|
|
5275
|
-
}
|
|
5276
|
-
if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
|
|
5277
|
-
mergedEntity.updated = parsed.updated;
|
|
5278
|
-
}
|
|
5279
|
-
const parsedCreated = parsed.created || parsed.updated;
|
|
5280
|
-
const mergedCreated = mergedEntity.created?.trim() || "";
|
|
5281
|
-
const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
|
|
5282
|
-
const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
|
|
5283
|
-
const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
|
|
5284
|
-
const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
|
|
5285
|
-
if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
|
|
5286
|
-
mergedEntity.created = parsedCreated;
|
|
5287
|
-
}
|
|
5288
|
-
if (parsed.name.length > mergedEntity.name.length) {
|
|
5289
|
-
mergedEntity.name = parsed.name;
|
|
5290
|
-
}
|
|
5291
|
-
const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
|
|
5292
|
-
const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
|
|
5293
|
-
if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
|
|
5294
|
-
mergedEntity.synthesis = parsed.synthesis;
|
|
5295
|
-
mergedEntity.summary = parsed.synthesis;
|
|
5296
|
-
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
5297
|
-
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
5298
|
-
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
5299
|
-
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
5300
|
-
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
5301
|
-
} else if (!mergedEntity.summary && parsed.summary) {
|
|
5302
|
-
mergedEntity.summary = parsed.summary;
|
|
5303
|
-
mergedEntity.synthesis = parsed.summary;
|
|
5304
|
-
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
5305
|
-
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
5306
|
-
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
5307
|
-
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
5308
|
-
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
5309
|
-
}
|
|
5310
|
-
mergedEntity.timeline.push(...parsed.timeline);
|
|
5311
|
-
mergedEntity.relationships.push(...parsed.relationships);
|
|
5312
|
-
mergedEntity.activity.push(...parsed.activity);
|
|
5313
|
-
mergedEntity.aliases.push(...parsed.aliases);
|
|
5314
|
-
const mergedStructuredSectionMap = new Map(
|
|
5315
|
-
(mergedEntity.structuredSections ?? []).map((section) => [section.key, {
|
|
5316
|
-
...section,
|
|
5317
|
-
facts: [...section.facts]
|
|
5318
|
-
}])
|
|
5319
|
-
);
|
|
5320
|
-
for (const section of parsed.structuredSections ?? []) {
|
|
5321
|
-
const existingSection = mergedStructuredSectionMap.get(section.key);
|
|
5322
|
-
if (!existingSection) {
|
|
5323
|
-
mergedStructuredSectionMap.set(section.key, {
|
|
5324
|
-
key: section.key,
|
|
5325
|
-
title: section.title,
|
|
5326
|
-
facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
|
|
5327
|
-
});
|
|
5328
|
-
continue;
|
|
5329
|
-
}
|
|
5330
|
-
const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
|
|
5331
|
-
for (const fact of section.facts) {
|
|
5332
|
-
const trimmed = fact.trim();
|
|
5333
|
-
if (!trimmed) continue;
|
|
5334
|
-
mergedFacts.add(trimmed);
|
|
5335
|
-
}
|
|
5336
|
-
existingSection.facts = Array.from(mergedFacts);
|
|
5337
|
-
if (!existingSection.title.trim() && section.title.trim()) {
|
|
5338
|
-
existingSection.title = section.title;
|
|
5339
|
-
}
|
|
5340
|
-
}
|
|
5341
|
-
mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
|
|
5342
|
-
mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
|
|
5343
|
-
mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
|
|
5344
|
-
mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
|
|
5345
|
-
title: section.title,
|
|
5346
|
-
lines: [...section.lines]
|
|
5347
|
-
})));
|
|
5348
|
-
} catch (err) {
|
|
5349
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5350
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5351
|
-
}
|
|
5352
|
-
}
|
|
5353
|
-
const timelineKeys = /* @__PURE__ */ new Set();
|
|
5354
|
-
mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
|
|
5355
|
-
const key = JSON.stringify([
|
|
5356
|
-
entry.timestamp,
|
|
5357
|
-
entry.source ?? "",
|
|
5358
|
-
entry.sessionKey ?? "",
|
|
5359
|
-
entry.principal ?? "",
|
|
5360
|
-
entry.text
|
|
5361
|
-
]);
|
|
5362
|
-
if (timelineKeys.has(key)) return false;
|
|
5363
|
-
timelineKeys.add(key);
|
|
5364
|
-
return true;
|
|
5365
|
-
});
|
|
5366
|
-
const relKeys = /* @__PURE__ */ new Set();
|
|
5367
|
-
mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
|
|
5368
|
-
const key = `${r.target}::${r.label}`;
|
|
5369
|
-
if (relKeys.has(key)) return false;
|
|
5370
|
-
relKeys.add(key);
|
|
5371
|
-
return true;
|
|
5372
|
-
});
|
|
5373
|
-
const actKeys = /* @__PURE__ */ new Set();
|
|
5374
|
-
mergedEntity.activity = mergedEntity.activity.filter((a) => {
|
|
5375
|
-
const key = `${a.date}::${a.note}`;
|
|
5376
|
-
if (actKeys.has(key)) return false;
|
|
5377
|
-
actKeys.add(key);
|
|
5378
|
-
return true;
|
|
5379
|
-
}).sort((a, b) => b.date.localeCompare(a.date));
|
|
5380
|
-
mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
|
|
5381
|
-
mergedEntity.structuredSections = sortStructuredSectionsBySchema(
|
|
5382
|
-
mergedEntity.type,
|
|
5383
|
-
mergedEntity.structuredSections ?? [],
|
|
5384
|
-
this.entitySchemas
|
|
5385
|
-
);
|
|
5386
|
-
mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
|
|
5387
|
-
const extraSectionKeys = /* @__PURE__ */ new Set();
|
|
5388
|
-
mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
|
|
5389
|
-
const key = `${section.title}::${section.lines.join("\n")}`;
|
|
5390
|
-
if (extraSectionKeys.has(key)) return false;
|
|
5391
|
-
extraSectionKeys.add(key);
|
|
5392
|
-
return true;
|
|
5393
|
-
});
|
|
5394
|
-
if (!mergedEntity.name) {
|
|
5395
|
-
const dashIdx = canonical.indexOf("-");
|
|
5396
|
-
mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
|
|
5397
|
-
}
|
|
5398
|
-
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5399
|
-
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
5400
|
-
const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
|
|
5401
|
-
await this.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas));
|
|
5402
|
-
for (const file of files) {
|
|
5403
|
-
const filePath = path.join(this.entitiesDir, file);
|
|
5404
|
-
if (filePath !== canonicalPath) {
|
|
5405
|
-
try {
|
|
5406
|
-
await unlink(filePath);
|
|
5407
|
-
merged++;
|
|
5408
|
-
log.debug(`merged entity ${file} \u2192 ${canonical}.md`);
|
|
5409
|
-
} catch {
|
|
5410
|
-
}
|
|
5411
|
-
}
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
} catch (err) {
|
|
5415
|
-
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
5416
|
-
}
|
|
5417
|
-
return merged;
|
|
5581
|
+
return this.entityStore.mergeFragmentedEntities();
|
|
5418
5582
|
}
|
|
5419
5583
|
async cleanExpiredCommitments(decayDays) {
|
|
5420
5584
|
const memories = await this.readAllMemories();
|
|
@@ -5429,7 +5593,7 @@ ${rows.join("\n")}
|
|
|
5429
5593
|
const updatedAt = new Date(m.frontmatter.updated).getTime();
|
|
5430
5594
|
if (updatedAt < cutoff) {
|
|
5431
5595
|
try {
|
|
5432
|
-
await
|
|
5596
|
+
await unlink2(m.path);
|
|
5433
5597
|
deleted.push(m);
|
|
5434
5598
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
5435
5599
|
} catch {
|
|
@@ -5499,7 +5663,7 @@ ${memory.content}
|
|
|
5499
5663
|
const filePaths = await this.collectActiveMemoryPaths();
|
|
5500
5664
|
const foundIds = /* @__PURE__ */ new Set();
|
|
5501
5665
|
for (const filePath of filePaths) {
|
|
5502
|
-
const basename =
|
|
5666
|
+
const basename = path3.basename(filePath, ".md");
|
|
5503
5667
|
if (wantedIds.has(basename)) {
|
|
5504
5668
|
foundIds.add(basename);
|
|
5505
5669
|
if (foundIds.size === wantedIds.size) break;
|
|
@@ -5689,14 +5853,14 @@ Reason: ${reason}`, {
|
|
|
5689
5853
|
// Memory Summarization (Phase 4A)
|
|
5690
5854
|
// ---------------------------------------------------------------------------
|
|
5691
5855
|
get summariesDir() {
|
|
5692
|
-
return
|
|
5856
|
+
return path3.join(this.baseDir, "summaries");
|
|
5693
5857
|
}
|
|
5694
5858
|
/**
|
|
5695
5859
|
* Write a memory summary.
|
|
5696
5860
|
*/
|
|
5697
5861
|
async writeSummary(summary) {
|
|
5698
5862
|
await mkdir(this.summariesDir, { recursive: true });
|
|
5699
|
-
const filePath =
|
|
5863
|
+
const filePath = path3.join(this.summariesDir, `${summary.id}.json`);
|
|
5700
5864
|
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
5701
5865
|
log.debug(`wrote summary ${summary.id}`);
|
|
5702
5866
|
}
|
|
@@ -5705,11 +5869,11 @@ Reason: ${reason}`, {
|
|
|
5705
5869
|
*/
|
|
5706
5870
|
async readSummaries() {
|
|
5707
5871
|
try {
|
|
5708
|
-
const files = await
|
|
5872
|
+
const files = await readdir3(this.summariesDir);
|
|
5709
5873
|
const summaries = [];
|
|
5710
5874
|
for (const file of files) {
|
|
5711
5875
|
if (!file.endsWith(".json")) continue;
|
|
5712
|
-
const filePath =
|
|
5876
|
+
const filePath = path3.join(this.summariesDir, file);
|
|
5713
5877
|
const raw = await this.readStorageSecureFile(filePath);
|
|
5714
5878
|
summaries.push(JSON.parse(raw));
|
|
5715
5879
|
}
|
|
@@ -5770,7 +5934,7 @@ ${memory.content}
|
|
|
5770
5934
|
* Save topic scores to meta.json.
|
|
5771
5935
|
*/
|
|
5772
5936
|
async saveTopics(topics) {
|
|
5773
|
-
const metaPath =
|
|
5937
|
+
const metaPath = path3.join(this.stateDir, "topics.json");
|
|
5774
5938
|
await mkdir(this.stateDir, { recursive: true });
|
|
5775
5939
|
await this.writeStorageSecureFile(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
5776
5940
|
log.debug(`saved ${topics.length} topic scores`);
|
|
@@ -5779,7 +5943,7 @@ ${memory.content}
|
|
|
5779
5943
|
* Load topic scores from meta.json.
|
|
5780
5944
|
*/
|
|
5781
5945
|
async loadTopics() {
|
|
5782
|
-
const metaPath =
|
|
5946
|
+
const metaPath = path3.join(this.stateDir, "topics.json");
|
|
5783
5947
|
try {
|
|
5784
5948
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
5785
5949
|
return JSON.parse(raw);
|
|
@@ -5857,14 +6021,23 @@ ${memory.content}
|
|
|
5857
6021
|
};
|
|
5858
6022
|
|
|
5859
6023
|
export {
|
|
6024
|
+
selfDeps,
|
|
6025
|
+
parseFrontmatterStringValue,
|
|
6026
|
+
parseFrontmatter,
|
|
6027
|
+
normalizeFrontmatterForPath,
|
|
5860
6028
|
normalizeEntityName,
|
|
5861
6029
|
ContentHashIndex,
|
|
5862
6030
|
normalizeAttributePairs,
|
|
6031
|
+
normalizeStructuredSectionFacts,
|
|
6032
|
+
compileEntityFacts,
|
|
5863
6033
|
compareEntityTimestamps,
|
|
6034
|
+
countEntityStructuredFacts,
|
|
5864
6035
|
fingerprintEntityStructuredFacts,
|
|
5865
6036
|
isEntitySynthesisStale,
|
|
5866
6037
|
parseEntityFile,
|
|
5867
6038
|
serializeEntityFile,
|
|
5868
|
-
|
|
6039
|
+
buildEntitySchemaCacheKey,
|
|
6040
|
+
isValidBufferSurpriseEvent,
|
|
6041
|
+
StorageManager3 as StorageManager
|
|
5869
6042
|
};
|
|
5870
|
-
//# sourceMappingURL=chunk-
|
|
6043
|
+
//# sourceMappingURL=chunk-7APVFQ34.js.map
|