@remnic/core 9.3.663 → 9.3.665

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.
Files changed (149) hide show
  1. package/dist/access-cli.js +25 -23
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.js +20 -18
  4. package/dist/access-mcp.js +19 -17
  5. package/dist/access-schema.d.ts +36 -36
  6. package/dist/access-schema.js +4 -3
  7. package/dist/access-service.js +17 -15
  8. package/dist/briefing.js +5 -4
  9. package/dist/{capsule-merge-T2JRE46P.js → capsule-merge-GK5E647P.js} +3 -2
  10. package/dist/{capsule-merge-T2JRE46P.js.map → capsule-merge-GK5E647P.js.map} +1 -1
  11. package/dist/causal-consolidation.js +6 -5
  12. package/dist/causal-consolidation.js.map +1 -1
  13. package/dist/{chunk-2KDQI363.js → chunk-2HEZXPYU.js} +4 -4
  14. package/dist/{chunk-HSCJYHYV.js → chunk-2OPARZ4B.js} +49 -19
  15. package/dist/chunk-2OPARZ4B.js.map +1 -0
  16. package/dist/chunk-5GPPACXK.js +16 -0
  17. package/dist/chunk-5GPPACXK.js.map +1 -0
  18. package/dist/{chunk-F6O7IOS3.js → chunk-6JBKHTQD.js} +2 -2
  19. package/dist/{chunk-YYQRVNSV.js → chunk-7C4MPEPE.js} +6 -6
  20. package/dist/{chunk-AL4RAJL5.js → chunk-7XH7VJN4.js} +6 -4
  21. package/dist/chunk-7XH7VJN4.js.map +1 -0
  22. package/dist/{chunk-Q4CAQGKQ.js → chunk-AER6MT24.js} +12 -21
  23. package/dist/chunk-AER6MT24.js.map +1 -0
  24. package/dist/{chunk-DHGSZ3UD.js → chunk-ARV3AUOM.js} +2 -2
  25. package/dist/{chunk-PXVFMQLD.js → chunk-BZG2CWOQ.js} +3 -3
  26. package/dist/{chunk-ANJOULTP.js → chunk-C7AF236A.js} +2 -2
  27. package/dist/{chunk-TBLGI2LT.js → chunk-D7IXTY5E.js} +31 -4
  28. package/dist/chunk-D7IXTY5E.js.map +1 -0
  29. package/dist/{chunk-FZC2WSDB.js → chunk-DOCTITOP.js} +2 -2
  30. package/dist/{chunk-WOQIHC67.js → chunk-DQY7NJ5L.js} +2 -2
  31. package/dist/{chunk-NMPEJV5M.js → chunk-DSLUOQDY.js} +2 -2
  32. package/dist/{chunk-A7EF2XRO.js → chunk-EXXBA5OM.js} +30 -8
  33. package/dist/chunk-EXXBA5OM.js.map +1 -0
  34. package/dist/{chunk-QXHBWFR3.js → chunk-IHG6CC7T.js} +2 -2
  35. package/dist/{chunk-4KDLCMLK.js → chunk-IROWLAWG.js} +5 -5
  36. package/dist/{chunk-ILXTATKK.js → chunk-J2HSAU72.js} +5 -5
  37. package/dist/chunk-J2HSAU72.js.map +1 -0
  38. package/dist/{chunk-DFAXGZKI.js → chunk-JIX3ZL2J.js} +8 -8
  39. package/dist/{chunk-GY3V3SUI.js → chunk-KHGE6PMF.js} +2 -2
  40. package/dist/{chunk-TWAJICBN.js → chunk-OHJFJ4HI.js} +2 -2
  41. package/dist/{chunk-WSQG37DV.js → chunk-OUWAQVDJ.js} +2 -2
  42. package/dist/{chunk-ZLDUQWT2.js → chunk-PWWWLD7D.js} +2 -2
  43. package/dist/{chunk-ZJH723NM.js → chunk-Q5ZU3RNY.js} +2 -2
  44. package/dist/{chunk-35HP3TGR.js → chunk-ROHLEUTH.js} +4 -4
  45. package/dist/{chunk-5RIRL3XL.js → chunk-RS25QOKZ.js} +2 -2
  46. package/dist/{chunk-RQGR3ETH.js → chunk-T2AN3BSP.js} +2 -2
  47. package/dist/{chunk-UAU5U5ML.js → chunk-UDJLF3BO.js} +2 -2
  48. package/dist/{chunk-ALEPI75L.js → chunk-VF4XKTX3.js} +6 -4
  49. package/dist/{chunk-ALEPI75L.js.map → chunk-VF4XKTX3.js.map} +1 -1
  50. package/dist/{chunk-AX5O25EF.js → chunk-VH6EIKVS.js} +152 -190
  51. package/dist/chunk-VH6EIKVS.js.map +1 -0
  52. package/dist/chunk-VS2IYZRU.js +43 -0
  53. package/dist/chunk-VS2IYZRU.js.map +1 -0
  54. package/dist/{chunk-TGOOJCGA.js → chunk-WH4SKYPX.js} +76 -54
  55. package/dist/chunk-WH4SKYPX.js.map +1 -0
  56. package/dist/{chunk-5AYAZN45.js → chunk-XRSIGVTS.js} +5 -5
  57. package/dist/{chunk-D2EFNQMY.js → chunk-XW3W4PV4.js} +2 -2
  58. package/dist/{chunk-TYIXG4VR.js → chunk-YW52BQSU.js} +2 -2
  59. package/dist/{cli-C6twwe84.d.ts → cli-BQRqR9N-.d.ts} +12 -1
  60. package/dist/cli.d.ts +1 -1
  61. package/dist/cli.js +32 -28
  62. package/dist/compounding/engine.js +5 -4
  63. package/dist/connectors/codex-materialize-runner.js +5 -4
  64. package/dist/connectors/index.js +5 -4
  65. package/dist/consolidation-provenance-check.js +3 -2
  66. package/dist/consolidation-undo.js +2 -1
  67. package/dist/consolidation-undo.js.map +1 -1
  68. package/dist/entity-retrieval.js +5 -4
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.js +39 -36
  71. package/dist/index.js.map +1 -1
  72. package/dist/maintenance/memory-governance.js +6 -4
  73. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -4
  74. package/dist/maintenance/rebuild-memory-projection.js +7 -5
  75. package/dist/namespaces/migrate.js +13 -11
  76. package/dist/namespaces/search.js +8 -6
  77. package/dist/namespaces/storage.d.ts +13 -0
  78. package/dist/namespaces/storage.js +5 -4
  79. package/dist/offline-sync.js +3 -2
  80. package/dist/operator-toolkit.js +16 -14
  81. package/dist/orchestrator.js +21 -19
  82. package/dist/page-versioning.js +2 -1
  83. package/dist/schemas.d.ts +64 -64
  84. package/dist/search/document-scanner.d.ts +11 -7
  85. package/dist/search/document-scanner.js +3 -1
  86. package/dist/search/factory.js +7 -5
  87. package/dist/search/index.js +7 -5
  88. package/dist/search/lancedb-backend.js +4 -2
  89. package/dist/search/meilisearch-backend.js +4 -2
  90. package/dist/search/orama-backend.js +4 -2
  91. package/dist/secure-store/index.js +3 -2
  92. package/dist/semantic-consolidation.js +6 -5
  93. package/dist/semantic-rule-promotion.js +5 -4
  94. package/dist/semantic-rule-verifier.js +5 -4
  95. package/dist/shared-context/manager.d.ts +2 -2
  96. package/dist/storage.d.ts +17 -3
  97. package/dist/storage.js +4 -3
  98. package/dist/transfer/capsule-import.js +3 -2
  99. package/dist/transfer/types.d.ts +12 -12
  100. package/dist/verified-recall.js +5 -4
  101. package/package.json +1 -1
  102. package/src/cli.ts +62 -23
  103. package/src/consolidation-provenance-check.ts +7 -6
  104. package/src/maintenance/memory-governance.ts +47 -7
  105. package/src/namespaces/catalog.test.ts +12 -12
  106. package/src/namespaces/storage.ts +28 -1
  107. package/src/orchestrator.ts +84 -58
  108. package/src/page-versioning.ts +7 -4
  109. package/src/search/document-scanner.test.ts +29 -0
  110. package/src/search/document-scanner.ts +17 -29
  111. package/src/secure-store/secure-fs.ts +19 -5
  112. package/src/secure-store/secure-store.test.ts +28 -0
  113. package/src/storage.ts +42 -43
  114. package/src/training-export/converter.test.ts +19 -0
  115. package/src/training-export/converter.ts +8 -5
  116. package/src/utils/category-dir.ts +10 -4
  117. package/src/utils/path-containment.ts +40 -0
  118. package/dist/chunk-A7EF2XRO.js.map +0 -1
  119. package/dist/chunk-AL4RAJL5.js.map +0 -1
  120. package/dist/chunk-AX5O25EF.js.map +0 -1
  121. package/dist/chunk-HSCJYHYV.js.map +0 -1
  122. package/dist/chunk-ILXTATKK.js.map +0 -1
  123. package/dist/chunk-Q4CAQGKQ.js.map +0 -1
  124. package/dist/chunk-TBLGI2LT.js.map +0 -1
  125. package/dist/chunk-TGOOJCGA.js.map +0 -1
  126. /package/dist/{chunk-2KDQI363.js.map → chunk-2HEZXPYU.js.map} +0 -0
  127. /package/dist/{chunk-F6O7IOS3.js.map → chunk-6JBKHTQD.js.map} +0 -0
  128. /package/dist/{chunk-YYQRVNSV.js.map → chunk-7C4MPEPE.js.map} +0 -0
  129. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-ARV3AUOM.js.map} +0 -0
  130. /package/dist/{chunk-PXVFMQLD.js.map → chunk-BZG2CWOQ.js.map} +0 -0
  131. /package/dist/{chunk-ANJOULTP.js.map → chunk-C7AF236A.js.map} +0 -0
  132. /package/dist/{chunk-FZC2WSDB.js.map → chunk-DOCTITOP.js.map} +0 -0
  133. /package/dist/{chunk-WOQIHC67.js.map → chunk-DQY7NJ5L.js.map} +0 -0
  134. /package/dist/{chunk-NMPEJV5M.js.map → chunk-DSLUOQDY.js.map} +0 -0
  135. /package/dist/{chunk-QXHBWFR3.js.map → chunk-IHG6CC7T.js.map} +0 -0
  136. /package/dist/{chunk-4KDLCMLK.js.map → chunk-IROWLAWG.js.map} +0 -0
  137. /package/dist/{chunk-DFAXGZKI.js.map → chunk-JIX3ZL2J.js.map} +0 -0
  138. /package/dist/{chunk-GY3V3SUI.js.map → chunk-KHGE6PMF.js.map} +0 -0
  139. /package/dist/{chunk-TWAJICBN.js.map → chunk-OHJFJ4HI.js.map} +0 -0
  140. /package/dist/{chunk-WSQG37DV.js.map → chunk-OUWAQVDJ.js.map} +0 -0
  141. /package/dist/{chunk-ZLDUQWT2.js.map → chunk-PWWWLD7D.js.map} +0 -0
  142. /package/dist/{chunk-ZJH723NM.js.map → chunk-Q5ZU3RNY.js.map} +0 -0
  143. /package/dist/{chunk-35HP3TGR.js.map → chunk-ROHLEUTH.js.map} +0 -0
  144. /package/dist/{chunk-5RIRL3XL.js.map → chunk-RS25QOKZ.js.map} +0 -0
  145. /package/dist/{chunk-RQGR3ETH.js.map → chunk-T2AN3BSP.js.map} +0 -0
  146. /package/dist/{chunk-UAU5U5ML.js.map → chunk-UDJLF3BO.js.map} +0 -0
  147. /package/dist/{chunk-5AYAZN45.js.map → chunk-XRSIGVTS.js.map} +0 -0
  148. /package/dist/{chunk-D2EFNQMY.js.map → chunk-XW3W4PV4.js.map} +0 -0
  149. /package/dist/{chunk-TYIXG4VR.js.map → chunk-YW52BQSU.js.map} +0 -0
@@ -7,14 +7,14 @@ import {
7
7
  materializeAfterSemanticConsolidation,
8
8
  parseConsolidationResponse,
9
9
  parseOperatorAwareConsolidationResponse
10
- } from "./chunk-D2EFNQMY.js";
11
- import "./chunk-ANJOULTP.js";
10
+ } from "./chunk-XW3W4PV4.js";
11
+ import "./chunk-C7AF236A.js";
12
12
  import "./chunk-LN4YGHTM.js";
13
13
  import {
14
14
  resolveExtensionsRoot
15
15
  } from "./chunk-JLNBQWZ2.js";
16
16
  import "./chunk-3UXOZBHV.js";
17
- import "./chunk-AX5O25EF.js";
17
+ import "./chunk-VH6EIKVS.js";
18
18
  import "./chunk-M7XQSUBB.js";
19
19
  import "./chunk-5UZXUTVO.js";
20
20
  import "./chunk-J6A3CX5N.js";
@@ -34,16 +34,17 @@ import {
34
34
  isSemanticConsolidationLlmOperator,
35
35
  isValidDerivedFromEntry
36
36
  } from "./chunk-G7D6GZ5J.js";
37
- import "./chunk-ALEPI75L.js";
37
+ import "./chunk-VF4XKTX3.js";
38
38
  import "./chunk-U3PN77QT.js";
39
39
  import "./chunk-4DJQYKMN.js";
40
40
  import "./chunk-EYIEWJNI.js";
41
41
  import "./chunk-JUC24CTX.js";
42
42
  import "./chunk-ZBJMUXZH.js";
43
43
  import "./chunk-2ODBA7MQ.js";
44
- import "./chunk-ILXTATKK.js";
44
+ import "./chunk-J2HSAU72.js";
45
45
  import "./chunk-A6XUJE5D.js";
46
46
  import "./chunk-P7FMDTKL.js";
47
+ import "./chunk-VS2IYZRU.js";
47
48
  import "./chunk-PZ5AY32C.js";
48
49
  export {
49
50
  CONSOLIDATION_OPERATORS,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  promoteSemanticRuleFromMemory,
3
3
  setSemanticRulePromotionTestHooks
4
- } from "./chunk-QXHBWFR3.js";
5
- import "./chunk-AX5O25EF.js";
4
+ } from "./chunk-IHG6CC7T.js";
5
+ import "./chunk-VH6EIKVS.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-J6A3CX5N.js";
@@ -17,12 +17,13 @@ import "./chunk-DM2T26WE.js";
17
17
  import "./chunk-LDXUBPMO.js";
18
18
  import "./chunk-FVQJYWH7.js";
19
19
  import "./chunk-G7D6GZ5J.js";
20
- import "./chunk-ALEPI75L.js";
20
+ import "./chunk-VF4XKTX3.js";
21
21
  import "./chunk-4DJQYKMN.js";
22
22
  import "./chunk-2ODBA7MQ.js";
23
- import "./chunk-ILXTATKK.js";
23
+ import "./chunk-J2HSAU72.js";
24
24
  import "./chunk-A6XUJE5D.js";
25
25
  import "./chunk-P7FMDTKL.js";
26
+ import "./chunk-VS2IYZRU.js";
26
27
  import "./chunk-PZ5AY32C.js";
27
28
  export {
28
29
  promoteSemanticRuleFromMemory,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  compareVerifiedSemanticRuleResults,
3
3
  searchVerifiedSemanticRules
4
- } from "./chunk-NMPEJV5M.js";
5
- import "./chunk-AX5O25EF.js";
4
+ } from "./chunk-DSLUOQDY.js";
5
+ import "./chunk-VH6EIKVS.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-J6A3CX5N.js";
@@ -17,13 +17,14 @@ import "./chunk-DM2T26WE.js";
17
17
  import "./chunk-LDXUBPMO.js";
18
18
  import "./chunk-FVQJYWH7.js";
19
19
  import "./chunk-G7D6GZ5J.js";
20
- import "./chunk-ALEPI75L.js";
20
+ import "./chunk-VF4XKTX3.js";
21
21
  import "./chunk-4DJQYKMN.js";
22
22
  import "./chunk-ZBJMUXZH.js";
23
23
  import "./chunk-2ODBA7MQ.js";
24
- import "./chunk-ILXTATKK.js";
24
+ import "./chunk-J2HSAU72.js";
25
25
  import "./chunk-A6XUJE5D.js";
26
26
  import "./chunk-P7FMDTKL.js";
27
+ import "./chunk-VS2IYZRU.js";
27
28
  import "./chunk-PZ5AY32C.js";
28
29
  export {
29
30
  compareVerifiedSemanticRuleResults,
@@ -22,9 +22,9 @@ declare const SharedFeedbackEntrySchema: z.ZodObject<{
22
22
  date: string;
23
23
  agent: string;
24
24
  reason: string;
25
+ confidence?: number | undefined;
25
26
  workflow?: string | undefined;
26
27
  tags?: string[] | undefined;
27
- confidence?: number | undefined;
28
28
  severity?: "high" | "low" | "medium" | undefined;
29
29
  outcome?: string | undefined;
30
30
  refs?: string[] | undefined;
@@ -36,9 +36,9 @@ declare const SharedFeedbackEntrySchema: z.ZodObject<{
36
36
  date: string;
37
37
  agent: string;
38
38
  reason: string;
39
+ confidence?: number | undefined;
39
40
  workflow?: string | undefined;
40
41
  tags?: string[] | undefined;
41
- confidence?: number | undefined;
42
42
  severity?: "high" | "low" | "medium" | undefined;
43
43
  outcome?: string | undefined;
44
44
  refs?: string[] | undefined;
package/dist/storage.d.ts CHANGED
@@ -375,6 +375,19 @@ declare class StorageManager {
375
375
  loadAliases(): Promise<void>;
376
376
  private loadAliasesSync;
377
377
  ensureDirectories(): Promise<void>;
378
+ /**
379
+ * Resolve the on-disk write path for a memory of the given category, creating
380
+ * the target directory. Category routing goes through the shared
381
+ * `getCategoryDir()` chokepoint (utils/category-dir.ts → CATEGORY_DIR_MAP) so
382
+ * decision/preference/moment/etc. outputs land in their dedicated dirs
383
+ * (`decisions/`, `preferences/`, ...) instead of collapsing into `facts/`
384
+ * (issue #1546; CLAUDE.md rule 39). `correction` keeps its historical flat
385
+ * layout (no `<date>` subdir) as the corrections pipeline expects; every other
386
+ * category — including `fact`/`entity`, which fall back to `facts/` — is dated
387
+ * as `<dir>/<date>/`. Read/scan/reindex already iterate every category dir
388
+ * (RECALL_FALLBACK_DIRS; QMD scans baseDir recursively), so writes stay found.
389
+ */
390
+ private resolveCategoryWritePath;
378
391
  writeMemory(category: MemoryCategory, content: string, options?: {
379
392
  actor?: string;
380
393
  confidence?: number;
@@ -521,9 +534,10 @@ declare class StorageManager {
521
534
  * Read all memories from the cold tier by scanning the entire cold/ root
522
535
  * tree. Previously this only scanned cold/facts/ and cold/corrections/, but
523
536
  * structuredAttributes can appear on any MemoryCategory (preference, decision,
524
- * entity, etc.). Although buildTierMemoryPath currently routes all
525
- * non-correction, non-artifact memories to cold/facts/, scanning the full
526
- * coldRoot ensures correctness if that routing ever changes and guards against
537
+ * entity, etc.). buildTierMemoryPath now routes each category to its own
538
+ * cold/<dir>/ subtree via the shared categoryDirName() chokepoint (issue
539
+ * #1546), so cold decisions/preferences/... live outside cold/facts/.
540
+ * Scanning the full coldRoot covers every category dir and guards against
527
541
  * files placed in unexpected subdirectories during manual operations or future
528
542
  * refactors.
529
543
  *
package/dist/storage.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  parseEntityFile,
10
10
  serializeEntityFile,
11
11
  stripAttributesSuffix
12
- } from "./chunk-AX5O25EF.js";
12
+ } from "./chunk-VH6EIKVS.js";
13
13
  import "./chunk-M7XQSUBB.js";
14
14
  import "./chunk-5UZXUTVO.js";
15
15
  import "./chunk-J6A3CX5N.js";
@@ -24,12 +24,13 @@ import "./chunk-DM2T26WE.js";
24
24
  import "./chunk-LDXUBPMO.js";
25
25
  import "./chunk-FVQJYWH7.js";
26
26
  import "./chunk-G7D6GZ5J.js";
27
- import "./chunk-ALEPI75L.js";
27
+ import "./chunk-VF4XKTX3.js";
28
28
  import "./chunk-4DJQYKMN.js";
29
29
  import "./chunk-2ODBA7MQ.js";
30
- import "./chunk-ILXTATKK.js";
30
+ import "./chunk-J2HSAU72.js";
31
31
  import "./chunk-A6XUJE5D.js";
32
32
  import "./chunk-P7FMDTKL.js";
33
+ import "./chunk-VS2IYZRU.js";
33
34
  import "./chunk-PZ5AY32C.js";
34
35
  export {
35
36
  ContentHashIndex,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  importCapsule
3
- } from "../chunk-DHGSZ3UD.js";
4
- import "../chunk-ALEPI75L.js";
3
+ } from "../chunk-ARV3AUOM.js";
4
+ import "../chunk-VF4XKTX3.js";
5
5
  import "../chunk-WEHSQBFR.js";
6
6
  import "../chunk-X7Y7WX73.js";
7
7
  import "../chunk-BJMBJZ2Y.js";
@@ -9,6 +9,7 @@ import "../chunk-UKJAGEXH.js";
9
9
  import "../chunk-FP2373TW.js";
10
10
  import "../chunk-GCGJW34D.js";
11
11
  import "../chunk-A6XUJE5D.js";
12
+ import "../chunk-VS2IYZRU.js";
12
13
  import "../chunk-PZ5AY32C.js";
13
14
  export {
14
15
  importCapsule
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
313
313
  peerProfiles: boolean;
314
314
  }>;
315
315
  }, "strip", z.ZodTypeAny, {
316
- schemaVersion: string;
317
316
  includes: {
318
317
  procedural: boolean;
319
318
  taxonomy: boolean;
320
319
  identityAnchors: boolean;
321
320
  peerProfiles: boolean;
322
321
  };
322
+ schemaVersion: string;
323
323
  id: string;
324
324
  description: string;
325
325
  version: string;
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
334
334
  directAnswerEnabled: boolean;
335
335
  };
336
336
  }, {
337
- schemaVersion: string;
338
337
  includes: {
339
338
  procedural: boolean;
340
339
  taxonomy: boolean;
341
340
  identityAnchors: boolean;
342
341
  peerProfiles: boolean;
343
342
  };
343
+ schemaVersion: string;
344
344
  id: string;
345
345
  description: string;
346
346
  version: string;
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
464
464
  peerProfiles: boolean;
465
465
  }>;
466
466
  }, "strip", z.ZodTypeAny, {
467
- schemaVersion: string;
468
467
  includes: {
469
468
  procedural: boolean;
470
469
  taxonomy: boolean;
471
470
  identityAnchors: boolean;
472
471
  peerProfiles: boolean;
473
472
  };
473
+ schemaVersion: string;
474
474
  id: string;
475
475
  description: string;
476
476
  version: string;
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
485
485
  directAnswerEnabled: boolean;
486
486
  };
487
487
  }, {
488
- schemaVersion: string;
489
488
  includes: {
490
489
  procedural: boolean;
491
490
  taxonomy: boolean;
492
491
  identityAnchors: boolean;
493
492
  peerProfiles: boolean;
494
493
  };
494
+ schemaVersion: string;
495
495
  id: string;
496
496
  description: string;
497
497
  version: string;
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
518
518
  pluginVersion: string;
519
519
  includesTranscripts: boolean;
520
520
  capsule: {
521
- schemaVersion: string;
522
521
  includes: {
523
522
  procedural: boolean;
524
523
  taxonomy: boolean;
525
524
  identityAnchors: boolean;
526
525
  peerProfiles: boolean;
527
526
  };
527
+ schemaVersion: string;
528
528
  id: string;
529
529
  description: string;
530
530
  version: string;
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
551
551
  pluginVersion: string;
552
552
  includesTranscripts: boolean;
553
553
  capsule: {
554
- schemaVersion: string;
555
554
  includes: {
556
555
  procedural: boolean;
557
556
  taxonomy: boolean;
558
557
  identityAnchors: boolean;
559
558
  peerProfiles: boolean;
560
559
  };
560
+ schemaVersion: string;
561
561
  id: string;
562
562
  description: string;
563
563
  version: string;
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
683
683
  peerProfiles: boolean;
684
684
  }>;
685
685
  }, "strip", z.ZodTypeAny, {
686
- schemaVersion: string;
687
686
  includes: {
688
687
  procedural: boolean;
689
688
  taxonomy: boolean;
690
689
  identityAnchors: boolean;
691
690
  peerProfiles: boolean;
692
691
  };
692
+ schemaVersion: string;
693
693
  id: string;
694
694
  description: string;
695
695
  version: string;
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
704
704
  directAnswerEnabled: boolean;
705
705
  };
706
706
  }, {
707
- schemaVersion: string;
708
707
  includes: {
709
708
  procedural: boolean;
710
709
  taxonomy: boolean;
711
710
  identityAnchors: boolean;
712
711
  peerProfiles: boolean;
713
712
  };
713
+ schemaVersion: string;
714
714
  id: string;
715
715
  description: string;
716
716
  version: string;
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
737
737
  pluginVersion: string;
738
738
  includesTranscripts: boolean;
739
739
  capsule: {
740
- schemaVersion: string;
741
740
  includes: {
742
741
  procedural: boolean;
743
742
  taxonomy: boolean;
744
743
  identityAnchors: boolean;
745
744
  peerProfiles: boolean;
746
745
  };
746
+ schemaVersion: string;
747
747
  id: string;
748
748
  description: string;
749
749
  version: string;
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
770
770
  pluginVersion: string;
771
771
  includesTranscripts: boolean;
772
772
  capsule: {
773
- schemaVersion: string;
774
773
  includes: {
775
774
  procedural: boolean;
776
775
  taxonomy: boolean;
777
776
  identityAnchors: boolean;
778
777
  peerProfiles: boolean;
779
778
  };
779
+ schemaVersion: string;
780
780
  id: string;
781
781
  description: string;
782
782
  version: string;
@@ -815,13 +815,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
815
815
  pluginVersion: string;
816
816
  includesTranscripts: boolean;
817
817
  capsule: {
818
- schemaVersion: string;
819
818
  includes: {
820
819
  procedural: boolean;
821
820
  taxonomy: boolean;
822
821
  identityAnchors: boolean;
823
822
  peerProfiles: boolean;
824
823
  };
824
+ schemaVersion: string;
825
825
  id: string;
826
826
  description: string;
827
827
  version: string;
@@ -854,13 +854,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
854
854
  pluginVersion: string;
855
855
  includesTranscripts: boolean;
856
856
  capsule: {
857
- schemaVersion: string;
858
857
  includes: {
859
858
  procedural: boolean;
860
859
  taxonomy: boolean;
861
860
  identityAnchors: boolean;
862
861
  peerProfiles: boolean;
863
862
  };
863
+ schemaVersion: string;
864
864
  id: string;
865
865
  description: string;
866
866
  version: string;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-TYIXG4VR.js";
4
+ } from "./chunk-YW52BQSU.js";
5
5
  import "./chunk-HQ6NIBL6.js";
6
- import "./chunk-AX5O25EF.js";
6
+ import "./chunk-VH6EIKVS.js";
7
7
  import "./chunk-M7XQSUBB.js";
8
8
  import "./chunk-5UZXUTVO.js";
9
9
  import "./chunk-J6A3CX5N.js";
@@ -18,13 +18,14 @@ import "./chunk-DM2T26WE.js";
18
18
  import "./chunk-LDXUBPMO.js";
19
19
  import "./chunk-FVQJYWH7.js";
20
20
  import "./chunk-G7D6GZ5J.js";
21
- import "./chunk-ALEPI75L.js";
21
+ import "./chunk-VF4XKTX3.js";
22
22
  import "./chunk-4DJQYKMN.js";
23
23
  import "./chunk-ZBJMUXZH.js";
24
24
  import "./chunk-2ODBA7MQ.js";
25
- import "./chunk-ILXTATKK.js";
25
+ import "./chunk-J2HSAU72.js";
26
26
  import "./chunk-A6XUJE5D.js";
27
27
  import "./chunk-P7FMDTKL.js";
28
+ import "./chunk-VS2IYZRU.js";
28
29
  import "./chunk-PZ5AY32C.js";
29
30
  export {
30
31
  compareVerifiedEpisodeResults,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.663",
3
+ "version": "9.3.665",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cli.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
- import { access, readFile, readdir, unlink } from "node:fs/promises";
3
+ import { access, lstat, readFile, readdir, realpath, unlink } from "node:fs/promises";
4
4
  import { createHash } from "node:crypto";
5
5
  import type { Readable, Writable } from "node:stream";
6
6
  import type { Orchestrator } from "./orchestrator.js";
@@ -215,6 +215,8 @@ import {
215
215
  } from "./policy-runtime.js";
216
216
  import { resolveHomeDir } from "./runtime/env.js";
217
217
  import { expandTildePath } from "./utils/path.js";
218
+ import { RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
219
+ import { assertPathInsideRoot } from "./utils/path-containment.js";
218
220
  import { convertMemoriesToRecords } from "./training-export/converter.js";
219
221
  import { parseStrictCliDate as parseStrictCliDateShared } from "./training-export/date-parse.js";
220
222
  import { getTrainingExportAdapter, listTrainingExportAdapters } from "./training-export/registry.js";
@@ -3320,32 +3322,69 @@ export async function resolveMemoryDirForNamespace(
3320
3322
  }
3321
3323
 
3322
3324
  /**
3323
- * Walk `memoryDir/{facts,corrections}` recursively and invoke `visit` for
3324
- * every `*.md` file. Intentionally swallows per-directory errors so a missing
3325
- * subdir reads as empty. Shared primitive for `listMemoryMarkdownFilePaths`,
3326
- * `readAllMemoryFiles`, and any future walker that needs the same roots +
3327
- * `.md` filter.
3325
+ * Walk every recall category directory under `memoryDir` recursively and invoke
3326
+ * `visit` for every `*.md` file. The directory set is `RECALL_FALLBACK_DIRS`
3327
+ * (single source of truth), so newly-routed categories (decisions/, ...) are
3328
+ * covered without touching this walker again (#1546). Swallows per-directory
3329
+ * errors so a missing subdir reads as empty. Shared primitive for
3330
+ * `listMemoryMarkdownFilePaths`, `readAllMemoryFiles`, and future walkers.
3331
+ *
3332
+ * Symlink/containment hardening (mirrors `scanDir` in
3333
+ * search/document-scanner.ts): downstream consumers include `readAllMemoryFiles`
3334
+ * → the `dedupe-exact` / `dedupe-aggressive` commands, which `unlink()` files.
3335
+ * Because this walker scans EVERY category root, a symlinked category dir
3336
+ * (e.g. `decisions/` → outside `memoryDir`) could otherwise redirect the walk —
3337
+ * and a destructive dedupe delete — outside the memory store. We resolve the
3338
+ * memory root once, skip symlinked dirs/entries, and assert every realpath stays
3339
+ * inside the root before descending or visiting. A single poisoned entry is
3340
+ * skipped (logged), never aborting a legitimate dedupe run.
3328
3341
  */
3329
3342
  async function walkMemoryMarkdownFiles(
3330
3343
  memoryDir: string,
3331
3344
  visit: (fullPath: string) => void | Promise<void>,
3332
3345
  ): Promise<void> {
3333
- const roots = [path.join(memoryDir, "facts"), path.join(memoryDir, "corrections")];
3346
+ let memoryRootReal: string;
3347
+ try {
3348
+ memoryRootReal = await realpath(memoryDir);
3349
+ } catch {
3350
+ return; // memoryDir itself does not exist — nothing to walk.
3351
+ }
3352
+
3353
+ const skip = (target: string, err: unknown): void => {
3354
+ // ENOENT (optional dir absent) is silent; containment/other errors log.
3355
+ if (!(err instanceof Error && /ENOENT/.test(err.message))) {
3356
+ console.debug(`walkMemoryMarkdownFiles: skipping ${target}: ${err instanceof Error ? err.message : String(err)}`);
3357
+ }
3358
+ };
3334
3359
 
3335
3360
  const walk = async (dir: string): Promise<void> => {
3336
- let entries: Array<{ isDirectory(): boolean; isFile(): boolean; name: string | Buffer }>;
3361
+ // Reject symlinked / non-directory roots and anything resolving outside the
3362
+ // memory store before reading it.
3337
3363
  try {
3338
- entries = (await readdir(dir, { withFileTypes: true })) as Array<{
3339
- isDirectory(): boolean;
3340
- isFile(): boolean;
3341
- name: string | Buffer;
3342
- }>;
3364
+ const dirStat = await lstat(dir);
3365
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
3366
+ assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
3367
+ } catch (err) {
3368
+ skip(dir, err);
3369
+ return;
3370
+ }
3371
+
3372
+ let entries: Array<{ isDirectory(): boolean; isFile(): boolean; isSymbolicLink(): boolean; name: string | Buffer }>;
3373
+ try {
3374
+ entries = (await readdir(dir, { withFileTypes: true })) as typeof entries;
3343
3375
  } catch {
3344
3376
  return;
3345
3377
  }
3346
3378
  for (const entry of entries) {
3379
+ if (entry.isSymbolicLink()) continue; // never follow symlinked entries
3347
3380
  const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
3348
3381
  const fullPath = path.join(dir, entryName);
3382
+ try {
3383
+ assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
3384
+ } catch (err) {
3385
+ skip(fullPath, err); // poisoned entry — skip, keep walking the rest.
3386
+ continue;
3387
+ }
3349
3388
  if (entry.isDirectory()) {
3350
3389
  await walk(fullPath);
3351
3390
  continue;
@@ -3355,22 +3394,22 @@ async function walkMemoryMarkdownFiles(
3355
3394
  }
3356
3395
  };
3357
3396
 
3358
- for (const root of roots) {
3397
+ for (const root of RECALL_FALLBACK_DIRS.map((dir) => path.join(memoryDir, dir))) {
3359
3398
  await walk(root);
3360
3399
  }
3361
3400
  }
3362
3401
 
3363
3402
  /**
3364
- * List absolute paths of every `*.md` file under `memoryDir/{facts,corrections}`.
3365
- * Used by the bulk-import CLI to derive a per-batch `memoriesCreated` count
3366
- * via set-subtraction of "paths after extraction" against "paths before
3367
- * extraction". Caveat: the extraction queue is shared across sessions, so
3368
- * concurrent organic extractions that write memories between the two
3369
- * snapshots will still inflate the reported count. Filename-set diff at
3370
- * least correctly ignores pre-existing files and files that were deleted
3371
- * while the batch ran.
3403
+ * List absolute paths of every `*.md` file under each recall category directory
3404
+ * of `memoryDir` (facts/, corrections/, decisions/, ...; see
3405
+ * `walkMemoryMarkdownFiles`). Used by the bulk-import CLI to derive a per-batch
3406
+ * `memoriesCreated` count via set-subtraction of "paths after extraction"
3407
+ * against "paths before extraction". Caveat: the extraction queue is shared
3408
+ * across sessions, so concurrent organic extractions between the two snapshots
3409
+ * can still inflate the count; the set diff at least ignores pre-existing and
3410
+ * deleted files. Exported for category-dir coverage tests (#1546).
3372
3411
  */
3373
- async function listMemoryMarkdownFilePaths(memoryDir: string): Promise<string[]> {
3412
+ export async function listMemoryMarkdownFilePaths(memoryDir: string): Promise<string[]> {
3374
3413
  const paths: string[] = [];
3375
3414
  await walkMemoryMarkdownFiles(memoryDir, (fullPath) => {
3376
3415
  paths.push(fullPath);
@@ -29,6 +29,7 @@ import {
29
29
  // review, cursor Medium) so a future key-format change stays in
30
30
  // lock-step with the doctor scan.
31
31
  import { sidecarKey } from "./page-versioning.js";
32
+ import { RECALL_FALLBACK_DIRS } from "./utils/category-dir.js";
32
33
 
33
34
  /**
34
35
  * Regex to spot a `derived_via: <value>` line in the raw YAML frontmatter
@@ -490,14 +491,14 @@ export async function runConsolidationProvenanceCheck(options: {
490
491
 
491
492
  // Parse-failure detection (PR #634 round-4 review, codex P2):
492
493
  // `readAllMemories()` silently drops files whose frontmatter
493
- // doesn't parse. Walk the facts/ and corrections/ directories for
494
- // `.md` files that DO reference provenance frontmatter but didn't
495
- // come back from the reader — those are the corruption cases the
496
- // doctor is meant to surface.
494
+ // doesn't parse. Walk every recall category directory for `.md` files
495
+ // that DO reference provenance frontmatter but didn't come back from the
496
+ // reader — those are the corruption cases the doctor is meant to surface.
497
+ // Uses RECALL_FALLBACK_DIRS (the single source of truth) so newly-routed
498
+ // categories (decisions/, preferences/, ...) are scanned too (#1546).
497
499
  try {
498
500
  const seenPaths = new Set(memories.map((m) => m.path));
499
- const scanRoots = ["facts", "corrections", "procedures", "reasoning-traces"];
500
- for (const rootName of scanRoots) {
501
+ for (const rootName of RECALL_FALLBACK_DIRS) {
501
502
  const rootPath = path.join(memoryDir, rootName);
502
503
  for await (const file of walkMarkdownFiles(rootPath, memoryDir)) {
503
504
  if (seenPaths.has(file)) continue;
@@ -1,8 +1,10 @@
1
1
  import path from "node:path";
2
- import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
2
+ import { lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
3
3
  import { StorageManager } from "../storage.js";
4
4
  import { decideLifecycleTransition } from "../lifecycle.js";
5
5
  import type { MemoryFile, MemoryStatus } from "../types.js";
6
+ import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
7
+ import { assertPathInsideRoot } from "../utils/path-containment.js";
6
8
 
7
9
  export type MemoryGovernanceMode = "shadow" | "apply";
8
10
  export type MemoryGovernanceReasonCode =
@@ -337,23 +339,42 @@ function buildExplicitCaptureReviewEntries(
337
339
  }));
338
340
  }
339
341
 
340
- async function listMarkdownFiles(root: string): Promise<string[]> {
342
+ /**
343
+ * List every `*.md` under `root`, refusing to follow symlinked directories or
344
+ * entries that resolve outside `containmentRoot` (a realpath-resolved memory
345
+ * store root). Mirrors the containment guard in consolidation-provenance's
346
+ * walkMarkdownFiles / the CLI walker, reusing the shared assertPathInsideRoot
347
+ * (rule 22). Without this, a symlinked category dir (e.g. decisions/ → outside
348
+ * memoryDir) would be walked and its files surfaced by the malformed-import
349
+ * governance sweep — an out-of-store info leak.
350
+ */
351
+ async function listMarkdownFiles(root: string, containmentRoot: string): Promise<string[]> {
341
352
  const files: string[] = [];
342
353
  const walk = async (dir: string) => {
343
354
  try {
355
+ const dirStat = await lstat(dir);
356
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) return;
357
+ assertPathInsideRoot(containmentRoot, await realpath(dir), dir);
344
358
  const entries = await readdir(dir, { withFileTypes: true });
345
359
  for (const entry of entries) {
360
+ if (entry.isSymbolicLink()) continue; // never follow symlinked entries
346
361
  const fullPath = path.join(dir, entry.name);
347
362
  if (entry.isDirectory()) {
348
363
  await walk(fullPath);
349
364
  continue;
350
365
  }
351
366
  if (entry.isFile() && entry.name.endsWith(".md")) {
367
+ try {
368
+ assertPathInsideRoot(containmentRoot, await realpath(fullPath), fullPath);
369
+ } catch {
370
+ continue; // poisoned entry escaping the root — skip it.
371
+ }
352
372
  files.push(fullPath);
353
373
  }
354
374
  }
355
375
  } catch {
356
- // Directory may not exist yet.
376
+ // Absent dir (ENOENT), symlinked/out-of-root dir, or containment
377
+ // violation — skip this subtree without aborting the sweep.
357
378
  }
358
379
  };
359
380
 
@@ -372,10 +393,29 @@ async function buildMalformedImportEntries(
372
393
  candidateFiles?: string[],
373
394
  ): Promise<MemoryGovernanceReviewQueueEntry[]> {
374
395
  const parsedPaths = new Set(parsedMemories.map((memory) => memory.path));
375
- const filesToInspect = candidateFiles ?? [
376
- ...await listMarkdownFiles(path.join(memoryDir, "facts")),
377
- ...await listMarkdownFiles(path.join(memoryDir, "corrections")),
378
- ];
396
+ // Inspect every recall category directory (RECALL_FALLBACK_DIRS — the single
397
+ // source of truth) so malformed files under newly-routed categories
398
+ // (decisions/, preferences/, ...) are surfaced too, not just facts/ (#1546).
399
+ // Resolve the containment root (realpath) once and thread it into the walker
400
+ // so a symlinked category dir can't escape memoryDir. If memoryDir itself
401
+ // can't be resolved, there is nothing valid to inspect.
402
+ let containmentRoot: string | null = null;
403
+ try {
404
+ containmentRoot = await realpath(memoryDir);
405
+ } catch {
406
+ containmentRoot = null;
407
+ }
408
+ const filesToInspect =
409
+ candidateFiles ??
410
+ (containmentRoot === null
411
+ ? []
412
+ : (
413
+ await Promise.all(
414
+ RECALL_FALLBACK_DIRS.map((dir) =>
415
+ listMarkdownFiles(path.join(memoryDir, dir), containmentRoot as string),
416
+ ),
417
+ )
418
+ ).flat());
379
419
  const entries: MemoryGovernanceReviewQueueEntry[] = [];
380
420
 
381
421
  for (const filePath of filesToInspect) {