@remnic/core 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/access-cli.d.ts +13 -3
- package/dist/access-cli.js +90 -75
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +10 -3
- package/dist/access-http.js +25 -18
- package/dist/access-mcp.d.ts +30 -3
- package/dist/access-mcp.js +16 -1
- package/dist/access-schema.d.ts +46 -46
- package/dist/access-schema.js +1 -1
- package/dist/access-service.d.ts +65 -4
- package/dist/access-service.js +21 -15
- package/dist/active-memory-bridge.d.ts +66 -0
- package/dist/active-memory-bridge.js +11 -0
- package/dist/active-recall.d.ts +96 -0
- package/dist/active-recall.js +308 -0
- package/dist/active-recall.js.map +1 -0
- package/dist/behavior-learner.js +1 -1
- package/dist/bootstrap.d.ts +6 -3
- package/dist/bootstrap.js +2 -2
- package/dist/boxes.js +2 -2
- package/dist/briefing.d.ts +169 -0
- package/dist/briefing.js +52 -0
- package/dist/briefing.js.map +1 -0
- package/dist/buffer.d.ts +19 -5
- package/dist/buffer.js +2 -2
- package/dist/calibration.js +6 -6
- package/dist/causal-behavior.js +5 -5
- package/dist/causal-chain.js +3 -3
- package/dist/causal-consolidation.d.ts +22 -2
- package/dist/causal-consolidation.js +36 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +6 -6
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.d.ts +14 -1
- package/dist/causal-trajectory.js +5 -1
- package/dist/{chunk-KWBU5S5U.js → chunk-2ODBA7MQ.js} +9 -3
- package/dist/chunk-2ODBA7MQ.js.map +1 -0
- package/dist/{chunk-6UJQNRIO.js → chunk-2VFW5K5U.js} +93 -36
- package/dist/chunk-2VFW5K5U.js.map +1 -0
- package/dist/chunk-3PG3H5TD.js +7 -0
- package/dist/chunk-3PG3H5TD.js.map +1 -0
- package/dist/{chunk-NTTLPF7F.js → chunk-3QFQGRHO.js} +5 -5
- package/dist/chunk-4DJQYKMN.js +187 -0
- package/dist/chunk-4DJQYKMN.js.map +1 -0
- package/dist/chunk-4KAN3GZ3.js +225 -0
- package/dist/chunk-4KAN3GZ3.js.map +1 -0
- package/dist/{chunk-ORZMT74A.js → chunk-4NRAJUDS.js} +11 -1
- package/dist/chunk-4NRAJUDS.js.map +1 -0
- package/dist/{chunk-B7LOFDVE.js → chunk-4WMCPJWX.js} +8 -3
- package/dist/chunk-4WMCPJWX.js.map +1 -0
- package/dist/{chunk-G3AG3KZN.js → chunk-5IZL4DCV.js} +2 -2
- package/dist/{chunk-BRK4ODMI.js → chunk-5NPGSAVB.js} +2 -2
- package/dist/chunk-6MKAMLQL.js +16 -0
- package/dist/chunk-6MKAMLQL.js.map +1 -0
- package/dist/{chunk-ESSMF2FR.js → chunk-6PFRXT4K.js} +15 -6
- package/dist/chunk-6PFRXT4K.js.map +1 -0
- package/dist/chunk-6ZH4TU6I.js +245 -0
- package/dist/chunk-6ZH4TU6I.js.map +1 -0
- package/dist/{chunk-V4YC4LUK.js → chunk-74JR4N5J.js} +175 -63
- package/dist/chunk-74JR4N5J.js.map +1 -0
- package/dist/{chunk-L5RPWGFK.js → chunk-7DHTMOND.js} +2 -2
- package/dist/{chunk-TVVVQQAK.js → chunk-7PA4OZEU.js} +53 -11
- package/dist/chunk-7PA4OZEU.js.map +1 -0
- package/dist/{chunk-Q6FETXJA.js → chunk-7SEAZFFB.js} +2 -2
- package/dist/chunk-ALXMCZEU.js +332 -0
- package/dist/chunk-ALXMCZEU.js.map +1 -0
- package/dist/{chunk-QANCTXQF.js → chunk-AYPYCLR7.js} +3 -3
- package/dist/{chunk-WWIQTB2Y.js → chunk-BKQJBXXX.js} +9 -2
- package/dist/chunk-BKQJBXXX.js.map +1 -0
- package/dist/{chunk-LP47L3ZX.js → chunk-BTY5RRRF.js} +7 -7
- package/dist/{chunk-SCHEKPYH.js → chunk-C2EFFULQ.js} +1 -1
- package/dist/{chunk-GJR6D6KC.js → chunk-D654IBA6.js} +2 -2
- package/dist/{chunk-UV2FO7J4.js → chunk-E6K4NIEU.js} +2 -2
- package/dist/{chunk-T4WRIV2C.js → chunk-EABGC2TL.js} +2 -2
- package/dist/chunk-ECKDIK5F.js +813 -0
- package/dist/chunk-ECKDIK5F.js.map +1 -0
- package/dist/chunk-EJI5XIBB.js +232 -0
- package/dist/chunk-EJI5XIBB.js.map +1 -0
- package/dist/{chunk-ONRU4L2N.js → chunk-FEMOX5AD.js} +2 -2
- package/dist/{chunk-IFFFR3MR.js → chunk-FSFEQI74.js} +3 -3
- package/dist/chunk-G4SK7DSQ.js +121 -0
- package/dist/chunk-G4SK7DSQ.js.map +1 -0
- package/dist/{chunk-UIYZ5T3I.js → chunk-GJQPH5G3.js} +8 -8
- package/dist/{chunk-2PO5ZRKV.js → chunk-GZCUW5IC.js} +16 -3
- package/dist/chunk-GZCUW5IC.js.map +1 -0
- package/dist/{chunk-IZME7KW2.js → chunk-HITJFT7E.js} +24 -10
- package/dist/{chunk-IZME7KW2.js.map → chunk-HITJFT7E.js.map} +1 -1
- package/dist/chunk-IQT3XTKW.js +121 -0
- package/dist/chunk-IQT3XTKW.js.map +1 -0
- package/dist/{chunk-BDFZXRSO.js → chunk-J4IYOZZ5.js} +15 -2
- package/dist/chunk-J4IYOZZ5.js.map +1 -0
- package/dist/{chunk-ZKYI7UVO.js → chunk-JR4ZC3G4.js} +2 -2
- package/dist/{chunk-UCYSTFZR.js → chunk-JRNQ3RNA.js} +2 -2
- package/dist/{chunk-UYSKNO6E.js → chunk-JROGC36Y.js} +15 -4
- package/dist/chunk-JROGC36Y.js.map +1 -0
- package/dist/{chunk-GPGBSNKM.js → chunk-K4FLSOR5.js} +2 -2
- package/dist/{chunk-M5ZBBBJI.js → chunk-KEG4GNGI.js} +2 -2
- package/dist/chunk-KVE7R4CG.js +320 -0
- package/dist/chunk-KVE7R4CG.js.map +1 -0
- package/dist/{chunk-L7WO3MZ4.js → chunk-KWP7T3DP.js} +2 -2
- package/dist/chunk-LAYN4LDC.js +267 -0
- package/dist/chunk-LAYN4LDC.js.map +1 -0
- package/dist/{chunk-PGK3VUHN.js → chunk-MTLYEMJB.js} +3 -2
- package/dist/chunk-MTLYEMJB.js.map +1 -0
- package/dist/{chunk-J47FNDR7.js → chunk-MYQWXITD.js} +7 -7
- package/dist/{chunk-YNI4S5WT.js → chunk-N53K2EXC.js} +2 -2
- package/dist/{chunk-763GUIOU.js → chunk-NBNN5GOB.js} +2 -2
- package/dist/{chunk-CXWFUJR2.js → chunk-NSB3WSYS.js} +125 -6
- package/dist/chunk-NSB3WSYS.js.map +1 -0
- package/dist/{chunk-KL4CP4SB.js → chunk-O5ETUNBT.js} +17 -5
- package/dist/chunk-O5ETUNBT.js.map +1 -0
- package/dist/{chunk-OOSWAUYB.js → chunk-ODWDQNRE.js} +2 -2
- package/dist/{chunk-ISY75RLM.js → chunk-OJFGVJS6.js} +288 -7
- package/dist/chunk-OJFGVJS6.js.map +1 -0
- package/dist/{chunk-HLBYLYRD.js → chunk-PAORGQRI.js} +70 -13
- package/dist/chunk-PAORGQRI.js.map +1 -0
- package/dist/{chunk-ZJLY4QSU.js → chunk-PMB3WGDL.js} +69 -6
- package/dist/chunk-PMB3WGDL.js.map +1 -0
- package/dist/{chunk-J3BT33K7.js → chunk-POBPGDWI.js} +5 -5
- package/dist/{chunk-QWUUMMIK.js → chunk-POMSFKTB.js} +1351 -76
- package/dist/chunk-POMSFKTB.js.map +1 -0
- package/dist/{chunk-OTAVQCSF.js → chunk-PYXS46O7.js} +2 -2
- package/dist/chunk-QDW3E4RD.js +108 -0
- package/dist/chunk-QDW3E4RD.js.map +1 -0
- package/dist/{chunk-YNCQ7E4M.js → chunk-QDYXG4CS.js} +4 -3
- package/dist/chunk-QDYXG4CS.js.map +1 -0
- package/dist/{chunk-XUHI52HK.js → chunk-QKAH5B6E.js} +4 -4
- package/dist/{chunk-HLXVTBF3.js → chunk-QNJMBKFK.js} +3 -2
- package/dist/chunk-QNJMBKFK.js.map +1 -0
- package/dist/chunk-RCICHSHL.js +789 -0
- package/dist/chunk-RCICHSHL.js.map +1 -0
- package/dist/{chunk-HG2NKWR2.js → chunk-S4LX5EBI.js} +2 -2
- package/dist/{chunk-4A24LIM2.js → chunk-S75M5ZRK.js} +2 -2
- package/dist/{chunk-QCCCQT3O.js → chunk-TBBDFYXW.js} +2 -2
- package/dist/chunk-TBBDFYXW.js.map +1 -0
- package/dist/{chunk-U4PV25RD.js → chunk-U2IQTSBY.js} +1 -1
- package/dist/chunk-U2IQTSBY.js.map +1 -0
- package/dist/chunk-U66YHYC7.js +31 -0
- package/dist/chunk-U66YHYC7.js.map +1 -0
- package/dist/{chunk-MWGVGUIS.js → chunk-UEYA6UC7.js} +36 -4
- package/dist/chunk-UEYA6UC7.js.map +1 -0
- package/dist/{chunk-MDDAA2AO.js → chunk-UPMD5XND.js} +2 -2
- package/dist/{chunk-M5KEYE5E.js → chunk-URB2WSKZ.js} +2 -2
- package/dist/chunk-UVJFDP7P.js +202 -0
- package/dist/chunk-UVJFDP7P.js.map +1 -0
- package/dist/{chunk-QY2BHY5O.js → chunk-V7XCAHIB.js} +265 -25
- package/dist/chunk-V7XCAHIB.js.map +1 -0
- package/dist/chunk-W6SL7OFG.js +180 -0
- package/dist/chunk-W6SL7OFG.js.map +1 -0
- package/dist/{chunk-QDOSNLB4.js → chunk-X4WESCKA.js} +17 -15
- package/dist/chunk-X4WESCKA.js.map +1 -0
- package/dist/{chunk-OTFNI3OO.js → chunk-XMGSSBFX.js} +1738 -383
- package/dist/chunk-XMGSSBFX.js.map +1 -0
- package/dist/chunk-YDBIWGNI.js +298 -0
- package/dist/chunk-YDBIWGNI.js.map +1 -0
- package/dist/chunk-YFYL2SIJ.js +7857 -0
- package/dist/chunk-YFYL2SIJ.js.map +1 -0
- package/dist/chunking.js +1 -1
- package/dist/citations.d.ts +67 -0
- package/dist/citations.js +13 -0
- package/dist/citations.js.map +1 -0
- package/dist/cli-DwIBnp2g.d.ts +1240 -0
- package/dist/cli.d.ts +31 -1147
- package/dist/cli.js +149 -7092
- package/dist/cli.js.map +1 -1
- package/dist/codex-materialize-CQlLTzke.d.ts +139 -0
- package/dist/codex-thread-key.d.ts +3 -0
- package/dist/codex-thread-key.js +7 -0
- package/dist/codex-thread-key.js.map +1 -0
- package/dist/config.js +3 -2
- package/dist/connectors/codex/instructions.md +160 -0
- package/dist/connectors/codex/resources/namespace-cheatsheet.md +48 -0
- package/dist/day-summary.d.ts +7 -2
- package/dist/day-summary.js +5 -2
- package/dist/embedding-fallback.d.ts +96 -2
- package/dist/embedding-fallback.js +6 -4
- package/dist/{engine-2A6J4XEX.js → engine-X7X3AAG3.js} +10 -7
- package/dist/engine-X7X3AAG3.js.map +1 -0
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +10 -7
- package/dist/entity-schema.d.ts +11 -0
- package/dist/entity-schema.js +19 -0
- package/dist/entity-schema.js.map +1 -0
- package/dist/explicit-capture.d.ts +6 -3
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge.d.ts +66 -0
- package/dist/extraction-judge.js +18 -0
- package/dist/extraction-judge.js.map +1 -0
- package/dist/extraction.d.ts +1 -0
- package/dist/extraction.js +12 -10
- package/dist/fallback-llm.js +4 -4
- package/dist/graph.js +1 -1
- package/dist/importance.d.ts +11 -1
- package/dist/importance.js +3 -1
- package/dist/index.d.ts +1140 -8
- package/dist/index.js +3350 -333
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +2 -1
- package/dist/intent.js +3 -1
- package/dist/lifecycle.js +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +1 -1
- package/dist/memory-cache.d.ts +2 -2
- package/dist/memory-cache.js +1 -1
- package/dist/{memory-projection-store-NxMkbocT.d.ts → memory-projection-store-DeSXPh1j.d.ts} +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/model-registry.js +2 -2
- package/dist/models-json.js +2 -2
- package/dist/native-knowledge.js +2 -2
- package/dist/negative.js +2 -2
- package/dist/operator-toolkit.js +20 -16
- package/dist/{orchestrator-CIvLFHx3.d.ts → orchestrator-B9kwlCep.d.ts} +254 -9
- package/dist/orchestrator.d.ts +6 -3
- package/dist/orchestrator.js +70 -58
- package/dist/page-versioning.d.ts +77 -0
- package/dist/page-versioning.js +15 -0
- package/dist/page-versioning.js.map +1 -0
- package/dist/plugin-id.d.ts +37 -0
- package/dist/plugin-id.js +11 -0
- package/dist/plugin-id.js.map +1 -0
- package/dist/policy-runtime.js +2 -2
- package/dist/profiling.js +2 -2
- package/dist/qmd.d.ts +5 -2
- package/dist/qmd.js +3 -3
- package/dist/recall-audit.d.ts +20 -0
- package/dist/recall-audit.js +50 -0
- package/dist/recall-audit.js.map +1 -0
- package/dist/recall-mmr.d.ts +152 -0
- package/dist/recall-mmr.js +17 -0
- package/dist/recall-mmr.js.map +1 -0
- package/dist/recall-qos.js +2 -2
- package/dist/recall-state.js +2 -2
- package/dist/relevance.js +2 -2
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/resume-bundles.js +5 -4
- package/dist/retrieval-agents.js +2 -2
- package/dist/retrieval.js +2 -2
- package/dist/schemas.d.ts +422 -64
- package/dist/schemas.js +3 -1
- package/dist/sdk-compat.d.ts +2 -0
- package/dist/sdk-compat.js +6 -3
- package/dist/sdk-compat.js.map +1 -1
- package/dist/semantic-chunking.d.ts +87 -0
- package/dist/semantic-chunking.js +20 -0
- package/dist/semantic-chunking.js.map +1 -0
- package/dist/semantic-consolidation-DrvSYRdB.d.ts +119 -0
- package/dist/semantic-consolidation.d.ts +4 -42
- package/dist/semantic-consolidation.js +23 -2
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/session-observer-state.js +2 -2
- package/dist/session-toggles.d.ts +22 -0
- package/dist/session-toggles.js +116 -0
- package/dist/session-toggles.js.map +1 -0
- package/dist/skills-registry.d.ts +47 -0
- package/dist/skills-registry.js +48 -0
- package/dist/skills-registry.js.map +1 -0
- package/dist/source-attribution.d.ts +169 -0
- package/dist/source-attribution.js +27 -0
- package/dist/source-attribution.js.map +1 -0
- package/dist/storage.d.ts +171 -10
- package/dist/storage.js +16 -5
- package/dist/summarizer.js +7 -7
- package/dist/temporal-supersession.d.ts +127 -0
- package/dist/temporal-supersession.js +20 -0
- package/dist/temporal-supersession.js.map +1 -0
- package/dist/threading.js +2 -2
- package/dist/tier-migration.d.ts +2 -1
- package/dist/tier-routing.js +2 -2
- package/dist/tokens.d.ts +21 -1
- package/dist/tokens.js +5 -1
- package/dist/transcript.js +2 -2
- package/dist/types.d.ts +497 -3
- package/dist/types.js +1 -1
- package/dist/utility-learner.js +2 -2
- package/dist/utility-runtime.js +3 -3
- package/dist/verified-recall.js +11 -8
- package/dist/whitespace.d.ts +4 -0
- package/dist/whitespace.js +9 -0
- package/dist/whitespace.js.map +1 -0
- package/package.json +14 -8
- package/dist/chunk-2CJCWDMR.js +0 -87
- package/dist/chunk-2CJCWDMR.js.map +0 -1
- package/dist/chunk-2PO5ZRKV.js.map +0 -1
- package/dist/chunk-6UJQNRIO.js.map +0 -1
- package/dist/chunk-B7LOFDVE.js.map +0 -1
- package/dist/chunk-BDFZXRSO.js.map +0 -1
- package/dist/chunk-CXWFUJR2.js.map +0 -1
- package/dist/chunk-DORBM6OB.js +0 -81
- package/dist/chunk-DORBM6OB.js.map +0 -1
- package/dist/chunk-ESSMF2FR.js.map +0 -1
- package/dist/chunk-HLBYLYRD.js.map +0 -1
- package/dist/chunk-HLXVTBF3.js.map +0 -1
- package/dist/chunk-ISY75RLM.js.map +0 -1
- package/dist/chunk-KL4CP4SB.js.map +0 -1
- package/dist/chunk-KWBU5S5U.js.map +0 -1
- package/dist/chunk-MWGVGUIS.js.map +0 -1
- package/dist/chunk-ORZMT74A.js.map +0 -1
- package/dist/chunk-OTFNI3OO.js.map +0 -1
- package/dist/chunk-PGK3VUHN.js.map +0 -1
- package/dist/chunk-QCCCQT3O.js.map +0 -1
- package/dist/chunk-QDOSNLB4.js.map +0 -1
- package/dist/chunk-QPKFPHOO.js +0 -178
- package/dist/chunk-QPKFPHOO.js.map +0 -1
- package/dist/chunk-QWUUMMIK.js.map +0 -1
- package/dist/chunk-QY2BHY5O.js.map +0 -1
- package/dist/chunk-TVVVQQAK.js.map +0 -1
- package/dist/chunk-U4PV25RD.js.map +0 -1
- package/dist/chunk-UYSKNO6E.js.map +0 -1
- package/dist/chunk-V4YC4LUK.js.map +0 -1
- package/dist/chunk-WWIQTB2Y.js.map +0 -1
- package/dist/chunk-YNCQ7E4M.js.map +0 -1
- package/dist/chunk-ZJLY4QSU.js.map +0 -1
- /package/dist/{engine-2A6J4XEX.js.map → active-memory-bridge.js.map} +0 -0
- /package/dist/{chunk-NTTLPF7F.js.map → chunk-3QFQGRHO.js.map} +0 -0
- /package/dist/{chunk-G3AG3KZN.js.map → chunk-5IZL4DCV.js.map} +0 -0
- /package/dist/{chunk-BRK4ODMI.js.map → chunk-5NPGSAVB.js.map} +0 -0
- /package/dist/{chunk-L5RPWGFK.js.map → chunk-7DHTMOND.js.map} +0 -0
- /package/dist/{chunk-Q6FETXJA.js.map → chunk-7SEAZFFB.js.map} +0 -0
- /package/dist/{chunk-QANCTXQF.js.map → chunk-AYPYCLR7.js.map} +0 -0
- /package/dist/{chunk-LP47L3ZX.js.map → chunk-BTY5RRRF.js.map} +0 -0
- /package/dist/{chunk-SCHEKPYH.js.map → chunk-C2EFFULQ.js.map} +0 -0
- /package/dist/{chunk-GJR6D6KC.js.map → chunk-D654IBA6.js.map} +0 -0
- /package/dist/{chunk-UV2FO7J4.js.map → chunk-E6K4NIEU.js.map} +0 -0
- /package/dist/{chunk-T4WRIV2C.js.map → chunk-EABGC2TL.js.map} +0 -0
- /package/dist/{chunk-ONRU4L2N.js.map → chunk-FEMOX5AD.js.map} +0 -0
- /package/dist/{chunk-IFFFR3MR.js.map → chunk-FSFEQI74.js.map} +0 -0
- /package/dist/{chunk-UIYZ5T3I.js.map → chunk-GJQPH5G3.js.map} +0 -0
- /package/dist/{chunk-ZKYI7UVO.js.map → chunk-JR4ZC3G4.js.map} +0 -0
- /package/dist/{chunk-UCYSTFZR.js.map → chunk-JRNQ3RNA.js.map} +0 -0
- /package/dist/{chunk-GPGBSNKM.js.map → chunk-K4FLSOR5.js.map} +0 -0
- /package/dist/{chunk-M5ZBBBJI.js.map → chunk-KEG4GNGI.js.map} +0 -0
- /package/dist/{chunk-L7WO3MZ4.js.map → chunk-KWP7T3DP.js.map} +0 -0
- /package/dist/{chunk-J47FNDR7.js.map → chunk-MYQWXITD.js.map} +0 -0
- /package/dist/{chunk-YNI4S5WT.js.map → chunk-N53K2EXC.js.map} +0 -0
- /package/dist/{chunk-763GUIOU.js.map → chunk-NBNN5GOB.js.map} +0 -0
- /package/dist/{chunk-OOSWAUYB.js.map → chunk-ODWDQNRE.js.map} +0 -0
- /package/dist/{chunk-J3BT33K7.js.map → chunk-POBPGDWI.js.map} +0 -0
- /package/dist/{chunk-OTAVQCSF.js.map → chunk-PYXS46O7.js.map} +0 -0
- /package/dist/{chunk-XUHI52HK.js.map → chunk-QKAH5B6E.js.map} +0 -0
- /package/dist/{chunk-HG2NKWR2.js.map → chunk-S4LX5EBI.js.map} +0 -0
- /package/dist/{chunk-4A24LIM2.js.map → chunk-S75M5ZRK.js.map} +0 -0
- /package/dist/{chunk-MDDAA2AO.js.map → chunk-UPMD5XND.js.map} +0 -0
- /package/dist/{chunk-M5KEYE5E.js.map → chunk-URB2WSKZ.js.map} +0 -0
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SPECULATIVE_TTL_DAYS,
|
|
3
3
|
confidenceTier
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U2IQTSBY.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_CITATION_FORMAT,
|
|
7
|
+
hasCitation,
|
|
8
|
+
stripCitationForTemplate
|
|
9
|
+
} from "./chunk-4KAN3GZ3.js";
|
|
10
|
+
import {
|
|
11
|
+
createVersion
|
|
12
|
+
} from "./chunk-6ZH4TU6I.js";
|
|
5
13
|
import {
|
|
6
14
|
getCachedEntities,
|
|
7
15
|
setCachedEntities
|
|
8
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-6PFRXT4K.js";
|
|
9
17
|
import {
|
|
10
18
|
inferMemoryStatus,
|
|
11
19
|
isArchivedMemoryPath,
|
|
@@ -22,9 +30,6 @@ import {
|
|
|
22
30
|
readProjectedMemoryState,
|
|
23
31
|
readProjectedMemoryTimeline
|
|
24
32
|
} from "./chunk-BOUYNNYD.js";
|
|
25
|
-
import {
|
|
26
|
-
rotateMarkdownFileToArchive
|
|
27
|
-
} from "./chunk-DM2T26WE.js";
|
|
28
33
|
import {
|
|
29
34
|
closeContinuityIncidentRecord,
|
|
30
35
|
createContinuityIncidentRecord,
|
|
@@ -34,12 +39,20 @@ import {
|
|
|
34
39
|
serializeContinuityIncident,
|
|
35
40
|
upsertContinuityLoopInMarkdown
|
|
36
41
|
} from "./chunk-QSVPYQPG.js";
|
|
42
|
+
import {
|
|
43
|
+
rotateMarkdownFileToArchive
|
|
44
|
+
} from "./chunk-DM2T26WE.js";
|
|
37
45
|
import {
|
|
38
46
|
sanitizeMemoryContent
|
|
39
47
|
} from "./chunk-M62O4P4T.js";
|
|
48
|
+
import {
|
|
49
|
+
matchEntitySchemaSection,
|
|
50
|
+
normalizeEntityStructuredSection,
|
|
51
|
+
sortStructuredSectionsBySchema
|
|
52
|
+
} from "./chunk-4DJQYKMN.js";
|
|
40
53
|
import {
|
|
41
54
|
log
|
|
42
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-2ODBA7MQ.js";
|
|
43
56
|
|
|
44
57
|
// src/storage.ts
|
|
45
58
|
import { access, readdir, readFile, stat, writeFile, mkdir, unlink, rename, appendFile } from "fs/promises";
|
|
@@ -146,6 +159,7 @@ function serializeFrontmatter(fm) {
|
|
|
146
159
|
if (fm.structuredAttributes && Object.keys(fm.structuredAttributes).length > 0) {
|
|
147
160
|
lines.push(`structuredAttributes: ${JSON.stringify(fm.structuredAttributes)}`);
|
|
148
161
|
}
|
|
162
|
+
if (fm.contentHash) lines.push(`contentHash: ${fm.contentHash}`);
|
|
149
163
|
lines.push("---");
|
|
150
164
|
return lines.join("\n");
|
|
151
165
|
}
|
|
@@ -280,7 +294,9 @@ function parseFrontmatter(raw) {
|
|
|
280
294
|
// v8.0 Phase 2B: HiMem episode/note classification
|
|
281
295
|
memoryKind: fm.memoryKind || void 0,
|
|
282
296
|
// Structured attributes (JSON on a single line)
|
|
283
|
-
structuredAttributes: parseStructuredAttributes(fm.structuredAttributes)
|
|
297
|
+
structuredAttributes: parseStructuredAttributes(fm.structuredAttributes),
|
|
298
|
+
// Raw-content dedup hash (format-agnostic archive/consolidation cleanup)
|
|
299
|
+
contentHash: fm.contentHash || void 0
|
|
284
300
|
},
|
|
285
301
|
content
|
|
286
302
|
};
|
|
@@ -303,14 +319,47 @@ function parseFrontmatter(raw) {
|
|
|
303
319
|
}
|
|
304
320
|
return result;
|
|
305
321
|
}
|
|
306
|
-
function
|
|
307
|
-
|
|
322
|
+
function inferEntityTypeFromContent(content) {
|
|
323
|
+
const typeMatch = content.match(/^\*\*Type:\*\*\s*([^\n]+)/m)?.[1]?.trim().toLowerCase();
|
|
324
|
+
return typeMatch || void 0;
|
|
325
|
+
}
|
|
326
|
+
var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
|
|
327
|
+
"company",
|
|
328
|
+
"other",
|
|
329
|
+
"person",
|
|
330
|
+
"place",
|
|
331
|
+
"project",
|
|
332
|
+
"tool",
|
|
333
|
+
"topic"
|
|
334
|
+
]);
|
|
335
|
+
function inferEntityTypeFromFilename(pathRel) {
|
|
336
|
+
const basename = path.basename(pathRel, ".md").toLowerCase();
|
|
337
|
+
const separator = basename.indexOf("-");
|
|
338
|
+
if (separator <= 0) return void 0;
|
|
339
|
+
const candidate = basename.slice(0, separator);
|
|
340
|
+
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
341
|
+
}
|
|
342
|
+
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
343
|
+
const normalizedPath = pathRel.split(path.sep).join("/");
|
|
344
|
+
let normalizedFrontmatter = frontmatter;
|
|
345
|
+
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
346
|
+
const basename = path.basename(pathRel, ".md");
|
|
347
|
+
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
348
|
+
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
349
|
+
normalizedFrontmatter = {
|
|
350
|
+
...normalizedFrontmatter,
|
|
351
|
+
id: typeof normalizedFrontmatter.id === "string" && normalizedFrontmatter.id.trim().length > 0 ? normalizedFrontmatter.id : basename,
|
|
352
|
+
category: "entity",
|
|
353
|
+
tags: existingTags.includes(inferredType) ? existingTags : [...existingTags, inferredType]
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (isArchivedMemoryPath(pathRel) && (!normalizedFrontmatter.status || normalizedFrontmatter.status === "active")) {
|
|
308
357
|
return {
|
|
309
|
-
...
|
|
358
|
+
...normalizedFrontmatter,
|
|
310
359
|
status: "archived"
|
|
311
360
|
};
|
|
312
361
|
}
|
|
313
|
-
return
|
|
362
|
+
return normalizedFrontmatter;
|
|
314
363
|
}
|
|
315
364
|
function inferCurrentStateStatus(frontmatter, pathRel, fallbackStatus) {
|
|
316
365
|
return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
|
|
@@ -406,6 +455,33 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
406
455
|
this.dirty = true;
|
|
407
456
|
}
|
|
408
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Remove a pre-computed SHA-256 hash directly from the index without
|
|
460
|
+
* re-hashing. Use this when the caller already holds the stored hash
|
|
461
|
+
* (e.g. `memory.frontmatter.contentHash`) to avoid the double-hash bug
|
|
462
|
+
* where `remove(hash)` would compute `hash(hash)` and never match the
|
|
463
|
+
* entry.
|
|
464
|
+
*/
|
|
465
|
+
removeByHash(hash) {
|
|
466
|
+
if (this.hashes.delete(hash)) {
|
|
467
|
+
this.dirty = true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Add a pre-computed SHA-256 hash directly to the index without re-hashing.
|
|
472
|
+
* Use this when the caller already holds the stored hash
|
|
473
|
+
* (e.g. `memory.frontmatter.contentHash`) so that the index records the raw
|
|
474
|
+
* content hash rather than re-hashing the citation-annotated body.
|
|
475
|
+
*
|
|
476
|
+
* @internal Only called from `StorageManager.ensureFactHashIndexAuthoritative`.
|
|
477
|
+
* Not part of the public API — prefer `add(content)` for external callers.
|
|
478
|
+
*/
|
|
479
|
+
addByHash(hash) {
|
|
480
|
+
if (!this.hashes.has(hash)) {
|
|
481
|
+
this.hashes.add(hash);
|
|
482
|
+
this.dirty = true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
409
485
|
/** Normalize content and compute SHA-256 hash. */
|
|
410
486
|
static normalizeContent(content) {
|
|
411
487
|
return content.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -416,36 +492,598 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
416
492
|
return createHash("sha256").update(normalized).digest("hex");
|
|
417
493
|
}
|
|
418
494
|
};
|
|
419
|
-
function
|
|
420
|
-
|
|
495
|
+
function normalizeAttributePairs(pairs) {
|
|
496
|
+
return Object.entries(pairs).map(([k, v]) => [k.trim().toLowerCase(), v.trim()]).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
497
|
+
}
|
|
498
|
+
function parseEntityFrontmatter(raw) {
|
|
499
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
500
|
+
if (!match) {
|
|
501
|
+
return { frontmatter: {}, body: raw };
|
|
502
|
+
}
|
|
503
|
+
const values = {};
|
|
504
|
+
const extraLines = [];
|
|
505
|
+
const recognizedKeys = /* @__PURE__ */ new Set([
|
|
506
|
+
"created",
|
|
507
|
+
"updated",
|
|
508
|
+
"synthesis_updated_at",
|
|
509
|
+
"synthesis_timeline_count",
|
|
510
|
+
"synthesis_structured_fact_count",
|
|
511
|
+
"synthesis_structured_fact_digest",
|
|
512
|
+
"synthesis_version"
|
|
513
|
+
]);
|
|
514
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
515
|
+
if (/^\s/.test(line)) {
|
|
516
|
+
extraLines.push(line);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const colonIdx = line.indexOf(":");
|
|
520
|
+
if (colonIdx === -1) {
|
|
521
|
+
extraLines.push(line);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const key = line.slice(0, colonIdx).trim();
|
|
525
|
+
if (!recognizedKeys.has(key)) {
|
|
526
|
+
extraLines.push(line);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const value = parseManagedFrontmatterValue(line.slice(colonIdx + 1));
|
|
530
|
+
values[key] = value;
|
|
531
|
+
}
|
|
532
|
+
const synthesisTimelineCount = Number.parseInt(values.synthesis_timeline_count ?? "", 10);
|
|
533
|
+
const synthesisStructuredFactCount = Number.parseInt(values.synthesis_structured_fact_count ?? "", 10);
|
|
534
|
+
const synthesisVersion = Number.parseInt(values.synthesis_version ?? "", 10);
|
|
535
|
+
return {
|
|
536
|
+
frontmatter: {
|
|
537
|
+
created: values.created || void 0,
|
|
538
|
+
updated: values.updated || void 0,
|
|
539
|
+
synthesisUpdatedAt: values.synthesis_updated_at || void 0,
|
|
540
|
+
synthesisTimelineCount: Number.isFinite(synthesisTimelineCount) ? synthesisTimelineCount : void 0,
|
|
541
|
+
synthesisStructuredFactCount: Number.isFinite(synthesisStructuredFactCount) ? synthesisStructuredFactCount : void 0,
|
|
542
|
+
synthesisStructuredFactDigest: values.synthesis_structured_fact_digest || void 0,
|
|
543
|
+
synthesisVersion: Number.isFinite(synthesisVersion) ? synthesisVersion : void 0,
|
|
544
|
+
extraLines
|
|
545
|
+
},
|
|
546
|
+
body: match[2]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function parseManagedFrontmatterValue(rawValue) {
|
|
550
|
+
const trimmed = rawValue.trim();
|
|
551
|
+
if (!trimmed) return "";
|
|
552
|
+
const openingQuote = trimmed[0];
|
|
553
|
+
if (openingQuote === '"' || openingQuote === "'") {
|
|
554
|
+
let escaped = false;
|
|
555
|
+
for (let index = 1; index < trimmed.length; index += 1) {
|
|
556
|
+
const char = trimmed[index];
|
|
557
|
+
if (openingQuote === '"' && !escaped && char === "\\") {
|
|
558
|
+
escaped = true;
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (!escaped && char === openingQuote) {
|
|
562
|
+
return trimmed.slice(1, index);
|
|
563
|
+
}
|
|
564
|
+
escaped = false;
|
|
565
|
+
}
|
|
566
|
+
return trimmed.slice(1).replace(new RegExp(`${openingQuote}$`), "");
|
|
567
|
+
}
|
|
568
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
569
|
+
if (trimmed[index] === "#" && (index === 0 || /\s/.test(trimmed[index - 1] ?? ""))) {
|
|
570
|
+
return trimmed.slice(0, index).trimEnd();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return trimmed;
|
|
574
|
+
}
|
|
575
|
+
function readEntitySectionText(lines, sectionNames, options = {}) {
|
|
576
|
+
const normalizedSections = new Set(sectionNames.map((name) => name.toLowerCase()));
|
|
577
|
+
let section = "";
|
|
578
|
+
const sectionLines = [];
|
|
579
|
+
for (const line of lines) {
|
|
580
|
+
if (line.startsWith("## ")) {
|
|
581
|
+
const nextSection = line.slice(3).trim().toLowerCase();
|
|
582
|
+
if (section && !normalizedSections.has(nextSection)) break;
|
|
583
|
+
section = normalizedSections.has(nextSection) ? nextSection : "";
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (!section) continue;
|
|
587
|
+
const trimmed = line.trim();
|
|
588
|
+
if (!trimmed) {
|
|
589
|
+
if (options.preserveBullets === true && sectionLines.length > 0 && sectionLines[sectionLines.length - 1] !== "") {
|
|
590
|
+
sectionLines.push("");
|
|
591
|
+
}
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (options.skipTimelineBullets === true && trimmed.startsWith("- ") && isEntitySynthesisTimelinePromotionBullet(trimmed.slice(2))) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (trimmed.startsWith("- ") && options.preserveBullets !== true) continue;
|
|
598
|
+
sectionLines.push(options.preserveBullets === true ? line.trimEnd() : trimmed);
|
|
599
|
+
}
|
|
600
|
+
while (sectionLines[sectionLines.length - 1] === "") {
|
|
601
|
+
sectionLines.pop();
|
|
602
|
+
}
|
|
603
|
+
if (sectionLines.length === 0) return void 0;
|
|
604
|
+
return sectionLines.join(options.preserveBullets === true ? "\n" : " ");
|
|
605
|
+
}
|
|
606
|
+
function parseEntityTimelineBullet(bullet, fallbackTimestamp) {
|
|
607
|
+
const trimmed = bullet.trim();
|
|
608
|
+
if (!trimmed) return null;
|
|
609
|
+
let rest = trimmed;
|
|
610
|
+
const entry = {
|
|
611
|
+
timestamp: trimmed.startsWith("[") ? "" : fallbackTimestamp,
|
|
612
|
+
text: ""
|
|
613
|
+
};
|
|
614
|
+
const consumedMetadataSegments = [];
|
|
615
|
+
let literalSingleSourceSegment;
|
|
616
|
+
if (!trimmed.startsWith("[")) {
|
|
617
|
+
entry.text = trimmed;
|
|
618
|
+
return entry.text ? entry : null;
|
|
619
|
+
}
|
|
620
|
+
const firstEnd = trimmed.indexOf("]");
|
|
621
|
+
if (firstEnd === -1) {
|
|
622
|
+
entry.text = trimmed;
|
|
623
|
+
return entry.text ? entry : null;
|
|
624
|
+
}
|
|
625
|
+
const firstToken = trimmed.slice(1, firstEnd).trim();
|
|
626
|
+
const parsedTimestamp = Date.parse(firstToken);
|
|
627
|
+
if (Number.isFinite(parsedTimestamp)) {
|
|
628
|
+
entry.timestamp = firstToken || fallbackTimestamp;
|
|
629
|
+
rest = trimmed.slice(firstEnd + 1).trimStart();
|
|
630
|
+
}
|
|
631
|
+
while (rest.startsWith("[")) {
|
|
632
|
+
const end = findEntityTimelineTokenEnd(rest);
|
|
633
|
+
if (end === -1) break;
|
|
634
|
+
const rawSegment = rest.slice(0, end + 1);
|
|
635
|
+
const token = rest.slice(1, end).trim();
|
|
636
|
+
const equalsIdx = token.indexOf("=");
|
|
637
|
+
if (equalsIdx === -1) {
|
|
638
|
+
if (rest === trimmed) {
|
|
639
|
+
entry.text = trimmed;
|
|
640
|
+
return entry.text ? entry : null;
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
const key = token.slice(0, equalsIdx).trim().toLowerCase();
|
|
645
|
+
const value = unescapeEntityTimelineMetadataValue(token.slice(equalsIdx + 1).trim());
|
|
646
|
+
if (!value) break;
|
|
647
|
+
const nextRest = rest.slice(end + 1).trimStart();
|
|
648
|
+
switch (key) {
|
|
649
|
+
case "source_meta":
|
|
650
|
+
entry.source = value;
|
|
651
|
+
break;
|
|
652
|
+
case "source":
|
|
653
|
+
if (consumedMetadataSegments.length === 0 && !nextRest.startsWith("[") && nextRest.length > 0 && !isManagedEntityTimelineSource(value)) {
|
|
654
|
+
literalSingleSourceSegment = rawSegment;
|
|
655
|
+
rest = nextRest;
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
entry.source = value;
|
|
659
|
+
break;
|
|
660
|
+
case "session":
|
|
661
|
+
case "sessionkey":
|
|
662
|
+
entry.sessionKey = value;
|
|
663
|
+
break;
|
|
664
|
+
case "principal":
|
|
665
|
+
entry.principal = value;
|
|
666
|
+
break;
|
|
667
|
+
default:
|
|
668
|
+
entry.text = rest.trim();
|
|
669
|
+
return entry.text ? entry : null;
|
|
670
|
+
}
|
|
671
|
+
if (literalSingleSourceSegment) break;
|
|
672
|
+
consumedMetadataSegments.push(rawSegment);
|
|
673
|
+
rest = nextRest;
|
|
674
|
+
}
|
|
675
|
+
if (literalSingleSourceSegment) {
|
|
676
|
+
return {
|
|
677
|
+
timestamp: entry.timestamp,
|
|
678
|
+
text: `${literalSingleSourceSegment} ${rest}`.trim()
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
entry.text = rest.trim();
|
|
682
|
+
if (!entry.text) return null;
|
|
683
|
+
return entry;
|
|
684
|
+
}
|
|
685
|
+
function isEntitySynthesisTimelinePromotionBullet(bullet) {
|
|
686
|
+
const trimmed = bullet.trim();
|
|
687
|
+
if (!trimmed.startsWith("[")) return false;
|
|
688
|
+
const firstEnd = findEntityTimelineTokenEnd(trimmed);
|
|
689
|
+
if (firstEnd === -1) return false;
|
|
690
|
+
const firstToken = trimmed.slice(1, firstEnd).trim();
|
|
691
|
+
return looksLikeEntityTimelineTimestamp(firstToken);
|
|
692
|
+
}
|
|
693
|
+
function looksLikeEntityTimelineTimestamp(token) {
|
|
694
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:[T\s].*)?$/.test(token)) return false;
|
|
695
|
+
return Number.isFinite(Date.parse(token));
|
|
696
|
+
}
|
|
697
|
+
function isManagedEntityTimelineSource(source) {
|
|
698
|
+
switch (source.trim().toLowerCase()) {
|
|
699
|
+
case "artifact":
|
|
700
|
+
case "chunking":
|
|
701
|
+
case "cli-migrate":
|
|
702
|
+
case "compounding-promotion":
|
|
703
|
+
case "consolidation":
|
|
704
|
+
case "contradiction-detection":
|
|
705
|
+
case "entity_extraction":
|
|
706
|
+
case "explicit":
|
|
707
|
+
case "explicit-inline":
|
|
708
|
+
case "explicit-inline-review":
|
|
709
|
+
case "explicit-review":
|
|
710
|
+
case "extraction":
|
|
711
|
+
case "extraction-shared-promotion":
|
|
712
|
+
case "manual":
|
|
713
|
+
case "migration":
|
|
714
|
+
case "migration-rechunk":
|
|
715
|
+
case "proactive":
|
|
716
|
+
case "replay":
|
|
717
|
+
case "semantic-consolidation":
|
|
718
|
+
case "unknown":
|
|
719
|
+
return true;
|
|
720
|
+
default:
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function findEntityTimelineTokenEnd(input) {
|
|
725
|
+
let escaped = false;
|
|
726
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
727
|
+
const char = input[index];
|
|
728
|
+
if (escaped) {
|
|
729
|
+
escaped = false;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (char === "\\") {
|
|
733
|
+
escaped = true;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (char === "]") return index;
|
|
737
|
+
}
|
|
738
|
+
return -1;
|
|
739
|
+
}
|
|
740
|
+
function escapeEntityTimelineMetadataValue(value) {
|
|
741
|
+
let escaped = "";
|
|
742
|
+
for (const char of value) {
|
|
743
|
+
switch (char) {
|
|
744
|
+
case "\\":
|
|
745
|
+
escaped += "\\\\";
|
|
746
|
+
break;
|
|
747
|
+
case "]":
|
|
748
|
+
escaped += "\\]";
|
|
749
|
+
break;
|
|
750
|
+
case "\n":
|
|
751
|
+
escaped += "\\n";
|
|
752
|
+
break;
|
|
753
|
+
case "\r":
|
|
754
|
+
escaped += "\\r";
|
|
755
|
+
break;
|
|
756
|
+
case " ":
|
|
757
|
+
escaped += "\\t";
|
|
758
|
+
break;
|
|
759
|
+
default: {
|
|
760
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
761
|
+
if (codePoint < 32) {
|
|
762
|
+
escaped += `\\u${codePoint.toString(16).padStart(4, "0")}`;
|
|
763
|
+
} else {
|
|
764
|
+
escaped += char;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return escaped;
|
|
770
|
+
}
|
|
771
|
+
function unescapeEntityTimelineMetadataValue(value) {
|
|
772
|
+
if (!value.includes("\\")) return value;
|
|
773
|
+
let result = "";
|
|
774
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
775
|
+
const char = value[index];
|
|
776
|
+
if (char !== "\\") {
|
|
777
|
+
result += char;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
const next = value[index + 1];
|
|
781
|
+
if (!next) {
|
|
782
|
+
result += "\\";
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
switch (next) {
|
|
786
|
+
case "n":
|
|
787
|
+
result += "\n";
|
|
788
|
+
index += 1;
|
|
789
|
+
break;
|
|
790
|
+
case "r":
|
|
791
|
+
result += "\r";
|
|
792
|
+
index += 1;
|
|
793
|
+
break;
|
|
794
|
+
case "t":
|
|
795
|
+
result += " ";
|
|
796
|
+
index += 1;
|
|
797
|
+
break;
|
|
798
|
+
case "u": {
|
|
799
|
+
const hex = value.slice(index + 2, index + 6);
|
|
800
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
801
|
+
result += String.fromCharCode(parseInt(hex, 16));
|
|
802
|
+
index += 5;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
result += "u";
|
|
806
|
+
index += 1;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
default:
|
|
810
|
+
result += next;
|
|
811
|
+
index += 1;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return result;
|
|
816
|
+
}
|
|
817
|
+
function serializeEntityTimelineEntry(entry) {
|
|
818
|
+
const tokens = [];
|
|
819
|
+
if (entry.timestamp.trim().length > 0) {
|
|
820
|
+
tokens.push(`[${entry.timestamp}]`);
|
|
821
|
+
}
|
|
822
|
+
if (entry.source) {
|
|
823
|
+
const sourceKey = isManagedEntityTimelineSource(entry.source) ? "source" : "source_meta";
|
|
824
|
+
tokens.push(`[${sourceKey}=${escapeEntityTimelineMetadataValue(entry.source)}]`);
|
|
825
|
+
}
|
|
826
|
+
if (entry.sessionKey) {
|
|
827
|
+
tokens.push(`[session=${escapeEntityTimelineMetadataValue(entry.sessionKey)}]`);
|
|
828
|
+
}
|
|
829
|
+
if (entry.principal) {
|
|
830
|
+
tokens.push(`[principal=${escapeEntityTimelineMetadataValue(entry.principal)}]`);
|
|
831
|
+
}
|
|
832
|
+
const serializedMetadata = tokens.length > 0 ? `${tokens.join(" ")} ` : "";
|
|
833
|
+
return `- ${serializedMetadata}${entry.text}`.trimEnd();
|
|
834
|
+
}
|
|
835
|
+
function dedupeEntityTimelineFacts(timeline) {
|
|
836
|
+
return [...new Set(
|
|
837
|
+
timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0)
|
|
838
|
+
)];
|
|
839
|
+
}
|
|
840
|
+
function normalizeEntitySectionFact(value) {
|
|
841
|
+
return value.replace(/\s+/g, " ").trim();
|
|
842
|
+
}
|
|
843
|
+
function normalizeStructuredSectionFacts(facts) {
|
|
844
|
+
return [...new Set(
|
|
845
|
+
facts.map((fact) => normalizeEntitySectionFact(fact)).filter((fact) => fact.length > 0)
|
|
846
|
+
)];
|
|
847
|
+
}
|
|
848
|
+
function collectStructuredSectionFacts(structuredSections) {
|
|
849
|
+
const facts = [];
|
|
850
|
+
for (const section of structuredSections) {
|
|
851
|
+
for (const fact of section.facts) {
|
|
852
|
+
const normalized = normalizeEntitySectionFact(fact);
|
|
853
|
+
if (!normalized) continue;
|
|
854
|
+
facts.push(normalized);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return [...new Set(facts)];
|
|
858
|
+
}
|
|
859
|
+
function compileEntityFacts(timeline, structuredSections) {
|
|
860
|
+
const facts = [];
|
|
861
|
+
const seen = /* @__PURE__ */ new Set();
|
|
862
|
+
for (const fact of dedupeEntityTimelineFacts(timeline)) {
|
|
863
|
+
if (seen.has(fact)) continue;
|
|
864
|
+
seen.add(fact);
|
|
865
|
+
facts.push(fact);
|
|
866
|
+
}
|
|
867
|
+
for (const fact of collectStructuredSectionFacts(structuredSections)) {
|
|
868
|
+
if (seen.has(fact)) continue;
|
|
869
|
+
seen.add(fact);
|
|
870
|
+
facts.push(fact);
|
|
871
|
+
}
|
|
872
|
+
return facts;
|
|
873
|
+
}
|
|
874
|
+
function parseEntityStructuredSectionFacts(lines) {
|
|
875
|
+
const facts = [];
|
|
876
|
+
let currentBlock = [];
|
|
877
|
+
const flushCurrentBlock = () => {
|
|
878
|
+
const normalized = normalizeEntitySectionFact(currentBlock.join(" "));
|
|
879
|
+
if (normalized.length > 0) facts.push(normalized);
|
|
880
|
+
currentBlock = [];
|
|
881
|
+
};
|
|
882
|
+
for (const rawLine of lines) {
|
|
883
|
+
const line = rawLine.trim();
|
|
884
|
+
if (!line) {
|
|
885
|
+
flushCurrentBlock();
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (line.startsWith("- ")) {
|
|
889
|
+
flushCurrentBlock();
|
|
890
|
+
currentBlock = [line.slice(2).trim()];
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
currentBlock.push(line);
|
|
894
|
+
}
|
|
895
|
+
flushCurrentBlock();
|
|
896
|
+
return [...new Set(facts)];
|
|
897
|
+
}
|
|
898
|
+
function looksLikeStructuredSectionFactList(lines) {
|
|
899
|
+
const firstNonBlank = lines.find((line) => line.trim().length > 0)?.trim() ?? "";
|
|
900
|
+
return firstNonBlank.startsWith("- ");
|
|
901
|
+
}
|
|
902
|
+
function partitionEntityStructuredSections(entityType, extraSections, entitySchemas) {
|
|
903
|
+
const structuredSections = [];
|
|
904
|
+
const remainingExtraSections = [];
|
|
905
|
+
const structuredSectionIndex = /* @__PURE__ */ new Map();
|
|
906
|
+
for (const section of extraSections) {
|
|
907
|
+
const matchedSection = matchEntitySchemaSection(entityType, section.title, entitySchemas);
|
|
908
|
+
if (!matchedSection && !looksLikeStructuredSectionFactList(section.lines)) {
|
|
909
|
+
remainingExtraSections.push(section);
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const facts = parseEntityStructuredSectionFacts(section.lines);
|
|
913
|
+
if (!matchedSection && facts.length === 0) {
|
|
914
|
+
remainingExtraSections.push(section);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
const normalizedSection = matchedSection ? { key: matchedSection.key, title: matchedSection.title } : normalizeEntityStructuredSection(
|
|
918
|
+
entityType,
|
|
919
|
+
{ key: section.title, title: section.title },
|
|
920
|
+
entitySchemas
|
|
921
|
+
);
|
|
922
|
+
if (facts.length === 0) {
|
|
923
|
+
remainingExtraSections.push(section);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const existing = structuredSectionIndex.get(normalizedSection.key);
|
|
927
|
+
if (existing) {
|
|
928
|
+
existing.facts = normalizeStructuredSectionFacts([...existing.facts, ...facts]);
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const structuredSection = {
|
|
932
|
+
key: normalizedSection.key,
|
|
933
|
+
title: normalizedSection.title,
|
|
934
|
+
facts: normalizeStructuredSectionFacts(facts)
|
|
935
|
+
};
|
|
936
|
+
structuredSections.push(structuredSection);
|
|
937
|
+
structuredSectionIndex.set(normalizedSection.key, structuredSection);
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
structuredSections,
|
|
941
|
+
remainingExtraSections
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function latestEntityTimelineTimestamp(entity) {
|
|
945
|
+
let latestRaw;
|
|
946
|
+
for (const entry of entity.timeline) {
|
|
947
|
+
const timestamp = entry.timestamp.trim();
|
|
948
|
+
if (!timestamp) continue;
|
|
949
|
+
if (!latestRaw || compareEntityTimestamps(timestamp, latestRaw) > 0) {
|
|
950
|
+
latestRaw = timestamp;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return latestRaw;
|
|
954
|
+
}
|
|
955
|
+
function compareEntityTimestamps(left, right) {
|
|
956
|
+
const leftValue = left?.trim() ?? "";
|
|
957
|
+
const rightValue = right?.trim() ?? "";
|
|
958
|
+
if (!leftValue && !rightValue) return 0;
|
|
959
|
+
if (!leftValue) return -1;
|
|
960
|
+
if (!rightValue) return 1;
|
|
961
|
+
const leftMs = Date.parse(leftValue);
|
|
962
|
+
const rightMs = Date.parse(rightValue);
|
|
963
|
+
const leftParsed = Number.isFinite(leftMs);
|
|
964
|
+
const rightParsed = Number.isFinite(rightMs);
|
|
965
|
+
if (leftParsed && rightParsed) {
|
|
966
|
+
if (leftMs === rightMs) return 0;
|
|
967
|
+
return leftMs > rightMs ? 1 : -1;
|
|
968
|
+
}
|
|
969
|
+
if (leftParsed) return 1;
|
|
970
|
+
if (rightParsed) return -1;
|
|
971
|
+
return leftValue.localeCompare(rightValue);
|
|
972
|
+
}
|
|
973
|
+
function countEntityStructuredFacts(entity) {
|
|
974
|
+
return (entity.structuredSections ?? []).reduce((count, section) => count + section.facts.length, 0);
|
|
975
|
+
}
|
|
976
|
+
function fingerprintEntityStructuredFacts(entity) {
|
|
977
|
+
const normalizedSections = (entity.structuredSections ?? []).map((section) => ({
|
|
978
|
+
key: section.key.trim().toLowerCase(),
|
|
979
|
+
title: section.title.replace(/\s+/g, " ").trim(),
|
|
980
|
+
facts: normalizeStructuredSectionFacts(section.facts).slice().sort((left, right) => left.localeCompare(right))
|
|
981
|
+
})).filter((section) => section.facts.length > 0).sort((left, right) => left.key.localeCompare(right.key) || left.title.localeCompare(right.title) || left.facts.join("\n").localeCompare(right.facts.join("\n")));
|
|
982
|
+
if (normalizedSections.length === 0) return void 0;
|
|
983
|
+
return createHash("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
|
|
984
|
+
}
|
|
985
|
+
function isEntitySynthesisStale(entity) {
|
|
986
|
+
const structuredFactCount = countEntityStructuredFacts(entity);
|
|
987
|
+
const structuredFactDigest = fingerprintEntityStructuredFacts(entity);
|
|
988
|
+
const storedStructuredFactDigest = entity.synthesisStructuredFactDigest?.trim() || void 0;
|
|
989
|
+
if (entity.timeline.length === 0 && structuredFactCount === 0) return false;
|
|
990
|
+
if (!entity.synthesis?.trim()) return true;
|
|
991
|
+
if (entity.synthesisTimelineCount === void 0) return true;
|
|
992
|
+
if (structuredFactCount > 0 && entity.synthesisStructuredFactCount === void 0) return true;
|
|
993
|
+
if (structuredFactCount > 0 && !storedStructuredFactDigest) return true;
|
|
994
|
+
const latestTimelineTimestamp = latestEntityTimelineTimestamp(entity);
|
|
995
|
+
if (!latestTimelineTimestamp) {
|
|
996
|
+
return entity.timeline.length > entity.synthesisTimelineCount || structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) || structuredFactDigest !== storedStructuredFactDigest;
|
|
997
|
+
}
|
|
998
|
+
if (!entity.synthesisUpdatedAt?.trim()) return true;
|
|
999
|
+
const timelineFreshness = compareEntityTimestamps(latestTimelineTimestamp, entity.synthesisUpdatedAt);
|
|
1000
|
+
if (timelineFreshness > 0) return true;
|
|
1001
|
+
return entity.timeline.length > entity.synthesisTimelineCount || structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) || structuredFactDigest !== storedStructuredFactDigest;
|
|
1002
|
+
}
|
|
1003
|
+
function parseEntityFile(content, entitySchemas) {
|
|
1004
|
+
const { frontmatter, body } = parseEntityFrontmatter(content);
|
|
1005
|
+
const lines = body.split("\n");
|
|
1006
|
+
const recognizedSections = /* @__PURE__ */ new Set([
|
|
1007
|
+
"facts",
|
|
1008
|
+
"timeline",
|
|
1009
|
+
"summary",
|
|
1010
|
+
"synthesis",
|
|
1011
|
+
"connected to",
|
|
1012
|
+
"activity",
|
|
1013
|
+
"aliases"
|
|
1014
|
+
]);
|
|
421
1015
|
let name = "";
|
|
422
1016
|
let type = "other";
|
|
1017
|
+
let created = frontmatter.created ?? "";
|
|
423
1018
|
let updated = "";
|
|
424
|
-
|
|
425
|
-
const facts = [];
|
|
1019
|
+
const legacyFacts = [];
|
|
426
1020
|
const relationships = [];
|
|
427
1021
|
const activity = [];
|
|
428
1022
|
const aliases = [];
|
|
1023
|
+
const timeline = [];
|
|
1024
|
+
const extraSections = [];
|
|
429
1025
|
const headingLine = lines.find((l) => l.startsWith("# "));
|
|
430
1026
|
if (headingLine) name = headingLine.slice(2).trim();
|
|
431
1027
|
const typeLine = lines.find((l) => l.startsWith("**Type:**"));
|
|
432
1028
|
if (typeLine) type = typeLine.replace("**Type:**", "").trim();
|
|
433
1029
|
const updatedLine = lines.find((l) => l.startsWith("**Updated:**"));
|
|
434
1030
|
if (updatedLine) updated = updatedLine.replace("**Updated:**", "").trim();
|
|
1031
|
+
if (!updated) updated = frontmatter.updated ?? frontmatter.created ?? "";
|
|
1032
|
+
if (!created) created = updated;
|
|
1033
|
+
const headingLineIndex = lines.findIndex((l) => l.startsWith("# "));
|
|
1034
|
+
const firstSectionIndex = lines.findIndex((l) => l.startsWith("## "));
|
|
1035
|
+
const preSectionStartIndex = headingLineIndex > -1 ? headingLineIndex + 1 : 0;
|
|
1036
|
+
const preSectionCandidates = firstSectionIndex > -1 ? lines.slice(preSectionStartIndex, firstSectionIndex) : lines.slice(preSectionStartIndex);
|
|
1037
|
+
const preSectionLines = preSectionCandidates.filter(
|
|
1038
|
+
(line) => !line.startsWith("**Type:**") && !line.startsWith("**Updated:**")
|
|
1039
|
+
);
|
|
1040
|
+
const normalizedPreSectionLines = [...preSectionLines];
|
|
1041
|
+
while (normalizedPreSectionLines[0] === "") {
|
|
1042
|
+
normalizedPreSectionLines.shift();
|
|
1043
|
+
}
|
|
1044
|
+
const preservedPreSectionLines = normalizedPreSectionLines.some((line) => line.trim().length > 0) ? normalizedPreSectionLines : [];
|
|
1045
|
+
const fallbackTimestamp = updated || created || "";
|
|
435
1046
|
let section = "";
|
|
1047
|
+
let currentExtraSection = null;
|
|
436
1048
|
for (const line of lines) {
|
|
437
1049
|
if (line.startsWith("## ")) {
|
|
438
|
-
|
|
1050
|
+
const heading = line.slice(3).trim();
|
|
1051
|
+
section = heading.toLowerCase();
|
|
1052
|
+
if (recognizedSections.has(section)) {
|
|
1053
|
+
currentExtraSection = null;
|
|
1054
|
+
} else {
|
|
1055
|
+
currentExtraSection = { title: heading, lines: [] };
|
|
1056
|
+
extraSections.push(currentExtraSection);
|
|
1057
|
+
}
|
|
439
1058
|
continue;
|
|
440
1059
|
}
|
|
1060
|
+
if (currentExtraSection) {
|
|
1061
|
+
currentExtraSection.lines.push(line);
|
|
1062
|
+
}
|
|
441
1063
|
if (!line.startsWith("- ")) continue;
|
|
442
1064
|
const bullet = line.slice(2).trim();
|
|
443
1065
|
if (!bullet) continue;
|
|
444
1066
|
switch (section) {
|
|
445
1067
|
case "facts":
|
|
446
|
-
|
|
1068
|
+
legacyFacts.push(bullet);
|
|
447
1069
|
break;
|
|
1070
|
+
case "timeline": {
|
|
1071
|
+
const parsed = parseEntityTimelineBullet(
|
|
1072
|
+
bullet,
|
|
1073
|
+
fallbackTimestamp
|
|
1074
|
+
);
|
|
1075
|
+
if (parsed) timeline.push(parsed);
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
448
1078
|
case "summary":
|
|
1079
|
+
case "synthesis":
|
|
1080
|
+
if (isEntitySynthesisTimelinePromotionBullet(bullet)) {
|
|
1081
|
+
const parsed = parseEntityTimelineBullet(
|
|
1082
|
+
bullet,
|
|
1083
|
+
fallbackTimestamp
|
|
1084
|
+
);
|
|
1085
|
+
if (parsed) timeline.push(parsed);
|
|
1086
|
+
}
|
|
449
1087
|
break;
|
|
450
1088
|
case "connected to": {
|
|
451
1089
|
const relMatch = bullet.match(/^\[\[([^\]]+)\]\]\s*[—–-]\s*(.+)$/);
|
|
@@ -466,34 +1104,128 @@ function parseEntityFile(content) {
|
|
|
466
1104
|
break;
|
|
467
1105
|
}
|
|
468
1106
|
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1107
|
+
const legacyFactTimelineEntries = legacyFacts.map((fact) => ({
|
|
1108
|
+
timestamp: fallbackTimestamp,
|
|
1109
|
+
text: fact,
|
|
1110
|
+
source: "migration"
|
|
1111
|
+
}));
|
|
1112
|
+
if (legacyFactTimelineEntries.length > 0) {
|
|
1113
|
+
const existingTimelineFacts = new Set(
|
|
1114
|
+
timeline.map((entry) => entry.text.trim()).filter((entry) => entry.length > 0)
|
|
1115
|
+
);
|
|
1116
|
+
for (const fact of legacyFactTimelineEntries) {
|
|
1117
|
+
const normalizedFact = fact.text.trim();
|
|
1118
|
+
if (!normalizedFact || existingTimelineFacts.has(normalizedFact)) continue;
|
|
1119
|
+
timeline.push(fact);
|
|
1120
|
+
existingTimelineFacts.add(normalizedFact);
|
|
476
1121
|
}
|
|
477
|
-
if (summaryLines.length > 0) summary = summaryLines.join(" ");
|
|
478
1122
|
}
|
|
479
|
-
|
|
1123
|
+
const synthesis = readEntitySectionText(lines, ["Synthesis"], { preserveBullets: true, skipTimelineBullets: true }) ?? readEntitySectionText(lines, ["Summary"], { preserveBullets: true, skipTimelineBullets: true });
|
|
1124
|
+
const synthesisUpdatedAt = frontmatter.synthesisUpdatedAt || void 0;
|
|
1125
|
+
const synthesisTimelineCount = frontmatter.synthesisTimelineCount;
|
|
1126
|
+
const synthesisStructuredFactCount = frontmatter.synthesisStructuredFactCount;
|
|
1127
|
+
const synthesisStructuredFactDigest = frontmatter.synthesisStructuredFactDigest;
|
|
1128
|
+
const { structuredSections, remainingExtraSections } = partitionEntityStructuredSections(
|
|
1129
|
+
type,
|
|
1130
|
+
extraSections,
|
|
1131
|
+
entitySchemas
|
|
1132
|
+
);
|
|
1133
|
+
const facts = compileEntityFacts(timeline, structuredSections);
|
|
1134
|
+
return {
|
|
1135
|
+
name,
|
|
1136
|
+
type,
|
|
1137
|
+
created,
|
|
1138
|
+
updated,
|
|
1139
|
+
extraFrontmatterLines: frontmatter.extraLines ?? [],
|
|
1140
|
+
preSectionLines: preservedPreSectionLines,
|
|
1141
|
+
facts,
|
|
1142
|
+
summary: synthesis,
|
|
1143
|
+
synthesis,
|
|
1144
|
+
synthesisUpdatedAt,
|
|
1145
|
+
synthesisTimelineCount,
|
|
1146
|
+
synthesisStructuredFactCount,
|
|
1147
|
+
synthesisStructuredFactDigest,
|
|
1148
|
+
synthesisVersion: frontmatter.synthesisVersion,
|
|
1149
|
+
timeline,
|
|
1150
|
+
structuredSections,
|
|
1151
|
+
relationships,
|
|
1152
|
+
activity,
|
|
1153
|
+
aliases,
|
|
1154
|
+
extraSections: remainingExtraSections
|
|
1155
|
+
};
|
|
480
1156
|
}
|
|
481
|
-
function serializeEntityFile(entity) {
|
|
1157
|
+
function serializeEntityFile(entity, entitySchemas) {
|
|
1158
|
+
const synthesis = entity.synthesis || entity.summary || "";
|
|
1159
|
+
const created = entity.created?.trim() || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
1160
|
+
const updated = entity.updated || created;
|
|
1161
|
+
const timeline = entity.timeline;
|
|
1162
|
+
const structuredSections = sortStructuredSectionsBySchema(
|
|
1163
|
+
entity.type,
|
|
1164
|
+
(entity.structuredSections ?? []).map((section) => ({
|
|
1165
|
+
...section,
|
|
1166
|
+
facts: normalizeStructuredSectionFacts(section.facts)
|
|
1167
|
+
})).filter((section) => section.facts.length > 0),
|
|
1168
|
+
entitySchemas
|
|
1169
|
+
);
|
|
1170
|
+
const sectionFacts = new Set(collectStructuredSectionFacts(structuredSections));
|
|
1171
|
+
const legacyFacts = timeline.length === 0 ? [...new Set(
|
|
1172
|
+
entity.facts.map((fact) => normalizeEntitySectionFact(fact)).filter((fact) => fact.length > 0 && !sectionFacts.has(fact))
|
|
1173
|
+
)] : [];
|
|
1174
|
+
const synthesisUpdatedAt = entity.synthesisUpdatedAt?.trim() || "";
|
|
1175
|
+
const synthesisTimelineCount = entity.synthesisTimelineCount;
|
|
1176
|
+
const synthesisStructuredFactCount = entity.synthesisStructuredFactCount;
|
|
1177
|
+
const synthesisStructuredFactDigest = entity.synthesisStructuredFactDigest?.trim() || "";
|
|
1178
|
+
const synthesisVersion = entity.synthesisVersion ?? (synthesis ? 1 : 0);
|
|
482
1179
|
const lines = [
|
|
1180
|
+
"---",
|
|
1181
|
+
`created: ${created}`,
|
|
1182
|
+
`updated: ${updated}`,
|
|
1183
|
+
`synthesis_updated_at: "${synthesisUpdatedAt}"`,
|
|
1184
|
+
...synthesisTimelineCount === void 0 ? [] : [`synthesis_timeline_count: ${synthesisTimelineCount}`],
|
|
1185
|
+
...synthesisStructuredFactCount === void 0 ? [] : [`synthesis_structured_fact_count: ${synthesisStructuredFactCount}`],
|
|
1186
|
+
...synthesisStructuredFactDigest ? [`synthesis_structured_fact_digest: "${synthesisStructuredFactDigest}"`] : [],
|
|
1187
|
+
`synthesis_version: ${synthesisVersion}`,
|
|
1188
|
+
...entity.extraFrontmatterLines ?? [],
|
|
1189
|
+
"---",
|
|
1190
|
+
"",
|
|
483
1191
|
`# ${entity.name}`,
|
|
484
1192
|
"",
|
|
485
1193
|
`**Type:** ${entity.type}`,
|
|
486
|
-
`**Updated:** ${
|
|
1194
|
+
`**Updated:** ${updated}`,
|
|
487
1195
|
""
|
|
488
1196
|
];
|
|
489
|
-
if (entity.
|
|
490
|
-
lines.push(
|
|
1197
|
+
if ((entity.preSectionLines ?? []).length > 0) {
|
|
1198
|
+
lines.push(...entity.preSectionLines ?? []);
|
|
1199
|
+
if (entity.preSectionLines?.[entity.preSectionLines.length - 1] !== "") {
|
|
1200
|
+
lines.push("");
|
|
1201
|
+
}
|
|
491
1202
|
}
|
|
492
|
-
lines.push("##
|
|
493
|
-
|
|
494
|
-
lines.push(
|
|
1203
|
+
lines.push("## Synthesis", "");
|
|
1204
|
+
if (synthesis) {
|
|
1205
|
+
lines.push(synthesis);
|
|
495
1206
|
}
|
|
496
1207
|
lines.push("");
|
|
1208
|
+
if (timeline.length > 0 || legacyFacts.length === 0) {
|
|
1209
|
+
lines.push("## Timeline", "");
|
|
1210
|
+
for (const entry of timeline) {
|
|
1211
|
+
lines.push(serializeEntityTimelineEntry(entry));
|
|
1212
|
+
}
|
|
1213
|
+
lines.push("");
|
|
1214
|
+
}
|
|
1215
|
+
if (legacyFacts.length > 0) {
|
|
1216
|
+
lines.push("## Facts", "");
|
|
1217
|
+
for (const fact of legacyFacts) {
|
|
1218
|
+
lines.push(`- ${fact}`);
|
|
1219
|
+
}
|
|
1220
|
+
lines.push("");
|
|
1221
|
+
}
|
|
1222
|
+
for (const section of structuredSections) {
|
|
1223
|
+
lines.push(`## ${section.title}`, "");
|
|
1224
|
+
for (const fact of section.facts) {
|
|
1225
|
+
lines.push(`- ${fact}`);
|
|
1226
|
+
}
|
|
1227
|
+
lines.push("");
|
|
1228
|
+
}
|
|
497
1229
|
if (entity.relationships.length > 0) {
|
|
498
1230
|
lines.push("## Connected to", "");
|
|
499
1231
|
for (const rel of entity.relationships) {
|
|
@@ -515,13 +1247,37 @@ function serializeEntityFile(entity) {
|
|
|
515
1247
|
}
|
|
516
1248
|
lines.push("");
|
|
517
1249
|
}
|
|
1250
|
+
for (const section of entity.extraSections ?? []) {
|
|
1251
|
+
lines.push(`## ${section.title}`);
|
|
1252
|
+
lines.push(...section.lines);
|
|
1253
|
+
if (section.lines.length > 0 && section.lines[section.lines.length - 1] !== "") {
|
|
1254
|
+
lines.push("");
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
518
1257
|
return lines.join("\n");
|
|
519
1258
|
}
|
|
1259
|
+
function buildEntitySchemaCacheKey(entitySchemas) {
|
|
1260
|
+
if (!entitySchemas) return "";
|
|
1261
|
+
const normalized = Object.entries(entitySchemas).sort(([left], [right]) => left.localeCompare(right)).map(([entityType, schema]) => [
|
|
1262
|
+
entityType,
|
|
1263
|
+
{
|
|
1264
|
+
sections: schema.sections.map((section) => ({
|
|
1265
|
+
key: section.key,
|
|
1266
|
+
title: section.title,
|
|
1267
|
+
description: section.description,
|
|
1268
|
+
aliases: section.aliases ? [...section.aliases] : void 0
|
|
1269
|
+
}))
|
|
1270
|
+
}
|
|
1271
|
+
]);
|
|
1272
|
+
return JSON.stringify(normalized);
|
|
1273
|
+
}
|
|
520
1274
|
var StorageManager = class _StorageManager {
|
|
521
|
-
constructor(baseDir) {
|
|
1275
|
+
constructor(baseDir, entitySchemas) {
|
|
522
1276
|
this.baseDir = baseDir;
|
|
1277
|
+
this.entitySchemas = entitySchemas;
|
|
523
1278
|
}
|
|
524
1279
|
baseDir;
|
|
1280
|
+
entitySchemas;
|
|
525
1281
|
knowledgeIndexCache = null;
|
|
526
1282
|
static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
|
|
527
1283
|
// 10 minutes (entity mutations invalidate)
|
|
@@ -530,6 +1286,9 @@ var StorageManager = class _StorageManager {
|
|
|
530
1286
|
// 1 minute
|
|
531
1287
|
static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
532
1288
|
static memoryStatusVersionByDir = /* @__PURE__ */ new Map();
|
|
1289
|
+
// In-process fallback for the cold-write sentinel (used when the disk file
|
|
1290
|
+
// is not accessible). The canonical source of truth is state/cold-write.log.
|
|
1291
|
+
static coldWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
533
1292
|
// Module-level cache for readAllMemories() keyed by base directory.
|
|
534
1293
|
// Shared across all StorageManager instances to avoid duplicate I/O when
|
|
535
1294
|
// multiple concurrent callers (e.g. verifiedRecall + verifiedRules) read the
|
|
@@ -541,6 +1300,29 @@ var StorageManager = class _StorageManager {
|
|
|
541
1300
|
// refresh. This eliminates the 13-60 s cold-scan penalty that would otherwise
|
|
542
1301
|
// block recall requests every 5 minutes on large memory collections (80k+ files).
|
|
543
1302
|
static allMemoriesInFlight = /* @__PURE__ */ new Map();
|
|
1303
|
+
// Cache for readAllColdMemories() — keyed by cold root directory path.
|
|
1304
|
+
// Prevents an uncached full-tree directory scan on every structured-attribute
|
|
1305
|
+
// write (Finding UOGi, PR #402 round-6). The cache is only invalidated when
|
|
1306
|
+
// cold-tier content actually changes (via invalidateColdMemoriesCache), NOT
|
|
1307
|
+
// on every hot-tier write. It also expires after COLD_SCAN_CACHE_TTL_MS as
|
|
1308
|
+
// a safety net.
|
|
1309
|
+
//
|
|
1310
|
+
// Finding UvUy (PR #402 round-11): cache entries now carry a `coldVersion`
|
|
1311
|
+
// sentinel that is bumped (via a file-size counter in state/cold-write.log)
|
|
1312
|
+
// on every write that modifies cold-tier content. Before serving a cached
|
|
1313
|
+
// result, readAllColdMemories() reads the sentinel from disk and compares.
|
|
1314
|
+
// If they differ the entry is dropped and the cold tree is re-scanned. This
|
|
1315
|
+
// makes the cache correct across process boundaries (gateway + CLI): a second
|
|
1316
|
+
// process that writes a new cold memory bumps the sentinel on disk, so the
|
|
1317
|
+
// first process's next readAllColdMemories() sees the change within one call
|
|
1318
|
+
// (rather than waiting up to 30s for TTL expiry).
|
|
1319
|
+
//
|
|
1320
|
+
// After Finding UTsP broadened readAllColdMemories to scan the entire cold/
|
|
1321
|
+
// subtree (not just facts/+corrections/), amortizing this I/O across
|
|
1322
|
+
// back-to-back writes in the same burst is even more important.
|
|
1323
|
+
static COLD_SCAN_CACHE_TTL_MS = 3e4;
|
|
1324
|
+
// 30 seconds
|
|
1325
|
+
static coldMemoriesCache = /* @__PURE__ */ new Map();
|
|
544
1326
|
// Cache for readQuestions() — avoids serially re-reading tens of thousands of
|
|
545
1327
|
// question files on every recall. 60-second TTL is intentionally short so that
|
|
546
1328
|
// newly written questions surface quickly.
|
|
@@ -551,6 +1333,26 @@ var StorageManager = class _StorageManager {
|
|
|
551
1333
|
factHashIndexLoadPromise = null;
|
|
552
1334
|
factHashIndexAuthoritative = null;
|
|
553
1335
|
factHashIndexAuthoritativePromise = null;
|
|
1336
|
+
/** Optional: set by the orchestrator after construction to enable template-aware citation stripping during legacy hash rebuild. */
|
|
1337
|
+
citationTemplate = DEFAULT_CITATION_FORMAT;
|
|
1338
|
+
/** Page-versioning configuration. Set by the orchestrator after construction. */
|
|
1339
|
+
_versioningConfig = null;
|
|
1340
|
+
/** Set the page-versioning configuration. When `enabled` is false (default), all versioning calls are no-ops. */
|
|
1341
|
+
setVersioningConfig(config) {
|
|
1342
|
+
this._versioningConfig = config;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Snapshot the current content of a page before overwriting.
|
|
1346
|
+
* No-op when versioning is disabled or the file does not yet exist.
|
|
1347
|
+
*/
|
|
1348
|
+
async snapshotBeforeWrite(filePath, trigger) {
|
|
1349
|
+
if (!this._versioningConfig || !this._versioningConfig.enabled) return;
|
|
1350
|
+
try {
|
|
1351
|
+
const existing = await readFile(filePath, "utf-8");
|
|
1352
|
+
await createVersion(filePath, existing, trigger, this._versioningConfig, log, void 0, this.baseDir);
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
554
1356
|
/** The root directory of this storage instance. */
|
|
555
1357
|
get dir() {
|
|
556
1358
|
return this.baseDir;
|
|
@@ -562,7 +1364,7 @@ var StorageManager = class _StorageManager {
|
|
|
562
1364
|
return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
563
1365
|
}
|
|
564
1366
|
versionFilePath(kind) {
|
|
565
|
-
const fileName = kind === "memory-status" ? ".memory-status-version.log" : ".artifact-write-version.log";
|
|
1367
|
+
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
|
|
566
1368
|
return path.join(this.stateDir, fileName);
|
|
567
1369
|
}
|
|
568
1370
|
bumpSharedVersion(kind, fallbackMap) {
|
|
@@ -605,12 +1407,18 @@ var StorageManager = class _StorageManager {
|
|
|
605
1407
|
get correctionsDir() {
|
|
606
1408
|
return path.join(this.baseDir, "corrections");
|
|
607
1409
|
}
|
|
1410
|
+
get proceduresDir() {
|
|
1411
|
+
return path.join(this.baseDir, "procedures");
|
|
1412
|
+
}
|
|
608
1413
|
get entitiesDir() {
|
|
609
1414
|
return path.join(this.baseDir, "entities");
|
|
610
1415
|
}
|
|
611
1416
|
get stateDir() {
|
|
612
1417
|
return path.join(this.baseDir, "state");
|
|
613
1418
|
}
|
|
1419
|
+
get entitySynthesisQueuePath() {
|
|
1420
|
+
return path.join(this.stateDir, "entity-synthesis-queue.json");
|
|
1421
|
+
}
|
|
614
1422
|
get factHashIndexReadyPath() {
|
|
615
1423
|
return path.join(this.stateDir, "fact-hashes.ready");
|
|
616
1424
|
}
|
|
@@ -647,10 +1455,35 @@ var StorageManager = class _StorageManager {
|
|
|
647
1455
|
}
|
|
648
1456
|
const factHashIndex = await this.getFactHashIndex();
|
|
649
1457
|
const existing = await this.readAllMemories();
|
|
1458
|
+
let legacyRecovered = 0;
|
|
650
1459
|
for (const memory of existing) {
|
|
651
1460
|
if (memory.frontmatter.category !== "fact") continue;
|
|
652
1461
|
if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
|
|
653
|
-
|
|
1462
|
+
if (memory.frontmatter.contentHash) {
|
|
1463
|
+
factHashIndex.addByHash(memory.frontmatter.contentHash);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const content = memory.content;
|
|
1467
|
+
const stripped = stripCitationForTemplate(content, this.citationTemplate);
|
|
1468
|
+
if (stripped !== content) {
|
|
1469
|
+
factHashIndex.addByHash(
|
|
1470
|
+
ContentHashIndex.computeHash(sanitizeMemoryContent(stripped).text)
|
|
1471
|
+
);
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
if (!hasCitation(content)) {
|
|
1475
|
+
factHashIndex.addByHash(
|
|
1476
|
+
ContentHashIndex.computeHash(sanitizeMemoryContent(content).text)
|
|
1477
|
+
);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
legacyRecovered++;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (legacyRecovered > 0) {
|
|
1484
|
+
log.info(
|
|
1485
|
+
`ensureFactHashIndexAuthoritative: skipped ${legacyRecovered} legacy fact(s) with no contentHash in frontmatter`
|
|
1486
|
+
);
|
|
654
1487
|
}
|
|
655
1488
|
await factHashIndex.save();
|
|
656
1489
|
await mkdir(path.dirname(this.factHashIndexReadyPath), { recursive: true });
|
|
@@ -733,6 +1566,7 @@ var StorageManager = class _StorageManager {
|
|
|
733
1566
|
async ensureDirectories() {
|
|
734
1567
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
735
1568
|
await mkdir(path.join(this.factsDir, today), { recursive: true });
|
|
1569
|
+
await mkdir(path.join(this.proceduresDir, today), { recursive: true });
|
|
736
1570
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
737
1571
|
await mkdir(this.entitiesDir, { recursive: true });
|
|
738
1572
|
await mkdir(this.stateDir, { recursive: true });
|
|
@@ -782,16 +1616,22 @@ var StorageManager = class _StorageManager {
|
|
|
782
1616
|
memoryKind: options.memoryKind,
|
|
783
1617
|
structuredAttributes: options.structuredAttributes
|
|
784
1618
|
};
|
|
1619
|
+
if (options.status !== void 0) {
|
|
1620
|
+
fm.status = options.status;
|
|
1621
|
+
}
|
|
785
1622
|
let enrichedContent = content;
|
|
786
1623
|
if (options.structuredAttributes && Object.keys(options.structuredAttributes).length > 0) {
|
|
787
|
-
const attrLines = Object.entries(options.structuredAttributes).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
788
1624
|
enrichedContent = `${content}
|
|
789
|
-
[Attributes: ${
|
|
1625
|
+
[Attributes: ${normalizeAttributePairs(options.structuredAttributes)}]`;
|
|
790
1626
|
}
|
|
791
1627
|
const sanitized = sanitizeMemoryContent(enrichedContent);
|
|
792
1628
|
if (!sanitized.clean) {
|
|
793
1629
|
log.warn(`memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
794
1630
|
}
|
|
1631
|
+
if (category === "fact") {
|
|
1632
|
+
const hashSource = options.contentHashSource !== void 0 && options.contentHashSource.length > 0 ? sanitizeMemoryContent(options.contentHashSource).text : sanitized.text;
|
|
1633
|
+
fm.contentHash = ContentHashIndex.computeHash(hashSource);
|
|
1634
|
+
}
|
|
795
1635
|
const fileContent = `${serializeFrontmatter(fm)}
|
|
796
1636
|
|
|
797
1637
|
${sanitized.text}
|
|
@@ -799,9 +1639,13 @@ ${sanitized.text}
|
|
|
799
1639
|
let filePath;
|
|
800
1640
|
if (category === "correction") {
|
|
801
1641
|
filePath = path.join(this.correctionsDir, `${id}.md`);
|
|
1642
|
+
} else if (category === "procedure") {
|
|
1643
|
+
await mkdir(path.join(this.proceduresDir, today), { recursive: true });
|
|
1644
|
+
filePath = path.join(this.proceduresDir, today, `${id}.md`);
|
|
802
1645
|
} else {
|
|
803
1646
|
filePath = path.join(this.factsDir, today, `${id}.md`);
|
|
804
1647
|
}
|
|
1648
|
+
await this.snapshotBeforeWrite(filePath, "write");
|
|
805
1649
|
await writeFile(filePath, fileContent, "utf-8");
|
|
806
1650
|
this.invalidateAllMemoriesCache();
|
|
807
1651
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
|
|
@@ -818,7 +1662,12 @@ ${sanitized.text}
|
|
|
818
1662
|
if (category === "fact") {
|
|
819
1663
|
try {
|
|
820
1664
|
const factHashIndex = await this.getFactHashIndex();
|
|
821
|
-
|
|
1665
|
+
if (options.contentHashSource !== void 0 && options.contentHashSource.length > 0) {
|
|
1666
|
+
const hashSourceSanitized = sanitizeMemoryContent(options.contentHashSource);
|
|
1667
|
+
factHashIndex.add(hashSourceSanitized.text);
|
|
1668
|
+
} else {
|
|
1669
|
+
factHashIndex.add(sanitized.text);
|
|
1670
|
+
}
|
|
822
1671
|
await factHashIndex.save();
|
|
823
1672
|
} catch (err) {
|
|
824
1673
|
log.warn(`storage.writeMemory completed but failed to update fact hash index: ${err}`);
|
|
@@ -941,7 +1790,7 @@ ${sanitized.text}
|
|
|
941
1790
|
hits.sort((a, b) => b.score - a.score);
|
|
942
1791
|
return hits.slice(0, maxResults).map((h) => h.memory);
|
|
943
1792
|
}
|
|
944
|
-
async writeEntity(name, type, facts) {
|
|
1793
|
+
async writeEntity(name, type, facts, options = {}) {
|
|
945
1794
|
await this.ensureDirectories();
|
|
946
1795
|
if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
|
|
947
1796
|
log.warn("writeEntity: invalid entity payload, skipping", {
|
|
@@ -950,7 +1799,9 @@ ${sanitized.text}
|
|
|
950
1799
|
});
|
|
951
1800
|
return "";
|
|
952
1801
|
}
|
|
953
|
-
const safeFacts = Array.isArray(facts) ?
|
|
1802
|
+
const safeFacts = Array.isArray(facts) ? [...new Set(
|
|
1803
|
+
facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0)
|
|
1804
|
+
)] : [];
|
|
954
1805
|
let normalized = normalizeEntityName(name, type);
|
|
955
1806
|
const match = await this.findMatchingEntity(name, type);
|
|
956
1807
|
if (match && match !== normalized) {
|
|
@@ -961,23 +1812,80 @@ ${sanitized.text}
|
|
|
961
1812
|
let entity = {
|
|
962
1813
|
name,
|
|
963
1814
|
type,
|
|
1815
|
+
created: "",
|
|
964
1816
|
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
965
1817
|
facts: [],
|
|
966
1818
|
summary: void 0,
|
|
1819
|
+
synthesis: void 0,
|
|
1820
|
+
synthesisUpdatedAt: void 0,
|
|
1821
|
+
synthesisVersion: void 0,
|
|
1822
|
+
synthesisStructuredFactCount: void 0,
|
|
1823
|
+
synthesisStructuredFactDigest: void 0,
|
|
1824
|
+
timeline: [],
|
|
967
1825
|
relationships: [],
|
|
968
1826
|
activity: [],
|
|
969
1827
|
aliases: []
|
|
970
1828
|
};
|
|
971
1829
|
try {
|
|
972
1830
|
const existing = await readFile(filePath, "utf-8");
|
|
973
|
-
entity = parseEntityFile(existing);
|
|
1831
|
+
entity = parseEntityFile(existing, this.entitySchemas);
|
|
974
1832
|
} catch {
|
|
975
1833
|
}
|
|
976
|
-
|
|
1834
|
+
const timestamp = options.timestamp?.trim() || (/* @__PURE__ */ new Date()).toISOString();
|
|
1835
|
+
const source = options.source?.trim() || void 0;
|
|
1836
|
+
const sessionKey = options.sessionKey?.trim() || void 0;
|
|
1837
|
+
const principal = options.principal?.trim() || void 0;
|
|
1838
|
+
const structuredSectionMap = new Map(
|
|
1839
|
+
(entity.structuredSections ?? []).map((section) => [section.key, {
|
|
1840
|
+
...section,
|
|
1841
|
+
facts: [...section.facts]
|
|
1842
|
+
}])
|
|
1843
|
+
);
|
|
1844
|
+
for (const section of options.structuredSections ?? []) {
|
|
1845
|
+
const normalizedSection = normalizeEntityStructuredSection(type, section, this.entitySchemas);
|
|
1846
|
+
const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
|
|
1847
|
+
if (normalizedFacts.length === 0) continue;
|
|
1848
|
+
const existingSection = structuredSectionMap.get(normalizedSection.key);
|
|
1849
|
+
if (!existingSection) {
|
|
1850
|
+
structuredSectionMap.set(normalizedSection.key, {
|
|
1851
|
+
key: normalizedSection.key,
|
|
1852
|
+
title: normalizedSection.title,
|
|
1853
|
+
facts: normalizedFacts
|
|
1854
|
+
});
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
|
|
1858
|
+
if (!existingSection.title.trim() && normalizedSection.title.trim()) {
|
|
1859
|
+
existingSection.title = normalizedSection.title;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
for (const fact of safeFacts) {
|
|
1863
|
+
const nextEntry = {
|
|
1864
|
+
timestamp,
|
|
1865
|
+
text: fact,
|
|
1866
|
+
...source ? { source } : {},
|
|
1867
|
+
...sessionKey ? { sessionKey } : {},
|
|
1868
|
+
...principal ? { principal } : {}
|
|
1869
|
+
};
|
|
1870
|
+
const alreadyPresent = entity.timeline.some(
|
|
1871
|
+
(entry) => entry.timestamp === nextEntry.timestamp && entry.text === nextEntry.text && entry.source === nextEntry.source && entry.sessionKey === nextEntry.sessionKey && entry.principal === nextEntry.principal
|
|
1872
|
+
);
|
|
1873
|
+
if (alreadyPresent) continue;
|
|
1874
|
+
entity.timeline.push(nextEntry);
|
|
1875
|
+
}
|
|
1876
|
+
entity.structuredSections = sortStructuredSectionsBySchema(
|
|
1877
|
+
type,
|
|
1878
|
+
Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
|
|
1879
|
+
this.entitySchemas
|
|
1880
|
+
);
|
|
1881
|
+
entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
|
|
1882
|
+
entity.summary = entity.synthesis || entity.summary;
|
|
977
1883
|
entity.name = name;
|
|
978
1884
|
entity.type = type;
|
|
1885
|
+
entity.created = entity.created || timestamp;
|
|
979
1886
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
980
|
-
await
|
|
1887
|
+
await this.snapshotBeforeWrite(filePath, "write");
|
|
1888
|
+
await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
981
1889
|
this.invalidateKnowledgeIndexCache();
|
|
982
1890
|
this.bumpMemoryStatusVersion();
|
|
983
1891
|
log.debug(`wrote entity ${normalized}`);
|
|
@@ -992,6 +1900,7 @@ ${sanitized.text}
|
|
|
992
1900
|
}
|
|
993
1901
|
async writeProfile(content) {
|
|
994
1902
|
await this.ensureDirectories();
|
|
1903
|
+
await this.snapshotBeforeWrite(this.profilePath, "consolidation");
|
|
995
1904
|
await writeFile(this.profilePath, content, "utf-8");
|
|
996
1905
|
log.debug("updated profile.md");
|
|
997
1906
|
}
|
|
@@ -1080,12 +1989,51 @@ ${sanitized.text}
|
|
|
1080
1989
|
static clearAllStaticCaches() {
|
|
1081
1990
|
_StorageManager.allMemoriesInFlight.clear();
|
|
1082
1991
|
_StorageManager.questionsCache.clear();
|
|
1992
|
+
_StorageManager.coldMemoriesCache.clear();
|
|
1083
1993
|
}
|
|
1084
1994
|
/** Cancel any in-flight concurrent read so the next readAllMemories()
|
|
1085
|
-
* starts a fresh disk scan and sees the just-written data.
|
|
1995
|
+
* starts a fresh disk scan and sees the just-written data.
|
|
1996
|
+
*
|
|
1997
|
+
* Finding UvBq (PR #402 round-11): this method intentionally does NOT
|
|
1998
|
+
* invalidate the cold-scan cache. Ordinary hot-tier writes (writeMemory)
|
|
1999
|
+
* do not change cold-tier content, so evicting the cold cache on every hot
|
|
2000
|
+
* write was defeating the burst-dedup optimisation — the cold cache was
|
|
2001
|
+
* cleared before applyTemporalSupersession ran, causing a full cold-tree
|
|
2002
|
+
* disk scan on every write in a burst. Cold cache invalidation is handled
|
|
2003
|
+
* exclusively by invalidateColdMemoriesCache(), which is called only when
|
|
2004
|
+
* cold content actually changes (hot→cold demotions, writeMemoryFileAtomic
|
|
2005
|
+
* inside cold/, archiveMemory, etc.). */
|
|
1086
2006
|
invalidateAllMemoriesCache() {
|
|
1087
2007
|
_StorageManager.allMemoriesInFlight.delete(this.baseDir);
|
|
1088
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Invalidate the cold-scan cache for this storage root and bump the
|
|
2011
|
+
* on-disk cold-version sentinel so that other processes (gateway, CLI) see
|
|
2012
|
+
* the change immediately on their next readAllColdMemories() call.
|
|
2013
|
+
*
|
|
2014
|
+
* Must be called whenever a memory is written INTO the cold tier — hot→cold
|
|
2015
|
+
* demotion, atomic writes inside cold/, archiving a cold memory, etc.
|
|
2016
|
+
* NOT called on ordinary hot-tier writes (those don't change cold contents).
|
|
2017
|
+
*
|
|
2018
|
+
* Finding UvUy (PR #402 round-11): bumping the sentinel here makes the
|
|
2019
|
+
* per-process in-memory cache safe across process boundaries.
|
|
2020
|
+
*/
|
|
2021
|
+
invalidateColdMemoriesCache() {
|
|
2022
|
+
const coldRoot = path.join(this.baseDir, "cold");
|
|
2023
|
+
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
2024
|
+
this.bumpColdWriteVersion();
|
|
2025
|
+
}
|
|
2026
|
+
/** Return the current cold-write version counter for this storage root.
|
|
2027
|
+
* Reads the on-disk sentinel (state/cold-write.log) so it reflects writes
|
|
2028
|
+
* made by other processes. */
|
|
2029
|
+
readColdWriteVersion() {
|
|
2030
|
+
return this.readSharedVersion("cold-write", _StorageManager.coldWriteVersionByDir);
|
|
2031
|
+
}
|
|
2032
|
+
/** Bump the on-disk cold-write version sentinel and update the in-process
|
|
2033
|
+
* fallback map. Called by invalidateColdMemoriesCache(). */
|
|
2034
|
+
bumpColdWriteVersion() {
|
|
2035
|
+
this.bumpSharedVersion("cold-write", _StorageManager.coldWriteVersionByDir);
|
|
2036
|
+
}
|
|
1089
2037
|
normalizeMemoryReadBatchSize(batchSize) {
|
|
1090
2038
|
if (typeof batchSize !== "number" || !Number.isFinite(batchSize)) {
|
|
1091
2039
|
return 50;
|
|
@@ -1113,6 +2061,7 @@ ${sanitized.text}
|
|
|
1113
2061
|
}
|
|
1114
2062
|
};
|
|
1115
2063
|
await collectPaths(this.factsDir);
|
|
2064
|
+
await collectPaths(this.proceduresDir);
|
|
1116
2065
|
await collectPaths(this.correctionsDir);
|
|
1117
2066
|
return filePaths;
|
|
1118
2067
|
}
|
|
@@ -1132,7 +2081,8 @@ ${sanitized.text}
|
|
|
1132
2081
|
path: fullPath,
|
|
1133
2082
|
frontmatter: normalizeFrontmatterForPath(
|
|
1134
2083
|
parsed.frontmatter,
|
|
1135
|
-
toMemoryPathRel(this.baseDir, fullPath)
|
|
2084
|
+
toMemoryPathRel(this.baseDir, fullPath),
|
|
2085
|
+
parsed.content
|
|
1136
2086
|
),
|
|
1137
2087
|
content: parsed.content
|
|
1138
2088
|
};
|
|
@@ -1261,6 +2211,62 @@ ${sanitized.text}
|
|
|
1261
2211
|
const filePaths = await this.collectActiveMemoryPaths();
|
|
1262
2212
|
return this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
1263
2213
|
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Read all memories from the cold tier by scanning the entire cold/ root
|
|
2216
|
+
* tree. Previously this only scanned cold/facts/ and cold/corrections/, but
|
|
2217
|
+
* structuredAttributes can appear on any MemoryCategory (preference, decision,
|
|
2218
|
+
* entity, etc.). Although buildTierMemoryPath currently routes all
|
|
2219
|
+
* non-correction, non-artifact memories to cold/facts/, scanning the full
|
|
2220
|
+
* coldRoot ensures correctness if that routing ever changes and guards against
|
|
2221
|
+
* files placed in unexpected subdirectories during manual operations or future
|
|
2222
|
+
* refactors.
|
|
2223
|
+
*
|
|
2224
|
+
* Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
|
|
2225
|
+
* corrections/ was a narrower-than-necessary subset of the cold directory
|
|
2226
|
+
* tree. Correctness trumps the minor performance difference — cold scans
|
|
2227
|
+
* already happen at most once per supersession write.
|
|
2228
|
+
*
|
|
2229
|
+
* Used by applyTemporalSupersession so that memories already demoted to
|
|
2230
|
+
* cold/ can still be marked superseded when a newer hot fact arrives.
|
|
2231
|
+
*
|
|
2232
|
+
* Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
|
|
2233
|
+
* structured-attribute writes in the same burst reuse the cached result
|
|
2234
|
+
* instead of re-scanning the cold tree on every call. The cache is
|
|
2235
|
+
* invalidated whenever a write calls invalidateAllMemoriesCache() (which
|
|
2236
|
+
* covers any hot→cold demotion that changes cold-tier contents) and
|
|
2237
|
+
* expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
|
|
2238
|
+
*/
|
|
2239
|
+
async readAllColdMemories() {
|
|
2240
|
+
const coldRoot = this.resolveTierRootDir("cold");
|
|
2241
|
+
const currentColdVersion = this.readColdWriteVersion();
|
|
2242
|
+
const cached = _StorageManager.coldMemoriesCache.get(coldRoot);
|
|
2243
|
+
if (cached && Date.now() - cached.loadedAt < _StorageManager.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
|
|
2244
|
+
return cached.memories;
|
|
2245
|
+
}
|
|
2246
|
+
const filePaths = [];
|
|
2247
|
+
const collectPaths = async (dir) => {
|
|
2248
|
+
try {
|
|
2249
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
2250
|
+
const subdirs = [];
|
|
2251
|
+
for (const entry of entries) {
|
|
2252
|
+
const fullPath = path.join(dir, entry.name);
|
|
2253
|
+
if (entry.isDirectory()) {
|
|
2254
|
+
subdirs.push(fullPath);
|
|
2255
|
+
} else if (entry.name.endsWith(".md")) {
|
|
2256
|
+
filePaths.push(fullPath);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
for (const subdir of subdirs) {
|
|
2260
|
+
await collectPaths(subdir);
|
|
2261
|
+
}
|
|
2262
|
+
} catch {
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
await collectPaths(coldRoot);
|
|
2266
|
+
const memories = await this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
2267
|
+
_StorageManager.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
|
|
2268
|
+
return memories;
|
|
2269
|
+
}
|
|
1264
2270
|
/**
|
|
1265
2271
|
* Read archived memory markdown files under archive/.
|
|
1266
2272
|
* Used by long-term recall fallback when hot recall has no hits.
|
|
@@ -1284,7 +2290,8 @@ ${sanitized.text}
|
|
|
1284
2290
|
path: fullPath,
|
|
1285
2291
|
frontmatter: normalizeFrontmatterForPath(
|
|
1286
2292
|
parsed.frontmatter,
|
|
1287
|
-
toMemoryPathRel(this.baseDir, fullPath)
|
|
2293
|
+
toMemoryPathRel(this.baseDir, fullPath),
|
|
2294
|
+
parsed.content
|
|
1288
2295
|
),
|
|
1289
2296
|
content: parsed.content
|
|
1290
2297
|
});
|
|
@@ -1309,14 +2316,15 @@ ${sanitized.text}
|
|
|
1309
2316
|
path: filePath,
|
|
1310
2317
|
frontmatter: normalizeFrontmatterForPath(
|
|
1311
2318
|
parsed.frontmatter,
|
|
1312
|
-
toMemoryPathRel(this.baseDir, filePath)
|
|
2319
|
+
toMemoryPathRel(this.baseDir, filePath),
|
|
2320
|
+
parsed.content
|
|
1313
2321
|
),
|
|
1314
2322
|
content: parsed.content
|
|
1315
2323
|
};
|
|
1316
2324
|
}
|
|
1317
2325
|
const normalizedPath = filePath.split(path.sep).join("/");
|
|
1318
2326
|
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
1319
|
-
const entity = parseEntityFile(raw);
|
|
2327
|
+
const entity = parseEntityFile(raw, this.entitySchemas);
|
|
1320
2328
|
if (!entity.name) return null;
|
|
1321
2329
|
const nameWithoutExt = path.basename(filePath, ".md");
|
|
1322
2330
|
const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
@@ -1361,6 +2369,9 @@ ${sanitized.text}
|
|
|
1361
2369
|
if (memory.frontmatter.category === "correction") {
|
|
1362
2370
|
return path.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
1363
2371
|
}
|
|
2372
|
+
if (memory.frontmatter.category === "procedure") {
|
|
2373
|
+
return path.join(root, "procedures", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
2374
|
+
}
|
|
1364
2375
|
return path.join(root, "facts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
1365
2376
|
}
|
|
1366
2377
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
@@ -1420,6 +2431,9 @@ ${memory.content}
|
|
|
1420
2431
|
}
|
|
1421
2432
|
await this.moveMemoryToPath(memory, targetPath);
|
|
1422
2433
|
this.invalidateAllMemoriesCache();
|
|
2434
|
+
if (targetTier === "cold") {
|
|
2435
|
+
this.invalidateColdMemoriesCache();
|
|
2436
|
+
}
|
|
1423
2437
|
this.bumpMemoryStatusVersion();
|
|
1424
2438
|
return { changed: true, targetPath };
|
|
1425
2439
|
}
|
|
@@ -1603,6 +2617,9 @@ ${memory.content}
|
|
|
1603
2617
|
`;
|
|
1604
2618
|
await writeFile(memory.path, fileContent, "utf-8");
|
|
1605
2619
|
this.invalidateAllMemoriesCache();
|
|
2620
|
+
if (memory.path.includes(`${path.sep}cold${path.sep}`)) {
|
|
2621
|
+
this.invalidateColdMemoriesCache();
|
|
2622
|
+
}
|
|
1606
2623
|
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
1607
2624
|
"storage.writeMemoryFrontmatter",
|
|
1608
2625
|
{
|
|
@@ -1678,14 +2695,30 @@ ${memory.content}
|
|
|
1678
2695
|
const metaPath = path.join(this.stateDir, "meta.json");
|
|
1679
2696
|
try {
|
|
1680
2697
|
const raw = await readFile(metaPath, "utf-8");
|
|
1681
|
-
|
|
2698
|
+
const parsed = JSON.parse(raw);
|
|
2699
|
+
return {
|
|
2700
|
+
extractionCount: typeof parsed.extractionCount === "number" ? parsed.extractionCount : 0,
|
|
2701
|
+
lastExtractionAt: parsed.lastExtractionAt ?? null,
|
|
2702
|
+
lastConsolidationAt: parsed.lastConsolidationAt ?? null,
|
|
2703
|
+
totalMemories: typeof parsed.totalMemories === "number" ? parsed.totalMemories : 0,
|
|
2704
|
+
totalEntities: typeof parsed.totalEntities === "number" ? parsed.totalEntities : 0,
|
|
2705
|
+
processedExtractionFingerprints: Array.isArray(
|
|
2706
|
+
parsed.processedExtractionFingerprints
|
|
2707
|
+
) ? parsed.processedExtractionFingerprints.filter(
|
|
2708
|
+
(entry) => entry && typeof entry === "object" && typeof entry.fingerprint === "string" && typeof entry.observedAt === "string"
|
|
2709
|
+
).map((entry) => ({
|
|
2710
|
+
fingerprint: entry.fingerprint,
|
|
2711
|
+
observedAt: entry.observedAt
|
|
2712
|
+
})) : []
|
|
2713
|
+
};
|
|
1682
2714
|
} catch {
|
|
1683
2715
|
return {
|
|
1684
2716
|
extractionCount: 0,
|
|
1685
2717
|
lastExtractionAt: null,
|
|
1686
2718
|
lastConsolidationAt: null,
|
|
1687
2719
|
totalMemories: 0,
|
|
1688
|
-
totalEntities: 0
|
|
2720
|
+
totalEntities: 0,
|
|
2721
|
+
processedExtractionFingerprints: []
|
|
1689
2722
|
};
|
|
1690
2723
|
}
|
|
1691
2724
|
}
|
|
@@ -2358,7 +3391,7 @@ ${reflection}
|
|
|
2358
3391
|
let entity;
|
|
2359
3392
|
try {
|
|
2360
3393
|
const content = await readFile(filePath, "utf-8");
|
|
2361
|
-
entity = parseEntityFile(content);
|
|
3394
|
+
entity = parseEntityFile(content, this.entitySchemas);
|
|
2362
3395
|
} catch {
|
|
2363
3396
|
log.debug(`addEntityRelationship: entity file ${name}.md not found`);
|
|
2364
3397
|
return;
|
|
@@ -2369,7 +3402,7 @@ ${reflection}
|
|
|
2369
3402
|
if (exists) return;
|
|
2370
3403
|
entity.relationships.push(rel);
|
|
2371
3404
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2372
|
-
await writeFile(filePath, serializeEntityFile(entity), "utf-8");
|
|
3405
|
+
await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
2373
3406
|
this.invalidateKnowledgeIndexCache();
|
|
2374
3407
|
}
|
|
2375
3408
|
/**
|
|
@@ -2381,7 +3414,7 @@ ${reflection}
|
|
|
2381
3414
|
let entity;
|
|
2382
3415
|
try {
|
|
2383
3416
|
const content = await readFile(filePath, "utf-8");
|
|
2384
|
-
entity = parseEntityFile(content);
|
|
3417
|
+
entity = parseEntityFile(content, this.entitySchemas);
|
|
2385
3418
|
} catch {
|
|
2386
3419
|
log.debug(`addEntityActivity: entity file ${name}.md not found`);
|
|
2387
3420
|
return;
|
|
@@ -2391,7 +3424,7 @@ ${reflection}
|
|
|
2391
3424
|
entity.activity = entity.activity.slice(0, maxEntries);
|
|
2392
3425
|
}
|
|
2393
3426
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2394
|
-
await writeFile(filePath, serializeEntityFile(entity), "utf-8");
|
|
3427
|
+
await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
2395
3428
|
this.invalidateKnowledgeIndexCache();
|
|
2396
3429
|
}
|
|
2397
3430
|
/**
|
|
@@ -2402,7 +3435,7 @@ ${reflection}
|
|
|
2402
3435
|
let entity;
|
|
2403
3436
|
try {
|
|
2404
3437
|
const content = await readFile(filePath, "utf-8");
|
|
2405
|
-
entity = parseEntityFile(content);
|
|
3438
|
+
entity = parseEntityFile(content, this.entitySchemas);
|
|
2406
3439
|
} catch {
|
|
2407
3440
|
log.debug(`addEntityAlias: entity file ${name}.md not found`);
|
|
2408
3441
|
return;
|
|
@@ -2410,28 +3443,142 @@ ${reflection}
|
|
|
2410
3443
|
if (entity.aliases.includes(alias)) return;
|
|
2411
3444
|
entity.aliases.push(alias);
|
|
2412
3445
|
entity.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2413
|
-
await writeFile(filePath, serializeEntityFile(entity), "utf-8");
|
|
3446
|
+
await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
2414
3447
|
this.invalidateKnowledgeIndexCache();
|
|
2415
3448
|
}
|
|
2416
3449
|
/**
|
|
2417
|
-
* Set or
|
|
3450
|
+
* Set or rewrite the synthesis layer of an entity file.
|
|
2418
3451
|
*/
|
|
2419
|
-
async
|
|
3452
|
+
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
2420
3453
|
const filePath = path.join(this.entitiesDir, `${name}.md`);
|
|
2421
3454
|
let entity;
|
|
2422
3455
|
try {
|
|
2423
3456
|
const content = await readFile(filePath, "utf-8");
|
|
2424
|
-
entity = parseEntityFile(content);
|
|
3457
|
+
entity = parseEntityFile(content, this.entitySchemas);
|
|
2425
3458
|
} catch {
|
|
2426
|
-
log.debug(`
|
|
3459
|
+
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
2427
3460
|
return;
|
|
2428
3461
|
}
|
|
2429
|
-
entity.
|
|
2430
|
-
entity.updated
|
|
2431
|
-
|
|
3462
|
+
const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || void 0;
|
|
3463
|
+
const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
3464
|
+
const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount) && (options.synthesisTimelineCount ?? 0) >= 0 ? options.synthesisTimelineCount : void 0;
|
|
3465
|
+
const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount) && (options.synthesisStructuredFactCount ?? 0) >= 0 ? options.synthesisStructuredFactCount : countEntityStructuredFacts(entity);
|
|
3466
|
+
const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim() || fingerprintEntityStructuredFacts(entity);
|
|
3467
|
+
entity.synthesis = synthesis.trim();
|
|
3468
|
+
entity.summary = entity.synthesis;
|
|
3469
|
+
entity.synthesisUpdatedAt = updatedAt;
|
|
3470
|
+
entity.synthesisTimelineCount = synthesisTimelineCount;
|
|
3471
|
+
entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
|
|
3472
|
+
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
3473
|
+
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0) + (options.incrementVersion === false ? 0 : 1);
|
|
3474
|
+
entity.updated = entityUpdatedAt;
|
|
3475
|
+
await writeFile(filePath, serializeEntityFile(entity, this.entitySchemas), "utf-8");
|
|
3476
|
+
await this.removeEntitySynthesisQueueEntries([
|
|
3477
|
+
.../* @__PURE__ */ new Set([name, normalizeEntityName(entity.name, entity.type)])
|
|
3478
|
+
]);
|
|
2432
3479
|
this.invalidateKnowledgeIndexCache();
|
|
2433
3480
|
this.bumpMemoryStatusVersion();
|
|
2434
3481
|
}
|
|
3482
|
+
/**
|
|
3483
|
+
* Backward-compatible alias for legacy callers/tests.
|
|
3484
|
+
*/
|
|
3485
|
+
async updateEntitySummary(name, summary) {
|
|
3486
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3487
|
+
let synthesisTimelineCount;
|
|
3488
|
+
try {
|
|
3489
|
+
const filePath = path.join(this.entitiesDir, `${name}.md`);
|
|
3490
|
+
const content = await readFile(filePath, "utf-8");
|
|
3491
|
+
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
3492
|
+
} catch {
|
|
3493
|
+
synthesisTimelineCount = void 0;
|
|
3494
|
+
}
|
|
3495
|
+
await this.updateEntitySynthesis(name, summary, {
|
|
3496
|
+
entityUpdatedAt: updatedAt,
|
|
3497
|
+
synthesisTimelineCount,
|
|
3498
|
+
updatedAt
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
async readEntitySynthesisQueue() {
|
|
3502
|
+
try {
|
|
3503
|
+
const raw = await readFile(this.entitySynthesisQueuePath, "utf-8");
|
|
3504
|
+
const parsed = JSON.parse(raw);
|
|
3505
|
+
return Array.isArray(parsed.entityNames) ? parsed.entityNames.filter((value) => typeof value === "string") : [];
|
|
3506
|
+
} catch {
|
|
3507
|
+
return [];
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
async refreshEntitySynthesisQueue() {
|
|
3511
|
+
const entityNames = await this.listEntityNames();
|
|
3512
|
+
const entityQueueEntries = await Promise.all(
|
|
3513
|
+
entityNames.map(async (entityName) => {
|
|
3514
|
+
const raw = await this.readEntity(entityName);
|
|
3515
|
+
if (!raw) return null;
|
|
3516
|
+
return {
|
|
3517
|
+
entityName,
|
|
3518
|
+
entity: parseEntityFile(raw, this.entitySchemas)
|
|
3519
|
+
};
|
|
3520
|
+
})
|
|
3521
|
+
);
|
|
3522
|
+
const staleEntityNames = entityQueueEntries.filter((entry) => entry !== null).filter(({ entity }) => isEntitySynthesisStale(entity)).sort((left, right) => {
|
|
3523
|
+
const leftTs = latestEntityTimelineTimestamp(left.entity) ?? "";
|
|
3524
|
+
const rightTs = latestEntityTimelineTimestamp(right.entity) ?? "";
|
|
3525
|
+
return compareEntityTimestamps(rightTs, leftTs);
|
|
3526
|
+
}).map(({ entityName }) => entityName);
|
|
3527
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
3528
|
+
await writeFile(
|
|
3529
|
+
this.entitySynthesisQueuePath,
|
|
3530
|
+
JSON.stringify(
|
|
3531
|
+
{
|
|
3532
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3533
|
+
entityNames: staleEntityNames
|
|
3534
|
+
},
|
|
3535
|
+
null,
|
|
3536
|
+
2
|
|
3537
|
+
) + "\n",
|
|
3538
|
+
"utf-8"
|
|
3539
|
+
);
|
|
3540
|
+
return staleEntityNames;
|
|
3541
|
+
}
|
|
3542
|
+
async removeEntitySynthesisQueueEntries(entityNames) {
|
|
3543
|
+
if (entityNames.length === 0) return;
|
|
3544
|
+
const queue = await this.readEntitySynthesisQueue();
|
|
3545
|
+
if (queue.length === 0) return;
|
|
3546
|
+
const removals = new Set(entityNames);
|
|
3547
|
+
const nextQueue = queue.filter((name) => !removals.has(name));
|
|
3548
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
3549
|
+
await writeFile(
|
|
3550
|
+
this.entitySynthesisQueuePath,
|
|
3551
|
+
JSON.stringify(
|
|
3552
|
+
{
|
|
3553
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3554
|
+
entityNames: nextQueue
|
|
3555
|
+
},
|
|
3556
|
+
null,
|
|
3557
|
+
2
|
|
3558
|
+
) + "\n",
|
|
3559
|
+
"utf-8"
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
async migrateEntityFilesToCompiledTruthTimeline() {
|
|
3563
|
+
const entityNames = await this.listEntityNames();
|
|
3564
|
+
let migrated = 0;
|
|
3565
|
+
for (const entityName of entityNames) {
|
|
3566
|
+
const raw = await this.readEntity(entityName);
|
|
3567
|
+
if (!raw) continue;
|
|
3568
|
+
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
3569
|
+
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
3570
|
+
await writeFile(path.join(this.entitiesDir, `${entityName}.md`), serialized, "utf-8");
|
|
3571
|
+
migrated += 1;
|
|
3572
|
+
}
|
|
3573
|
+
if (migrated > 0) {
|
|
3574
|
+
this.invalidateKnowledgeIndexCache();
|
|
3575
|
+
this.bumpMemoryStatusVersion();
|
|
3576
|
+
}
|
|
3577
|
+
return {
|
|
3578
|
+
total: entityNames.length,
|
|
3579
|
+
migrated
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
2435
3582
|
// ---------------------------------------------------------------------------
|
|
2436
3583
|
// Scoring + Knowledge Index (Knowledge Graph v7.0)
|
|
2437
3584
|
// ---------------------------------------------------------------------------
|
|
@@ -2441,7 +3588,8 @@ ${reflection}
|
|
|
2441
3588
|
*/
|
|
2442
3589
|
async readAllEntityFiles() {
|
|
2443
3590
|
const currentVersion = this.getMemoryStatusVersion();
|
|
2444
|
-
const
|
|
3591
|
+
const schemaCacheKey = buildEntitySchemaCacheKey(this.entitySchemas);
|
|
3592
|
+
const cached = getCachedEntities(this.baseDir, currentVersion, schemaCacheKey);
|
|
2445
3593
|
if (cached) return cached;
|
|
2446
3594
|
try {
|
|
2447
3595
|
const entries = await readdir(this.entitiesDir);
|
|
@@ -2457,10 +3605,10 @@ ${reflection}
|
|
|
2457
3605
|
)
|
|
2458
3606
|
);
|
|
2459
3607
|
for (const content of results) {
|
|
2460
|
-
if (content !== null) entities.push(parseEntityFile(content));
|
|
3608
|
+
if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
|
|
2461
3609
|
}
|
|
2462
3610
|
}
|
|
2463
|
-
setCachedEntities(this.baseDir, entities, currentVersion);
|
|
3611
|
+
setCachedEntities(this.baseDir, entities, currentVersion, schemaCacheKey);
|
|
2464
3612
|
return entities;
|
|
2465
3613
|
} catch {
|
|
2466
3614
|
return [];
|
|
@@ -2510,7 +3658,7 @@ ${reflection}
|
|
|
2510
3658
|
type: e.type,
|
|
2511
3659
|
score: _StorageManager.scoreEntity(e, now),
|
|
2512
3660
|
factCount: e.facts.length,
|
|
2513
|
-
summary: e.summary,
|
|
3661
|
+
summary: e.synthesis ?? e.summary,
|
|
2514
3662
|
topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
|
|
2515
3663
|
}));
|
|
2516
3664
|
scored.sort((a, b) => b.score - a.score);
|
|
@@ -2573,38 +3721,121 @@ ${rows.join("\n")}
|
|
|
2573
3721
|
const mergedEntity = {
|
|
2574
3722
|
name: "",
|
|
2575
3723
|
type: "other",
|
|
3724
|
+
created: "",
|
|
2576
3725
|
updated: "",
|
|
3726
|
+
extraFrontmatterLines: [],
|
|
3727
|
+
preSectionLines: [],
|
|
2577
3728
|
facts: [],
|
|
2578
3729
|
summary: void 0,
|
|
3730
|
+
synthesis: void 0,
|
|
3731
|
+
synthesisUpdatedAt: void 0,
|
|
3732
|
+
synthesisTimelineCount: void 0,
|
|
3733
|
+
synthesisStructuredFactCount: void 0,
|
|
3734
|
+
synthesisStructuredFactDigest: void 0,
|
|
3735
|
+
synthesisVersion: void 0,
|
|
3736
|
+
timeline: [],
|
|
2579
3737
|
relationships: [],
|
|
2580
3738
|
activity: [],
|
|
2581
|
-
aliases: []
|
|
3739
|
+
aliases: [],
|
|
3740
|
+
structuredSections: [],
|
|
3741
|
+
extraSections: []
|
|
2582
3742
|
};
|
|
2583
3743
|
for (const file of files) {
|
|
2584
3744
|
const filePath = path.join(this.entitiesDir, file);
|
|
2585
3745
|
try {
|
|
2586
3746
|
const content = await readFile(filePath, "utf-8");
|
|
2587
|
-
const parsed = parseEntityFile(content);
|
|
3747
|
+
const parsed = parseEntityFile(content, this.entitySchemas);
|
|
2588
3748
|
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
2589
3749
|
mergedEntity.type = parsed.type;
|
|
2590
3750
|
}
|
|
2591
|
-
if (!mergedEntity.updated || parsed.updated
|
|
3751
|
+
if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
|
|
2592
3752
|
mergedEntity.updated = parsed.updated;
|
|
2593
3753
|
}
|
|
3754
|
+
const parsedCreated = parsed.created || parsed.updated;
|
|
3755
|
+
const mergedCreated = mergedEntity.created?.trim() || "";
|
|
3756
|
+
const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
|
|
3757
|
+
const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
|
|
3758
|
+
const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
|
|
3759
|
+
const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
|
|
3760
|
+
if (parsedCreated && (!mergedCreated || parsedCreatedIsValid && !mergedCreatedIsValid || parsedCreatedIsValid && mergedCreatedIsValid && parsedCreatedMs < mergedCreatedMs || !parsedCreatedIsValid && !mergedCreatedIsValid && compareEntityTimestamps(parsedCreated, mergedCreated) < 0)) {
|
|
3761
|
+
mergedEntity.created = parsedCreated;
|
|
3762
|
+
}
|
|
2594
3763
|
if (parsed.name.length > mergedEntity.name.length) {
|
|
2595
3764
|
mergedEntity.name = parsed.name;
|
|
2596
3765
|
}
|
|
2597
|
-
|
|
3766
|
+
const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || void 0;
|
|
3767
|
+
const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || void 0;
|
|
3768
|
+
if (parsed.synthesis && (!mergedEntity.synthesis || !mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt) || Boolean(mergedSynthesisUpdatedAt) && Boolean(parsedSynthesisUpdatedAt) && compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0)) {
|
|
3769
|
+
mergedEntity.synthesis = parsed.synthesis;
|
|
3770
|
+
mergedEntity.summary = parsed.synthesis;
|
|
3771
|
+
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
3772
|
+
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
3773
|
+
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
3774
|
+
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
3775
|
+
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
3776
|
+
} else if (!mergedEntity.summary && parsed.summary) {
|
|
2598
3777
|
mergedEntity.summary = parsed.summary;
|
|
3778
|
+
mergedEntity.synthesis = parsed.summary;
|
|
3779
|
+
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
3780
|
+
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
3781
|
+
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
3782
|
+
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
3783
|
+
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
2599
3784
|
}
|
|
2600
|
-
mergedEntity.
|
|
3785
|
+
mergedEntity.timeline.push(...parsed.timeline);
|
|
2601
3786
|
mergedEntity.relationships.push(...parsed.relationships);
|
|
2602
3787
|
mergedEntity.activity.push(...parsed.activity);
|
|
2603
3788
|
mergedEntity.aliases.push(...parsed.aliases);
|
|
3789
|
+
const mergedStructuredSectionMap = new Map(
|
|
3790
|
+
(mergedEntity.structuredSections ?? []).map((section) => [section.key, {
|
|
3791
|
+
...section,
|
|
3792
|
+
facts: [...section.facts]
|
|
3793
|
+
}])
|
|
3794
|
+
);
|
|
3795
|
+
for (const section of parsed.structuredSections ?? []) {
|
|
3796
|
+
const existingSection = mergedStructuredSectionMap.get(section.key);
|
|
3797
|
+
if (!existingSection) {
|
|
3798
|
+
mergedStructuredSectionMap.set(section.key, {
|
|
3799
|
+
key: section.key,
|
|
3800
|
+
title: section.title,
|
|
3801
|
+
facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))]
|
|
3802
|
+
});
|
|
3803
|
+
continue;
|
|
3804
|
+
}
|
|
3805
|
+
const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
|
|
3806
|
+
for (const fact of section.facts) {
|
|
3807
|
+
const trimmed = fact.trim();
|
|
3808
|
+
if (!trimmed) continue;
|
|
3809
|
+
mergedFacts.add(trimmed);
|
|
3810
|
+
}
|
|
3811
|
+
existingSection.facts = Array.from(mergedFacts);
|
|
3812
|
+
if (!existingSection.title.trim() && section.title.trim()) {
|
|
3813
|
+
existingSection.title = section.title;
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
|
|
3817
|
+
mergedEntity.extraFrontmatterLines.push(...parsed.extraFrontmatterLines ?? []);
|
|
3818
|
+
mergedEntity.preSectionLines.push(...parsed.preSectionLines ?? []);
|
|
3819
|
+
mergedEntity.extraSections.push(...(parsed.extraSections ?? []).map((section) => ({
|
|
3820
|
+
title: section.title,
|
|
3821
|
+
lines: [...section.lines]
|
|
3822
|
+
})));
|
|
2604
3823
|
} catch {
|
|
2605
3824
|
}
|
|
2606
3825
|
}
|
|
2607
|
-
|
|
3826
|
+
const timelineKeys = /* @__PURE__ */ new Set();
|
|
3827
|
+
mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
|
|
3828
|
+
const key = JSON.stringify([
|
|
3829
|
+
entry.timestamp,
|
|
3830
|
+
entry.source ?? "",
|
|
3831
|
+
entry.sessionKey ?? "",
|
|
3832
|
+
entry.principal ?? "",
|
|
3833
|
+
entry.text
|
|
3834
|
+
]);
|
|
3835
|
+
if (timelineKeys.has(key)) return false;
|
|
3836
|
+
timelineKeys.add(key);
|
|
3837
|
+
return true;
|
|
3838
|
+
});
|
|
2608
3839
|
const relKeys = /* @__PURE__ */ new Set();
|
|
2609
3840
|
mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
|
|
2610
3841
|
const key = `${r.target}::${r.label}`;
|
|
@@ -2620,13 +3851,27 @@ ${rows.join("\n")}
|
|
|
2620
3851
|
return true;
|
|
2621
3852
|
}).sort((a, b) => b.date.localeCompare(a.date));
|
|
2622
3853
|
mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
|
|
3854
|
+
mergedEntity.structuredSections = sortStructuredSectionsBySchema(
|
|
3855
|
+
mergedEntity.type,
|
|
3856
|
+
mergedEntity.structuredSections ?? [],
|
|
3857
|
+
this.entitySchemas
|
|
3858
|
+
);
|
|
3859
|
+
mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
|
|
3860
|
+
const extraSectionKeys = /* @__PURE__ */ new Set();
|
|
3861
|
+
mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
|
|
3862
|
+
const key = `${section.title}::${section.lines.join("\n")}`;
|
|
3863
|
+
if (extraSectionKeys.has(key)) return false;
|
|
3864
|
+
extraSectionKeys.add(key);
|
|
3865
|
+
return true;
|
|
3866
|
+
});
|
|
2623
3867
|
if (!mergedEntity.name) {
|
|
2624
3868
|
const dashIdx = canonical.indexOf("-");
|
|
2625
3869
|
mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
|
|
2626
3870
|
}
|
|
3871
|
+
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
2627
3872
|
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
2628
3873
|
const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
|
|
2629
|
-
await writeFile(canonicalPath, serializeEntityFile(mergedEntity), "utf-8");
|
|
3874
|
+
await writeFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas), "utf-8");
|
|
2630
3875
|
for (const file of files) {
|
|
2631
3876
|
const filePath = path.join(this.entitiesDir, file);
|
|
2632
3877
|
if (filePath !== canonicalPath) {
|
|
@@ -2711,6 +3956,29 @@ ${memory.content}
|
|
|
2711
3956
|
const memories = await this.readAllMemories();
|
|
2712
3957
|
return memories.find((m) => m.frontmatter.id === id) ?? null;
|
|
2713
3958
|
}
|
|
3959
|
+
/**
|
|
3960
|
+
* Check which of the given memory IDs actually exist on disk.
|
|
3961
|
+
*
|
|
3962
|
+
* Uses a lightweight directory scan (collectActiveMemoryPaths) that reads
|
|
3963
|
+
* file names without parsing frontmatter — much cheaper than readAllMemories()
|
|
3964
|
+
* for simple existence checks like citation usage tracking.
|
|
3965
|
+
*
|
|
3966
|
+
* Returns the subset of `ids` that correspond to real memory files.
|
|
3967
|
+
*/
|
|
3968
|
+
async filterExistingMemoryIds(ids) {
|
|
3969
|
+
if (ids.length === 0) return /* @__PURE__ */ new Set();
|
|
3970
|
+
const wantedIds = new Set(ids);
|
|
3971
|
+
const filePaths = await this.collectActiveMemoryPaths();
|
|
3972
|
+
const foundIds = /* @__PURE__ */ new Set();
|
|
3973
|
+
for (const filePath of filePaths) {
|
|
3974
|
+
const basename = path.basename(filePath, ".md");
|
|
3975
|
+
if (wantedIds.has(basename)) {
|
|
3976
|
+
foundIds.add(basename);
|
|
3977
|
+
if (foundIds.size === wantedIds.size) break;
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
return foundIds;
|
|
3981
|
+
}
|
|
2714
3982
|
async getProjectedMemoryState(id) {
|
|
2715
3983
|
const projected = readProjectedMemoryState(this.baseDir, id);
|
|
2716
3984
|
if (projected) return projected;
|
|
@@ -2802,6 +4070,9 @@ ${sanitized.text}
|
|
|
2802
4070
|
let filePath;
|
|
2803
4071
|
if (category === "correction") {
|
|
2804
4072
|
filePath = path.join(this.correctionsDir, `${id}.md`);
|
|
4073
|
+
} else if (category === "procedure") {
|
|
4074
|
+
await mkdir(path.join(this.proceduresDir, today), { recursive: true });
|
|
4075
|
+
filePath = path.join(this.proceduresDir, today, `${id}.md`);
|
|
2805
4076
|
} else {
|
|
2806
4077
|
filePath = path.join(this.factsDir, today, `${id}.md`);
|
|
2807
4078
|
}
|
|
@@ -3038,8 +4309,12 @@ ${memory.content}
|
|
|
3038
4309
|
export {
|
|
3039
4310
|
normalizeEntityName,
|
|
3040
4311
|
ContentHashIndex,
|
|
4312
|
+
normalizeAttributePairs,
|
|
4313
|
+
compareEntityTimestamps,
|
|
4314
|
+
fingerprintEntityStructuredFacts,
|
|
4315
|
+
isEntitySynthesisStale,
|
|
3041
4316
|
parseEntityFile,
|
|
3042
4317
|
serializeEntityFile,
|
|
3043
4318
|
StorageManager
|
|
3044
4319
|
};
|
|
3045
|
-
//# sourceMappingURL=chunk-
|
|
4320
|
+
//# sourceMappingURL=chunk-POMSFKTB.js.map
|