@remnic/core 9.3.753 → 9.3.755
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +5 -5
- package/dist/access-admin-ops-surface.js +26 -26
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +27 -27
- package/dist/access-cli.js +75 -75
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +34 -34
- package/dist/access-lcm-surface.d.ts +188 -0
- package/dist/access-lcm-surface.js +128 -0
- package/dist/access-lcm-surface.js.map +1 -0
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +31 -31
- package/dist/access-operations-batch.js +28 -28
- package/dist/access-operations.d.ts +9 -9
- package/dist/access-operations.js +30 -30
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +26 -26
- package/dist/access-schema.d.ts +70 -70
- package/dist/access-schema.js +6 -6
- package/dist/{access-service-MitduesD.d.ts → access-service-C3jkLi2U.d.ts} +7 -67
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +26 -26
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-IEBKPGAM.js → auto-sync-G4YKBPOU.js} +5 -5
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +2 -2
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-BEmNVRN0.d.ts → catalog-DaKAFbSJ.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -8
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-HAZVVOYT.js → chunk-24463PIV.js} +2 -2
- package/dist/{chunk-GWGWBK5Y.js → chunk-2MR3MFQB.js} +7 -7
- package/dist/{chunk-YTU7WKYN.js → chunk-2OZ2JEUP.js} +2 -2
- package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
- package/dist/{chunk-EHYB325U.js → chunk-3DOD6ICJ.js} +2 -2
- package/dist/{chunk-KYHGLDFU.js → chunk-4LQUYBZM.js} +6 -6
- package/dist/{chunk-VUA4S7P6.js → chunk-5J4OL7TJ.js} +4 -4
- package/dist/{chunk-TVISHYL5.js → chunk-CIDGUFAL.js} +2 -2
- package/dist/{chunk-OUXPVWTZ.js → chunk-FLVL4L3L.js} +5 -5
- package/dist/{chunk-3HGZX26P.js → chunk-FOJY75BL.js} +8 -8
- package/dist/{chunk-VVSV2QVY.js → chunk-FQLM6ZUP.js} +3 -3
- package/dist/{chunk-LZ73Y56P.js → chunk-G7LCG56O.js} +4 -4
- package/dist/{chunk-ZECLLIMX.js → chunk-GCEV6SXP.js} +4989 -4960
- package/dist/chunk-GCEV6SXP.js.map +1 -0
- package/dist/{chunk-ZMHDSIEX.js → chunk-GGYUR4M2.js} +4 -4
- package/dist/{chunk-YNBEHUZH.js → chunk-GMWKWA6N.js} +2 -2
- package/dist/{chunk-JGPURIEH.js → chunk-GPFXPC2V.js} +2 -2
- package/dist/{chunk-E6LENGYV.js → chunk-H6S3DJVE.js} +2 -2
- package/dist/{chunk-E3MRYDUQ.js → chunk-I3UWEZTK.js} +2 -2
- package/dist/{chunk-ZIFPFKB2.js → chunk-I6IAJCJS.js} +2 -2
- package/dist/{chunk-JES2O5Q6.js → chunk-JITAVROY.js} +4 -4
- package/dist/{chunk-DKIZ4BUQ.js → chunk-KKOCFUI5.js} +7 -7
- package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
- package/dist/{chunk-N3VE7OCY.js → chunk-LFKEFIJA.js} +2 -2
- package/dist/{chunk-EEO4YWQ7.js → chunk-N4AGSRVA.js} +2 -2
- package/dist/{chunk-QIRZI3C3.js → chunk-NCNOYM6F.js} +2 -2
- package/dist/{chunk-3ZIKDH6C.js → chunk-OCFITZRV.js} +4 -4
- package/dist/{chunk-ITLJATQI.js → chunk-PJ7DIEFC.js} +2 -2
- package/dist/{chunk-OZMBT7N3.js → chunk-PJQMISPD.js} +2 -2
- package/dist/{chunk-XOPLMSVV.js → chunk-S6UCPBPL.js} +2 -2
- package/dist/{chunk-243P7YYV.js → chunk-SP42M3XP.js} +2 -2
- package/dist/{chunk-DCWIQFNA.js → chunk-T422SYM6.js} +5 -5
- package/dist/{chunk-AXBRVBT6.js → chunk-T6WHI3JW.js} +2 -2
- package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
- package/dist/{chunk-3NN73CM7.js → chunk-TH7WMHVK.js} +2 -2
- package/dist/{chunk-RNWWAS5V.js → chunk-TJATYB3F.js} +11 -11
- package/dist/{chunk-C3IHRWIC.js → chunk-UQJ6YTKR.js} +2 -2
- package/dist/{chunk-PN5AMCK2.js → chunk-VL5QHTJI.js} +56 -56
- package/dist/{chunk-34THARMH.js → chunk-XBM5DG4O.js} +665 -577
- package/dist/chunk-XBM5DG4O.js.map +1 -0
- package/dist/{chunk-BWOEYBRB.js → chunk-XCXN3D2U.js} +2 -2
- package/dist/{chunk-KY4WOZ3P.js → chunk-XD6JVK5P.js} +42 -42
- package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
- package/dist/{cli-DqonYBZ0.d.ts → cli-BJQ2ahrh.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +55 -55
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/fallback-llm.js +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +149 -149
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +17 -17
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +7 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +21 -21
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-D7ZGOQLX.d.ts → orchestrator-Xm6sK5jw.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +75 -75
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +100 -100
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-7Sndms2M.d.ts → semantic-consolidation-bfXJNKVf.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +11 -21
- package/dist/storage.js +14 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DTEFxFuL.d.ts → types-D1iBqhJA.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +7 -7
- package/package.json +2 -2
- package/src/access-lcm-surface.ts +517 -0
- package/src/access-service.ts +21 -417
- package/src/storage/entity-store.ts +672 -0
- package/src/storage.ts +45 -565
- package/dist/chunk-34THARMH.js.map +0 -1
- package/dist/chunk-ZECLLIMX.js.map +0 -1
- /package/dist/{auto-sync-IEBKPGAM.js.map → auto-sync-G4YKBPOU.js.map} +0 -0
- /package/dist/{chunk-HAZVVOYT.js.map → chunk-24463PIV.js.map} +0 -0
- /package/dist/{chunk-GWGWBK5Y.js.map → chunk-2MR3MFQB.js.map} +0 -0
- /package/dist/{chunk-YTU7WKYN.js.map → chunk-2OZ2JEUP.js.map} +0 -0
- /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-EHYB325U.js.map → chunk-3DOD6ICJ.js.map} +0 -0
- /package/dist/{chunk-KYHGLDFU.js.map → chunk-4LQUYBZM.js.map} +0 -0
- /package/dist/{chunk-VUA4S7P6.js.map → chunk-5J4OL7TJ.js.map} +0 -0
- /package/dist/{chunk-TVISHYL5.js.map → chunk-CIDGUFAL.js.map} +0 -0
- /package/dist/{chunk-OUXPVWTZ.js.map → chunk-FLVL4L3L.js.map} +0 -0
- /package/dist/{chunk-3HGZX26P.js.map → chunk-FOJY75BL.js.map} +0 -0
- /package/dist/{chunk-VVSV2QVY.js.map → chunk-FQLM6ZUP.js.map} +0 -0
- /package/dist/{chunk-LZ73Y56P.js.map → chunk-G7LCG56O.js.map} +0 -0
- /package/dist/{chunk-ZMHDSIEX.js.map → chunk-GGYUR4M2.js.map} +0 -0
- /package/dist/{chunk-YNBEHUZH.js.map → chunk-GMWKWA6N.js.map} +0 -0
- /package/dist/{chunk-JGPURIEH.js.map → chunk-GPFXPC2V.js.map} +0 -0
- /package/dist/{chunk-E6LENGYV.js.map → chunk-H6S3DJVE.js.map} +0 -0
- /package/dist/{chunk-E3MRYDUQ.js.map → chunk-I3UWEZTK.js.map} +0 -0
- /package/dist/{chunk-ZIFPFKB2.js.map → chunk-I6IAJCJS.js.map} +0 -0
- /package/dist/{chunk-JES2O5Q6.js.map → chunk-JITAVROY.js.map} +0 -0
- /package/dist/{chunk-DKIZ4BUQ.js.map → chunk-KKOCFUI5.js.map} +0 -0
- /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
- /package/dist/{chunk-N3VE7OCY.js.map → chunk-LFKEFIJA.js.map} +0 -0
- /package/dist/{chunk-EEO4YWQ7.js.map → chunk-N4AGSRVA.js.map} +0 -0
- /package/dist/{chunk-QIRZI3C3.js.map → chunk-NCNOYM6F.js.map} +0 -0
- /package/dist/{chunk-3ZIKDH6C.js.map → chunk-OCFITZRV.js.map} +0 -0
- /package/dist/{chunk-ITLJATQI.js.map → chunk-PJ7DIEFC.js.map} +0 -0
- /package/dist/{chunk-OZMBT7N3.js.map → chunk-PJQMISPD.js.map} +0 -0
- /package/dist/{chunk-XOPLMSVV.js.map → chunk-S6UCPBPL.js.map} +0 -0
- /package/dist/{chunk-243P7YYV.js.map → chunk-SP42M3XP.js.map} +0 -0
- /package/dist/{chunk-DCWIQFNA.js.map → chunk-T422SYM6.js.map} +0 -0
- /package/dist/{chunk-AXBRVBT6.js.map → chunk-T6WHI3JW.js.map} +0 -0
- /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
- /package/dist/{chunk-3NN73CM7.js.map → chunk-TH7WMHVK.js.map} +0 -0
- /package/dist/{chunk-RNWWAS5V.js.map → chunk-TJATYB3F.js.map} +0 -0
- /package/dist/{chunk-C3IHRWIC.js.map → chunk-UQJ6YTKR.js.map} +0 -0
- /package/dist/{chunk-PN5AMCK2.js.map → chunk-VL5QHTJI.js.map} +0 -0
- /package/dist/{chunk-BWOEYBRB.js.map → chunk-XCXN3D2U.js.map} +0 -0
- /package/dist/{chunk-KY4WOZ3P.js.map → chunk-XD6JVK5P.js.map} +0 -0
- /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
package/src/storage.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { createHash } from "node:crypto"
|
|
|
4
4
|
import { normalizeContent, computeContentHash } from "./content-hash.js";;
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { log } from "./logger.js";
|
|
7
|
+
import { EntityStore } from "./storage/entity-store.js";
|
|
8
|
+
import { selfDeps } from "./orchestration/self-deps.js";
|
|
7
9
|
import { isErrnoCode } from "./utils/errno.js";
|
|
8
10
|
import { RECALL_FALLBACK_DIRS, getCategoryDir, categoryDirName } from "./utils/category-dir.js";
|
|
9
11
|
import { assertPathInsideRoot } from "./utils/path-containment.js";
|
|
@@ -1651,7 +1653,7 @@ function normalizeEntitySectionFact(value: string): string {
|
|
|
1651
1653
|
return value.replace(/\s+/g, " ").trim();
|
|
1652
1654
|
}
|
|
1653
1655
|
|
|
1654
|
-
function normalizeStructuredSectionFacts(facts: string[]): string[] {
|
|
1656
|
+
export function normalizeStructuredSectionFacts(facts: string[]): string[] {
|
|
1655
1657
|
return [...new Set(
|
|
1656
1658
|
facts
|
|
1657
1659
|
.map((fact) => normalizeEntitySectionFact(fact))
|
|
@@ -1671,7 +1673,7 @@ function collectStructuredSectionFacts(structuredSections: EntityStructuredSecti
|
|
|
1671
1673
|
return [...new Set(facts)];
|
|
1672
1674
|
}
|
|
1673
1675
|
|
|
1674
|
-
function compileEntityFacts(
|
|
1676
|
+
export function compileEntityFacts(
|
|
1675
1677
|
timeline: EntityTimelineEntry[],
|
|
1676
1678
|
structuredSections: EntityStructuredSection[],
|
|
1677
1679
|
): string[] {
|
|
@@ -1811,7 +1813,7 @@ export function compareEntityTimestamps(left?: string, right?: string): number {
|
|
|
1811
1813
|
return leftValue.localeCompare(rightValue);
|
|
1812
1814
|
}
|
|
1813
1815
|
|
|
1814
|
-
function countEntityStructuredFacts(entity: EntityFile): number {
|
|
1816
|
+
export function countEntityStructuredFacts(entity: EntityFile): number {
|
|
1815
1817
|
return (entity.structuredSections ?? []).reduce((count, section) => count + section.facts.length, 0);
|
|
1816
1818
|
}
|
|
1817
1819
|
|
|
@@ -2189,7 +2191,7 @@ export function serializeEntityFile(
|
|
|
2189
2191
|
return lines.join("\n");
|
|
2190
2192
|
}
|
|
2191
2193
|
|
|
2192
|
-
function buildEntitySchemaCacheKey(entitySchemas?: PluginConfig["entitySchemas"]): string {
|
|
2194
|
+
export function buildEntitySchemaCacheKey(entitySchemas?: PluginConfig["entitySchemas"]): string {
|
|
2193
2195
|
if (!entitySchemas) return "";
|
|
2194
2196
|
const normalized = Object.entries(entitySchemas)
|
|
2195
2197
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -2259,8 +2261,9 @@ export interface MemoryWriteResult {
|
|
|
2259
2261
|
|
|
2260
2262
|
export class StorageManager {
|
|
2261
2263
|
private knowledgeIndexCache: { result: string; builtAt: number } | null = null;
|
|
2262
|
-
private static readonly KNOWLEDGE_INDEX_CACHE_TTL_MS = 600_000; // 10 minutes (entity mutations invalidate)
|
|
2263
2264
|
private artifactIndexCache: { memories: MemoryFile[]; loadedAtMs: number; writeVersion: number } | null = null;
|
|
2265
|
+
/** Read by storage/entity-store.ts (decomposition), hence not `private`. */
|
|
2266
|
+
static readonly KNOWLEDGE_INDEX_CACHE_TTL_MS = 600_000; // 10 minutes (entity mutations invalidate)
|
|
2264
2267
|
private static readonly ARTIFACT_INDEX_CACHE_TTL_MS = 60_000; // 1 minute
|
|
2265
2268
|
private static readonly artifactWriteVersionByDir = new Map<string, number>();
|
|
2266
2269
|
private static readonly memoryStatusVersionByDir = new Map<string, number>();
|
|
@@ -2514,6 +2517,18 @@ export class StorageManager {
|
|
|
2514
2517
|
}
|
|
2515
2518
|
|
|
2516
2519
|
/** The root directory of this storage instance. */
|
|
2520
|
+
/** EntityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
2521
|
+
private _entityStore: EntityStore | undefined;
|
|
2522
|
+
|
|
2523
|
+
private get entityStore(): EntityStore {
|
|
2524
|
+
if (!this._entityStore) {
|
|
2525
|
+
this._entityStore = new EntityStore(
|
|
2526
|
+
selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this),
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
return this._entityStore;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2517
2532
|
get dir(): string {
|
|
2518
2533
|
return this.baseDir;
|
|
2519
2534
|
}
|
|
@@ -3476,9 +3491,11 @@ export class StorageManager {
|
|
|
3476
3491
|
*/
|
|
3477
3492
|
private userAliases: Record<string, string> = {};
|
|
3478
3493
|
|
|
3479
|
-
/** Normalize an entity name using this store's alias table. */
|
|
3480
3494
|
normalizeEntityName(raw: string, type: string): string {
|
|
3481
|
-
return normalizeEntityName(
|
|
3495
|
+
return this.entityStore.normalizeEntityName(
|
|
3496
|
+
raw,
|
|
3497
|
+
type,
|
|
3498
|
+
);
|
|
3482
3499
|
}
|
|
3483
3500
|
|
|
3484
3501
|
/**
|
|
@@ -4109,124 +4126,12 @@ export class StorageManager {
|
|
|
4109
4126
|
structuredSections?: EntityStructuredSection[];
|
|
4110
4127
|
} = {},
|
|
4111
4128
|
): Promise<string> {
|
|
4112
|
-
|
|
4113
|
-
if (typeof name !== "string" || !name.trim() || typeof type !== "string" || !type.trim()) {
|
|
4114
|
-
log.warn("writeEntity: invalid entity payload, skipping", {
|
|
4115
|
-
nameType: typeof name,
|
|
4116
|
-
typeType: typeof type,
|
|
4117
|
-
});
|
|
4118
|
-
return "";
|
|
4119
|
-
}
|
|
4120
|
-
const safeFacts = Array.isArray(facts)
|
|
4121
|
-
? [...new Set(
|
|
4122
|
-
facts
|
|
4123
|
-
.filter((fact) => typeof fact === "string")
|
|
4124
|
-
.map((fact) => fact.trim())
|
|
4125
|
-
.filter((fact) => fact.length > 0),
|
|
4126
|
-
)]
|
|
4127
|
-
: [];
|
|
4128
|
-
let normalized = this.normalizeEntityName(name, type);
|
|
4129
|
-
|
|
4130
|
-
// Check for fuzzy match against existing entities before creating a new file
|
|
4131
|
-
const match = await this.findMatchingEntity(name, type);
|
|
4132
|
-
if (match && match !== normalized) {
|
|
4133
|
-
log.debug(`fuzzy match: "${normalized}" → existing "${match}"`);
|
|
4134
|
-
normalized = match;
|
|
4135
|
-
}
|
|
4136
|
-
|
|
4137
|
-
const filePath = path.join(this.entitiesDir, `${normalized}.md`);
|
|
4138
|
-
|
|
4139
|
-
// Parse existing file to preserve relationships/activity/aliases/summary
|
|
4140
|
-
let entity: EntityFile = {
|
|
4129
|
+
return this.entityStore.writeEntity(
|
|
4141
4130
|
name,
|
|
4142
4131
|
type,
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
facts: [],
|
|
4146
|
-
summary: undefined,
|
|
4147
|
-
synthesis: undefined,
|
|
4148
|
-
synthesisUpdatedAt: undefined,
|
|
4149
|
-
synthesisVersion: undefined,
|
|
4150
|
-
synthesisStructuredFactCount: undefined,
|
|
4151
|
-
synthesisStructuredFactDigest: undefined,
|
|
4152
|
-
timeline: [],
|
|
4153
|
-
relationships: [],
|
|
4154
|
-
activity: [],
|
|
4155
|
-
aliases: [],
|
|
4156
|
-
};
|
|
4157
|
-
try {
|
|
4158
|
-
const existing = await this.readStorageSecureFile(filePath);
|
|
4159
|
-
entity = parseEntityFile(existing, this.entitySchemas);
|
|
4160
|
-
} catch (err) {
|
|
4161
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4162
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4163
|
-
// File doesn't exist yet
|
|
4164
|
-
}
|
|
4165
|
-
|
|
4166
|
-
const timestamp = options.timestamp?.trim() || new Date().toISOString();
|
|
4167
|
-
const source = options.source?.trim() || undefined;
|
|
4168
|
-
const sessionKey = options.sessionKey?.trim() || undefined;
|
|
4169
|
-
const principal = options.principal?.trim() || undefined;
|
|
4170
|
-
const structuredSectionMap = new Map(
|
|
4171
|
-
(entity.structuredSections ?? []).map((section) => [section.key, {
|
|
4172
|
-
...section,
|
|
4173
|
-
facts: [...section.facts],
|
|
4174
|
-
}]),
|
|
4175
|
-
);
|
|
4176
|
-
for (const section of options.structuredSections ?? []) {
|
|
4177
|
-
const normalizedSection = normalizeEntityStructuredSection(type, section, this.entitySchemas);
|
|
4178
|
-
const normalizedFacts = normalizeStructuredSectionFacts(section.facts);
|
|
4179
|
-
if (normalizedFacts.length === 0) continue;
|
|
4180
|
-
const existingSection = structuredSectionMap.get(normalizedSection.key);
|
|
4181
|
-
if (!existingSection) {
|
|
4182
|
-
structuredSectionMap.set(normalizedSection.key, {
|
|
4183
|
-
key: normalizedSection.key,
|
|
4184
|
-
title: normalizedSection.title,
|
|
4185
|
-
facts: normalizedFacts,
|
|
4186
|
-
});
|
|
4187
|
-
continue;
|
|
4188
|
-
}
|
|
4189
|
-
existingSection.facts = normalizeStructuredSectionFacts([...existingSection.facts, ...normalizedFacts]);
|
|
4190
|
-
if (!existingSection.title.trim() && normalizedSection.title.trim()) {
|
|
4191
|
-
existingSection.title = normalizedSection.title;
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
for (const fact of safeFacts) {
|
|
4195
|
-
const nextEntry = {
|
|
4196
|
-
timestamp,
|
|
4197
|
-
text: fact,
|
|
4198
|
-
...(source ? { source } : {}),
|
|
4199
|
-
...(sessionKey ? { sessionKey } : {}),
|
|
4200
|
-
...(principal ? { principal } : {}),
|
|
4201
|
-
};
|
|
4202
|
-
const alreadyPresent = entity.timeline.some((entry) =>
|
|
4203
|
-
entry.timestamp === nextEntry.timestamp
|
|
4204
|
-
&& entry.text === nextEntry.text
|
|
4205
|
-
&& entry.source === nextEntry.source
|
|
4206
|
-
&& entry.sessionKey === nextEntry.sessionKey
|
|
4207
|
-
&& entry.principal === nextEntry.principal
|
|
4208
|
-
);
|
|
4209
|
-
if (alreadyPresent) continue;
|
|
4210
|
-
entity.timeline.push(nextEntry);
|
|
4211
|
-
}
|
|
4212
|
-
entity.structuredSections = sortStructuredSectionsBySchema(
|
|
4213
|
-
type,
|
|
4214
|
-
Array.from(structuredSectionMap.values()).filter((section) => section.facts.length > 0),
|
|
4215
|
-
this.entitySchemas,
|
|
4132
|
+
facts,
|
|
4133
|
+
options,
|
|
4216
4134
|
);
|
|
4217
|
-
entity.facts = compileEntityFacts(entity.timeline, entity.structuredSections);
|
|
4218
|
-
entity.summary = entity.synthesis || entity.summary;
|
|
4219
|
-
entity.name = name;
|
|
4220
|
-
entity.type = type;
|
|
4221
|
-
entity.created = entity.created || timestamp;
|
|
4222
|
-
entity.updated = new Date().toISOString();
|
|
4223
|
-
|
|
4224
|
-
await this.snapshotBeforeWrite(filePath, "write");
|
|
4225
|
-
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
4226
|
-
this.invalidateKnowledgeIndexCache();
|
|
4227
|
-
this.bumpMemoryStatusVersion(); // invalidate entity cache
|
|
4228
|
-
log.debug(`wrote entity ${normalized}`);
|
|
4229
|
-
return normalized;
|
|
4230
4135
|
}
|
|
4231
4136
|
|
|
4232
4137
|
async readProfile(): Promise<string> {
|
|
@@ -5106,30 +5011,14 @@ export class StorageManager {
|
|
|
5106
5011
|
}
|
|
5107
5012
|
|
|
5108
5013
|
async readEntity(name: string): Promise<string> {
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
}
|
|
5113
|
-
try {
|
|
5114
|
-
return await this.readStorageSecureFile(filePath);
|
|
5115
|
-
} catch (err) {
|
|
5116
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5117
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5118
|
-
return "";
|
|
5119
|
-
}
|
|
5014
|
+
return this.entityStore.readEntity(
|
|
5015
|
+
name,
|
|
5016
|
+
);
|
|
5120
5017
|
}
|
|
5121
5018
|
|
|
5122
|
-
/** Return sorted list of entity filenames (without .md extension) */
|
|
5123
5019
|
async listEntityNames(): Promise<string[]> {
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
return entries
|
|
5127
|
-
.filter((e) => e.endsWith(".md"))
|
|
5128
|
-
.map((e) => e.replace(".md", ""))
|
|
5129
|
-
.sort();
|
|
5130
|
-
} catch {
|
|
5131
|
-
return [];
|
|
5132
|
-
}
|
|
5020
|
+
return this.entityStore.listEntityNames(
|
|
5021
|
+
);
|
|
5133
5022
|
}
|
|
5134
5023
|
|
|
5135
5024
|
/**
|
|
@@ -6554,9 +6443,6 @@ export class StorageManager {
|
|
|
6554
6443
|
this.invalidateKnowledgeIndexCache();
|
|
6555
6444
|
}
|
|
6556
6445
|
|
|
6557
|
-
/**
|
|
6558
|
-
* Set or rewrite the synthesis layer of an entity file.
|
|
6559
|
-
*/
|
|
6560
6446
|
async updateEntitySynthesis(
|
|
6561
6447
|
name: string,
|
|
6562
6448
|
synthesis: string,
|
|
@@ -6569,45 +6455,11 @@ export class StorageManager {
|
|
|
6569
6455
|
incrementVersion?: boolean;
|
|
6570
6456
|
} = {},
|
|
6571
6457
|
): Promise<void> {
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
} catch (err) {
|
|
6578
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
6579
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
6580
|
-
log.debug(`updateEntitySynthesis: entity file ${name}.md not found`);
|
|
6581
|
-
return;
|
|
6582
|
-
}
|
|
6583
|
-
|
|
6584
|
-
const updatedAt = options.updatedAt?.trim() || entity.synthesisUpdatedAt?.trim() || undefined;
|
|
6585
|
-
const entityUpdatedAt = options.entityUpdatedAt?.trim() || updatedAt || entity.updated || new Date().toISOString();
|
|
6586
|
-
const synthesisTimelineCount = Number.isInteger(options.synthesisTimelineCount)
|
|
6587
|
-
&& (options.synthesisTimelineCount ?? 0) >= 0
|
|
6588
|
-
? options.synthesisTimelineCount
|
|
6589
|
-
: undefined;
|
|
6590
|
-
const synthesisStructuredFactCount = Number.isInteger(options.synthesisStructuredFactCount)
|
|
6591
|
-
&& (options.synthesisStructuredFactCount ?? 0) >= 0
|
|
6592
|
-
? options.synthesisStructuredFactCount
|
|
6593
|
-
: countEntityStructuredFacts(entity);
|
|
6594
|
-
const synthesisStructuredFactDigest = options.synthesisStructuredFactDigest?.trim()
|
|
6595
|
-
|| fingerprintEntityStructuredFacts(entity);
|
|
6596
|
-
entity.synthesis = synthesis.trim();
|
|
6597
|
-
entity.summary = entity.synthesis;
|
|
6598
|
-
entity.synthesisUpdatedAt = updatedAt;
|
|
6599
|
-
entity.synthesisTimelineCount = synthesisTimelineCount;
|
|
6600
|
-
entity.synthesisStructuredFactCount = synthesisStructuredFactCount;
|
|
6601
|
-
entity.synthesisStructuredFactDigest = synthesisStructuredFactDigest;
|
|
6602
|
-
entity.synthesisVersion = Math.max(0, entity.synthesisVersion ?? 0)
|
|
6603
|
-
+ (options.incrementVersion === false ? 0 : 1);
|
|
6604
|
-
entity.updated = entityUpdatedAt;
|
|
6605
|
-
await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
|
|
6606
|
-
await this.removeEntitySynthesisQueueEntries([
|
|
6607
|
-
...new Set([name, this.normalizeEntityName(entity.name, entity.type)]),
|
|
6608
|
-
]);
|
|
6609
|
-
this.invalidateKnowledgeIndexCache();
|
|
6610
|
-
this.bumpMemoryStatusVersion(); // invalidate entity cache
|
|
6458
|
+
return this.entityStore.updateEntitySynthesis(
|
|
6459
|
+
name,
|
|
6460
|
+
synthesis,
|
|
6461
|
+
options,
|
|
6462
|
+
);
|
|
6611
6463
|
}
|
|
6612
6464
|
|
|
6613
6465
|
/**
|
|
@@ -6731,52 +6583,9 @@ export class StorageManager {
|
|
|
6731
6583
|
// Scoring + Knowledge Index (Knowledge Graph v7.0)
|
|
6732
6584
|
// ---------------------------------------------------------------------------
|
|
6733
6585
|
|
|
6734
|
-
/**
|
|
6735
|
-
* Read all entity files and return lightweight EntityFile objects.
|
|
6736
|
-
* Parsing is fast (~50-100ms for ~1,800 files) since entity files are small.
|
|
6737
|
-
*/
|
|
6738
6586
|
async readAllEntityFiles(): Promise<EntityFile[]> {
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
const cacheKey = `${this.getEntityCacheSecureStoreKey()}\u0000${schemaCacheKey}`;
|
|
6742
|
-
const cached = getCachedEntities(this.baseDir, currentVersion, cacheKey);
|
|
6743
|
-
if (cached) return cached;
|
|
6744
|
-
|
|
6745
|
-
try {
|
|
6746
|
-
const entries = await readdir(this.entitiesDir);
|
|
6747
|
-
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
6748
|
-
if (mdFiles.length === 0) return [];
|
|
6749
|
-
|
|
6750
|
-
// Read all entity files in parallel batches to avoid O(N) sequential I/O.
|
|
6751
|
-
// With 3000+ entity files, sequential reads can take 15-20s under load.
|
|
6752
|
-
// Batching at 100 keeps file-descriptor pressure manageable while staying fast.
|
|
6753
|
-
const BATCH_SIZE = 100;
|
|
6754
|
-
const entities: EntityFile[] = [];
|
|
6755
|
-
for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
|
|
6756
|
-
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
6757
|
-
const results = await Promise.all(
|
|
6758
|
-
batch.map(async (entry) => {
|
|
6759
|
-
try {
|
|
6760
|
-
return await this.readStorageSecureFile(path.join(this.entitiesDir, entry));
|
|
6761
|
-
} catch (err) {
|
|
6762
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
6763
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
6764
|
-
return null;
|
|
6765
|
-
}
|
|
6766
|
-
}),
|
|
6767
|
-
);
|
|
6768
|
-
for (const content of results) {
|
|
6769
|
-
if (content !== null) entities.push(parseEntityFile(content, this.entitySchemas));
|
|
6770
|
-
}
|
|
6771
|
-
}
|
|
6772
|
-
|
|
6773
|
-
setCachedEntities(this.baseDir, entities, currentVersion, cacheKey);
|
|
6774
|
-
return entities;
|
|
6775
|
-
} catch (err) {
|
|
6776
|
-
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
6777
|
-
// Directory doesn't exist yet
|
|
6778
|
-
return [];
|
|
6779
|
-
}
|
|
6587
|
+
return this.entityStore.readAllEntityFiles(
|
|
6588
|
+
);
|
|
6780
6589
|
}
|
|
6781
6590
|
|
|
6782
6591
|
/**
|
|
@@ -6820,77 +6629,14 @@ export class StorageManager {
|
|
|
6820
6629
|
);
|
|
6821
6630
|
}
|
|
6822
6631
|
|
|
6823
|
-
/**
|
|
6824
|
-
* Build the Knowledge Index: a compact markdown table of top-scored entities.
|
|
6825
|
-
* Respects maxEntities and maxChars limits from config.
|
|
6826
|
-
*/
|
|
6827
6632
|
async buildKnowledgeIndex(
|
|
6828
6633
|
config: PluginConfig,
|
|
6829
6634
|
overrides?: { maxEntities?: number; maxChars?: number },
|
|
6830
6635
|
): Promise<{ result: string; cached: boolean }> {
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
overrides
|
|
6834
|
-
|
|
6835
|
-
if (
|
|
6836
|
-
useDefaultLimits &&
|
|
6837
|
-
this.knowledgeIndexCache &&
|
|
6838
|
-
Date.now() - this.knowledgeIndexCache.builtAt < StorageManager.KNOWLEDGE_INDEX_CACHE_TTL_MS
|
|
6839
|
-
) {
|
|
6840
|
-
return { result: this.knowledgeIndexCache.result, cached: true };
|
|
6841
|
-
}
|
|
6842
|
-
|
|
6843
|
-
const entities = await this.readAllEntityFiles();
|
|
6844
|
-
if (entities.length === 0) {
|
|
6845
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
6846
|
-
return { result: "", cached: false };
|
|
6847
|
-
}
|
|
6848
|
-
|
|
6849
|
-
const now = new Date();
|
|
6850
|
-
const scored: ScoredEntity[] = entities.map((e) => ({
|
|
6851
|
-
name: e.name,
|
|
6852
|
-
type: e.type,
|
|
6853
|
-
score: StorageManager.scoreEntity(e, now),
|
|
6854
|
-
factCount: e.facts.length,
|
|
6855
|
-
summary: e.synthesis ?? e.summary,
|
|
6856
|
-
topRelationships: e.relationships.slice(0, 3).map((r) => r.target),
|
|
6857
|
-
}));
|
|
6858
|
-
|
|
6859
|
-
// Sort by score descending, take top N
|
|
6860
|
-
scored.sort((a, b) => b.score - a.score);
|
|
6861
|
-
const maxEntities = typeof overrides?.maxEntities === "number"
|
|
6862
|
-
? Math.max(0, Math.floor(overrides.maxEntities))
|
|
6863
|
-
: config.knowledgeIndexMaxEntities;
|
|
6864
|
-
const topN = scored.slice(0, maxEntities);
|
|
6865
|
-
|
|
6866
|
-
if (topN.length === 0) {
|
|
6867
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result: "", builtAt: Date.now() };
|
|
6868
|
-
return { result: "", cached: false };
|
|
6869
|
-
}
|
|
6870
|
-
|
|
6871
|
-
// Build markdown table
|
|
6872
|
-
const header = "## Knowledge Index\n\n| Entity | Type | Summary | Connected to |\n|--------|------|---------|-------------|";
|
|
6873
|
-
const rows: string[] = [];
|
|
6874
|
-
let totalChars = header.length;
|
|
6875
|
-
const maxChars = typeof overrides?.maxChars === "number"
|
|
6876
|
-
? Math.max(0, Math.floor(overrides.maxChars))
|
|
6877
|
-
: config.knowledgeIndexMaxChars;
|
|
6878
|
-
|
|
6879
|
-
for (const entity of topN) {
|
|
6880
|
-
const summary = entity.summary || `${entity.factCount} facts`;
|
|
6881
|
-
const connected = entity.topRelationships.length > 0
|
|
6882
|
-
? entity.topRelationships.join(", ")
|
|
6883
|
-
: "—";
|
|
6884
|
-
const row = `| ${entity.name} | ${entity.type} | ${summary} | ${connected} |`;
|
|
6885
|
-
|
|
6886
|
-
if (totalChars + row.length + 1 > maxChars) break;
|
|
6887
|
-
rows.push(row);
|
|
6888
|
-
totalChars += row.length + 1;
|
|
6889
|
-
}
|
|
6890
|
-
|
|
6891
|
-
const result = rows.length === 0 ? "" : `${header}\n${rows.join("\n")}\n`;
|
|
6892
|
-
if (useDefaultLimits) this.knowledgeIndexCache = { result, builtAt: Date.now() };
|
|
6893
|
-
return { result, cached: false };
|
|
6636
|
+
return this.entityStore.buildKnowledgeIndex(
|
|
6637
|
+
config,
|
|
6638
|
+
overrides,
|
|
6639
|
+
);
|
|
6894
6640
|
}
|
|
6895
6641
|
|
|
6896
6642
|
/** Invalidate the Knowledge Index cache (call after entity mutations). */
|
|
@@ -6905,275 +6651,9 @@ export class StorageManager {
|
|
|
6905
6651
|
/** Max lines for profile.md before LLM consolidation triggers */
|
|
6906
6652
|
private static readonly PROFILE_MAX_LINES = 300;
|
|
6907
6653
|
|
|
6908
|
-
/**
|
|
6909
|
-
* Merge fragmented entity files that resolve to the same canonical name.
|
|
6910
|
-
* Preserves relationships, activity, aliases, and summary from all fragments.
|
|
6911
|
-
* Returns count of files merged.
|
|
6912
|
-
*/
|
|
6913
6654
|
async mergeFragmentedEntities(): Promise<number> {
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
const entries = await readdir(this.entitiesDir);
|
|
6917
|
-
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
6918
|
-
|
|
6919
|
-
// Group files by their canonical name
|
|
6920
|
-
const groups = new Map<string, string[]>();
|
|
6921
|
-
for (const file of mdFiles) {
|
|
6922
|
-
const baseName = file.replace(".md", "");
|
|
6923
|
-
// Extract type and name from filename (type-rest-of-name)
|
|
6924
|
-
const dashIdx = baseName.indexOf("-");
|
|
6925
|
-
if (dashIdx === -1) continue;
|
|
6926
|
-
const type = baseName.slice(0, dashIdx);
|
|
6927
|
-
const restOfName = baseName.slice(dashIdx + 1);
|
|
6928
|
-
const canonical = this.normalizeEntityName(restOfName, type);
|
|
6929
|
-
|
|
6930
|
-
if (!groups.has(canonical)) groups.set(canonical, []);
|
|
6931
|
-
groups.get(canonical)!.push(file);
|
|
6932
|
-
}
|
|
6933
|
-
|
|
6934
|
-
// Merge groups with more than one file
|
|
6935
|
-
for (const [canonical, files] of groups) {
|
|
6936
|
-
if (files.length <= 1) continue;
|
|
6937
|
-
|
|
6938
|
-
// Parse all files and merge into a single EntityFile
|
|
6939
|
-
const mergedEntity: EntityFile = {
|
|
6940
|
-
name: "",
|
|
6941
|
-
type: "other",
|
|
6942
|
-
created: "",
|
|
6943
|
-
updated: "",
|
|
6944
|
-
extraFrontmatterLines: [],
|
|
6945
|
-
preSectionLines: [],
|
|
6946
|
-
facts: [],
|
|
6947
|
-
summary: undefined,
|
|
6948
|
-
synthesis: undefined,
|
|
6949
|
-
synthesisUpdatedAt: undefined,
|
|
6950
|
-
synthesisTimelineCount: undefined,
|
|
6951
|
-
synthesisStructuredFactCount: undefined,
|
|
6952
|
-
synthesisStructuredFactDigest: undefined,
|
|
6953
|
-
synthesisVersion: undefined,
|
|
6954
|
-
timeline: [],
|
|
6955
|
-
relationships: [],
|
|
6956
|
-
activity: [],
|
|
6957
|
-
aliases: [],
|
|
6958
|
-
structuredSections: [],
|
|
6959
|
-
extraSections: [],
|
|
6960
|
-
};
|
|
6961
|
-
|
|
6962
|
-
for (const file of files) {
|
|
6963
|
-
const filePath = path.join(this.entitiesDir, file);
|
|
6964
|
-
try {
|
|
6965
|
-
const content = await this.readStorageSecureFile(filePath);
|
|
6966
|
-
const parsed = parseEntityFile(content, this.entitySchemas);
|
|
6967
|
-
|
|
6968
|
-
// Prefer specific types over "other"
|
|
6969
|
-
if (!mergedEntity.type || mergedEntity.type === "other") {
|
|
6970
|
-
mergedEntity.type = parsed.type;
|
|
6971
|
-
}
|
|
6972
|
-
|
|
6973
|
-
// Keep latest update time
|
|
6974
|
-
if (!mergedEntity.updated || compareEntityTimestamps(parsed.updated, mergedEntity.updated) > 0) {
|
|
6975
|
-
mergedEntity.updated = parsed.updated;
|
|
6976
|
-
}
|
|
6977
|
-
|
|
6978
|
-
const parsedCreated = parsed.created || parsed.updated;
|
|
6979
|
-
const mergedCreated = mergedEntity.created?.trim() || "";
|
|
6980
|
-
const parsedCreatedMs = parsedCreated ? Date.parse(parsedCreated) : Number.NaN;
|
|
6981
|
-
const mergedCreatedMs = mergedCreated ? Date.parse(mergedCreated) : Number.NaN;
|
|
6982
|
-
const parsedCreatedIsValid = Number.isFinite(parsedCreatedMs);
|
|
6983
|
-
const mergedCreatedIsValid = Number.isFinite(mergedCreatedMs);
|
|
6984
|
-
if (
|
|
6985
|
-
parsedCreated &&
|
|
6986
|
-
(
|
|
6987
|
-
!mergedCreated
|
|
6988
|
-
|| (parsedCreatedIsValid && !mergedCreatedIsValid)
|
|
6989
|
-
|| (
|
|
6990
|
-
parsedCreatedIsValid
|
|
6991
|
-
&& mergedCreatedIsValid
|
|
6992
|
-
&& parsedCreatedMs < mergedCreatedMs
|
|
6993
|
-
)
|
|
6994
|
-
|| (
|
|
6995
|
-
!parsedCreatedIsValid
|
|
6996
|
-
&& !mergedCreatedIsValid
|
|
6997
|
-
&& compareEntityTimestamps(parsedCreated, mergedCreated) < 0
|
|
6998
|
-
)
|
|
6999
|
-
)
|
|
7000
|
-
) {
|
|
7001
|
-
mergedEntity.created = parsedCreated;
|
|
7002
|
-
}
|
|
7003
|
-
|
|
7004
|
-
// Keep longest/best name
|
|
7005
|
-
if (parsed.name.length > mergedEntity.name.length) {
|
|
7006
|
-
mergedEntity.name = parsed.name;
|
|
7007
|
-
}
|
|
7008
|
-
|
|
7009
|
-
const parsedSynthesisUpdatedAt = parsed.synthesisUpdatedAt?.trim() || undefined;
|
|
7010
|
-
const mergedSynthesisUpdatedAt = mergedEntity.synthesisUpdatedAt?.trim() || undefined;
|
|
7011
|
-
|
|
7012
|
-
// Prefer the freshest synthesis/summary available.
|
|
7013
|
-
if (
|
|
7014
|
-
parsed.synthesis &&
|
|
7015
|
-
(!mergedEntity.synthesis
|
|
7016
|
-
|| (!mergedSynthesisUpdatedAt && Boolean(parsedSynthesisUpdatedAt))
|
|
7017
|
-
|| (Boolean(mergedSynthesisUpdatedAt)
|
|
7018
|
-
&& Boolean(parsedSynthesisUpdatedAt)
|
|
7019
|
-
&& compareEntityTimestamps(parsedSynthesisUpdatedAt, mergedSynthesisUpdatedAt) > 0))
|
|
7020
|
-
) {
|
|
7021
|
-
mergedEntity.synthesis = parsed.synthesis;
|
|
7022
|
-
mergedEntity.summary = parsed.synthesis;
|
|
7023
|
-
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
7024
|
-
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
7025
|
-
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
7026
|
-
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
7027
|
-
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
7028
|
-
} else if (!mergedEntity.summary && parsed.summary) {
|
|
7029
|
-
mergedEntity.summary = parsed.summary;
|
|
7030
|
-
mergedEntity.synthesis = parsed.summary;
|
|
7031
|
-
mergedEntity.synthesisUpdatedAt = parsedSynthesisUpdatedAt;
|
|
7032
|
-
mergedEntity.synthesisTimelineCount = parsed.synthesisTimelineCount;
|
|
7033
|
-
mergedEntity.synthesisStructuredFactCount = parsed.synthesisStructuredFactCount;
|
|
7034
|
-
mergedEntity.synthesisStructuredFactDigest = parsed.synthesisStructuredFactDigest;
|
|
7035
|
-
mergedEntity.synthesisVersion = parsed.synthesisVersion;
|
|
7036
|
-
}
|
|
7037
|
-
|
|
7038
|
-
// Collect all timeline evidence; facts are derived below.
|
|
7039
|
-
mergedEntity.timeline.push(...parsed.timeline);
|
|
7040
|
-
|
|
7041
|
-
// Collect relationships (dedup later)
|
|
7042
|
-
mergedEntity.relationships.push(...parsed.relationships);
|
|
7043
|
-
|
|
7044
|
-
// Collect activity entries
|
|
7045
|
-
mergedEntity.activity.push(...parsed.activity);
|
|
7046
|
-
|
|
7047
|
-
// Collect aliases
|
|
7048
|
-
mergedEntity.aliases.push(...parsed.aliases);
|
|
7049
|
-
|
|
7050
|
-
const mergedStructuredSectionMap = new Map(
|
|
7051
|
-
(mergedEntity.structuredSections ?? []).map((section) => [section.key, {
|
|
7052
|
-
...section,
|
|
7053
|
-
facts: [...section.facts],
|
|
7054
|
-
}]),
|
|
7055
|
-
);
|
|
7056
|
-
for (const section of parsed.structuredSections ?? []) {
|
|
7057
|
-
const existingSection = mergedStructuredSectionMap.get(section.key);
|
|
7058
|
-
if (!existingSection) {
|
|
7059
|
-
mergedStructuredSectionMap.set(section.key, {
|
|
7060
|
-
key: section.key,
|
|
7061
|
-
title: section.title,
|
|
7062
|
-
facts: [...new Set(section.facts.map((fact) => fact.trim()).filter((fact) => fact.length > 0))],
|
|
7063
|
-
});
|
|
7064
|
-
continue;
|
|
7065
|
-
}
|
|
7066
|
-
|
|
7067
|
-
const mergedFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
|
|
7068
|
-
for (const fact of section.facts) {
|
|
7069
|
-
const trimmed = fact.trim();
|
|
7070
|
-
if (!trimmed) continue;
|
|
7071
|
-
mergedFacts.add(trimmed);
|
|
7072
|
-
}
|
|
7073
|
-
existingSection.facts = Array.from(mergedFacts);
|
|
7074
|
-
if (!existingSection.title.trim() && section.title.trim()) {
|
|
7075
|
-
existingSection.title = section.title;
|
|
7076
|
-
}
|
|
7077
|
-
}
|
|
7078
|
-
mergedEntity.structuredSections = Array.from(mergedStructuredSectionMap.values());
|
|
7079
|
-
|
|
7080
|
-
// Preserve custom metadata and user-authored freeform content from fragments.
|
|
7081
|
-
mergedEntity.extraFrontmatterLines!.push(...(parsed.extraFrontmatterLines ?? []));
|
|
7082
|
-
mergedEntity.preSectionLines!.push(...(parsed.preSectionLines ?? []));
|
|
7083
|
-
mergedEntity.extraSections!.push(...(parsed.extraSections ?? []).map((section) => ({
|
|
7084
|
-
title: section.title,
|
|
7085
|
-
lines: [...section.lines],
|
|
7086
|
-
})));
|
|
7087
|
-
} catch (err) {
|
|
7088
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
7089
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
7090
|
-
// Skip unreadable
|
|
7091
|
-
}
|
|
7092
|
-
}
|
|
7093
|
-
|
|
7094
|
-
// Deduplicate timeline entries and derive facts from the timeline.
|
|
7095
|
-
const timelineKeys = new Set<string>();
|
|
7096
|
-
mergedEntity.timeline = mergedEntity.timeline.filter((entry) => {
|
|
7097
|
-
const key = JSON.stringify([
|
|
7098
|
-
entry.timestamp,
|
|
7099
|
-
entry.source ?? "",
|
|
7100
|
-
entry.sessionKey ?? "",
|
|
7101
|
-
entry.principal ?? "",
|
|
7102
|
-
entry.text,
|
|
7103
|
-
]);
|
|
7104
|
-
if (timelineKeys.has(key)) return false;
|
|
7105
|
-
timelineKeys.add(key);
|
|
7106
|
-
return true;
|
|
7107
|
-
});
|
|
7108
|
-
// Deduplicate relationships by target+label
|
|
7109
|
-
const relKeys = new Set<string>();
|
|
7110
|
-
mergedEntity.relationships = mergedEntity.relationships.filter((r) => {
|
|
7111
|
-
const key = `${r.target}::${r.label}`;
|
|
7112
|
-
if (relKeys.has(key)) return false;
|
|
7113
|
-
relKeys.add(key);
|
|
7114
|
-
return true;
|
|
7115
|
-
});
|
|
7116
|
-
|
|
7117
|
-
// Sort activity by date descending, deduplicate by date+note
|
|
7118
|
-
const actKeys = new Set<string>();
|
|
7119
|
-
mergedEntity.activity = mergedEntity.activity
|
|
7120
|
-
.filter((a) => {
|
|
7121
|
-
const key = `${a.date}::${a.note}`;
|
|
7122
|
-
if (actKeys.has(key)) return false;
|
|
7123
|
-
actKeys.add(key);
|
|
7124
|
-
return true;
|
|
7125
|
-
})
|
|
7126
|
-
.sort((a, b) => b.date.localeCompare(a.date));
|
|
7127
|
-
|
|
7128
|
-
// Deduplicate aliases
|
|
7129
|
-
mergedEntity.aliases = [...new Set(mergedEntity.aliases)];
|
|
7130
|
-
mergedEntity.structuredSections = sortStructuredSectionsBySchema(
|
|
7131
|
-
mergedEntity.type,
|
|
7132
|
-
mergedEntity.structuredSections ?? [],
|
|
7133
|
-
this.entitySchemas,
|
|
7134
|
-
);
|
|
7135
|
-
mergedEntity.facts = compileEntityFacts(mergedEntity.timeline, mergedEntity.structuredSections);
|
|
7136
|
-
|
|
7137
|
-
const extraSectionKeys = new Set<string>();
|
|
7138
|
-
mergedEntity.extraSections = (mergedEntity.extraSections ?? []).filter((section) => {
|
|
7139
|
-
const key = `${section.title}::${section.lines.join("\n")}`;
|
|
7140
|
-
if (extraSectionKeys.has(key)) return false;
|
|
7141
|
-
extraSectionKeys.add(key);
|
|
7142
|
-
return true;
|
|
7143
|
-
});
|
|
7144
|
-
|
|
7145
|
-
// Fallback name from canonical
|
|
7146
|
-
if (!mergedEntity.name) {
|
|
7147
|
-
const dashIdx = canonical.indexOf("-");
|
|
7148
|
-
mergedEntity.name = dashIdx !== -1 ? canonical.slice(dashIdx + 1) : canonical;
|
|
7149
|
-
}
|
|
7150
|
-
|
|
7151
|
-
mergedEntity.created = mergedEntity.created || mergedEntity.updated || new Date().toISOString();
|
|
7152
|
-
mergedEntity.updated = mergedEntity.updated || new Date().toISOString();
|
|
7153
|
-
|
|
7154
|
-
const canonicalPath = path.join(this.entitiesDir, `${canonical}.md`);
|
|
7155
|
-
await this.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.entitySchemas));
|
|
7156
|
-
|
|
7157
|
-
// Remove non-canonical files
|
|
7158
|
-
for (const file of files) {
|
|
7159
|
-
const filePath = path.join(this.entitiesDir, file);
|
|
7160
|
-
if (filePath !== canonicalPath) {
|
|
7161
|
-
try {
|
|
7162
|
-
await unlink(filePath);
|
|
7163
|
-
merged++;
|
|
7164
|
-
log.debug(`merged entity ${file} → ${canonical}.md`);
|
|
7165
|
-
} catch {
|
|
7166
|
-
// Ignore
|
|
7167
|
-
}
|
|
7168
|
-
}
|
|
7169
|
-
}
|
|
7170
|
-
}
|
|
7171
|
-
} catch (err) {
|
|
7172
|
-
if (err instanceof SecureStoreLockedError || !isErrnoCode(err, "ENOENT")) throw err;
|
|
7173
|
-
// Directory doesn't exist yet
|
|
7174
|
-
}
|
|
7175
|
-
|
|
7176
|
-
return merged;
|
|
6655
|
+
return this.entityStore.mergeFragmentedEntities(
|
|
6656
|
+
);
|
|
7177
6657
|
}
|
|
7178
6658
|
|
|
7179
6659
|
async cleanExpiredCommitments(decayDays: number): Promise<MemoryFile[]> {
|