@remnic/core 9.3.755 → 9.3.757
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 +1 -1
- package/dist/access-admin-ops-surface.js +45 -45
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +46 -46
- package/dist/access-cli.js +105 -104
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +55 -55
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +45 -45
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +52 -52
- package/dist/access-observe-write-surface.d.ts +225 -0
- package/dist/access-observe-write-surface.js +128 -0
- package/dist/access-operations-batch.js +49 -49
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +51 -51
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +45 -45
- package/dist/access-schema.d.ts +70 -70
- package/dist/access-schema.js +4 -4
- package/dist/{access-service-C3jkLi2U.d.ts → access-service-CJwcDoQp.d.ts} +4 -5
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +45 -45
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-G4YKBPOU.js → auto-sync-2SOUW4JW.js} +6 -6
- package/dist/briefing.js +12 -12
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-RAARDY3M.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +20 -20
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-T6WHI3JW.js → chunk-2NTWULEQ.js} +6 -6
- package/dist/chunk-3I4FUAVJ.js +20 -0
- package/dist/chunk-3I4FUAVJ.js.map +1 -0
- package/dist/{chunk-TH7WMHVK.js → chunk-3NN73CM7.js} +2 -2
- package/dist/{chunk-GCEV6SXP.js → chunk-4L2KHKLY.js} +2300 -2220
- package/dist/chunk-4L2KHKLY.js.map +1 -0
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-XBM5DG4O.js → chunk-5PHPNUZN.js} +684 -599
- package/dist/chunk-5PHPNUZN.js.map +1 -0
- package/dist/{chunk-CIDGUFAL.js → chunk-6A76LSX6.js} +2 -2
- package/dist/{chunk-UQJ6YTKR.js → chunk-6DMTYBNY.js} +7 -7
- package/dist/{chunk-S6UCPBPL.js → chunk-6FYRCQ7K.js} +2 -2
- package/dist/{chunk-7XH7VJN4.js → chunk-6T4LTI2F.js} +4 -4
- package/dist/{chunk-PJ7DIEFC.js → chunk-7HJ3XBMD.js} +5 -5
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-I3UWEZTK.js → chunk-AINK3KDE.js} +2 -2
- package/dist/{chunk-ZQNCHPTZ.js → chunk-CTGK5VK3.js} +2 -2
- package/dist/{chunk-G7LCG56O.js → chunk-CXW6Y7SI.js} +4 -4
- package/dist/{chunk-M5QKGHCR.js → chunk-CYWCKFCL.js} +5 -5
- package/dist/{chunk-T422SYM6.js → chunk-DCWIQFNA.js} +5 -5
- package/dist/{chunk-2BFLDH5F.js → chunk-EEIROWEG.js} +2 -2
- package/dist/{chunk-DS4QM6I7.js → chunk-EEKDRAWX.js} +4 -4
- package/dist/{chunk-OCFITZRV.js → chunk-FBS6V6LJ.js} +4 -4
- package/dist/{chunk-I6IAJCJS.js → chunk-FFPH47IX.js} +7 -7
- package/dist/{chunk-N4AGSRVA.js → chunk-GI7PRVMV.js} +9 -9
- package/dist/{chunk-FQLM6ZUP.js → chunk-GIJDW2YO.js} +3 -3
- package/dist/{chunk-LFKEFIJA.js → chunk-I65IQD7Y.js} +4 -4
- package/dist/{chunk-3DOD6ICJ.js → chunk-IBAU2RZN.js} +6 -6
- package/dist/{chunk-24463PIV.js → chunk-IF6BTZCB.js} +2 -2
- package/dist/{chunk-GMWKWA6N.js → chunk-IR6ATBUI.js} +5 -5
- package/dist/{chunk-TJATYB3F.js → chunk-IZP62CEF.js} +7 -7
- package/dist/{chunk-FLVL4L3L.js → chunk-KTULX4YO.js} +4 -4
- package/dist/{chunk-XCXN3D2U.js → chunk-L5DU7THE.js} +6 -6
- package/dist/{chunk-437S3G37.js → chunk-LNQUAJNC.js} +1 -1
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/{chunk-NCNOYM6F.js → chunk-LVMVYLNB.js} +2 -2
- package/dist/{chunk-XD6JVK5P.js → chunk-MQQO56LN.js} +47 -47
- package/dist/{chunk-PJQMISPD.js → chunk-NNL4LJAD.js} +6 -6
- package/dist/{chunk-GGYUR4M2.js → chunk-PHWBKQHK.js} +12 -12
- package/dist/{chunk-TQT3USCG.js → chunk-PMBWVQ7D.js} +2 -2
- package/dist/{chunk-JITAVROY.js → chunk-PXHY2E3S.js} +4 -4
- package/dist/{chunk-GPFXPC2V.js → chunk-QOGXUNWL.js} +8 -8
- package/dist/{chunk-SP42M3XP.js → chunk-QVQG7W2H.js} +7 -7
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-VL5QHTJI.js → chunk-RQXVVENY.js} +128 -128
- package/dist/{chunk-KKOCFUI5.js → chunk-SUN6XVNI.js} +10 -10
- package/dist/{chunk-FOJY75BL.js → chunk-T3UW6NRQ.js} +13 -13
- package/dist/{chunk-2MR3MFQB.js → chunk-UPSAT4PY.js} +2 -2
- package/dist/{chunk-5J4OL7TJ.js → chunk-VUA4S7P6.js} +4 -4
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-H6S3DJVE.js → chunk-XKUYMM34.js} +2 -2
- package/dist/{chunk-EHQ5HO5T.js → chunk-XROP4ZHB.js} +4 -4
- package/dist/{chunk-2OZ2JEUP.js → chunk-YTU7WKYN.js} +2 -2
- package/dist/{chunk-V5UV7BOA.js → chunk-ZE7GJ76H.js} +1 -1
- package/dist/{chunk-ER3CCWK2.js → chunk-ZG2WP425.js} +2 -2
- package/dist/{chunk-4LQUYBZM.js → chunk-ZL3Y3ZTQ.js} +7 -7
- package/dist/{cli-BJQ2ahrh.d.ts → cli-cZyANcmY.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +72 -72
- package/dist/compounding/engine.js +13 -13
- package/dist/config-record.d.ts +11 -0
- package/dist/config-record.js +8 -0
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +13 -13
- package/dist/connectors/index.js +14 -14
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/contradiction/index.js +6 -6
- package/dist/{contradiction-scan-2SODRNUF.js → contradiction-scan-3FJYWS3G.js} +3 -3
- package/dist/contradiction-scan-3FJYWS3G.js.map +1 -0
- package/dist/conversation-index/backend.js +2 -2
- package/dist/entity-retrieval.js +13 -13
- package/dist/extraction-faithfulness.js +3 -3
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.js +4 -4
- package/dist/fallback-llm.js +2 -2
- package/dist/{first-start-migration-DY6YCRC2.js → first-start-migration-RYY7PPGF.js} +4 -4
- package/dist/{graph-edge-decay-RXUCJIN4.js → graph-edge-decay-U2GKDAVM.js} +4 -4
- package/dist/graph-edge-decay-U2GKDAVM.js.map +1 -0
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +162 -158
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/maintenance/memory-governance.js +13 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.js +14 -14
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/namespaces/migrate.js +24 -24
- package/dist/namespaces/search.js +10 -10
- package/dist/namespaces/storage.js +14 -14
- package/dist/operator-toolkit.js +32 -32
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/orchestrator.js +107 -106
- package/dist/recall-planner-llm.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/schemas.d.ts +100 -100
- package/dist/search/factory.js +9 -9
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +16 -16
- package/dist/semantic-rule-promotion.js +12 -12
- package/dist/semantic-rule-verifier.js +13 -13
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/storage.d.ts +60 -78
- package/dist/storage.js +19 -11
- package/dist/summarizer.js +4 -4
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +13 -13
- package/package.json +2 -2
- package/src/access-observe-write-surface.ts +1007 -0
- package/src/access-service.ts +48 -794
- package/src/config-record.ts +25 -0
- package/src/index.ts +1 -0
- package/src/orchestration/self-deps.ts +9 -1
- package/src/storage/entity-store.ts +4 -2
- package/src/storage/memory-read-store.ts +756 -0
- package/src/storage.ts +67 -627
- package/dist/chunk-GCEV6SXP.js.map +0 -1
- package/dist/chunk-XBM5DG4O.js.map +0 -1
- /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-observe-write-surface.js.map} +0 -0
- /package/dist/{auto-sync-G4YKBPOU.js.map → auto-sync-2SOUW4JW.js.map} +0 -0
- /package/dist/{contradiction-scan-2SODRNUF.js.map → capsule-crypto-RAARDY3M.js.map} +0 -0
- /package/dist/{chunk-T6WHI3JW.js.map → chunk-2NTWULEQ.js.map} +0 -0
- /package/dist/{chunk-TH7WMHVK.js.map → chunk-3NN73CM7.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-CIDGUFAL.js.map → chunk-6A76LSX6.js.map} +0 -0
- /package/dist/{chunk-UQJ6YTKR.js.map → chunk-6DMTYBNY.js.map} +0 -0
- /package/dist/{chunk-S6UCPBPL.js.map → chunk-6FYRCQ7K.js.map} +0 -0
- /package/dist/{chunk-7XH7VJN4.js.map → chunk-6T4LTI2F.js.map} +0 -0
- /package/dist/{chunk-PJ7DIEFC.js.map → chunk-7HJ3XBMD.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-I3UWEZTK.js.map → chunk-AINK3KDE.js.map} +0 -0
- /package/dist/{chunk-ZQNCHPTZ.js.map → chunk-CTGK5VK3.js.map} +0 -0
- /package/dist/{chunk-G7LCG56O.js.map → chunk-CXW6Y7SI.js.map} +0 -0
- /package/dist/{chunk-M5QKGHCR.js.map → chunk-CYWCKFCL.js.map} +0 -0
- /package/dist/{chunk-T422SYM6.js.map → chunk-DCWIQFNA.js.map} +0 -0
- /package/dist/{chunk-2BFLDH5F.js.map → chunk-EEIROWEG.js.map} +0 -0
- /package/dist/{chunk-DS4QM6I7.js.map → chunk-EEKDRAWX.js.map} +0 -0
- /package/dist/{chunk-OCFITZRV.js.map → chunk-FBS6V6LJ.js.map} +0 -0
- /package/dist/{chunk-I6IAJCJS.js.map → chunk-FFPH47IX.js.map} +0 -0
- /package/dist/{chunk-N4AGSRVA.js.map → chunk-GI7PRVMV.js.map} +0 -0
- /package/dist/{chunk-FQLM6ZUP.js.map → chunk-GIJDW2YO.js.map} +0 -0
- /package/dist/{chunk-LFKEFIJA.js.map → chunk-I65IQD7Y.js.map} +0 -0
- /package/dist/{chunk-3DOD6ICJ.js.map → chunk-IBAU2RZN.js.map} +0 -0
- /package/dist/{chunk-24463PIV.js.map → chunk-IF6BTZCB.js.map} +0 -0
- /package/dist/{chunk-GMWKWA6N.js.map → chunk-IR6ATBUI.js.map} +0 -0
- /package/dist/{chunk-TJATYB3F.js.map → chunk-IZP62CEF.js.map} +0 -0
- /package/dist/{chunk-FLVL4L3L.js.map → chunk-KTULX4YO.js.map} +0 -0
- /package/dist/{chunk-XCXN3D2U.js.map → chunk-L5DU7THE.js.map} +0 -0
- /package/dist/{chunk-437S3G37.js.map → chunk-LNQUAJNC.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-NCNOYM6F.js.map → chunk-LVMVYLNB.js.map} +0 -0
- /package/dist/{chunk-XD6JVK5P.js.map → chunk-MQQO56LN.js.map} +0 -0
- /package/dist/{chunk-PJQMISPD.js.map → chunk-NNL4LJAD.js.map} +0 -0
- /package/dist/{chunk-GGYUR4M2.js.map → chunk-PHWBKQHK.js.map} +0 -0
- /package/dist/{chunk-TQT3USCG.js.map → chunk-PMBWVQ7D.js.map} +0 -0
- /package/dist/{chunk-JITAVROY.js.map → chunk-PXHY2E3S.js.map} +0 -0
- /package/dist/{chunk-GPFXPC2V.js.map → chunk-QOGXUNWL.js.map} +0 -0
- /package/dist/{chunk-SP42M3XP.js.map → chunk-QVQG7W2H.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-VL5QHTJI.js.map → chunk-RQXVVENY.js.map} +0 -0
- /package/dist/{chunk-KKOCFUI5.js.map → chunk-SUN6XVNI.js.map} +0 -0
- /package/dist/{chunk-FOJY75BL.js.map → chunk-T3UW6NRQ.js.map} +0 -0
- /package/dist/{chunk-2MR3MFQB.js.map → chunk-UPSAT4PY.js.map} +0 -0
- /package/dist/{chunk-5J4OL7TJ.js.map → chunk-VUA4S7P6.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-H6S3DJVE.js.map → chunk-XKUYMM34.js.map} +0 -0
- /package/dist/{chunk-EHQ5HO5T.js.map → chunk-XROP4ZHB.js.map} +0 -0
- /package/dist/{chunk-2OZ2JEUP.js.map → chunk-YTU7WKYN.js.map} +0 -0
- /package/dist/{chunk-V5UV7BOA.js.map → chunk-ZE7GJ76H.js.map} +0 -0
- /package/dist/{chunk-ER3CCWK2.js.map → chunk-ZG2WP425.js.map} +0 -0
- /package/dist/{chunk-4LQUYBZM.js.map → chunk-ZL3Y3ZTQ.js.map} +0 -0
- /package/dist/{graph-edge-decay-RXUCJIN4.js.map → config-record.js.map} +0 -0
- /package/dist/{first-start-migration-DY6YCRC2.js.map → first-start-migration-RYY7PPGF.js.map} +0 -0
|
@@ -16,28 +16,18 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
supersessionKeysForFact
|
|
18
18
|
} from "./chunk-TKZ3AL5V.js";
|
|
19
|
-
import {
|
|
20
|
-
stripAttributesSuffix
|
|
21
|
-
} from "./chunk-SVOZFLIQ.js";
|
|
22
19
|
import {
|
|
23
20
|
DEFAULT_CITATION_FORMAT,
|
|
24
21
|
hasCitation,
|
|
25
22
|
stripCitationForTemplate
|
|
26
23
|
} from "./chunk-J6A3CX5N.js";
|
|
24
|
+
import {
|
|
25
|
+
stripAttributesSuffix
|
|
26
|
+
} from "./chunk-SVOZFLIQ.js";
|
|
27
27
|
import {
|
|
28
28
|
SPECULATIVE_TTL_DAYS,
|
|
29
29
|
confidenceTier
|
|
30
30
|
} from "./chunk-JS2HKKOK.js";
|
|
31
|
-
import {
|
|
32
|
-
normalizeProjectionPreview,
|
|
33
|
-
normalizeProjectionTags
|
|
34
|
-
} from "./chunk-SCU65EZI.js";
|
|
35
|
-
import {
|
|
36
|
-
readProjectedGovernanceRecord,
|
|
37
|
-
readProjectedMemoryBrowse,
|
|
38
|
-
readProjectedMemoryState,
|
|
39
|
-
readProjectedMemoryTimeline
|
|
40
|
-
} from "./chunk-MB5RSUW6.js";
|
|
41
31
|
import {
|
|
42
32
|
getCachedEntities,
|
|
43
33
|
invalidateAllForDir,
|
|
@@ -49,6 +39,16 @@ import {
|
|
|
49
39
|
sortMemoryLifecycleEvents,
|
|
50
40
|
toMemoryPathRel
|
|
51
41
|
} from "./chunk-RULE4VG5.js";
|
|
42
|
+
import {
|
|
43
|
+
normalizeProjectionPreview,
|
|
44
|
+
normalizeProjectionTags
|
|
45
|
+
} from "./chunk-SCU65EZI.js";
|
|
46
|
+
import {
|
|
47
|
+
readProjectedGovernanceRecord,
|
|
48
|
+
readProjectedMemoryBrowse,
|
|
49
|
+
readProjectedMemoryState,
|
|
50
|
+
readProjectedMemoryTimeline
|
|
51
|
+
} from "./chunk-MB5RSUW6.js";
|
|
52
52
|
import {
|
|
53
53
|
rotateMarkdownFileToArchive
|
|
54
54
|
} from "./chunk-DM2T26WE.js";
|
|
@@ -64,32 +64,32 @@ import {
|
|
|
64
64
|
import {
|
|
65
65
|
parseFaithfulnessField,
|
|
66
66
|
serializeFaithfulnessFields
|
|
67
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-YTU7WKYN.js";
|
|
68
68
|
import {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} from "./chunk-
|
|
69
|
+
computeContentHash,
|
|
70
|
+
normalizeContent
|
|
71
|
+
} from "./chunk-W5H6ZRPT.js";
|
|
72
72
|
import {
|
|
73
73
|
createVersion
|
|
74
74
|
} from "./chunk-VF4XKTX3.js";
|
|
75
75
|
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} from "./chunk-
|
|
76
|
+
isConsolidationOperator,
|
|
77
|
+
isValidDerivedFromEntry
|
|
78
|
+
} from "./chunk-G7D6GZ5J.js";
|
|
79
79
|
import {
|
|
80
80
|
parseProvenanceSources,
|
|
81
81
|
parseProvenanceTag,
|
|
82
82
|
reconcileProvenanceRead,
|
|
83
83
|
serializeProvenanceFields
|
|
84
84
|
} from "./chunk-OEHCMALU.js";
|
|
85
|
+
import {
|
|
86
|
+
sanitizeMemoryContent
|
|
87
|
+
} from "./chunk-5GKPFL3S.js";
|
|
85
88
|
import {
|
|
86
89
|
matchEntitySchemaSection,
|
|
87
90
|
normalizeEntityStructuredSection,
|
|
88
91
|
sortStructuredSectionsBySchema
|
|
89
92
|
} from "./chunk-4DJQYKMN.js";
|
|
90
|
-
import {
|
|
91
|
-
sanitizeMemoryContent
|
|
92
|
-
} from "./chunk-5GKPFL3S.js";
|
|
93
93
|
import {
|
|
94
94
|
log
|
|
95
95
|
} from "./chunk-NU3CSQ4H.js";
|
|
@@ -111,15 +111,480 @@ import {
|
|
|
111
111
|
getCategoryDir
|
|
112
112
|
} from "./chunk-VS2IYZRU.js";
|
|
113
113
|
|
|
114
|
-
// src/storage.ts
|
|
115
|
-
import { access,
|
|
116
|
-
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
|
|
117
|
-
import { createHash } from "crypto";
|
|
118
|
-
import
|
|
114
|
+
// src/storage.ts
|
|
115
|
+
import { access, readdir as readdir3, readFile, stat as stat2, writeFile, mkdir, unlink as unlink2, appendFile, open } from "fs/promises";
|
|
116
|
+
import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "fs";
|
|
117
|
+
import { createHash } from "crypto";
|
|
118
|
+
import path3 from "path";
|
|
119
|
+
|
|
120
|
+
// src/storage/memory-read-store.ts
|
|
121
|
+
import { lstat, readdir, realpath, stat } from "fs/promises";
|
|
122
|
+
import path from "path";
|
|
123
|
+
var MemoryReadStore = class {
|
|
124
|
+
constructor(deps) {
|
|
125
|
+
this.deps = deps;
|
|
126
|
+
}
|
|
127
|
+
deps;
|
|
128
|
+
/**
|
|
129
|
+
* List stored transcript days, newest first, optionally scoped to one
|
|
130
|
+
* source. Non-transcript files in the tree are ignored.
|
|
131
|
+
*/
|
|
132
|
+
async listWearableTranscriptDays(sourceId) {
|
|
133
|
+
const days = [];
|
|
134
|
+
let sources;
|
|
135
|
+
if (sourceId !== void 0) {
|
|
136
|
+
sources = [sourceId];
|
|
137
|
+
} else {
|
|
138
|
+
try {
|
|
139
|
+
const entries = await readdir(this.deps.wearablesDir, { withFileTypes: true });
|
|
140
|
+
sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err.code === "ENOENT") return [];
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const source of sources) {
|
|
147
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = await readdir(path.join(this.deps.wearablesDir, source));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (err.code === "ENOENT") continue;
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.endsWith(".md")) continue;
|
|
157
|
+
const date = entry.slice(0, -3);
|
|
158
|
+
if (!isValidTranscriptDate(date)) continue;
|
|
159
|
+
days.push({ source, date });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
days.sort((a, b) => {
|
|
163
|
+
if (a.date > b.date) return -1;
|
|
164
|
+
if (a.date < b.date) return 1;
|
|
165
|
+
if (a.source < b.source) return -1;
|
|
166
|
+
if (a.source > b.source) return 1;
|
|
167
|
+
return 0;
|
|
168
|
+
});
|
|
169
|
+
return days;
|
|
170
|
+
}
|
|
171
|
+
async readAllArtifactsCached() {
|
|
172
|
+
if (this.deps.artifactIndexCache && Date.now() - this.deps.artifactIndexCache.loadedAtMs <= this.deps.storageManagerClass.ARTIFACT_INDEX_CACHE_TTL_MS && this.deps.artifactIndexCache.writeVersion === this.deps.getArtifactWriteVersion()) {
|
|
173
|
+
return this.deps.artifactIndexCache.memories;
|
|
174
|
+
}
|
|
175
|
+
const scanArtifacts = async () => {
|
|
176
|
+
const artifacts = [];
|
|
177
|
+
const readDir = async (dir) => {
|
|
178
|
+
try {
|
|
179
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const fullPath = path.join(dir, entry.name);
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
await readDir(fullPath);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
187
|
+
const memory = await this.deps.readMemoryByPath(fullPath);
|
|
188
|
+
if (!memory) continue;
|
|
189
|
+
artifacts.push(memory);
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
await readDir(this.deps.artifactsDir);
|
|
195
|
+
return artifacts;
|
|
196
|
+
};
|
|
197
|
+
const MAX_REBUILD_RETRIES = 2;
|
|
198
|
+
let latestArtifacts = [];
|
|
199
|
+
for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
|
|
200
|
+
const versionBefore = this.deps.getArtifactWriteVersion();
|
|
201
|
+
const artifacts = await scanArtifacts();
|
|
202
|
+
const versionAfter = this.deps.getArtifactWriteVersion();
|
|
203
|
+
latestArtifacts = artifacts;
|
|
204
|
+
if (versionAfter === versionBefore) {
|
|
205
|
+
this.deps.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
|
|
206
|
+
return artifacts;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.deps.artifactIndexCache = null;
|
|
210
|
+
return latestArtifacts;
|
|
211
|
+
}
|
|
212
|
+
async collectActiveMemoryPaths() {
|
|
213
|
+
const filePaths = [];
|
|
214
|
+
let memoryRootReal;
|
|
215
|
+
try {
|
|
216
|
+
memoryRootReal = await realpath(this.deps.baseDir);
|
|
217
|
+
} catch {
|
|
218
|
+
return filePaths;
|
|
219
|
+
}
|
|
220
|
+
const collectPaths = async (dir) => {
|
|
221
|
+
let entries;
|
|
222
|
+
try {
|
|
223
|
+
const dirStat = await lstat(dir);
|
|
224
|
+
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
|
|
225
|
+
assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
|
|
226
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
227
|
+
} catch {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const subdirs = [];
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
if (entry.isSymbolicLink()) continue;
|
|
233
|
+
const fullPath = path.join(dir, entry.name);
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
subdirs.push(fullPath);
|
|
236
|
+
} else if (entry.name.endsWith(".md")) {
|
|
237
|
+
try {
|
|
238
|
+
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
239
|
+
filePaths.push(fullPath);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const subdir of subdirs) {
|
|
245
|
+
await collectPaths(subdir);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
for (const dir of RECALL_FALLBACK_DIRS) {
|
|
249
|
+
await collectPaths(path.join(this.deps.baseDir, dir));
|
|
250
|
+
}
|
|
251
|
+
return filePaths;
|
|
252
|
+
}
|
|
253
|
+
async readMemoriesWindow(options = {}) {
|
|
254
|
+
const allPaths = await this.deps.collectActiveMemoryPaths();
|
|
255
|
+
const sortedPaths = this.deps.orderWindowPaths(allPaths);
|
|
256
|
+
const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
|
|
257
|
+
const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
|
|
258
|
+
const updatedAfterMs = options.updatedAfter?.getTime();
|
|
259
|
+
const normalizedBatchSize = this.deps.normalizeMemoryReadBatchSize(options.batchSize);
|
|
260
|
+
const memories = [];
|
|
261
|
+
const selectedPaths = [];
|
|
262
|
+
for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
|
|
263
|
+
if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
|
|
264
|
+
return { memories, filePaths: selectedPaths };
|
|
265
|
+
}
|
|
266
|
+
const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
|
|
267
|
+
const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.deps.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
|
|
268
|
+
const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
|
|
269
|
+
const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
|
|
270
|
+
const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
|
|
271
|
+
memories: await this.deps.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
|
|
272
|
+
filePaths: candidateBatchPaths
|
|
273
|
+
} : await this.deps.readWindowBoundedBatch(
|
|
274
|
+
candidateBatchPaths,
|
|
275
|
+
remainingSlots,
|
|
276
|
+
remainingInspectionBudget ?? remainingSlots,
|
|
277
|
+
normalizedBatchSize
|
|
278
|
+
);
|
|
279
|
+
selectedPaths.push(...parsedCandidatePaths);
|
|
280
|
+
for (const memory of batchMemories) {
|
|
281
|
+
memories.push(memory);
|
|
282
|
+
if (maxMemories !== void 0 && memories.length >= maxMemories) {
|
|
283
|
+
return { memories, filePaths: selectedPaths };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { memories, filePaths: selectedPaths };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Read all memories from the cold tier by scanning the entire cold/ root
|
|
291
|
+
* tree. Previously this only scanned cold/facts/ and cold/corrections/, but
|
|
292
|
+
* structuredAttributes can appear on any MemoryCategory (preference, decision,
|
|
293
|
+
* entity, etc.). buildTierMemoryPath now routes each category to its own
|
|
294
|
+
* cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
|
|
295
|
+
* #1546), so cold decisions/preferences/... live outside cold/facts/.
|
|
296
|
+
* Scanning the full coldRoot covers every category dir and guards against
|
|
297
|
+
* files placed in unexpected subdirectories during manual operations or future
|
|
298
|
+
* refactors.
|
|
299
|
+
*
|
|
300
|
+
* Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
|
|
301
|
+
* corrections/ was a narrower-than-necessary subset of the cold directory
|
|
302
|
+
* tree. Correctness trumps the minor performance difference — cold scans
|
|
303
|
+
* already happen at most once per supersession write.
|
|
304
|
+
*
|
|
305
|
+
* Used by applyTemporalSupersession so that memories already demoted to
|
|
306
|
+
* cold/ can still be marked superseded when a newer hot fact arrives.
|
|
307
|
+
*
|
|
308
|
+
* Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
|
|
309
|
+
* structured-attribute writes in the same burst reuse the cached result
|
|
310
|
+
* instead of re-scanning the cold tree on every call. The cache is
|
|
311
|
+
* invalidated whenever a write calls invalidateAllMemoriesCache() (which
|
|
312
|
+
* covers any hot→cold demotion that changes cold-tier contents) and
|
|
313
|
+
* expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
|
|
314
|
+
*/
|
|
315
|
+
async readAllColdMemories() {
|
|
316
|
+
const coldRoot = this.deps.resolveTierRootDir("cold");
|
|
317
|
+
const currentColdVersion = this.deps.readColdWriteVersion();
|
|
318
|
+
const cached = this.deps.storageManagerClass.coldMemoriesCache.get(coldRoot);
|
|
319
|
+
if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
|
|
320
|
+
return cached.memories;
|
|
321
|
+
}
|
|
322
|
+
const filePaths = [];
|
|
323
|
+
const collectPaths = async (dir) => {
|
|
324
|
+
try {
|
|
325
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
326
|
+
const subdirs = [];
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
const fullPath = path.join(dir, entry.name);
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
subdirs.push(fullPath);
|
|
331
|
+
} else if (entry.name.endsWith(".md")) {
|
|
332
|
+
filePaths.push(fullPath);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const subdir of subdirs) {
|
|
336
|
+
await collectPaths(subdir);
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
await collectPaths(coldRoot);
|
|
342
|
+
const memories = await this.deps.readParsedMemoriesFromPaths(filePaths, 50);
|
|
343
|
+
this.deps.storageManagerClass.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
|
|
344
|
+
return memories;
|
|
345
|
+
}
|
|
346
|
+
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
347
|
+
async readMemoryByPath(filePath) {
|
|
348
|
+
try {
|
|
349
|
+
const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
|
|
350
|
+
const parsed = parseFrontmatter(raw);
|
|
351
|
+
if (parsed) {
|
|
352
|
+
return {
|
|
353
|
+
path: filePath,
|
|
354
|
+
frontmatter: normalizeFrontmatterForPath(
|
|
355
|
+
parsed.frontmatter,
|
|
356
|
+
toMemoryPathRel(this.deps.baseDir, filePath),
|
|
357
|
+
parsed.content
|
|
358
|
+
),
|
|
359
|
+
content: parsed.content
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const normalizedPath = filePath.split(path.sep).join("/");
|
|
363
|
+
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
364
|
+
const entity = parseEntityFile(raw, this.deps.entitySchemas);
|
|
365
|
+
if (!entity.name) return null;
|
|
366
|
+
const nameWithoutExt = path.basename(filePath, ".md");
|
|
367
|
+
const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
368
|
+
return {
|
|
369
|
+
path: filePath,
|
|
370
|
+
frontmatter: {
|
|
371
|
+
id: nameWithoutExt,
|
|
372
|
+
category: "entity",
|
|
373
|
+
created: fileMtime,
|
|
374
|
+
updated: fileMtime,
|
|
375
|
+
source: "entity_extraction",
|
|
376
|
+
confidence: 0.9,
|
|
377
|
+
confidenceTier: confidenceTier(0.9),
|
|
378
|
+
tags: entity.type ? [entity.type] : []
|
|
379
|
+
},
|
|
380
|
+
content: raw
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
} catch (err) {
|
|
385
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
|
|
391
|
+
*
|
|
392
|
+
* Each event records a single buffer flush decision driven by the
|
|
393
|
+
* surprise gate. The ledger is consumed by
|
|
394
|
+
* `reportBufferSurpriseDistribution` (Doctor report) and by downstream
|
|
395
|
+
* benchmark analysis. This method is fire-and-forget by contract:
|
|
396
|
+
* callers log but do not fail the hot path if the append throws.
|
|
397
|
+
*/
|
|
398
|
+
async appendBufferSurpriseEvents(events) {
|
|
399
|
+
if (events.length === 0) return 0;
|
|
400
|
+
await this.deps.ensureDirectories();
|
|
401
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
402
|
+
const payload = events.map((event) => {
|
|
403
|
+
const normalized = {
|
|
404
|
+
...event,
|
|
405
|
+
event: "BUFFER_SURPRISE",
|
|
406
|
+
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
407
|
+
};
|
|
408
|
+
return `${JSON.stringify(normalized)}
|
|
409
|
+
`;
|
|
410
|
+
}).join("");
|
|
411
|
+
await this.deps.appendStorageSecureFile(this.deps.bufferSurpriseLedgerPath, payload);
|
|
412
|
+
return events.length;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Read the buffer-surprise ledger, most recent rows last.
|
|
416
|
+
*
|
|
417
|
+
* `limit` bounds the number of **valid rows** returned (not the
|
|
418
|
+
* number of raw lines parsed). We parse every row, discard malformed
|
|
419
|
+
* ones, then take the tail — so a partial/truncated trailing line
|
|
420
|
+
* (the common failure mode after an interrupted append) cannot hide
|
|
421
|
+
* otherwise-valid recent data above it.
|
|
422
|
+
*
|
|
423
|
+
* Non-positive / non-integer / non-finite limits return `[]` rather
|
|
424
|
+
* than the entire file, matching the other ledger readers in this
|
|
425
|
+
* class and protecting against `slice(-0.5)` → `slice(-0)` silently
|
|
426
|
+
* devolving into an unbounded parse.
|
|
427
|
+
*
|
|
428
|
+
* # Performance note
|
|
429
|
+
*
|
|
430
|
+
* For very large ledgers (issue #563 follow-up), a tail-first reader
|
|
431
|
+
* would avoid parsing the full file when only a recent window is
|
|
432
|
+
* needed. We keep the full-scan implementation here because:
|
|
433
|
+
*
|
|
434
|
+
* - the ledger is opt-in (flag off by default), so early deployments
|
|
435
|
+
* accumulate rows slowly;
|
|
436
|
+
* - telemetry rows are small (~200 bytes), so even 100k rows parse
|
|
437
|
+
* in well under a second;
|
|
438
|
+
* - the governance archive/cleanup flow can trim the ledger when
|
|
439
|
+
* size becomes a concern, reusing the existing maintenance hooks.
|
|
440
|
+
*
|
|
441
|
+
* Swap to a chunked tail-reader if production logs show this is a
|
|
442
|
+
* hot path — leaving that work for a follow-up keeps this PR scoped
|
|
443
|
+
* to correctness, not optimization.
|
|
444
|
+
*/
|
|
445
|
+
async readBufferSurpriseEvents(options = {}) {
|
|
446
|
+
let raw;
|
|
447
|
+
try {
|
|
448
|
+
raw = await this.deps.readStorageSecureFile(this.deps.bufferSurpriseLedgerPath);
|
|
449
|
+
} catch (err) {
|
|
450
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
451
|
+
const code = err.code;
|
|
452
|
+
if (code === "ENOENT") return [];
|
|
453
|
+
throw err;
|
|
454
|
+
}
|
|
455
|
+
let effectiveLimit = null;
|
|
456
|
+
if (options.limit !== void 0) {
|
|
457
|
+
if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
const floored = Math.floor(options.limit);
|
|
461
|
+
if (floored <= 0) return [];
|
|
462
|
+
effectiveLimit = floored;
|
|
463
|
+
}
|
|
464
|
+
const lines = raw.split("\n");
|
|
465
|
+
const events = [];
|
|
466
|
+
for (const line of lines) {
|
|
467
|
+
const trimmed = line.trim();
|
|
468
|
+
if (trimmed.length === 0) continue;
|
|
469
|
+
try {
|
|
470
|
+
const parsed = JSON.parse(trimmed);
|
|
471
|
+
if (isValidBufferSurpriseEvent(parsed)) {
|
|
472
|
+
events.push(parsed);
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
events.sort(
|
|
478
|
+
(left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
|
|
479
|
+
);
|
|
480
|
+
if (effectiveLimit === null) return events;
|
|
481
|
+
return events.slice(-effectiveLimit);
|
|
482
|
+
}
|
|
483
|
+
async readCompressionGuidelineStateFile(filePath) {
|
|
484
|
+
const isFiniteNonNegativeInteger = (value) => typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
485
|
+
const isValidActionSummary = (value) => {
|
|
486
|
+
if (!value || typeof value !== "object") return false;
|
|
487
|
+
const summary = value;
|
|
488
|
+
return typeof summary.action === "string" && isFiniteNonNegativeInteger(summary.total) && summary.outcomes !== null && typeof summary.outcomes === "object" && isFiniteNonNegativeInteger(summary.outcomes.applied) && isFiniteNonNegativeInteger(summary.outcomes.skipped) && isFiniteNonNegativeInteger(summary.outcomes.failed) && summary.quality !== null && typeof summary.quality === "object" && isFiniteNonNegativeInteger(summary.quality.good) && isFiniteNonNegativeInteger(summary.quality.poor) && isFiniteNonNegativeInteger(summary.quality.unknown);
|
|
489
|
+
};
|
|
490
|
+
const isValidRuleUpdate = (value) => {
|
|
491
|
+
if (!value || typeof value !== "object") return false;
|
|
492
|
+
const rule = value;
|
|
493
|
+
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
494
|
+
};
|
|
495
|
+
try {
|
|
496
|
+
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
497
|
+
const parsed = JSON.parse(raw);
|
|
498
|
+
const sourceWindow = parsed?.sourceWindow;
|
|
499
|
+
const eventCounts = parsed?.eventCounts;
|
|
500
|
+
const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
|
|
501
|
+
const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
|
|
502
|
+
const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
|
|
503
|
+
const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
|
|
504
|
+
if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
version: parsed.version,
|
|
509
|
+
updatedAt: parsed.updatedAt,
|
|
510
|
+
sourceWindow: {
|
|
511
|
+
from: sourceWindow.from,
|
|
512
|
+
to: sourceWindow.to
|
|
513
|
+
},
|
|
514
|
+
eventCounts: {
|
|
515
|
+
total: eventCounts.total,
|
|
516
|
+
applied: eventCounts.applied,
|
|
517
|
+
skipped: eventCounts.skipped,
|
|
518
|
+
failed: eventCounts.failed
|
|
519
|
+
},
|
|
520
|
+
guidelineVersion: parsed.guidelineVersion,
|
|
521
|
+
...contentHash ? { contentHash } : {},
|
|
522
|
+
...activationState ? { activationState } : {},
|
|
523
|
+
...actionSummaries ? { actionSummaries } : {},
|
|
524
|
+
...ruleUpdates ? { ruleUpdates } : {}
|
|
525
|
+
};
|
|
526
|
+
} catch (err) {
|
|
527
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
528
|
+
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async readQuestions(opts) {
|
|
533
|
+
const cacheKey = this.deps.questionsDir;
|
|
534
|
+
const cached = this.deps.storageManagerClass.questionsCache.get(cacheKey);
|
|
535
|
+
if (cached && Date.now() - cached.loadedAt < this.deps.storageManagerClass.QUESTIONS_CACHE_TTL_MS) {
|
|
536
|
+
try {
|
|
537
|
+
const dirStat = await stat(this.deps.questionsDir);
|
|
538
|
+
if (dirStat.mtimeMs <= cached.loadedAt) {
|
|
539
|
+
const all = cached.questions;
|
|
540
|
+
return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
const files = await readdir(this.deps.questionsDir);
|
|
547
|
+
const questions = [];
|
|
548
|
+
for (const file of files) {
|
|
549
|
+
if (!file.endsWith(".md")) continue;
|
|
550
|
+
const filePath = path.join(this.deps.questionsDir, file);
|
|
551
|
+
const raw = await readMaybeEncryptedFile(filePath, this.deps._secureStoreKey, this.deps.baseDir);
|
|
552
|
+
const parsed = this.deps.parseQuestionFile(raw, filePath);
|
|
553
|
+
if (parsed) {
|
|
554
|
+
questions.push(parsed);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const sorted = questions.sort((a, b) => b.priority - a.priority);
|
|
558
|
+
this.deps.storageManagerClass.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
|
|
559
|
+
return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
|
|
560
|
+
} catch {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// src/orchestration/self-deps.ts
|
|
567
|
+
function selfDeps(self) {
|
|
568
|
+
const source = self;
|
|
569
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
570
|
+
get(_target, prop) {
|
|
571
|
+
const value = source[prop];
|
|
572
|
+
return typeof value === "function" && !Object.prototype.hasOwnProperty.call(value, "prototype") ? value.bind(self) : value;
|
|
573
|
+
},
|
|
574
|
+
set(_target, prop, value) {
|
|
575
|
+
if (!(prop in source)) return false;
|
|
576
|
+
source[prop] = value;
|
|
577
|
+
return true;
|
|
578
|
+
},
|
|
579
|
+
has(_target, prop) {
|
|
580
|
+
return prop in source;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
119
584
|
|
|
120
585
|
// src/storage/entity-store.ts
|
|
121
|
-
import { readdir, unlink } from "fs/promises";
|
|
122
|
-
import
|
|
586
|
+
import { readdir as readdir2, unlink } from "fs/promises";
|
|
587
|
+
import path2 from "path";
|
|
123
588
|
var EntityStore = class {
|
|
124
589
|
constructor(deps) {
|
|
125
590
|
this.deps = deps;
|
|
@@ -147,7 +612,7 @@ var EntityStore = class {
|
|
|
147
612
|
log.debug(`fuzzy match: "${normalized}" \u2192 existing "${match}"`);
|
|
148
613
|
normalized = match;
|
|
149
614
|
}
|
|
150
|
-
const filePath =
|
|
615
|
+
const filePath = path2.join(this.deps.entitiesDir, `${normalized}.md`);
|
|
151
616
|
let entity = {
|
|
152
617
|
name,
|
|
153
618
|
type,
|
|
@@ -248,7 +713,7 @@ var EntityStore = class {
|
|
|
248
713
|
/** Return sorted list of entity filenames (without .md extension) */
|
|
249
714
|
async listEntityNames() {
|
|
250
715
|
try {
|
|
251
|
-
const entries = await
|
|
716
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
252
717
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", "")).sort();
|
|
253
718
|
} catch {
|
|
254
719
|
return [];
|
|
@@ -258,7 +723,7 @@ var EntityStore = class {
|
|
|
258
723
|
* Set or rewrite the synthesis layer of an entity file.
|
|
259
724
|
*/
|
|
260
725
|
async updateEntitySynthesis(name, synthesis, options = {}) {
|
|
261
|
-
const filePath =
|
|
726
|
+
const filePath = path2.join(this.deps.entitiesDir, `${name}.md`);
|
|
262
727
|
let entity;
|
|
263
728
|
try {
|
|
264
729
|
const content = await this.deps.readStorageSecureFile(filePath);
|
|
@@ -300,7 +765,7 @@ var EntityStore = class {
|
|
|
300
765
|
const cached = getCachedEntities(this.deps.baseDir, currentVersion, cacheKey);
|
|
301
766
|
if (cached) return cached;
|
|
302
767
|
try {
|
|
303
|
-
const entries = await
|
|
768
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
304
769
|
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
305
770
|
if (mdFiles.length === 0) return [];
|
|
306
771
|
const BATCH_SIZE = 100;
|
|
@@ -310,7 +775,7 @@ var EntityStore = class {
|
|
|
310
775
|
const results = await Promise.all(
|
|
311
776
|
batch.map(async (entry) => {
|
|
312
777
|
try {
|
|
313
|
-
return await this.deps.readStorageSecureFile(
|
|
778
|
+
return await this.deps.readStorageSecureFile(path2.join(this.deps.entitiesDir, entry));
|
|
314
779
|
} catch (err) {
|
|
315
780
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
316
781
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -335,7 +800,7 @@ var EntityStore = class {
|
|
|
335
800
|
*/
|
|
336
801
|
async buildKnowledgeIndex(config, overrides) {
|
|
337
802
|
const useDefaultLimits = overrides?.maxEntities === void 0 && overrides?.maxChars === void 0;
|
|
338
|
-
if (useDefaultLimits && this.deps.knowledgeIndexCache && Date.now() - this.deps.knowledgeIndexCache.builtAt <
|
|
803
|
+
if (useDefaultLimits && this.deps.knowledgeIndexCache && Date.now() - this.deps.knowledgeIndexCache.builtAt < this.deps.storageManagerClass.KNOWLEDGE_INDEX_CACHE_TTL_MS) {
|
|
339
804
|
return { result: this.deps.knowledgeIndexCache.result, cached: true };
|
|
340
805
|
}
|
|
341
806
|
const entities = await this.deps.readAllEntityFiles();
|
|
@@ -347,7 +812,7 @@ var EntityStore = class {
|
|
|
347
812
|
const scored = entities.map((e) => ({
|
|
348
813
|
name: e.name,
|
|
349
814
|
type: e.type,
|
|
350
|
-
score:
|
|
815
|
+
score: this.deps.storageManagerClass.scoreEntity(e, now),
|
|
351
816
|
factCount: e.facts.length,
|
|
352
817
|
summary: e.synthesis ?? e.summary,
|
|
353
818
|
topRelationships: e.relationships.slice(0, 3).map((r) => r.target)
|
|
@@ -385,7 +850,7 @@ ${rows.join("\n")}
|
|
|
385
850
|
async mergeFragmentedEntities() {
|
|
386
851
|
let merged = 0;
|
|
387
852
|
try {
|
|
388
|
-
const entries = await
|
|
853
|
+
const entries = await readdir2(this.deps.entitiesDir);
|
|
389
854
|
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
390
855
|
const groups = /* @__PURE__ */ new Map();
|
|
391
856
|
for (const file of mdFiles) {
|
|
@@ -423,7 +888,7 @@ ${rows.join("\n")}
|
|
|
423
888
|
extraSections: []
|
|
424
889
|
};
|
|
425
890
|
for (const file of files) {
|
|
426
|
-
const filePath =
|
|
891
|
+
const filePath = path2.join(this.deps.entitiesDir, file);
|
|
427
892
|
try {
|
|
428
893
|
const content = await this.deps.readStorageSecureFile(filePath);
|
|
429
894
|
const parsed = parseEntityFile(content, this.deps.entitySchemas);
|
|
@@ -554,10 +1019,10 @@ ${rows.join("\n")}
|
|
|
554
1019
|
}
|
|
555
1020
|
mergedEntity.created = mergedEntity.created || mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
556
1021
|
mergedEntity.updated = mergedEntity.updated || (/* @__PURE__ */ new Date()).toISOString();
|
|
557
|
-
const canonicalPath =
|
|
1022
|
+
const canonicalPath = path2.join(this.deps.entitiesDir, `${canonical}.md`);
|
|
558
1023
|
await this.deps.writeStorageSecureFile(canonicalPath, serializeEntityFile(mergedEntity, this.deps.entitySchemas));
|
|
559
1024
|
for (const file of files) {
|
|
560
|
-
const filePath =
|
|
1025
|
+
const filePath = path2.join(this.deps.entitiesDir, file);
|
|
561
1026
|
if (filePath !== canonicalPath) {
|
|
562
1027
|
try {
|
|
563
1028
|
await unlink(filePath);
|
|
@@ -575,25 +1040,6 @@ ${rows.join("\n")}
|
|
|
575
1040
|
}
|
|
576
1041
|
};
|
|
577
1042
|
|
|
578
|
-
// src/orchestration/self-deps.ts
|
|
579
|
-
function selfDeps(self) {
|
|
580
|
-
const source = self;
|
|
581
|
-
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
582
|
-
get(_target, prop) {
|
|
583
|
-
const value = source[prop];
|
|
584
|
-
return typeof value === "function" ? value.bind(self) : value;
|
|
585
|
-
},
|
|
586
|
-
set(_target, prop, value) {
|
|
587
|
-
if (!(prop in source)) return false;
|
|
588
|
-
source[prop] = value;
|
|
589
|
-
return true;
|
|
590
|
-
},
|
|
591
|
-
has(_target, prop) {
|
|
592
|
-
return prop in source;
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
|
|
597
1043
|
// src/storage.ts
|
|
598
1044
|
var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
|
|
599
1045
|
"a",
|
|
@@ -1199,17 +1645,17 @@ var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set([
|
|
|
1199
1645
|
"topic"
|
|
1200
1646
|
]);
|
|
1201
1647
|
function inferEntityTypeFromFilename(pathRel) {
|
|
1202
|
-
const basename =
|
|
1648
|
+
const basename = path3.basename(pathRel, ".md").toLowerCase();
|
|
1203
1649
|
const separator = basename.indexOf("-");
|
|
1204
1650
|
if (separator <= 0) return void 0;
|
|
1205
1651
|
const candidate = basename.slice(0, separator);
|
|
1206
1652
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
1207
1653
|
}
|
|
1208
1654
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
1209
|
-
const normalizedPath = pathRel.split(
|
|
1655
|
+
const normalizedPath = pathRel.split(path3.sep).join("/");
|
|
1210
1656
|
let normalizedFrontmatter = frontmatter;
|
|
1211
1657
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
1212
|
-
const basename =
|
|
1658
|
+
const basename = path3.basename(pathRel, ".md");
|
|
1213
1659
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
1214
1660
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
1215
1661
|
normalizedFrontmatter = {
|
|
@@ -1276,8 +1722,8 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
1276
1722
|
secureStoreKeyProvider;
|
|
1277
1723
|
secureStoreWriteKeyProvider;
|
|
1278
1724
|
memoryDir;
|
|
1279
|
-
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir =
|
|
1280
|
-
this.filePath =
|
|
1725
|
+
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path3.dirname(stateDir)) {
|
|
1726
|
+
this.filePath = path3.join(stateDir, "fact-hashes.txt");
|
|
1281
1727
|
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
1282
1728
|
this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
|
|
1283
1729
|
this.memoryDir = memoryDir;
|
|
@@ -1324,7 +1770,7 @@ var ContentHashIndex = class _ContentHashIndex {
|
|
|
1324
1770
|
/** Persist index to disk if changed. */
|
|
1325
1771
|
async save() {
|
|
1326
1772
|
if (!this.dirty) return;
|
|
1327
|
-
await mkdir(
|
|
1773
|
+
await mkdir(path3.dirname(this.filePath), { recursive: true });
|
|
1328
1774
|
await writeMaybeEncryptedFile(
|
|
1329
1775
|
this.filePath,
|
|
1330
1776
|
[...this.hashes].join("\n") + "\n",
|
|
@@ -2178,7 +2624,7 @@ function isValidBufferSurpriseEvent(value) {
|
|
|
2178
2624
|
}
|
|
2179
2625
|
return true;
|
|
2180
2626
|
}
|
|
2181
|
-
var
|
|
2627
|
+
var StorageManager3 = class _StorageManager {
|
|
2182
2628
|
constructor(baseDir, entitySchemas) {
|
|
2183
2629
|
this.baseDir = baseDir;
|
|
2184
2630
|
this.entitySchemas = entitySchemas;
|
|
@@ -2191,6 +2637,7 @@ var StorageManager = class _StorageManager {
|
|
|
2191
2637
|
/** Read by storage/entity-store.ts (decomposition), hence not `private`. */
|
|
2192
2638
|
static KNOWLEDGE_INDEX_CACHE_TTL_MS = 6e5;
|
|
2193
2639
|
// 10 minutes (entity mutations invalidate)
|
|
2640
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2194
2641
|
static ARTIFACT_INDEX_CACHE_TTL_MS = 6e4;
|
|
2195
2642
|
// 1 minute
|
|
2196
2643
|
static artifactWriteVersionByDir = /* @__PURE__ */ new Map();
|
|
@@ -2219,14 +2666,18 @@ var StorageManager = class _StorageManager {
|
|
|
2219
2666
|
// on every cold-tier write, making the cache correct across process boundaries
|
|
2220
2667
|
// (gateway + CLI). After Finding UTsP broadened the scan to the entire cold/
|
|
2221
2668
|
// subtree, amortizing across back-to-back writes is even more important.
|
|
2669
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2222
2670
|
static COLD_SCAN_CACHE_TTL_MS = 3e4;
|
|
2223
2671
|
// 30 seconds
|
|
2672
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2224
2673
|
static coldMemoriesCache = /* @__PURE__ */ new Map();
|
|
2225
2674
|
// Cache for readQuestions() — avoids serially re-reading tens of thousands of
|
|
2226
2675
|
// question files on every recall. 60-second TTL is intentionally short so that
|
|
2227
2676
|
// newly written questions surface quickly.
|
|
2677
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2228
2678
|
static QUESTIONS_CACHE_TTL_MS = 6e4;
|
|
2229
2679
|
// 1 minute
|
|
2680
|
+
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2230
2681
|
static questionsCache = /* @__PURE__ */ new Map();
|
|
2231
2682
|
factHashIndex = null;
|
|
2232
2683
|
factHashIndexLoadPromise = null;
|
|
@@ -2385,7 +2836,7 @@ var StorageManager = class _StorageManager {
|
|
|
2385
2836
|
void 0,
|
|
2386
2837
|
this.baseDir
|
|
2387
2838
|
);
|
|
2388
|
-
const rel =
|
|
2839
|
+
const rel = path3.relative(this.baseDir, filePath).split(path3.sep).join("/");
|
|
2389
2840
|
return `${rel}:${version.versionId}`;
|
|
2390
2841
|
} catch (err) {
|
|
2391
2842
|
log.warn(
|
|
@@ -2394,7 +2845,29 @@ var StorageManager = class _StorageManager {
|
|
|
2394
2845
|
return null;
|
|
2395
2846
|
}
|
|
2396
2847
|
}
|
|
2848
|
+
/**
|
|
2849
|
+
* The LIVE class object of this instance (issue #1809 review of the
|
|
2850
|
+
* storage decomposition): extracted store modules must read/write the
|
|
2851
|
+
* shared static caches (questionsCache, coldMemoriesCache, ...) through
|
|
2852
|
+
* the SAME class binding the host methods use. Referencing the imported
|
|
2853
|
+
* `StorageManager` symbol from a split bundle chunk can yield a second
|
|
2854
|
+
* class copy with its own statics, split-braining the caches
|
|
2855
|
+
* (resolveQuestion clears copy A while readQuestions reads copy B).
|
|
2856
|
+
*/
|
|
2857
|
+
get storageManagerClass() {
|
|
2858
|
+
return this.constructor;
|
|
2859
|
+
}
|
|
2397
2860
|
/** The root directory of this storage instance. */
|
|
2861
|
+
/** MemoryReadStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
2862
|
+
_memoryReadStore;
|
|
2863
|
+
get memoryReadStore() {
|
|
2864
|
+
if (!this._memoryReadStore) {
|
|
2865
|
+
this._memoryReadStore = new MemoryReadStore(
|
|
2866
|
+
selfDeps(this)
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
return this._memoryReadStore;
|
|
2870
|
+
}
|
|
2398
2871
|
/** EntityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
2399
2872
|
_entityStore;
|
|
2400
2873
|
get entityStore() {
|
|
@@ -2410,13 +2883,13 @@ var StorageManager = class _StorageManager {
|
|
|
2410
2883
|
}
|
|
2411
2884
|
identityFilePath(workspaceDir, namespace) {
|
|
2412
2885
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
2413
|
-
if (!rawNamespace) return
|
|
2886
|
+
if (!rawNamespace) return path3.join(workspaceDir, "IDENTITY.md");
|
|
2414
2887
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
2415
|
-
return
|
|
2888
|
+
return path3.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
2416
2889
|
}
|
|
2417
2890
|
versionFilePath(kind) {
|
|
2418
2891
|
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : ".cold-write-version.log";
|
|
2419
|
-
return
|
|
2892
|
+
return path3.join(this.stateDir, fileName);
|
|
2420
2893
|
}
|
|
2421
2894
|
bumpSharedVersion(kind, fallbackMap) {
|
|
2422
2895
|
const filePath = this.versionFilePath(kind);
|
|
@@ -2463,7 +2936,7 @@ var StorageManager = class _StorageManager {
|
|
|
2463
2936
|
// memories.
|
|
2464
2937
|
// -------------------------------------------------------------------------
|
|
2465
2938
|
get wearablesDir() {
|
|
2466
|
-
return
|
|
2939
|
+
return path3.join(this.baseDir, WEARABLES_DIR_NAME);
|
|
2467
2940
|
}
|
|
2468
2941
|
/**
|
|
2469
2942
|
* Resolve the on-disk path for a source/day transcript. Throws on
|
|
@@ -2481,7 +2954,7 @@ var StorageManager = class _StorageManager {
|
|
|
2481
2954
|
`invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`
|
|
2482
2955
|
);
|
|
2483
2956
|
}
|
|
2484
|
-
return
|
|
2957
|
+
return path3.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
2485
2958
|
}
|
|
2486
2959
|
async writeWearableDayTranscript(sourceId, date, serialized) {
|
|
2487
2960
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
@@ -2497,48 +2970,10 @@ var StorageManager = class _StorageManager {
|
|
|
2497
2970
|
throw err;
|
|
2498
2971
|
}
|
|
2499
2972
|
}
|
|
2500
|
-
/**
|
|
2501
|
-
* List stored transcript days, newest first, optionally scoped to one
|
|
2502
|
-
* source. Non-transcript files in the tree are ignored.
|
|
2503
|
-
*/
|
|
2504
2973
|
async listWearableTranscriptDays(sourceId) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
sources = [sourceId];
|
|
2509
|
-
} else {
|
|
2510
|
-
try {
|
|
2511
|
-
const entries = await readdir2(this.wearablesDir, { withFileTypes: true });
|
|
2512
|
-
sources = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2513
|
-
} catch (err) {
|
|
2514
|
-
if (err.code === "ENOENT") return [];
|
|
2515
|
-
throw err;
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
for (const source of sources) {
|
|
2519
|
-
if (!/^[a-z][a-z0-9-]{0,63}$/.test(source)) continue;
|
|
2520
|
-
let entries;
|
|
2521
|
-
try {
|
|
2522
|
-
entries = await readdir2(path2.join(this.wearablesDir, source));
|
|
2523
|
-
} catch (err) {
|
|
2524
|
-
if (err.code === "ENOENT") continue;
|
|
2525
|
-
throw err;
|
|
2526
|
-
}
|
|
2527
|
-
for (const entry of entries) {
|
|
2528
|
-
if (!entry.endsWith(".md")) continue;
|
|
2529
|
-
const date = entry.slice(0, -3);
|
|
2530
|
-
if (!isValidTranscriptDate(date)) continue;
|
|
2531
|
-
days.push({ source, date });
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
days.sort((a, b) => {
|
|
2535
|
-
if (a.date > b.date) return -1;
|
|
2536
|
-
if (a.date < b.date) return 1;
|
|
2537
|
-
if (a.source < b.source) return -1;
|
|
2538
|
-
if (a.source > b.source) return 1;
|
|
2539
|
-
return 0;
|
|
2540
|
-
});
|
|
2541
|
-
return days;
|
|
2974
|
+
return this.memoryReadStore.listWearableTranscriptDays(
|
|
2975
|
+
sourceId
|
|
2976
|
+
);
|
|
2542
2977
|
}
|
|
2543
2978
|
/**
|
|
2544
2979
|
* Locate a wearable-sourced memory by exact (trimmed) content,
|
|
@@ -2606,25 +3041,25 @@ var StorageManager = class _StorageManager {
|
|
|
2606
3041
|
});
|
|
2607
3042
|
}
|
|
2608
3043
|
get factsDir() {
|
|
2609
|
-
return
|
|
3044
|
+
return path3.join(this.baseDir, "facts");
|
|
2610
3045
|
}
|
|
2611
3046
|
get correctionsDir() {
|
|
2612
|
-
return
|
|
3047
|
+
return path3.join(this.baseDir, "corrections");
|
|
2613
3048
|
}
|
|
2614
3049
|
get proceduresDir() {
|
|
2615
|
-
return
|
|
3050
|
+
return path3.join(this.baseDir, "procedures");
|
|
2616
3051
|
}
|
|
2617
3052
|
get reasoningTracesDir() {
|
|
2618
|
-
return
|
|
3053
|
+
return path3.join(this.baseDir, "reasoning-traces");
|
|
2619
3054
|
}
|
|
2620
3055
|
get entitiesDir() {
|
|
2621
|
-
return
|
|
3056
|
+
return path3.join(this.baseDir, "entities");
|
|
2622
3057
|
}
|
|
2623
3058
|
resolveEntityFilePath(name) {
|
|
2624
3059
|
if (typeof name !== "string") return null;
|
|
2625
|
-
const filePath =
|
|
2626
|
-
const relative =
|
|
2627
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
3060
|
+
const filePath = path3.resolve(this.entitiesDir, `${name}.md`);
|
|
3061
|
+
const relative = path3.relative(this.entitiesDir, filePath);
|
|
3062
|
+
if (relative === "" || relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
2628
3063
|
return null;
|
|
2629
3064
|
}
|
|
2630
3065
|
return filePath;
|
|
@@ -2639,10 +3074,10 @@ var StorageManager = class _StorageManager {
|
|
|
2639
3074
|
return writeMaybeEncryptedFileFromChunks(filePath, chunks, this.resolveWriteKey(), {}, this.baseDir).then(() => this.notifyCatalogWrite());
|
|
2640
3075
|
}
|
|
2641
3076
|
assertManagedStoragePath(filePath, method) {
|
|
2642
|
-
const resolved =
|
|
2643
|
-
const base =
|
|
2644
|
-
const rel =
|
|
2645
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
3077
|
+
const resolved = path3.resolve(filePath);
|
|
3078
|
+
const base = path3.resolve(this.baseDir);
|
|
3079
|
+
const rel = path3.relative(base, resolved);
|
|
3080
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path3.sep}`) || path3.isAbsolute(rel)) {
|
|
2646
3081
|
throw new Error(`${method}: file path escapes memory dir`);
|
|
2647
3082
|
}
|
|
2648
3083
|
return resolved;
|
|
@@ -2653,8 +3088,8 @@ var StorageManager = class _StorageManager {
|
|
|
2653
3088
|
}
|
|
2654
3089
|
async digestOfflineSyncFile(filePath) {
|
|
2655
3090
|
const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
|
|
2656
|
-
const st = await
|
|
2657
|
-
const relPath =
|
|
3091
|
+
const st = await stat2(target);
|
|
3092
|
+
const relPath = path3.relative(this.baseDir, target).split(path3.sep).join("/");
|
|
2658
3093
|
const cache = await this.loadOfflineSyncDigestCache();
|
|
2659
3094
|
const cached = cache.get(relPath);
|
|
2660
3095
|
if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
|
|
@@ -2715,7 +3150,7 @@ var StorageManager = class _StorageManager {
|
|
|
2715
3150
|
const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
|
|
2716
3151
|
const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
|
|
2717
3152
|
const encrypted = record.encrypted === true;
|
|
2718
|
-
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") ||
|
|
3153
|
+
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path3.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
|
|
2719
3154
|
continue;
|
|
2720
3155
|
}
|
|
2721
3156
|
cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
|
|
@@ -2757,7 +3192,7 @@ var StorageManager = class _StorageManager {
|
|
|
2757
3192
|
const cache = this.offlineSyncDigestCache;
|
|
2758
3193
|
if (!cache) return;
|
|
2759
3194
|
const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
2760
|
-
await mkdir(
|
|
3195
|
+
await mkdir(path3.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
2761
3196
|
await writeFile(
|
|
2762
3197
|
this.offlineSyncDigestCachePath,
|
|
2763
3198
|
`${JSON.stringify({ version: 1, entries })}
|
|
@@ -2810,10 +3245,10 @@ var StorageManager = class _StorageManager {
|
|
|
2810
3245
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2811
3246
|
throw error;
|
|
2812
3247
|
});
|
|
2813
|
-
if (filePath.includes(`${
|
|
3248
|
+
if (filePath.includes(`${path3.sep}cold${path3.sep}`)) {
|
|
2814
3249
|
this.invalidateColdMemoriesCache();
|
|
2815
3250
|
}
|
|
2816
|
-
if (filePath.includes(`${
|
|
3251
|
+
if (filePath.includes(`${path3.sep}artifacts${path3.sep}`)) {
|
|
2817
3252
|
this.bumpArtifactWriteVersion();
|
|
2818
3253
|
}
|
|
2819
3254
|
this.bumpMemoryStatusVersion();
|
|
@@ -2841,7 +3276,7 @@ var StorageManager = class _StorageManager {
|
|
|
2841
3276
|
}
|
|
2842
3277
|
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
2843
3278
|
const writeKey = this.resolveWriteKey();
|
|
2844
|
-
await mkdir(
|
|
3279
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
2845
3280
|
if (writeKey === null) {
|
|
2846
3281
|
try {
|
|
2847
3282
|
if (isEncryptedFile(await readFile(filePath))) {
|
|
@@ -2867,16 +3302,16 @@ var StorageManager = class _StorageManager {
|
|
|
2867
3302
|
this.notifyCatalogWrite();
|
|
2868
3303
|
}
|
|
2869
3304
|
get stateDir() {
|
|
2870
|
-
return
|
|
3305
|
+
return path3.join(this.baseDir, "state");
|
|
2871
3306
|
}
|
|
2872
3307
|
get offlineSyncDigestCachePath() {
|
|
2873
|
-
return
|
|
3308
|
+
return path3.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
|
|
2874
3309
|
}
|
|
2875
3310
|
get entitySynthesisQueuePath() {
|
|
2876
|
-
return
|
|
3311
|
+
return path3.join(this.stateDir, "entity-synthesis-queue.json");
|
|
2877
3312
|
}
|
|
2878
3313
|
get factHashIndexReadyPath() {
|
|
2879
|
-
return
|
|
3314
|
+
return path3.join(this.stateDir, "fact-hashes.ready");
|
|
2880
3315
|
}
|
|
2881
3316
|
// ── Tombstone store access (issue #1579) ─────────────────────────────────
|
|
2882
3317
|
/**
|
|
@@ -2886,7 +3321,7 @@ var StorageManager = class _StorageManager {
|
|
|
2886
3321
|
* helpers are injected into the store).
|
|
2887
3322
|
*/
|
|
2888
3323
|
get tombstonesPath() {
|
|
2889
|
-
return
|
|
3324
|
+
return path3.join(this.stateDir, "tombstones.jsonl");
|
|
2890
3325
|
}
|
|
2891
3326
|
/**
|
|
2892
3327
|
* Configure the tombstone invariant for this storage instance. Installed
|
|
@@ -3085,7 +3520,7 @@ var StorageManager = class _StorageManager {
|
|
|
3085
3520
|
);
|
|
3086
3521
|
}
|
|
3087
3522
|
await factHashIndex.save();
|
|
3088
|
-
await mkdir(
|
|
3523
|
+
await mkdir(path3.dirname(this.factHashIndexReadyPath), { recursive: true });
|
|
3089
3524
|
await writeFile(this.factHashIndexReadyPath, "v1\n", "utf-8");
|
|
3090
3525
|
this.factHashIndexAuthoritative = true;
|
|
3091
3526
|
})().finally(() => {
|
|
@@ -3094,55 +3529,55 @@ var StorageManager = class _StorageManager {
|
|
|
3094
3529
|
await this.factHashIndexAuthoritativePromise;
|
|
3095
3530
|
}
|
|
3096
3531
|
get questionsDir() {
|
|
3097
|
-
return
|
|
3532
|
+
return path3.join(this.baseDir, "questions");
|
|
3098
3533
|
}
|
|
3099
3534
|
get artifactsDir() {
|
|
3100
|
-
return
|
|
3535
|
+
return path3.join(this.baseDir, "artifacts");
|
|
3101
3536
|
}
|
|
3102
3537
|
get identityDir() {
|
|
3103
|
-
return
|
|
3538
|
+
return path3.join(this.baseDir, "identity");
|
|
3104
3539
|
}
|
|
3105
3540
|
get identityAnchorPath() {
|
|
3106
|
-
return
|
|
3541
|
+
return path3.join(this.identityDir, "identity-anchor.md");
|
|
3107
3542
|
}
|
|
3108
3543
|
get identityIncidentsDir() {
|
|
3109
|
-
return
|
|
3544
|
+
return path3.join(this.identityDir, "incidents");
|
|
3110
3545
|
}
|
|
3111
3546
|
get identityAuditsWeeklyDir() {
|
|
3112
|
-
return
|
|
3547
|
+
return path3.join(this.identityDir, "audits", "weekly");
|
|
3113
3548
|
}
|
|
3114
3549
|
get identityAuditsMonthlyDir() {
|
|
3115
|
-
return
|
|
3550
|
+
return path3.join(this.identityDir, "audits", "monthly");
|
|
3116
3551
|
}
|
|
3117
3552
|
get identityImprovementLoopsPath() {
|
|
3118
|
-
return
|
|
3553
|
+
return path3.join(this.identityDir, "improvement-loops.md");
|
|
3119
3554
|
}
|
|
3120
3555
|
get identityReflectionsPath() {
|
|
3121
|
-
return
|
|
3556
|
+
return path3.join(this.identityDir, "reflections.md");
|
|
3122
3557
|
}
|
|
3123
3558
|
get profilePath() {
|
|
3124
|
-
return
|
|
3559
|
+
return path3.join(this.baseDir, "profile.md");
|
|
3125
3560
|
}
|
|
3126
3561
|
get memoryActionsPath() {
|
|
3127
|
-
return
|
|
3562
|
+
return path3.join(this.stateDir, "memory-actions.jsonl");
|
|
3128
3563
|
}
|
|
3129
3564
|
get memoryLifecycleLedgerPath() {
|
|
3130
|
-
return
|
|
3565
|
+
return path3.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
3131
3566
|
}
|
|
3132
3567
|
get compressionGuidelinesPath() {
|
|
3133
|
-
return
|
|
3568
|
+
return path3.join(this.stateDir, "compression-guidelines.md");
|
|
3134
3569
|
}
|
|
3135
3570
|
get compressionGuidelineDraftPath() {
|
|
3136
|
-
return
|
|
3571
|
+
return path3.join(this.stateDir, "compression-guidelines.draft.md");
|
|
3137
3572
|
}
|
|
3138
3573
|
get compressionGuidelineStatePath() {
|
|
3139
|
-
return
|
|
3574
|
+
return path3.join(this.stateDir, "compression-guideline-state.json");
|
|
3140
3575
|
}
|
|
3141
3576
|
get compressionGuidelineDraftStatePath() {
|
|
3142
|
-
return
|
|
3577
|
+
return path3.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
3143
3578
|
}
|
|
3144
3579
|
get behaviorSignalsPath() {
|
|
3145
|
-
return
|
|
3580
|
+
return path3.join(this.stateDir, "behavior-signals.jsonl");
|
|
3146
3581
|
}
|
|
3147
3582
|
/**
|
|
3148
3583
|
* Buffer surprise telemetry ledger (issue #563 PR 3).
|
|
@@ -3155,7 +3590,7 @@ var StorageManager = class _StorageManager {
|
|
|
3155
3590
|
* governance sweeps can treat it uniformly.
|
|
3156
3591
|
*/
|
|
3157
3592
|
get bufferSurpriseLedgerPath() {
|
|
3158
|
-
return
|
|
3593
|
+
return path3.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
3159
3594
|
}
|
|
3160
3595
|
/**
|
|
3161
3596
|
* Entity alias table loaded from THIS store's config/aliases.json.
|
|
@@ -3189,7 +3624,7 @@ var StorageManager = class _StorageManager {
|
|
|
3189
3624
|
this.loadAliasesSync();
|
|
3190
3625
|
}
|
|
3191
3626
|
loadAliasesSync() {
|
|
3192
|
-
const aliasPath =
|
|
3627
|
+
const aliasPath = path3.join(this.baseDir, "config", "aliases.json");
|
|
3193
3628
|
this.userAliases = {};
|
|
3194
3629
|
try {
|
|
3195
3630
|
const raw = readFileSync(aliasPath, "utf-8");
|
|
@@ -3214,9 +3649,9 @@ var StorageManager = class _StorageManager {
|
|
|
3214
3649
|
}
|
|
3215
3650
|
async ensureDirectories() {
|
|
3216
3651
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3217
|
-
await mkdir(
|
|
3218
|
-
await mkdir(
|
|
3219
|
-
await mkdir(
|
|
3652
|
+
await mkdir(path3.join(this.factsDir, today), { recursive: true });
|
|
3653
|
+
await mkdir(path3.join(this.proceduresDir, today), { recursive: true });
|
|
3654
|
+
await mkdir(path3.join(this.reasoningTracesDir, today), { recursive: true });
|
|
3220
3655
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
3221
3656
|
await mkdir(this.entitiesDir, { recursive: true });
|
|
3222
3657
|
await mkdir(this.stateDir, { recursive: true });
|
|
@@ -3226,7 +3661,7 @@ var StorageManager = class _StorageManager {
|
|
|
3226
3661
|
await mkdir(this.identityIncidentsDir, { recursive: true });
|
|
3227
3662
|
await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
|
|
3228
3663
|
await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
|
|
3229
|
-
await mkdir(
|
|
3664
|
+
await mkdir(path3.join(this.baseDir, "config"), { recursive: true });
|
|
3230
3665
|
}
|
|
3231
3666
|
/**
|
|
3232
3667
|
* Resolve the on-disk write path for a memory of the given category, creating
|
|
@@ -3243,11 +3678,11 @@ var StorageManager = class _StorageManager {
|
|
|
3243
3678
|
async resolveCategoryWritePath(category, id, today) {
|
|
3244
3679
|
if (category === "correction") {
|
|
3245
3680
|
await mkdir(this.correctionsDir, { recursive: true });
|
|
3246
|
-
return
|
|
3681
|
+
return path3.join(this.correctionsDir, `${id}.md`);
|
|
3247
3682
|
}
|
|
3248
|
-
const datedDir =
|
|
3683
|
+
const datedDir = path3.join(getCategoryDir(this.baseDir, category), today);
|
|
3249
3684
|
await mkdir(datedDir, { recursive: true });
|
|
3250
|
-
return
|
|
3685
|
+
return path3.join(datedDir, `${id}.md`);
|
|
3251
3686
|
}
|
|
3252
3687
|
async writeMemory(category, content, options = {}) {
|
|
3253
3688
|
await this.ensureDirectories();
|
|
@@ -3497,7 +3932,7 @@ ${sanitized.text}
|
|
|
3497
3932
|
await this.ensureDirectories();
|
|
3498
3933
|
const now = /* @__PURE__ */ new Date();
|
|
3499
3934
|
const day = now.toISOString().slice(0, 10);
|
|
3500
|
-
const dir =
|
|
3935
|
+
const dir = path3.join(this.artifactsDir, day);
|
|
3501
3936
|
await mkdir(dir, { recursive: true });
|
|
3502
3937
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
3503
3938
|
const fm = {
|
|
@@ -3521,7 +3956,7 @@ ${sanitized.text}
|
|
|
3521
3956
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
3522
3957
|
return "";
|
|
3523
3958
|
}
|
|
3524
|
-
const filePath =
|
|
3959
|
+
const filePath = path3.join(dir, `${id}.md`);
|
|
3525
3960
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
|
|
3526
3961
|
|
|
3527
3962
|
${sanitized.text}
|
|
@@ -3540,45 +3975,7 @@ ${sanitized.text}
|
|
|
3540
3975
|
return id;
|
|
3541
3976
|
}
|
|
3542
3977
|
async readAllArtifactsCached() {
|
|
3543
|
-
|
|
3544
|
-
return this.artifactIndexCache.memories;
|
|
3545
|
-
}
|
|
3546
|
-
const scanArtifacts = async () => {
|
|
3547
|
-
const artifacts = [];
|
|
3548
|
-
const readDir = async (dir) => {
|
|
3549
|
-
try {
|
|
3550
|
-
const entries = await readdir2(dir, { withFileTypes: true });
|
|
3551
|
-
for (const entry of entries) {
|
|
3552
|
-
const fullPath = path2.join(dir, entry.name);
|
|
3553
|
-
if (entry.isDirectory()) {
|
|
3554
|
-
await readDir(fullPath);
|
|
3555
|
-
continue;
|
|
3556
|
-
}
|
|
3557
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
3558
|
-
const memory = await this.readMemoryByPath(fullPath);
|
|
3559
|
-
if (!memory) continue;
|
|
3560
|
-
artifacts.push(memory);
|
|
3561
|
-
}
|
|
3562
|
-
} catch {
|
|
3563
|
-
}
|
|
3564
|
-
};
|
|
3565
|
-
await readDir(this.artifactsDir);
|
|
3566
|
-
return artifacts;
|
|
3567
|
-
};
|
|
3568
|
-
const MAX_REBUILD_RETRIES = 2;
|
|
3569
|
-
let latestArtifacts = [];
|
|
3570
|
-
for (let attempt = 0; attempt <= MAX_REBUILD_RETRIES; attempt += 1) {
|
|
3571
|
-
const versionBefore = this.getArtifactWriteVersion();
|
|
3572
|
-
const artifacts = await scanArtifacts();
|
|
3573
|
-
const versionAfter = this.getArtifactWriteVersion();
|
|
3574
|
-
latestArtifacts = artifacts;
|
|
3575
|
-
if (versionAfter === versionBefore) {
|
|
3576
|
-
this.artifactIndexCache = { memories: artifacts, loadedAtMs: Date.now(), writeVersion: versionAfter };
|
|
3577
|
-
return artifacts;
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
this.artifactIndexCache = null;
|
|
3581
|
-
return latestArtifacts;
|
|
3978
|
+
return this.memoryReadStore.readAllArtifactsCached();
|
|
3582
3979
|
}
|
|
3583
3980
|
async searchArtifacts(query, maxResults) {
|
|
3584
3981
|
const tokens = tokenizeArtifactSearchText(query);
|
|
@@ -3761,7 +4158,7 @@ ${sanitized.text}
|
|
|
3761
4158
|
* per-process in-memory cache safe across process boundaries.
|
|
3762
4159
|
*/
|
|
3763
4160
|
invalidateColdMemoriesCache() {
|
|
3764
|
-
const coldRoot =
|
|
4161
|
+
const coldRoot = path3.join(this.baseDir, "cold");
|
|
3765
4162
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
3766
4163
|
this.bumpColdWriteVersion();
|
|
3767
4164
|
invalidateAllForDir(this.baseDir);
|
|
@@ -3784,45 +4181,7 @@ ${sanitized.text}
|
|
|
3784
4181
|
return Math.max(1, Math.floor(batchSize));
|
|
3785
4182
|
}
|
|
3786
4183
|
async collectActiveMemoryPaths() {
|
|
3787
|
-
|
|
3788
|
-
let memoryRootReal;
|
|
3789
|
-
try {
|
|
3790
|
-
memoryRootReal = await realpath(this.baseDir);
|
|
3791
|
-
} catch {
|
|
3792
|
-
return filePaths;
|
|
3793
|
-
}
|
|
3794
|
-
const collectPaths = async (dir) => {
|
|
3795
|
-
let entries;
|
|
3796
|
-
try {
|
|
3797
|
-
const dirStat = await lstat(dir);
|
|
3798
|
-
if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
|
|
3799
|
-
assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
|
|
3800
|
-
entries = await readdir2(dir, { withFileTypes: true });
|
|
3801
|
-
} catch {
|
|
3802
|
-
return;
|
|
3803
|
-
}
|
|
3804
|
-
const subdirs = [];
|
|
3805
|
-
for (const entry of entries) {
|
|
3806
|
-
if (entry.isSymbolicLink()) continue;
|
|
3807
|
-
const fullPath = path2.join(dir, entry.name);
|
|
3808
|
-
if (entry.isDirectory()) {
|
|
3809
|
-
subdirs.push(fullPath);
|
|
3810
|
-
} else if (entry.name.endsWith(".md")) {
|
|
3811
|
-
try {
|
|
3812
|
-
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
3813
|
-
filePaths.push(fullPath);
|
|
3814
|
-
} catch {
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
for (const subdir of subdirs) {
|
|
3819
|
-
await collectPaths(subdir);
|
|
3820
|
-
}
|
|
3821
|
-
};
|
|
3822
|
-
for (const dir of RECALL_FALLBACK_DIRS) {
|
|
3823
|
-
await collectPaths(path2.join(this.baseDir, dir));
|
|
3824
|
-
}
|
|
3825
|
-
return filePaths;
|
|
4184
|
+
return this.memoryReadStore.collectActiveMemoryPaths();
|
|
3826
4185
|
}
|
|
3827
4186
|
async readParsedMemoriesFromPaths(filePaths, batchSize) {
|
|
3828
4187
|
if (filePaths.length === 0) return [];
|
|
@@ -3877,7 +4236,7 @@ ${sanitized.text}
|
|
|
3877
4236
|
return updatedMs >= updatedAfterMs ? filePath : null;
|
|
3878
4237
|
}
|
|
3879
4238
|
try {
|
|
3880
|
-
const fileStat = await
|
|
4239
|
+
const fileStat = await stat2(filePath);
|
|
3881
4240
|
return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
|
|
3882
4241
|
} catch {
|
|
3883
4242
|
return filePath;
|
|
@@ -3889,7 +4248,7 @@ ${sanitized.text}
|
|
|
3889
4248
|
const correctionPaths = [];
|
|
3890
4249
|
const factPaths = [];
|
|
3891
4250
|
for (const filePath of filePaths) {
|
|
3892
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
4251
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path3.sep}`)) {
|
|
3893
4252
|
correctionPaths.push(filePath);
|
|
3894
4253
|
} else {
|
|
3895
4254
|
factPaths.push(filePath);
|
|
@@ -3922,111 +4281,26 @@ ${sanitized.text}
|
|
|
3922
4281
|
index,
|
|
3923
4282
|
index + Math.min(parallelWindow, availableInspectionBudget)
|
|
3924
4283
|
);
|
|
3925
|
-
index += candidatePaths.length;
|
|
3926
|
-
if (candidatePaths.length === 0) break;
|
|
3927
|
-
filePaths.push(...candidatePaths);
|
|
3928
|
-
const parsedMemories = await this.readParsedMemoriesFromPaths(candidatePaths, candidatePaths.length);
|
|
3929
|
-
if (parsedMemories.length === 0) continue;
|
|
3930
|
-
memories.push(...parsedMemories.slice(0, availableSlots));
|
|
3931
|
-
}
|
|
3932
|
-
return { memories, filePaths };
|
|
3933
|
-
}
|
|
3934
|
-
async readMemoriesWindow(options = {}) {
|
|
3935
|
-
const allPaths = await this.collectActiveMemoryPaths();
|
|
3936
|
-
const sortedPaths = this.orderWindowPaths(allPaths);
|
|
3937
|
-
const maxMemories = typeof options.maxMemories === "number" && Number.isFinite(options.maxMemories) ? Math.max(1, Math.floor(options.maxMemories)) : void 0;
|
|
3938
|
-
const maxCandidatePaths = maxMemories === void 0 ? void 0 : maxMemories * 2;
|
|
3939
|
-
const updatedAfterMs = options.updatedAfter?.getTime();
|
|
3940
|
-
const normalizedBatchSize = this.normalizeMemoryReadBatchSize(options.batchSize);
|
|
3941
|
-
const memories = [];
|
|
3942
|
-
const selectedPaths = [];
|
|
3943
|
-
for (let i = 0; i < sortedPaths.length; i += normalizedBatchSize) {
|
|
3944
|
-
if (maxMemories !== void 0 && (memories.length >= maxMemories || maxCandidatePaths !== void 0 && selectedPaths.length >= maxCandidatePaths)) {
|
|
3945
|
-
return { memories, filePaths: selectedPaths };
|
|
3946
|
-
}
|
|
3947
|
-
const batchPaths = sortedPaths.slice(i, i + normalizedBatchSize);
|
|
3948
|
-
const candidateBatchPaths = updatedAfterMs === void 0 ? batchPaths : await this.filterWindowPathsByUpdatedAfter(batchPaths, updatedAfterMs);
|
|
3949
|
-
const remainingSlots = maxMemories === void 0 ? void 0 : Math.max(0, maxMemories - memories.length);
|
|
3950
|
-
const remainingInspectionBudget = maxCandidatePaths === void 0 ? void 0 : Math.max(0, maxCandidatePaths - selectedPaths.length);
|
|
3951
|
-
const { memories: batchMemories, filePaths: parsedCandidatePaths } = remainingSlots === void 0 ? {
|
|
3952
|
-
memories: await this.readParsedMemoriesFromPaths(candidateBatchPaths, normalizedBatchSize),
|
|
3953
|
-
filePaths: candidateBatchPaths
|
|
3954
|
-
} : await this.readWindowBoundedBatch(
|
|
3955
|
-
candidateBatchPaths,
|
|
3956
|
-
remainingSlots,
|
|
3957
|
-
remainingInspectionBudget ?? remainingSlots,
|
|
3958
|
-
normalizedBatchSize
|
|
3959
|
-
);
|
|
3960
|
-
selectedPaths.push(...parsedCandidatePaths);
|
|
3961
|
-
for (const memory of batchMemories) {
|
|
3962
|
-
memories.push(memory);
|
|
3963
|
-
if (maxMemories !== void 0 && memories.length >= maxMemories) {
|
|
3964
|
-
return { memories, filePaths: selectedPaths };
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
4284
|
+
index += candidatePaths.length;
|
|
4285
|
+
if (candidatePaths.length === 0) break;
|
|
4286
|
+
filePaths.push(...candidatePaths);
|
|
4287
|
+
const parsedMemories = await this.readParsedMemoriesFromPaths(candidatePaths, candidatePaths.length);
|
|
4288
|
+
if (parsedMemories.length === 0) continue;
|
|
4289
|
+
memories.push(...parsedMemories.slice(0, availableSlots));
|
|
3967
4290
|
}
|
|
3968
|
-
return { memories, filePaths
|
|
4291
|
+
return { memories, filePaths };
|
|
4292
|
+
}
|
|
4293
|
+
async readMemoriesWindow(options = {}) {
|
|
4294
|
+
return this.memoryReadStore.readMemoriesWindow(
|
|
4295
|
+
options
|
|
4296
|
+
);
|
|
3969
4297
|
}
|
|
3970
4298
|
async _readAllMemoriesFromDisk() {
|
|
3971
4299
|
const filePaths = await this.collectActiveMemoryPaths();
|
|
3972
4300
|
return this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
3973
4301
|
}
|
|
3974
|
-
/**
|
|
3975
|
-
* Read all memories from the cold tier by scanning the entire cold/ root
|
|
3976
|
-
* tree. Previously this only scanned cold/facts/ and cold/corrections/, but
|
|
3977
|
-
* structuredAttributes can appear on any MemoryCategory (preference, decision,
|
|
3978
|
-
* entity, etc.). buildTierMemoryPath now routes each category to its own
|
|
3979
|
-
* cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
|
|
3980
|
-
* #1546), so cold decisions/preferences/... live outside cold/facts/.
|
|
3981
|
-
* Scanning the full coldRoot covers every category dir and guards against
|
|
3982
|
-
* files placed in unexpected subdirectories during manual operations or future
|
|
3983
|
-
* refactors.
|
|
3984
|
-
*
|
|
3985
|
-
* Broadened in PR #402 round-6 (Finding UTsP): scanning only facts/ and
|
|
3986
|
-
* corrections/ was a narrower-than-necessary subset of the cold directory
|
|
3987
|
-
* tree. Correctness trumps the minor performance difference — cold scans
|
|
3988
|
-
* already happen at most once per supersession write.
|
|
3989
|
-
*
|
|
3990
|
-
* Used by applyTemporalSupersession so that memories already demoted to
|
|
3991
|
-
* cold/ can still be marked superseded when a newer hot fact arrives.
|
|
3992
|
-
*
|
|
3993
|
-
* Cached with a TTL (Finding UOGi, PR #402 round-6): back-to-back
|
|
3994
|
-
* structured-attribute writes in the same burst reuse the cached result
|
|
3995
|
-
* instead of re-scanning the cold tree on every call. The cache is
|
|
3996
|
-
* invalidated whenever a write calls invalidateAllMemoriesCache() (which
|
|
3997
|
-
* covers any hot→cold demotion that changes cold-tier contents) and
|
|
3998
|
-
* expires after COLD_SCAN_CACHE_TTL_MS as a safety net.
|
|
3999
|
-
*/
|
|
4000
4302
|
async readAllColdMemories() {
|
|
4001
|
-
|
|
4002
|
-
const currentColdVersion = this.readColdWriteVersion();
|
|
4003
|
-
const cached = _StorageManager.coldMemoriesCache.get(coldRoot);
|
|
4004
|
-
if (cached && Date.now() - cached.loadedAt < _StorageManager.COLD_SCAN_CACHE_TTL_MS && cached.coldVersion === currentColdVersion) {
|
|
4005
|
-
return cached.memories;
|
|
4006
|
-
}
|
|
4007
|
-
const filePaths = [];
|
|
4008
|
-
const collectPaths = async (dir) => {
|
|
4009
|
-
try {
|
|
4010
|
-
const entries = await readdir2(dir, { withFileTypes: true });
|
|
4011
|
-
const subdirs = [];
|
|
4012
|
-
for (const entry of entries) {
|
|
4013
|
-
const fullPath = path2.join(dir, entry.name);
|
|
4014
|
-
if (entry.isDirectory()) {
|
|
4015
|
-
subdirs.push(fullPath);
|
|
4016
|
-
} else if (entry.name.endsWith(".md")) {
|
|
4017
|
-
filePaths.push(fullPath);
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
for (const subdir of subdirs) {
|
|
4021
|
-
await collectPaths(subdir);
|
|
4022
|
-
}
|
|
4023
|
-
} catch {
|
|
4024
|
-
}
|
|
4025
|
-
};
|
|
4026
|
-
await collectPaths(coldRoot);
|
|
4027
|
-
const memories = await this.readParsedMemoriesFromPaths(filePaths, 50);
|
|
4028
|
-
_StorageManager.coldMemoriesCache.set(coldRoot, { memories, loadedAt: Date.now(), coldVersion: currentColdVersion });
|
|
4029
|
-
return memories;
|
|
4303
|
+
return this.memoryReadStore.readAllColdMemories();
|
|
4030
4304
|
}
|
|
4031
4305
|
/**
|
|
4032
4306
|
* Read archived memory markdown files under archive/.
|
|
@@ -4037,9 +4311,9 @@ ${sanitized.text}
|
|
|
4037
4311
|
const root = this.archiveDir;
|
|
4038
4312
|
const readDir = async (dir) => {
|
|
4039
4313
|
try {
|
|
4040
|
-
const entries = await
|
|
4314
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
4041
4315
|
for (const entry of entries) {
|
|
4042
|
-
const fullPath =
|
|
4316
|
+
const fullPath = path3.join(dir, entry.name);
|
|
4043
4317
|
if (entry.isDirectory()) {
|
|
4044
4318
|
await readDir(fullPath);
|
|
4045
4319
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -4068,51 +4342,13 @@ ${sanitized.text}
|
|
|
4068
4342
|
await readDir(root);
|
|
4069
4343
|
return memories;
|
|
4070
4344
|
}
|
|
4071
|
-
/** Read a single memory file by its absolute path. Returns null if unreadable. */
|
|
4072
4345
|
async readMemoryByPath(filePath) {
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
if (parsed) {
|
|
4077
|
-
return {
|
|
4078
|
-
path: filePath,
|
|
4079
|
-
frontmatter: normalizeFrontmatterForPath(
|
|
4080
|
-
parsed.frontmatter,
|
|
4081
|
-
toMemoryPathRel(this.baseDir, filePath),
|
|
4082
|
-
parsed.content
|
|
4083
|
-
),
|
|
4084
|
-
content: parsed.content
|
|
4085
|
-
};
|
|
4086
|
-
}
|
|
4087
|
-
const normalizedPath = filePath.split(path2.sep).join("/");
|
|
4088
|
-
if (normalizedPath.includes("/entities/") && filePath.endsWith(".md")) {
|
|
4089
|
-
const entity = parseEntityFile(raw, this.entitySchemas);
|
|
4090
|
-
if (!entity.name) return null;
|
|
4091
|
-
const nameWithoutExt = path2.basename(filePath, ".md");
|
|
4092
|
-
const fileMtime = entity.updated || await stat(filePath).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
|
|
4093
|
-
return {
|
|
4094
|
-
path: filePath,
|
|
4095
|
-
frontmatter: {
|
|
4096
|
-
id: nameWithoutExt,
|
|
4097
|
-
category: "entity",
|
|
4098
|
-
created: fileMtime,
|
|
4099
|
-
updated: fileMtime,
|
|
4100
|
-
source: "entity_extraction",
|
|
4101
|
-
confidence: 0.9,
|
|
4102
|
-
confidenceTier: confidenceTier(0.9),
|
|
4103
|
-
tags: entity.type ? [entity.type] : []
|
|
4104
|
-
},
|
|
4105
|
-
content: raw
|
|
4106
|
-
};
|
|
4107
|
-
}
|
|
4108
|
-
return null;
|
|
4109
|
-
} catch (err) {
|
|
4110
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4111
|
-
return null;
|
|
4112
|
-
}
|
|
4346
|
+
return this.memoryReadStore.readMemoryByPath(
|
|
4347
|
+
filePath
|
|
4348
|
+
);
|
|
4113
4349
|
}
|
|
4114
4350
|
resolveTierRootDir(tier) {
|
|
4115
|
-
return tier === "cold" ?
|
|
4351
|
+
return tier === "cold" ? path3.join(this.baseDir, "cold") : this.baseDir;
|
|
4116
4352
|
}
|
|
4117
4353
|
resolveMemoryDateDir(memory) {
|
|
4118
4354
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -4127,13 +4363,13 @@ ${sanitized.text}
|
|
|
4127
4363
|
buildTierMemoryPath(memory, tier) {
|
|
4128
4364
|
const root = this.resolveTierRootDir(tier);
|
|
4129
4365
|
if (this.isArtifactMemory(memory)) {
|
|
4130
|
-
return
|
|
4366
|
+
return path3.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4131
4367
|
}
|
|
4132
4368
|
if (memory.frontmatter.category === "correction") {
|
|
4133
|
-
return
|
|
4369
|
+
return path3.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
4134
4370
|
}
|
|
4135
4371
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
4136
|
-
return
|
|
4372
|
+
return path3.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
4137
4373
|
}
|
|
4138
4374
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
4139
4375
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}
|
|
@@ -4146,8 +4382,8 @@ ${memory.content}
|
|
|
4146
4382
|
}
|
|
4147
4383
|
async moveMemoryToPath(memory, targetPath) {
|
|
4148
4384
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
4149
|
-
const sourcePath =
|
|
4150
|
-
const destPath =
|
|
4385
|
+
const sourcePath = path3.resolve(memory.path);
|
|
4386
|
+
const destPath = path3.resolve(targetPath);
|
|
4151
4387
|
if (sourcePath !== destPath) {
|
|
4152
4388
|
try {
|
|
4153
4389
|
await unlink2(memory.path);
|
|
@@ -4162,8 +4398,8 @@ ${memory.content}
|
|
|
4162
4398
|
}
|
|
4163
4399
|
async migrateMemoryToTier(memory, targetTier) {
|
|
4164
4400
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
4165
|
-
const sourcePath =
|
|
4166
|
-
const destPath =
|
|
4401
|
+
const sourcePath = path3.resolve(memory.path);
|
|
4402
|
+
const destPath = path3.resolve(targetPath);
|
|
4167
4403
|
if (sourcePath === destPath) {
|
|
4168
4404
|
return { changed: false, targetPath };
|
|
4169
4405
|
}
|
|
@@ -4189,7 +4425,7 @@ ${memory.content}
|
|
|
4189
4425
|
return { changed: true, targetPath };
|
|
4190
4426
|
}
|
|
4191
4427
|
get archiveDir() {
|
|
4192
|
-
return
|
|
4428
|
+
return path3.join(this.baseDir, "archive");
|
|
4193
4429
|
}
|
|
4194
4430
|
/**
|
|
4195
4431
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -4200,7 +4436,7 @@ ${memory.content}
|
|
|
4200
4436
|
try {
|
|
4201
4437
|
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
4202
4438
|
const today = now.toISOString().slice(0, 10);
|
|
4203
|
-
const destDir =
|
|
4439
|
+
const destDir = path3.join(this.archiveDir, today);
|
|
4204
4440
|
await mkdir(destDir, { recursive: true });
|
|
4205
4441
|
const updatedFm = {
|
|
4206
4442
|
...memory.frontmatter,
|
|
@@ -4212,7 +4448,7 @@ ${memory.content}
|
|
|
4212
4448
|
|
|
4213
4449
|
${memory.content}
|
|
4214
4450
|
`;
|
|
4215
|
-
const destPath =
|
|
4451
|
+
const destPath = path3.join(destDir, path3.basename(memory.path));
|
|
4216
4452
|
await this.writeStorageSecureFile(destPath, fileContent);
|
|
4217
4453
|
await unlink2(memory.path);
|
|
4218
4454
|
this.invalidateAllMemoriesCache();
|
|
@@ -4241,7 +4477,7 @@ ${memory.content}
|
|
|
4241
4477
|
}
|
|
4242
4478
|
async readEntities() {
|
|
4243
4479
|
try {
|
|
4244
|
-
const entries = await
|
|
4480
|
+
const entries = await readdir3(this.entitiesDir);
|
|
4245
4481
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(".md", ""));
|
|
4246
4482
|
} catch {
|
|
4247
4483
|
return [];
|
|
@@ -4360,7 +4596,7 @@ ${memory.content}
|
|
|
4360
4596
|
`;
|
|
4361
4597
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
4362
4598
|
this.invalidateAllMemoriesCache();
|
|
4363
|
-
if (memory.path.includes(`${
|
|
4599
|
+
if (memory.path.includes(`${path3.sep}cold${path3.sep}`)) {
|
|
4364
4600
|
this.invalidateColdMemoriesCache();
|
|
4365
4601
|
}
|
|
4366
4602
|
try {
|
|
@@ -4432,7 +4668,7 @@ ${memory.content}
|
|
|
4432
4668
|
return deleted;
|
|
4433
4669
|
}
|
|
4434
4670
|
async loadBuffer() {
|
|
4435
|
-
const bufferPath =
|
|
4671
|
+
const bufferPath = path3.join(this.stateDir, "buffer.json");
|
|
4436
4672
|
try {
|
|
4437
4673
|
const raw = await this.readStorageSecureFile(bufferPath);
|
|
4438
4674
|
return JSON.parse(raw);
|
|
@@ -4444,11 +4680,11 @@ ${memory.content}
|
|
|
4444
4680
|
}
|
|
4445
4681
|
async saveBuffer(state) {
|
|
4446
4682
|
await this.ensureDirectories();
|
|
4447
|
-
const bufferPath =
|
|
4683
|
+
const bufferPath = path3.join(this.stateDir, "buffer.json");
|
|
4448
4684
|
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
4449
4685
|
}
|
|
4450
4686
|
async loadMeta() {
|
|
4451
|
-
const metaPath =
|
|
4687
|
+
const metaPath = path3.join(this.stateDir, "meta.json");
|
|
4452
4688
|
try {
|
|
4453
4689
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
4454
4690
|
const parsed = JSON.parse(raw);
|
|
@@ -4482,7 +4718,7 @@ ${memory.content}
|
|
|
4482
4718
|
}
|
|
4483
4719
|
async saveMeta(state) {
|
|
4484
4720
|
await this.ensureDirectories();
|
|
4485
|
-
const metaPath =
|
|
4721
|
+
const metaPath = path3.join(this.stateDir, "meta.json");
|
|
4486
4722
|
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
4487
4723
|
}
|
|
4488
4724
|
async appendMemoryActionEvents(events) {
|
|
@@ -4515,99 +4751,15 @@ ${memory.content}
|
|
|
4515
4751
|
await this.appendStorageSecureFile(this.memoryLifecycleLedgerPath, payload);
|
|
4516
4752
|
return events.length;
|
|
4517
4753
|
}
|
|
4518
|
-
/**
|
|
4519
|
-
* Append a batch of `BUFFER_SURPRISE` telemetry events (issue #563 PR 3).
|
|
4520
|
-
*
|
|
4521
|
-
* Each event records a single buffer flush decision driven by the
|
|
4522
|
-
* surprise gate. The ledger is consumed by
|
|
4523
|
-
* `reportBufferSurpriseDistribution` (Doctor report) and by downstream
|
|
4524
|
-
* benchmark analysis. This method is fire-and-forget by contract:
|
|
4525
|
-
* callers log but do not fail the hot path if the append throws.
|
|
4526
|
-
*/
|
|
4527
4754
|
async appendBufferSurpriseEvents(events) {
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
const payload = events.map((event) => {
|
|
4532
|
-
const normalized = {
|
|
4533
|
-
...event,
|
|
4534
|
-
event: "BUFFER_SURPRISE",
|
|
4535
|
-
timestamp: event.timestamp && event.timestamp.length > 0 ? event.timestamp : nowIso
|
|
4536
|
-
};
|
|
4537
|
-
return `${JSON.stringify(normalized)}
|
|
4538
|
-
`;
|
|
4539
|
-
}).join("");
|
|
4540
|
-
await this.appendStorageSecureFile(this.bufferSurpriseLedgerPath, payload);
|
|
4541
|
-
return events.length;
|
|
4755
|
+
return this.memoryReadStore.appendBufferSurpriseEvents(
|
|
4756
|
+
events
|
|
4757
|
+
);
|
|
4542
4758
|
}
|
|
4543
|
-
/**
|
|
4544
|
-
* Read the buffer-surprise ledger, most recent rows last.
|
|
4545
|
-
*
|
|
4546
|
-
* `limit` bounds the number of **valid rows** returned (not the
|
|
4547
|
-
* number of raw lines parsed). We parse every row, discard malformed
|
|
4548
|
-
* ones, then take the tail — so a partial/truncated trailing line
|
|
4549
|
-
* (the common failure mode after an interrupted append) cannot hide
|
|
4550
|
-
* otherwise-valid recent data above it.
|
|
4551
|
-
*
|
|
4552
|
-
* Non-positive / non-integer / non-finite limits return `[]` rather
|
|
4553
|
-
* than the entire file, matching the other ledger readers in this
|
|
4554
|
-
* class and protecting against `slice(-0.5)` → `slice(-0)` silently
|
|
4555
|
-
* devolving into an unbounded parse.
|
|
4556
|
-
*
|
|
4557
|
-
* # Performance note
|
|
4558
|
-
*
|
|
4559
|
-
* For very large ledgers (issue #563 follow-up), a tail-first reader
|
|
4560
|
-
* would avoid parsing the full file when only a recent window is
|
|
4561
|
-
* needed. We keep the full-scan implementation here because:
|
|
4562
|
-
*
|
|
4563
|
-
* - the ledger is opt-in (flag off by default), so early deployments
|
|
4564
|
-
* accumulate rows slowly;
|
|
4565
|
-
* - telemetry rows are small (~200 bytes), so even 100k rows parse
|
|
4566
|
-
* in well under a second;
|
|
4567
|
-
* - the governance archive/cleanup flow can trim the ledger when
|
|
4568
|
-
* size becomes a concern, reusing the existing maintenance hooks.
|
|
4569
|
-
*
|
|
4570
|
-
* Swap to a chunked tail-reader if production logs show this is a
|
|
4571
|
-
* hot path — leaving that work for a follow-up keeps this PR scoped
|
|
4572
|
-
* to correctness, not optimization.
|
|
4573
|
-
*/
|
|
4574
4759
|
async readBufferSurpriseEvents(options = {}) {
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
raw = await this.readStorageSecureFile(this.bufferSurpriseLedgerPath);
|
|
4578
|
-
} catch (err) {
|
|
4579
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4580
|
-
const code = err.code;
|
|
4581
|
-
if (code === "ENOENT") return [];
|
|
4582
|
-
throw err;
|
|
4583
|
-
}
|
|
4584
|
-
let effectiveLimit = null;
|
|
4585
|
-
if (options.limit !== void 0) {
|
|
4586
|
-
if (typeof options.limit !== "number" || !Number.isFinite(options.limit) || options.limit <= 0) {
|
|
4587
|
-
return [];
|
|
4588
|
-
}
|
|
4589
|
-
const floored = Math.floor(options.limit);
|
|
4590
|
-
if (floored <= 0) return [];
|
|
4591
|
-
effectiveLimit = floored;
|
|
4592
|
-
}
|
|
4593
|
-
const lines = raw.split("\n");
|
|
4594
|
-
const events = [];
|
|
4595
|
-
for (const line of lines) {
|
|
4596
|
-
const trimmed = line.trim();
|
|
4597
|
-
if (trimmed.length === 0) continue;
|
|
4598
|
-
try {
|
|
4599
|
-
const parsed = JSON.parse(trimmed);
|
|
4600
|
-
if (isValidBufferSurpriseEvent(parsed)) {
|
|
4601
|
-
events.push(parsed);
|
|
4602
|
-
}
|
|
4603
|
-
} catch {
|
|
4604
|
-
}
|
|
4605
|
-
}
|
|
4606
|
-
events.sort(
|
|
4607
|
-
(left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)
|
|
4760
|
+
return this.memoryReadStore.readBufferSurpriseEvents(
|
|
4761
|
+
options
|
|
4608
4762
|
);
|
|
4609
|
-
if (effectiveLimit === null) return events;
|
|
4610
|
-
return events.slice(-effectiveLimit);
|
|
4611
4763
|
}
|
|
4612
4764
|
async appendBehaviorSignals(events) {
|
|
4613
4765
|
if (events.length === 0) return 0;
|
|
@@ -4652,7 +4804,7 @@ ${memory.content}
|
|
|
4652
4804
|
async appendReextractJobs(events) {
|
|
4653
4805
|
if (events.length === 0) return 0;
|
|
4654
4806
|
await this.ensureDirectories();
|
|
4655
|
-
const filePath =
|
|
4807
|
+
const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4656
4808
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
4657
4809
|
try {
|
|
4658
4810
|
await this.appendStorageSecureFile(filePath, lines);
|
|
@@ -4665,7 +4817,7 @@ ${memory.content}
|
|
|
4665
4817
|
}
|
|
4666
4818
|
async readReextractJobs(limit = 200) {
|
|
4667
4819
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
4668
|
-
const filePath =
|
|
4820
|
+
const filePath = path3.join(this.stateDir, "reextract-jobs.jsonl");
|
|
4669
4821
|
try {
|
|
4670
4822
|
const raw = await this.readStorageSecureFile(filePath);
|
|
4671
4823
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
@@ -4852,53 +5004,9 @@ ${memory.content}
|
|
|
4852
5004
|
return true;
|
|
4853
5005
|
}
|
|
4854
5006
|
async readCompressionGuidelineStateFile(filePath) {
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
const summary = value;
|
|
4859
|
-
return typeof summary.action === "string" && isFiniteNonNegativeInteger(summary.total) && summary.outcomes !== null && typeof summary.outcomes === "object" && isFiniteNonNegativeInteger(summary.outcomes.applied) && isFiniteNonNegativeInteger(summary.outcomes.skipped) && isFiniteNonNegativeInteger(summary.outcomes.failed) && summary.quality !== null && typeof summary.quality === "object" && isFiniteNonNegativeInteger(summary.quality.good) && isFiniteNonNegativeInteger(summary.quality.poor) && isFiniteNonNegativeInteger(summary.quality.unknown);
|
|
4860
|
-
};
|
|
4861
|
-
const isValidRuleUpdate = (value) => {
|
|
4862
|
-
if (!value || typeof value !== "object") return false;
|
|
4863
|
-
const rule = value;
|
|
4864
|
-
return typeof rule.action === "string" && typeof rule.delta === "number" && Number.isFinite(rule.delta) && (rule.direction === "increase" || rule.direction === "decrease" || rule.direction === "hold") && (rule.confidence === "low" || rule.confidence === "medium" || rule.confidence === "high") && Array.isArray(rule.notes) && rule.notes.every((note) => typeof note === "string");
|
|
4865
|
-
};
|
|
4866
|
-
try {
|
|
4867
|
-
const raw = await this.readStorageSecureFile(filePath);
|
|
4868
|
-
const parsed = JSON.parse(raw);
|
|
4869
|
-
const sourceWindow = parsed?.sourceWindow;
|
|
4870
|
-
const eventCounts = parsed?.eventCounts;
|
|
4871
|
-
const activationState = parsed?.activationState === "draft" || parsed?.activationState === "active" ? parsed.activationState : void 0;
|
|
4872
|
-
const contentHash = typeof parsed?.contentHash === "string" && parsed.contentHash.length > 0 ? parsed.contentHash : void 0;
|
|
4873
|
-
const actionSummaries = Array.isArray(parsed?.actionSummaries) ? parsed.actionSummaries.filter(isValidActionSummary) : void 0;
|
|
4874
|
-
const ruleUpdates = Array.isArray(parsed?.ruleUpdates) ? parsed.ruleUpdates.filter(isValidRuleUpdate) : void 0;
|
|
4875
|
-
if (!isFiniteNonNegativeInteger(parsed?.version) || typeof parsed?.updatedAt !== "string" || parsed.updatedAt.length === 0 || !sourceWindow || typeof sourceWindow.from !== "string" || sourceWindow.from.length === 0 || typeof sourceWindow.to !== "string" || sourceWindow.to.length === 0 || !eventCounts || !isFiniteNonNegativeInteger(eventCounts.total) || !isFiniteNonNegativeInteger(eventCounts.applied) || !isFiniteNonNegativeInteger(eventCounts.skipped) || !isFiniteNonNegativeInteger(eventCounts.failed) || !isFiniteNonNegativeInteger(parsed?.guidelineVersion)) {
|
|
4876
|
-
return null;
|
|
4877
|
-
}
|
|
4878
|
-
return {
|
|
4879
|
-
version: parsed.version,
|
|
4880
|
-
updatedAt: parsed.updatedAt,
|
|
4881
|
-
sourceWindow: {
|
|
4882
|
-
from: sourceWindow.from,
|
|
4883
|
-
to: sourceWindow.to
|
|
4884
|
-
},
|
|
4885
|
-
eventCounts: {
|
|
4886
|
-
total: eventCounts.total,
|
|
4887
|
-
applied: eventCounts.applied,
|
|
4888
|
-
skipped: eventCounts.skipped,
|
|
4889
|
-
failed: eventCounts.failed
|
|
4890
|
-
},
|
|
4891
|
-
guidelineVersion: parsed.guidelineVersion,
|
|
4892
|
-
...contentHash ? { contentHash } : {},
|
|
4893
|
-
...activationState ? { activationState } : {},
|
|
4894
|
-
...actionSummaries ? { actionSummaries } : {},
|
|
4895
|
-
...ruleUpdates ? { ruleUpdates } : {}
|
|
4896
|
-
};
|
|
4897
|
-
} catch (err) {
|
|
4898
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
4899
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
4900
|
-
return null;
|
|
4901
|
-
}
|
|
5007
|
+
return this.memoryReadStore.readCompressionGuidelineStateFile(
|
|
5008
|
+
filePath
|
|
5009
|
+
);
|
|
4902
5010
|
}
|
|
4903
5011
|
async writeIdentityAnchor(content) {
|
|
4904
5012
|
await this.ensureDirectories();
|
|
@@ -4919,7 +5027,7 @@ ${memory.content}
|
|
|
4919
5027
|
const date = nowIso.slice(0, 10);
|
|
4920
5028
|
const id = this.generateId("incident");
|
|
4921
5029
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
4922
|
-
const filePath =
|
|
5030
|
+
const filePath = path3.join(this.identityIncidentsDir, `${date}-${id}.md`);
|
|
4923
5031
|
await this.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
4924
5032
|
return { ...incident, filePath };
|
|
4925
5033
|
}
|
|
@@ -4932,7 +5040,7 @@ ${memory.content}
|
|
|
4932
5040
|
const incidents = [];
|
|
4933
5041
|
for (const file of candidates) {
|
|
4934
5042
|
if (incidents.length >= cappedLimit) break;
|
|
4935
|
-
const filePath =
|
|
5043
|
+
const filePath = path3.join(this.identityIncidentsDir, file);
|
|
4936
5044
|
try {
|
|
4937
5045
|
const raw = await this.readStorageSecureFile(filePath);
|
|
4938
5046
|
const parsed = parseContinuityIncident(raw);
|
|
@@ -4963,7 +5071,7 @@ ${memory.content}
|
|
|
4963
5071
|
await this.ensureDirectories();
|
|
4964
5072
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4965
5073
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4966
|
-
const filePath =
|
|
5074
|
+
const filePath = path3.join(dir, `${safeKey}.md`);
|
|
4967
5075
|
await this.writeStorageSecureFile(filePath, content);
|
|
4968
5076
|
return filePath;
|
|
4969
5077
|
}
|
|
@@ -4971,7 +5079,7 @@ ${memory.content}
|
|
|
4971
5079
|
try {
|
|
4972
5080
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
4973
5081
|
const dir = period === "weekly" ? this.identityAuditsWeeklyDir : this.identityAuditsMonthlyDir;
|
|
4974
|
-
return await this.readStorageSecureFile(
|
|
5082
|
+
return await this.readStorageSecureFile(path3.join(dir, `${safeKey}.md`));
|
|
4975
5083
|
} catch (err) {
|
|
4976
5084
|
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
4977
5085
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -5018,7 +5126,7 @@ ${memory.content}
|
|
|
5018
5126
|
return `${prefix}-${ts}-${rand}`;
|
|
5019
5127
|
}
|
|
5020
5128
|
async readContinuityIncidentFileNames() {
|
|
5021
|
-
const files = await
|
|
5129
|
+
const files = await readdir3(this.identityIncidentsDir);
|
|
5022
5130
|
return files.filter((file) => file.endsWith(".md")).sort().reverse();
|
|
5023
5131
|
}
|
|
5024
5132
|
async readContinuityIncidentFile(filePath) {
|
|
@@ -5035,12 +5143,12 @@ ${memory.content}
|
|
|
5035
5143
|
const fileNames = await this.readContinuityIncidentFileNames();
|
|
5036
5144
|
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
5037
5145
|
if (directMatch) {
|
|
5038
|
-
const directPath =
|
|
5146
|
+
const directPath = path3.join(this.identityIncidentsDir, directMatch);
|
|
5039
5147
|
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
5040
5148
|
if (parsed?.id === id) return directPath;
|
|
5041
5149
|
}
|
|
5042
5150
|
for (const fileName of fileNames) {
|
|
5043
|
-
const filePath =
|
|
5151
|
+
const filePath = path3.join(this.identityIncidentsDir, fileName);
|
|
5044
5152
|
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
5045
5153
|
if (parsed?.id === id) return filePath;
|
|
5046
5154
|
}
|
|
@@ -5070,43 +5178,16 @@ ${question}
|
|
|
5070
5178
|
|
|
5071
5179
|
**Context:** ${context}
|
|
5072
5180
|
`;
|
|
5073
|
-
const filePath =
|
|
5181
|
+
const filePath = path3.join(this.questionsDir, `${id}.md`);
|
|
5074
5182
|
await this.writeStorageSecureFile(filePath, content);
|
|
5075
5183
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
5076
5184
|
this.invalidateQuestionsCache();
|
|
5077
5185
|
return id;
|
|
5078
5186
|
}
|
|
5079
5187
|
async readQuestions(opts) {
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
try {
|
|
5084
|
-
const dirStat = await stat(this.questionsDir);
|
|
5085
|
-
if (dirStat.mtimeMs <= cached.loadedAt) {
|
|
5086
|
-
const all = cached.questions;
|
|
5087
|
-
return opts?.unresolvedOnly ? all.filter((q) => !q.resolved) : all;
|
|
5088
|
-
}
|
|
5089
|
-
} catch {
|
|
5090
|
-
}
|
|
5091
|
-
}
|
|
5092
|
-
try {
|
|
5093
|
-
const files = await readdir2(this.questionsDir);
|
|
5094
|
-
const questions = [];
|
|
5095
|
-
for (const file of files) {
|
|
5096
|
-
if (!file.endsWith(".md")) continue;
|
|
5097
|
-
const filePath = path2.join(this.questionsDir, file);
|
|
5098
|
-
const raw = await readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
5099
|
-
const parsed = this.parseQuestionFile(raw, filePath);
|
|
5100
|
-
if (parsed) {
|
|
5101
|
-
questions.push(parsed);
|
|
5102
|
-
}
|
|
5103
|
-
}
|
|
5104
|
-
const sorted = questions.sort((a, b) => b.priority - a.priority);
|
|
5105
|
-
_StorageManager.questionsCache.set(cacheKey, { questions: sorted, loadedAt: Date.now() });
|
|
5106
|
-
return opts?.unresolvedOnly ? sorted.filter((q) => !q.resolved) : sorted;
|
|
5107
|
-
} catch {
|
|
5108
|
-
return [];
|
|
5109
|
-
}
|
|
5188
|
+
return this.memoryReadStore.readQuestions(
|
|
5189
|
+
opts
|
|
5190
|
+
);
|
|
5110
5191
|
}
|
|
5111
5192
|
/** Invalidate the questions cache (call after writing a question). */
|
|
5112
5193
|
invalidateQuestionsCache() {
|
|
@@ -5117,7 +5198,7 @@ ${question}
|
|
|
5117
5198
|
if (!match) return null;
|
|
5118
5199
|
const frontmatterStr = match[1];
|
|
5119
5200
|
const body = match[2].trim();
|
|
5120
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
5201
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path3.basename(filePath, ".md");
|
|
5121
5202
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
5122
5203
|
const priority = parseFloat(
|
|
5123
5204
|
this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5"
|
|
@@ -5179,11 +5260,11 @@ ${question}
|
|
|
5179
5260
|
} catch {
|
|
5180
5261
|
}
|
|
5181
5262
|
const hygiene = opts?.hygiene;
|
|
5182
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
5263
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path3.basename(identityPath));
|
|
5183
5264
|
if (rotateEnabled) {
|
|
5184
5265
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
5185
5266
|
if (existing.length > maxBytes) {
|
|
5186
|
-
const archiveDir =
|
|
5267
|
+
const archiveDir = path3.join(workspaceDir, hygiene.archiveDir);
|
|
5187
5268
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
5188
5269
|
filePath: identityPath,
|
|
5189
5270
|
archiveDir,
|
|
@@ -5277,7 +5358,7 @@ ${reflection}
|
|
|
5277
5358
|
* Deduplicates by target+label.
|
|
5278
5359
|
*/
|
|
5279
5360
|
async addEntityRelationship(name, rel) {
|
|
5280
|
-
const filePath =
|
|
5361
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
5281
5362
|
let entity;
|
|
5282
5363
|
try {
|
|
5283
5364
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -5302,7 +5383,7 @@ ${reflection}
|
|
|
5302
5383
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
5303
5384
|
*/
|
|
5304
5385
|
async addEntityActivity(name, entry, maxEntries) {
|
|
5305
|
-
const filePath =
|
|
5386
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
5306
5387
|
let entity;
|
|
5307
5388
|
try {
|
|
5308
5389
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -5325,7 +5406,7 @@ ${reflection}
|
|
|
5325
5406
|
* Add an alias to an entity file. Deduplicates.
|
|
5326
5407
|
*/
|
|
5327
5408
|
async addEntityAlias(name, alias) {
|
|
5328
|
-
const filePath =
|
|
5409
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
5329
5410
|
let entity;
|
|
5330
5411
|
try {
|
|
5331
5412
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -5356,7 +5437,7 @@ ${reflection}
|
|
|
5356
5437
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5357
5438
|
let synthesisTimelineCount;
|
|
5358
5439
|
try {
|
|
5359
|
-
const filePath =
|
|
5440
|
+
const filePath = path3.join(this.entitiesDir, `${name}.md`);
|
|
5360
5441
|
const content = await this.readStorageSecureFile(filePath);
|
|
5361
5442
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
5362
5443
|
} catch (err) {
|
|
@@ -5439,7 +5520,7 @@ ${reflection}
|
|
|
5439
5520
|
if (!raw) continue;
|
|
5440
5521
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
5441
5522
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
5442
|
-
await this.writeStorageSecureFile(
|
|
5523
|
+
await this.writeStorageSecureFile(path3.join(this.entitiesDir, `${entityName}.md`), serialized);
|
|
5443
5524
|
migrated += 1;
|
|
5444
5525
|
}
|
|
5445
5526
|
if (migrated > 0) {
|
|
@@ -5582,7 +5663,7 @@ ${memory.content}
|
|
|
5582
5663
|
const filePaths = await this.collectActiveMemoryPaths();
|
|
5583
5664
|
const foundIds = /* @__PURE__ */ new Set();
|
|
5584
5665
|
for (const filePath of filePaths) {
|
|
5585
|
-
const basename =
|
|
5666
|
+
const basename = path3.basename(filePath, ".md");
|
|
5586
5667
|
if (wantedIds.has(basename)) {
|
|
5587
5668
|
foundIds.add(basename);
|
|
5588
5669
|
if (foundIds.size === wantedIds.size) break;
|
|
@@ -5772,14 +5853,14 @@ Reason: ${reason}`, {
|
|
|
5772
5853
|
// Memory Summarization (Phase 4A)
|
|
5773
5854
|
// ---------------------------------------------------------------------------
|
|
5774
5855
|
get summariesDir() {
|
|
5775
|
-
return
|
|
5856
|
+
return path3.join(this.baseDir, "summaries");
|
|
5776
5857
|
}
|
|
5777
5858
|
/**
|
|
5778
5859
|
* Write a memory summary.
|
|
5779
5860
|
*/
|
|
5780
5861
|
async writeSummary(summary) {
|
|
5781
5862
|
await mkdir(this.summariesDir, { recursive: true });
|
|
5782
|
-
const filePath =
|
|
5863
|
+
const filePath = path3.join(this.summariesDir, `${summary.id}.json`);
|
|
5783
5864
|
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
5784
5865
|
log.debug(`wrote summary ${summary.id}`);
|
|
5785
5866
|
}
|
|
@@ -5788,11 +5869,11 @@ Reason: ${reason}`, {
|
|
|
5788
5869
|
*/
|
|
5789
5870
|
async readSummaries() {
|
|
5790
5871
|
try {
|
|
5791
|
-
const files = await
|
|
5872
|
+
const files = await readdir3(this.summariesDir);
|
|
5792
5873
|
const summaries = [];
|
|
5793
5874
|
for (const file of files) {
|
|
5794
5875
|
if (!file.endsWith(".json")) continue;
|
|
5795
|
-
const filePath =
|
|
5876
|
+
const filePath = path3.join(this.summariesDir, file);
|
|
5796
5877
|
const raw = await this.readStorageSecureFile(filePath);
|
|
5797
5878
|
summaries.push(JSON.parse(raw));
|
|
5798
5879
|
}
|
|
@@ -5853,7 +5934,7 @@ ${memory.content}
|
|
|
5853
5934
|
* Save topic scores to meta.json.
|
|
5854
5935
|
*/
|
|
5855
5936
|
async saveTopics(topics) {
|
|
5856
|
-
const metaPath =
|
|
5937
|
+
const metaPath = path3.join(this.stateDir, "topics.json");
|
|
5857
5938
|
await mkdir(this.stateDir, { recursive: true });
|
|
5858
5939
|
await this.writeStorageSecureFile(metaPath, JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
5859
5940
|
log.debug(`saved ${topics.length} topic scores`);
|
|
@@ -5862,7 +5943,7 @@ ${memory.content}
|
|
|
5862
5943
|
* Load topic scores from meta.json.
|
|
5863
5944
|
*/
|
|
5864
5945
|
async loadTopics() {
|
|
5865
|
-
const metaPath =
|
|
5946
|
+
const metaPath = path3.join(this.stateDir, "topics.json");
|
|
5866
5947
|
try {
|
|
5867
5948
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
5868
5949
|
return JSON.parse(raw);
|
|
@@ -5941,6 +6022,9 @@ ${memory.content}
|
|
|
5941
6022
|
|
|
5942
6023
|
export {
|
|
5943
6024
|
selfDeps,
|
|
6025
|
+
parseFrontmatterStringValue,
|
|
6026
|
+
parseFrontmatter,
|
|
6027
|
+
normalizeFrontmatterForPath,
|
|
5944
6028
|
normalizeEntityName,
|
|
5945
6029
|
ContentHashIndex,
|
|
5946
6030
|
normalizeAttributePairs,
|
|
@@ -5953,6 +6037,7 @@ export {
|
|
|
5953
6037
|
parseEntityFile,
|
|
5954
6038
|
serializeEntityFile,
|
|
5955
6039
|
buildEntitySchemaCacheKey,
|
|
5956
|
-
|
|
6040
|
+
isValidBufferSurpriseEvent,
|
|
6041
|
+
StorageManager3 as StorageManager
|
|
5957
6042
|
};
|
|
5958
|
-
//# sourceMappingURL=chunk-
|
|
6043
|
+
//# sourceMappingURL=chunk-5PHPNUZN.js.map
|