@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
@@ -104,9 +104,9 @@ declare const recallRequestSchema: z.ZodObject<{
104
104
  includeLowConfidence: z.ZodOptional<z.ZodBoolean>;
105
105
  }, "strip", z.ZodTypeAny, {
106
106
  query: string;
107
+ namespace?: string | undefined;
107
108
  sessionKey?: string | undefined;
108
109
  tags?: string[] | undefined;
109
- namespace?: string | undefined;
110
110
  topK?: number | undefined;
111
111
  mode?: "auto" | "no_recall" | "minimal" | "full" | "graph_mode" | undefined;
112
112
  cwd?: string | undefined;
@@ -125,9 +125,9 @@ declare const recallRequestSchema: z.ZodObject<{
125
125
  tagMatch?: "all" | "any" | undefined;
126
126
  }, {
127
127
  query: string;
128
+ namespace?: string | undefined;
128
129
  sessionKey?: string | undefined;
129
130
  tags?: string[] | undefined;
130
- namespace?: string | undefined;
131
131
  topK?: number | undefined;
132
132
  mode?: "auto" | "no_recall" | "minimal" | "full" | "graph_mode" | undefined;
133
133
  cwd?: string | undefined;
@@ -149,11 +149,11 @@ declare const recallExplainRequestSchema: z.ZodObject<{
149
149
  sessionKey: z.ZodOptional<z.ZodString>;
150
150
  namespace: z.ZodOptional<z.ZodString>;
151
151
  }, "strip", z.ZodTypeAny, {
152
- sessionKey?: string | undefined;
153
152
  namespace?: string | undefined;
154
- }, {
155
153
  sessionKey?: string | undefined;
154
+ }, {
156
155
  namespace?: string | undefined;
156
+ sessionKey?: string | undefined;
157
157
  }>;
158
158
  /**
159
159
  * Standalone "set coding context" request. Used by the HTTP endpoint
@@ -365,30 +365,30 @@ declare const memoryStoreRequestSchema: z.ZodObject<{
365
365
  projectTag: z.ZodOptional<z.ZodString>;
366
366
  }, "strip", z.ZodTypeAny, {
367
367
  content: string;
368
+ namespace?: string | undefined;
369
+ category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
370
+ confidence?: number | undefined;
371
+ ttl?: string | undefined;
368
372
  schemaVersion?: number | undefined;
369
373
  sessionKey?: string | undefined;
370
374
  tags?: string[] | undefined;
371
375
  dryRun?: boolean | undefined;
372
- namespace?: string | undefined;
373
- category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
374
- confidence?: number | undefined;
375
376
  entityRef?: string | undefined;
376
- ttl?: string | undefined;
377
377
  sourceReason?: string | undefined;
378
378
  cwd?: string | undefined;
379
379
  idempotencyKey?: string | undefined;
380
380
  projectTag?: string | undefined;
381
381
  }, {
382
382
  content: string;
383
+ namespace?: string | undefined;
384
+ category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
385
+ confidence?: number | undefined;
386
+ ttl?: string | undefined;
383
387
  schemaVersion?: number | undefined;
384
388
  sessionKey?: string | undefined;
385
389
  tags?: string[] | undefined;
386
390
  dryRun?: boolean | undefined;
387
- namespace?: string | undefined;
388
- category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
389
- confidence?: number | undefined;
390
391
  entityRef?: string | undefined;
391
- ttl?: string | undefined;
392
392
  sourceReason?: string | undefined;
393
393
  cwd?: string | undefined;
394
394
  idempotencyKey?: string | undefined;
@@ -411,30 +411,30 @@ declare const suggestionSubmitRequestSchema: z.ZodObject<{
411
411
  projectTag: z.ZodOptional<z.ZodString>;
412
412
  }, "strip", z.ZodTypeAny, {
413
413
  content: string;
414
+ namespace?: string | undefined;
415
+ category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
416
+ confidence?: number | undefined;
417
+ ttl?: string | undefined;
414
418
  schemaVersion?: number | undefined;
415
419
  sessionKey?: string | undefined;
416
420
  tags?: string[] | undefined;
417
421
  dryRun?: boolean | undefined;
418
- namespace?: string | undefined;
419
- category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
420
- confidence?: number | undefined;
421
422
  entityRef?: string | undefined;
422
- ttl?: string | undefined;
423
423
  sourceReason?: string | undefined;
424
424
  cwd?: string | undefined;
425
425
  idempotencyKey?: string | undefined;
426
426
  projectTag?: string | undefined;
427
427
  }, {
428
428
  content: string;
429
+ namespace?: string | undefined;
430
+ category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
431
+ confidence?: number | undefined;
432
+ ttl?: string | undefined;
429
433
  schemaVersion?: number | undefined;
430
434
  sessionKey?: string | undefined;
431
435
  tags?: string[] | undefined;
432
436
  dryRun?: boolean | undefined;
433
- namespace?: string | undefined;
434
- category?: "fact" | "preference" | "correction" | "entity" | "decision" | "relationship" | "principle" | "commitment" | "moment" | "skill" | "rule" | "procedure" | "reasoning_trace" | undefined;
435
- confidence?: number | undefined;
436
437
  entityRef?: string | undefined;
437
- ttl?: string | undefined;
438
438
  sourceReason?: string | undefined;
439
439
  cwd?: string | undefined;
440
440
  idempotencyKey?: string | undefined;
@@ -468,18 +468,18 @@ declare const trustZonePromoteRequestSchema: z.ZodObject<{
468
468
  recordId: string;
469
469
  targetZone: "working" | "trusted";
470
470
  promotionReason: string;
471
+ namespace?: string | undefined;
471
472
  recordedAt?: string | undefined;
472
473
  summary?: string | undefined;
473
474
  dryRun?: boolean | undefined;
474
- namespace?: string | undefined;
475
475
  }, {
476
476
  recordId: string;
477
477
  targetZone: "working" | "trusted";
478
478
  promotionReason: string;
479
+ namespace?: string | undefined;
479
480
  recordedAt?: string | undefined;
480
481
  summary?: string | undefined;
481
482
  dryRun?: boolean | undefined;
482
- namespace?: string | undefined;
483
483
  }>;
484
484
  declare const trustZoneDemoSeedRequestSchema: z.ZodObject<{
485
485
  scenario: z.ZodOptional<z.ZodString>;
@@ -487,14 +487,14 @@ declare const trustZoneDemoSeedRequestSchema: z.ZodObject<{
487
487
  dryRun: z.ZodOptional<z.ZodBoolean>;
488
488
  namespace: z.ZodOptional<z.ZodString>;
489
489
  }, "strip", z.ZodTypeAny, {
490
+ namespace?: string | undefined;
490
491
  recordedAt?: string | undefined;
491
492
  dryRun?: boolean | undefined;
492
- namespace?: string | undefined;
493
493
  scenario?: string | undefined;
494
494
  }, {
495
+ namespace?: string | undefined;
495
496
  recordedAt?: string | undefined;
496
497
  dryRun?: boolean | undefined;
497
- namespace?: string | undefined;
498
498
  scenario?: string | undefined;
499
499
  }>;
500
500
  declare const lcmSearchRequestSchema: z.ZodObject<{
@@ -505,15 +505,15 @@ declare const lcmSearchRequestSchema: z.ZodObject<{
505
505
  limit: z.ZodOptional<z.ZodNumber>;
506
506
  }, "strip", z.ZodTypeAny, {
507
507
  query: string;
508
- sessionKey?: string | undefined;
509
508
  namespace?: string | undefined;
510
509
  limit?: number | undefined;
510
+ sessionKey?: string | undefined;
511
511
  sessionPrefix?: string | undefined;
512
512
  }, {
513
513
  query: string;
514
- sessionKey?: string | undefined;
515
514
  namespace?: string | undefined;
516
515
  limit?: number | undefined;
516
+ sessionKey?: string | undefined;
517
517
  sessionPrefix?: string | undefined;
518
518
  }>;
519
519
  declare const lcmCompactionFlushRequestSchema: z.ZodObject<{
@@ -548,14 +548,14 @@ declare const daySummaryRequestSchema: z.ZodObject<{
548
548
  namespace: z.ZodOptional<z.ZodString>;
549
549
  timeZone: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
550
550
  }, "strip", z.ZodTypeAny, {
551
+ namespace?: string | undefined;
551
552
  sessionKey?: string | undefined;
552
553
  timeZone?: string | undefined;
553
- namespace?: string | undefined;
554
554
  memories?: string | undefined;
555
555
  }, {
556
+ namespace?: string | undefined;
556
557
  sessionKey?: string | undefined;
557
558
  timeZone?: string | undefined;
558
- namespace?: string | undefined;
559
559
  memories?: string | undefined;
560
560
  }>;
561
561
  declare const capsuleExportRequestSchema: z.ZodObject<{
@@ -603,11 +603,11 @@ declare const capsuleListRequestSchema: z.ZodObject<{
603
603
  namespace: z.ZodOptional<z.ZodString>;
604
604
  sessionKey: z.ZodOptional<z.ZodString>;
605
605
  }, "strip", z.ZodTypeAny, {
606
- sessionKey?: string | undefined;
607
606
  namespace?: string | undefined;
608
- }, {
609
607
  sessionKey?: string | undefined;
608
+ }, {
610
609
  namespace?: string | undefined;
610
+ sessionKey?: string | undefined;
611
611
  }>;
612
612
  declare const offlineSyncSnapshotRequestSchema: z.ZodObject<{
613
613
  namespace: z.ZodOptional<z.ZodString>;
@@ -748,11 +748,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
748
748
  safety: z.ZodEffects<z.ZodNullable<z.ZodOptional<z.ZodEnum<["safe", "requires-review", "blocked"]>>>, NonNullable<"blocked" | "safe" | "requires-review"> | undefined, "blocked" | "safe" | "requires-review" | null | undefined>;
749
749
  safetyReasons: z.ZodEffects<z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[] | undefined, string[] | null | undefined>;
750
750
  }, "strict", z.ZodTypeAny, {
751
+ confidence?: number | undefined;
751
752
  source?: string | undefined;
752
753
  stale?: boolean | undefined;
753
754
  created?: string | undefined;
754
755
  updated?: string | undefined;
755
- confidence?: number | undefined;
756
756
  scope?: string | undefined;
757
757
  retrievalReason?: string | undefined;
758
758
  safety?: NonNullable<"blocked" | "safe" | "requires-review"> | undefined;
@@ -762,11 +762,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
762
762
  safetyReasons?: string[] | undefined;
763
763
  userContextScopes?: string[] | undefined;
764
764
  }, {
765
+ confidence?: number | null | undefined;
765
766
  source?: string | null | undefined;
766
767
  stale?: boolean | null | undefined;
767
768
  created?: string | null | undefined;
768
769
  updated?: string | null | undefined;
769
- confidence?: number | null | undefined;
770
770
  scope?: string | null | undefined;
771
771
  retrievalReason?: string | null | undefined;
772
772
  safety?: "blocked" | "safe" | "requires-review" | null | undefined;
@@ -776,11 +776,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
776
776
  safetyReasons?: string[] | null | undefined;
777
777
  userContextScopes?: string[] | null | undefined;
778
778
  }>, "many">>>, {
779
+ confidence?: number | undefined;
779
780
  source?: string | undefined;
780
781
  stale?: boolean | undefined;
781
782
  created?: string | undefined;
782
783
  updated?: string | undefined;
783
- confidence?: number | undefined;
784
784
  scope?: string | undefined;
785
785
  retrievalReason?: string | undefined;
786
786
  safety?: NonNullable<"blocked" | "safe" | "requires-review"> | undefined;
@@ -790,11 +790,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
790
790
  safetyReasons?: string[] | undefined;
791
791
  userContextScopes?: string[] | undefined;
792
792
  }[] | undefined, {
793
+ confidence?: number | null | undefined;
793
794
  source?: string | null | undefined;
794
795
  stale?: boolean | null | undefined;
795
796
  created?: string | null | undefined;
796
797
  updated?: string | null | undefined;
797
- confidence?: number | null | undefined;
798
798
  scope?: string | null | undefined;
799
799
  retrievalReason?: string | null | undefined;
800
800
  safety?: "blocked" | "safe" | "requires-review" | null | undefined;
@@ -816,11 +816,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
816
816
  matched?: boolean | undefined;
817
817
  }[] | undefined;
818
818
  retrievedMemories?: {
819
+ confidence?: number | undefined;
819
820
  source?: string | undefined;
820
821
  stale?: boolean | undefined;
821
822
  created?: string | undefined;
822
823
  updated?: string | undefined;
823
- confidence?: number | undefined;
824
824
  scope?: string | undefined;
825
825
  retrievalReason?: string | undefined;
826
826
  safety?: NonNullable<"blocked" | "safe" | "requires-review"> | undefined;
@@ -842,11 +842,11 @@ declare const actionConfidenceRequestSchema: z.ZodObject<{
842
842
  matched?: boolean | null | undefined;
843
843
  }[] | null | undefined;
844
844
  retrievedMemories?: {
845
+ confidence?: number | null | undefined;
845
846
  source?: string | null | undefined;
846
847
  stale?: boolean | null | undefined;
847
848
  created?: string | null | undefined;
848
849
  updated?: string | null | undefined;
849
- confidence?: number | null | undefined;
850
850
  scope?: string | null | undefined;
851
851
  retrievalReason?: string | null | undefined;
852
852
  safety?: "blocked" | "safe" | "requires-review" | null | undefined;
@@ -25,7 +25,7 @@ import {
25
25
  trustZoneDemoSeedRequestSchema,
26
26
  trustZonePromoteRequestSchema,
27
27
  validateRequest
28
- } from "./chunk-UAU5U5ML.js";
28
+ } from "./chunk-UDJLF3BO.js";
29
29
  import "./chunk-KQAFEZQX.js";
30
30
  import "./chunk-WEHSQBFR.js";
31
31
  import "./chunk-X7Y7WX73.js";
@@ -33,12 +33,13 @@ import "./chunk-J4EB7DNW.js";
33
33
  import "./chunk-BJMBJZ2Y.js";
34
34
  import "./chunk-UKJAGEXH.js";
35
35
  import "./chunk-FP2373TW.js";
36
- import "./chunk-ZLDUQWT2.js";
36
+ import "./chunk-PWWWLD7D.js";
37
37
  import "./chunk-UI3NYK34.js";
38
38
  import "./chunk-GCGJW34D.js";
39
- import "./chunk-ILXTATKK.js";
39
+ import "./chunk-J2HSAU72.js";
40
40
  import "./chunk-A6XUJE5D.js";
41
41
  import "./chunk-P7FMDTKL.js";
42
+ import "./chunk-VS2IYZRU.js";
42
43
  import "./chunk-AH2JUU6X.js";
43
44
  import "./chunk-AC5LO7IU.js";
44
45
  import "./chunk-SOAU2OE2.js";
@@ -3,31 +3,32 @@ import {
3
3
  EngramAccessInputError,
4
4
  EngramAccessService,
5
5
  shapeMemorySummary
6
- } from "./chunk-DFAXGZKI.js";
6
+ } from "./chunk-JIX3ZL2J.js";
7
7
  import "./chunk-GDASG7NC.js";
8
8
  import "./chunk-GDB4J2H3.js";
9
- import "./chunk-DHGSZ3UD.js";
9
+ import "./chunk-ARV3AUOM.js";
10
10
  import "./chunk-H7XKCNR6.js";
11
11
  import "./chunk-TIJYQXDI.js";
12
12
  import "./chunk-SOBJ6NEY.js";
13
13
  import "./chunk-BT7NVCML.js";
14
- import "./chunk-A7EF2XRO.js";
14
+ import "./chunk-EXXBA5OM.js";
15
15
  import "./chunk-GYSYLGNE.js";
16
- import "./chunk-5RIRL3XL.js";
16
+ import "./chunk-RS25QOKZ.js";
17
17
  import "./chunk-JGSKJHF7.js";
18
18
  import "./chunk-FF4KLI5W.js";
19
19
  import "./chunk-2MXEVL75.js";
20
- import "./chunk-WOQIHC67.js";
21
- import "./chunk-35HP3TGR.js";
22
- import "./chunk-FZC2WSDB.js";
20
+ import "./chunk-DQY7NJ5L.js";
21
+ import "./chunk-ROHLEUTH.js";
22
+ import "./chunk-DOCTITOP.js";
23
23
  import "./chunk-CYEPCZN5.js";
24
- import "./chunk-ZJH723NM.js";
24
+ import "./chunk-Q5ZU3RNY.js";
25
25
  import "./chunk-JOASJWQR.js";
26
26
  import "./chunk-RN7MUWON.js";
27
- import "./chunk-WSQG37DV.js";
28
- import "./chunk-Q4CAQGKQ.js";
27
+ import "./chunk-OUWAQVDJ.js";
28
+ import "./chunk-AER6MT24.js";
29
29
  import "./chunk-CINZGPSJ.js";
30
30
  import "./chunk-ZFXCQPNO.js";
31
+ import "./chunk-5GPPACXK.js";
31
32
  import "./chunk-7OGJQP7T.js";
32
33
  import "./chunk-E6ZDCOHM.js";
33
34
  import "./chunk-OIF36KGD.js";
@@ -49,8 +50,8 @@ import "./chunk-3ONXXHQO.js";
49
50
  import "./chunk-Y56J7CXW.js";
50
51
  import "./chunk-2LSZVONP.js";
51
52
  import "./chunk-DEUNUKTD.js";
52
- import "./chunk-GY3V3SUI.js";
53
- import "./chunk-AX5O25EF.js";
53
+ import "./chunk-KHGE6PMF.js";
54
+ import "./chunk-VH6EIKVS.js";
54
55
  import "./chunk-M7XQSUBB.js";
55
56
  import "./chunk-5UZXUTVO.js";
56
57
  import "./chunk-J6A3CX5N.js";
@@ -65,7 +66,7 @@ import "./chunk-DM2T26WE.js";
65
66
  import "./chunk-LDXUBPMO.js";
66
67
  import "./chunk-FVQJYWH7.js";
67
68
  import "./chunk-G7D6GZ5J.js";
68
- import "./chunk-ALEPI75L.js";
69
+ import "./chunk-VF4XKTX3.js";
69
70
  import "./chunk-RGMVMVMF.js";
70
71
  import "./chunk-ZY2MNJR6.js";
71
72
  import "./chunk-SSOMTUCA.js";
@@ -91,12 +92,13 @@ import "./chunk-J4EB7DNW.js";
91
92
  import "./chunk-BJMBJZ2Y.js";
92
93
  import "./chunk-UKJAGEXH.js";
93
94
  import "./chunk-FP2373TW.js";
94
- import "./chunk-ZLDUQWT2.js";
95
+ import "./chunk-PWWWLD7D.js";
95
96
  import "./chunk-UI3NYK34.js";
96
97
  import "./chunk-GCGJW34D.js";
97
- import "./chunk-ILXTATKK.js";
98
+ import "./chunk-J2HSAU72.js";
98
99
  import "./chunk-A6XUJE5D.js";
99
100
  import "./chunk-P7FMDTKL.js";
101
+ import "./chunk-VS2IYZRU.js";
100
102
  import "./chunk-AH2JUU6X.js";
101
103
  import "./chunk-AC5LO7IU.js";
102
104
  import "./chunk-SOAU2OE2.js";
package/dist/briefing.js CHANGED
@@ -17,8 +17,8 @@ import {
17
17
  renderBriefingMarkdown,
18
18
  resolveBriefingSaveDir,
19
19
  validateBriefingFormat
20
- } from "./chunk-GY3V3SUI.js";
21
- import "./chunk-AX5O25EF.js";
20
+ } from "./chunk-KHGE6PMF.js";
21
+ import "./chunk-VH6EIKVS.js";
22
22
  import "./chunk-M7XQSUBB.js";
23
23
  import "./chunk-5UZXUTVO.js";
24
24
  import "./chunk-J6A3CX5N.js";
@@ -33,14 +33,15 @@ import "./chunk-DM2T26WE.js";
33
33
  import "./chunk-LDXUBPMO.js";
34
34
  import "./chunk-FVQJYWH7.js";
35
35
  import "./chunk-G7D6GZ5J.js";
36
- import "./chunk-ALEPI75L.js";
36
+ import "./chunk-VF4XKTX3.js";
37
37
  import "./chunk-RGMVMVMF.js";
38
38
  import "./chunk-4DJQYKMN.js";
39
39
  import "./chunk-JUC24CTX.js";
40
40
  import "./chunk-2ODBA7MQ.js";
41
- import "./chunk-ILXTATKK.js";
41
+ import "./chunk-J2HSAU72.js";
42
42
  import "./chunk-A6XUJE5D.js";
43
43
  import "./chunk-P7FMDTKL.js";
44
+ import "./chunk-VS2IYZRU.js";
44
45
  import "./chunk-PZ5AY32C.js";
45
46
  export {
46
47
  BRIEFING_FOLLOWUP_DEFAULT_MODEL,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createVersion
3
- } from "./chunk-ALEPI75L.js";
3
+ } from "./chunk-VF4XKTX3.js";
4
4
  import {
5
5
  parseExportBundle
6
6
  } from "./chunk-WEHSQBFR.js";
@@ -11,6 +11,7 @@ import {
11
11
  isPathInsideRoot,
12
12
  sha256String
13
13
  } from "./chunk-GCGJW34D.js";
14
+ import "./chunk-VS2IYZRU.js";
14
15
  import "./chunk-PZ5AY32C.js";
15
16
 
16
17
  // src/transfer/capsule-merge.ts
@@ -186,4 +187,4 @@ async function readLocalFile(absPath) {
186
187
  export {
187
188
  mergeCapsule
188
189
  };
189
- //# sourceMappingURL=capsule-merge-T2JRE46P.js.map
190
+ //# sourceMappingURL=capsule-merge-GK5E647P.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transfer/capsule-merge.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { gunzipSync } from \"node:zlib\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\n\n/**\n * Three-way conflict-resolution mode for {@link mergeCapsule}.\n *\n * A \"conflict\" is defined as: the same memory-file path exists in both the\n * source archive and the target directory AND the content hash of the local\n * file differs from the archive's manifest entry for that path.\n *\n * Files that exist only in the archive (no local counterpart) are always\n * written regardless of mode — there is no conflict to resolve.\n *\n * Files that are byte-identical (same content hash in both locations) are\n * recorded as {@link MergeCapsuleResult.skipped} with reason `\"identical\"` and\n * are never re-written regardless of mode; this is a no-op optimisation rather\n * than a conflict.\n *\n * - `\"skip-conflicts\"` (default) — log the conflict, skip the conflicting\n * archive entries, but continue importing non-conflicting entries. The\n * resulting merge is the union of:\n * - all non-conflicting archive files (written to target)\n * - all pre-existing local files (left unchanged)\n *\n * - `\"prefer-source\"` — for conflicting files, snapshot the local content via\n * page-versioning (gotcha #54: snapshot before overwrite) then overwrite\n * with the archive content.\n *\n * - `\"prefer-local\"` — for conflicting files, keep the local content; the\n * archive entry is skipped.\n */\nexport type MergeCapsuleConflictMode =\n | \"skip-conflicts\"\n | \"prefer-source\"\n | \"prefer-local\";\n\n/**\n * Options accepted by {@link mergeCapsule}.\n *\n * `sourceArchive` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. Must be a V2 bundle.\n *\n * `targetRoot` — absolute or cwd-relative path to the memory directory that\n * receives the merged records. Must be an existing, non-symlink directory.\n *\n * `conflictMode` — see {@link MergeCapsuleConflictMode}. Defaults to\n * `\"skip-conflicts\"`.\n *\n * `versioning` — optional page-versioning config forwarded to\n * {@link createVersion} in `\"prefer-source\"` mode. When omitted or disabled,\n * overwrites proceed without snapshotting (not recommended for production).\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n */\nexport interface MergeCapsuleOptions {\n sourceArchive: string;\n targetRoot: string;\n conflictMode?: MergeCapsuleConflictMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n}\n\nexport interface MergeCapsuleWrittenRecord {\n /** Capsule-relative posix path. */\n sourcePath: string;\n /** Memory-dir-relative posix path written on disk. */\n targetPath: string;\n /** Whether a page-versioning snapshot was taken before overwriting. */\n snapshotted: boolean;\n}\n\nexport interface MergeCapsuleSkippedRecord {\n /** Capsule-relative posix path. */\n path: string;\n /**\n * Why the archive entry was not written.\n *\n * `\"conflict\"` — the entry existed locally with different content and the\n * active mode did not resolve the conflict with a write (`\"skip-conflicts\"` /\n * `\"prefer-local\"`).\n *\n * `\"identical\"` — the entry's content hash matches what is already on disk;\n * no write is needed.\n */\n reason: \"conflict\" | \"identical\";\n}\n\nexport interface MergeCapsuleConflictRecord {\n /** Capsule-relative posix path of the conflicting entry. */\n path: string;\n /** SHA-256 of the archive's copy. */\n archiveSha256: string;\n /** SHA-256 of the local copy. */\n localSha256: string;\n}\n\nexport interface MergeCapsuleResult {\n /** Records that were written to the target directory. */\n merged: MergeCapsuleWrittenRecord[];\n /**\n * Records that were NOT written (conflict skipped, or byte-identical).\n * Includes conflicts that were resolved by `\"prefer-local\"`.\n */\n skipped: MergeCapsuleSkippedRecord[];\n /**\n * Metadata about every detected conflict, regardless of which mode resolved\n * it. Callers can use this to report \"N conflicts encountered; M overwritten\".\n */\n conflicts: MergeCapsuleConflictRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Merge a V2 capsule archive into an existing memory directory using\n * three-way conflict semantics.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate through `parseExportBundle` (V1 rejected).\n * 3. Verify every record's content sha256 against the manifest.\n * Any mismatch aborts BEFORE any file is written (gotcha #25).\n * 4. Classify each record as: new (no local copy), identical (same hash),\n * or conflicting (different hash).\n * 5. Apply the selected {@link MergeCapsuleConflictMode} to conflicting\n * entries; always write new entries; always skip identical entries.\n *\n * Determinism: `merged`, `skipped`, and `conflicts` are all returned sorted\n * by `path`/`sourcePath` so callers get stable output regardless of bundle\n * order.\n */\nexport async function mergeCapsule(\n opts: MergeCapsuleOptions,\n): Promise<MergeCapsuleResult> {\n const archiveAbs = path.resolve(opts.sourceArchive);\n const rootAbs = path.resolve(opts.targetRoot);\n\n await assertIsDirectoryNotSymlink(rootAbs, \"mergeCapsule\", \"targetRoot\");\n\n const conflictMode: MergeCapsuleConflictMode =\n opts.conflictMode ?? \"skip-conflicts\";\n\n // Rule 51: reject invalid conflictMode values up-front before any I/O.\n if (\n conflictMode !== \"skip-conflicts\" &&\n conflictMode !== \"prefer-source\" &&\n conflictMode !== \"prefer-local\"\n ) {\n throw new Error(\n `mergeCapsule: unknown conflictMode ${JSON.stringify(conflictMode)}; ` +\n `expected \"skip-conflicts\", \"prefer-source\", or \"prefer-local\"`,\n );\n }\n\n // ---------------------------------------------------------------------------\n // Parse + validate archive\n // ---------------------------------------------------------------------------\n\n const raw = await readFile(archiveAbs);\n let json: string;\n try {\n json = gunzipSync(raw).toString(\"utf-8\");\n } catch (cause) {\n throw new Error(\n `mergeCapsule: archive is not a valid gzip file: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `mergeCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n throw new Error(\n \"mergeCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n\n const bundle = parsed.bundle as {\n manifest: ExportManifestV2;\n records: ExportMemoryRecordV1[];\n };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build path → manifest entry index for O(1) checksum lookup.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\"mergeCapsule: manifest contains duplicate file paths\");\n }\n\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `mergeCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // ---------------------------------------------------------------------------\n // Phase 1: verify checksums + validate paths before ANY filesystem mutation.\n // (gotcha #25: don't destroy old state before confirming new state succeeds)\n // ---------------------------------------------------------------------------\n\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalise to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both refer to the same inode. In\n // `skip-conflicts`/`prefer-local` modes one entry would be misclassified as a\n // local conflict against the OTHER entry's just-written content; in\n // `prefer-source` the second entry would silently overwrite the first. We\n // reject the import up-front before any write (Codex P2 thread on PR #748,\n // mirroring `capsule-import.ts`).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Path-traversal validation (mirrors capsule-import.ts).\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `mergeCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `mergeCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Lexical root containment check.\n const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `mergeCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (shared helper from fs-utils).\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"mergeCapsule\");\n\n // Target-file symlink guard: if the target already exists as a symlink,\n // reject — writes through symlinks can redirect to unexpected locations.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `mergeCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #748, mirrors\n // capsule-import.ts). `path.join` already normalises `.` segments\n // (e.g. `subdir/./file.md` → `subdir/file.md`). On case-insensitive\n // filesystems (macOS default, Windows), two paths that differ only in case\n // would resolve to the same inode. We fold the dedup key to lowercase so\n // that `subdir/File.md` and `subdir/file.md` are detected as duplicates\n // before any write occurs. This is intentionally unconditional: the cost\n // of an extra `.toLowerCase()` on case-sensitive filesystems is negligible,\n // and a defensive lowercase is far simpler than probing filesystem\n // case-sensitivity at runtime. Without this guard, prefer-source mode\n // would silently overwrite one entry with the other, and skip-conflicts /\n // prefer-local would misclassify the second entry as a local conflict\n // against the first entry's freshly written content.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `mergeCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${rec.path}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Phase 2: classify records and apply conflict mode.\n // ---------------------------------------------------------------------------\n\n const merged: MergeCapsuleWrittenRecord[] = [];\n const skipped: MergeCapsuleSkippedRecord[] = [];\n const conflicts: MergeCapsuleConflictRecord[] = [];\n\n // Sort by source path for deterministic output (mirrors capsule-import.ts).\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n for (const rec of sortedRecords) {\n const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));\n const entry = manifestIndex.get(rec.path)!; // validated above\n\n const localContent = await readLocalFile(targetAbs);\n\n if (localContent === null) {\n // No local copy — always write regardless of mode.\n await mkdir(path.dirname(targetAbs), { recursive: true });\n await writeFile(targetAbs, rec.content, \"utf-8\");\n merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted: false });\n continue;\n }\n\n // Local file exists. Check if it is byte-identical to the archive entry.\n const { sha256: localSha256 } = sha256String(localContent);\n\n if (localSha256 === entry.sha256) {\n // Byte-identical — no write needed.\n skipped.push({ path: rec.path, reason: \"identical\" });\n continue;\n }\n\n // Content differs → conflict.\n const { sha256: archiveSha256 } = sha256String(rec.content);\n conflicts.push({\n path: rec.path,\n archiveSha256,\n localSha256,\n });\n\n if (conflictMode === \"skip-conflicts\" || conflictMode === \"prefer-local\") {\n // Keep local copy, skip archive entry.\n skipped.push({ path: rec.path, reason: \"conflict\" });\n continue;\n }\n\n // conflictMode === \"prefer-source\": snapshot local then overwrite.\n let snapshotted = false;\n if (opts.versioning && opts.versioning.enabled) {\n // Gotcha #54: snapshot BEFORE overwriting.\n await createVersion(\n targetAbs,\n localContent,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-merge: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n\n await writeFile(targetAbs, rec.content, \"utf-8\");\n merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted });\n }\n\n // Sort output lists for determinism.\n merged.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n conflicts.sort((a, b) => a.path.localeCompare(b.path));\n\n return { merged, skipped, conflicts, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nasync function readLocalFile(absPath: string): Promise<string | null> {\n const st = await stat(absPath).catch(() => null);\n if (!st || !st.isFile()) return null;\n return readFile(absPath, \"utf-8\");\n}\n\n// Re-export CapsuleBlock so callers don't need a deep import from types.ts.\nexport type { CapsuleBlock };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/transfer/capsule-merge.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { gunzipSync } from \"node:zlib\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\n\n/**\n * Three-way conflict-resolution mode for {@link mergeCapsule}.\n *\n * A \"conflict\" is defined as: the same memory-file path exists in both the\n * source archive and the target directory AND the content hash of the local\n * file differs from the archive's manifest entry for that path.\n *\n * Files that exist only in the archive (no local counterpart) are always\n * written regardless of mode — there is no conflict to resolve.\n *\n * Files that are byte-identical (same content hash in both locations) are\n * recorded as {@link MergeCapsuleResult.skipped} with reason `\"identical\"` and\n * are never re-written regardless of mode; this is a no-op optimisation rather\n * than a conflict.\n *\n * - `\"skip-conflicts\"` (default) — log the conflict, skip the conflicting\n * archive entries, but continue importing non-conflicting entries. The\n * resulting merge is the union of:\n * - all non-conflicting archive files (written to target)\n * - all pre-existing local files (left unchanged)\n *\n * - `\"prefer-source\"` — for conflicting files, snapshot the local content via\n * page-versioning (gotcha #54: snapshot before overwrite) then overwrite\n * with the archive content.\n *\n * - `\"prefer-local\"` — for conflicting files, keep the local content; the\n * archive entry is skipped.\n */\nexport type MergeCapsuleConflictMode =\n | \"skip-conflicts\"\n | \"prefer-source\"\n | \"prefer-local\";\n\n/**\n * Options accepted by {@link mergeCapsule}.\n *\n * `sourceArchive` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. Must be a V2 bundle.\n *\n * `targetRoot` — absolute or cwd-relative path to the memory directory that\n * receives the merged records. Must be an existing, non-symlink directory.\n *\n * `conflictMode` — see {@link MergeCapsuleConflictMode}. Defaults to\n * `\"skip-conflicts\"`.\n *\n * `versioning` — optional page-versioning config forwarded to\n * {@link createVersion} in `\"prefer-source\"` mode. When omitted or disabled,\n * overwrites proceed without snapshotting (not recommended for production).\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n */\nexport interface MergeCapsuleOptions {\n sourceArchive: string;\n targetRoot: string;\n conflictMode?: MergeCapsuleConflictMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n}\n\nexport interface MergeCapsuleWrittenRecord {\n /** Capsule-relative posix path. */\n sourcePath: string;\n /** Memory-dir-relative posix path written on disk. */\n targetPath: string;\n /** Whether a page-versioning snapshot was taken before overwriting. */\n snapshotted: boolean;\n}\n\nexport interface MergeCapsuleSkippedRecord {\n /** Capsule-relative posix path. */\n path: string;\n /**\n * Why the archive entry was not written.\n *\n * `\"conflict\"` — the entry existed locally with different content and the\n * active mode did not resolve the conflict with a write (`\"skip-conflicts\"` /\n * `\"prefer-local\"`).\n *\n * `\"identical\"` — the entry's content hash matches what is already on disk;\n * no write is needed.\n */\n reason: \"conflict\" | \"identical\";\n}\n\nexport interface MergeCapsuleConflictRecord {\n /** Capsule-relative posix path of the conflicting entry. */\n path: string;\n /** SHA-256 of the archive's copy. */\n archiveSha256: string;\n /** SHA-256 of the local copy. */\n localSha256: string;\n}\n\nexport interface MergeCapsuleResult {\n /** Records that were written to the target directory. */\n merged: MergeCapsuleWrittenRecord[];\n /**\n * Records that were NOT written (conflict skipped, or byte-identical).\n * Includes conflicts that were resolved by `\"prefer-local\"`.\n */\n skipped: MergeCapsuleSkippedRecord[];\n /**\n * Metadata about every detected conflict, regardless of which mode resolved\n * it. Callers can use this to report \"N conflicts encountered; M overwritten\".\n */\n conflicts: MergeCapsuleConflictRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Merge a V2 capsule archive into an existing memory directory using\n * three-way conflict semantics.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate through `parseExportBundle` (V1 rejected).\n * 3. Verify every record's content sha256 against the manifest.\n * Any mismatch aborts BEFORE any file is written (gotcha #25).\n * 4. Classify each record as: new (no local copy), identical (same hash),\n * or conflicting (different hash).\n * 5. Apply the selected {@link MergeCapsuleConflictMode} to conflicting\n * entries; always write new entries; always skip identical entries.\n *\n * Determinism: `merged`, `skipped`, and `conflicts` are all returned sorted\n * by `path`/`sourcePath` so callers get stable output regardless of bundle\n * order.\n */\nexport async function mergeCapsule(\n opts: MergeCapsuleOptions,\n): Promise<MergeCapsuleResult> {\n const archiveAbs = path.resolve(opts.sourceArchive);\n const rootAbs = path.resolve(opts.targetRoot);\n\n await assertIsDirectoryNotSymlink(rootAbs, \"mergeCapsule\", \"targetRoot\");\n\n const conflictMode: MergeCapsuleConflictMode =\n opts.conflictMode ?? \"skip-conflicts\";\n\n // Rule 51: reject invalid conflictMode values up-front before any I/O.\n if (\n conflictMode !== \"skip-conflicts\" &&\n conflictMode !== \"prefer-source\" &&\n conflictMode !== \"prefer-local\"\n ) {\n throw new Error(\n `mergeCapsule: unknown conflictMode ${JSON.stringify(conflictMode)}; ` +\n `expected \"skip-conflicts\", \"prefer-source\", or \"prefer-local\"`,\n );\n }\n\n // ---------------------------------------------------------------------------\n // Parse + validate archive\n // ---------------------------------------------------------------------------\n\n const raw = await readFile(archiveAbs);\n let json: string;\n try {\n json = gunzipSync(raw).toString(\"utf-8\");\n } catch (cause) {\n throw new Error(\n `mergeCapsule: archive is not a valid gzip file: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `mergeCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n throw new Error(\n \"mergeCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n\n const bundle = parsed.bundle as {\n manifest: ExportManifestV2;\n records: ExportMemoryRecordV1[];\n };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build path → manifest entry index for O(1) checksum lookup.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\"mergeCapsule: manifest contains duplicate file paths\");\n }\n\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `mergeCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // ---------------------------------------------------------------------------\n // Phase 1: verify checksums + validate paths before ANY filesystem mutation.\n // (gotcha #25: don't destroy old state before confirming new state succeeds)\n // ---------------------------------------------------------------------------\n\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalise to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both refer to the same inode. In\n // `skip-conflicts`/`prefer-local` modes one entry would be misclassified as a\n // local conflict against the OTHER entry's just-written content; in\n // `prefer-source` the second entry would silently overwrite the first. We\n // reject the import up-front before any write (Codex P2 thread on PR #748,\n // mirroring `capsule-import.ts`).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Path-traversal validation (mirrors capsule-import.ts).\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `mergeCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `mergeCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Lexical root containment check.\n const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `mergeCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (shared helper from fs-utils).\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"mergeCapsule\");\n\n // Target-file symlink guard: if the target already exists as a symlink,\n // reject — writes through symlinks can redirect to unexpected locations.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `mergeCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #748, mirrors\n // capsule-import.ts). `path.join` already normalises `.` segments\n // (e.g. `subdir/./file.md` → `subdir/file.md`). On case-insensitive\n // filesystems (macOS default, Windows), two paths that differ only in case\n // would resolve to the same inode. We fold the dedup key to lowercase so\n // that `subdir/File.md` and `subdir/file.md` are detected as duplicates\n // before any write occurs. This is intentionally unconditional: the cost\n // of an extra `.toLowerCase()` on case-sensitive filesystems is negligible,\n // and a defensive lowercase is far simpler than probing filesystem\n // case-sensitivity at runtime. Without this guard, prefer-source mode\n // would silently overwrite one entry with the other, and skip-conflicts /\n // prefer-local would misclassify the second entry as a local conflict\n // against the first entry's freshly written content.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `mergeCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${rec.path}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `mergeCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // ---------------------------------------------------------------------------\n // Phase 2: classify records and apply conflict mode.\n // ---------------------------------------------------------------------------\n\n const merged: MergeCapsuleWrittenRecord[] = [];\n const skipped: MergeCapsuleSkippedRecord[] = [];\n const conflicts: MergeCapsuleConflictRecord[] = [];\n\n // Sort by source path for deterministic output (mirrors capsule-import.ts).\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n for (const rec of sortedRecords) {\n const targetAbs = path.join(rootReal, fromPosixRelPath(rec.path));\n const entry = manifestIndex.get(rec.path)!; // validated above\n\n const localContent = await readLocalFile(targetAbs);\n\n if (localContent === null) {\n // No local copy — always write regardless of mode.\n await mkdir(path.dirname(targetAbs), { recursive: true });\n await writeFile(targetAbs, rec.content, \"utf-8\");\n merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted: false });\n continue;\n }\n\n // Local file exists. Check if it is byte-identical to the archive entry.\n const { sha256: localSha256 } = sha256String(localContent);\n\n if (localSha256 === entry.sha256) {\n // Byte-identical — no write needed.\n skipped.push({ path: rec.path, reason: \"identical\" });\n continue;\n }\n\n // Content differs → conflict.\n const { sha256: archiveSha256 } = sha256String(rec.content);\n conflicts.push({\n path: rec.path,\n archiveSha256,\n localSha256,\n });\n\n if (conflictMode === \"skip-conflicts\" || conflictMode === \"prefer-local\") {\n // Keep local copy, skip archive entry.\n skipped.push({ path: rec.path, reason: \"conflict\" });\n continue;\n }\n\n // conflictMode === \"prefer-source\": snapshot local then overwrite.\n let snapshotted = false;\n if (opts.versioning && opts.versioning.enabled) {\n // Gotcha #54: snapshot BEFORE overwriting.\n await createVersion(\n targetAbs,\n localContent,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-merge: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n\n await writeFile(targetAbs, rec.content, \"utf-8\");\n merged.push({ sourcePath: rec.path, targetPath: rec.path, snapshotted });\n }\n\n // Sort output lists for determinism.\n merged.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n conflicts.sort((a, b) => a.path.localeCompare(b.path));\n\n return { merged, skipped, conflicts, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nasync function readLocalFile(absPath: string): Promise<string | null> {\n const st = await stat(absPath).catch(() => null);\n if (!st || !st.isFile()) return null;\n return readFile(absPath, \"utf-8\");\n}\n\n// Re-export CapsuleBlock so callers don't need a deep import from types.ts.\nexport type { CapsuleBlock };\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,OAAO,UAAU,UAAU,MAAM,iBAAiB;AAClE,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAwJ3B,eAAsB,aACpB,MAC6B;AAC7B,QAAM,aAAa,KAAK,QAAQ,KAAK,aAAa;AAClD,QAAM,UAAU,KAAK,QAAQ,KAAK,UAAU;AAE5C,QAAM,4BAA4B,SAAS,gBAAgB,YAAY;AAEvE,QAAM,eACJ,KAAK,gBAAgB;AAGvB,MACE,iBAAiB,oBACjB,iBAAiB,mBACjB,iBAAiB,gBACjB;AACA,UAAM,IAAI;AAAA,MACR,sCAAsC,KAAK,UAAU,YAAY,CAAC;AAAA,IAEpE;AAAA,EACF;AAMA,QAAM,MAAM,MAAM,SAAS,UAAU;AACrC,MAAI;AACJ,MAAI;AACF,WAAO,WAAW,GAAG,EAAE,SAAS,OAAO;AAAA,EACzC,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,mDAAmD,UAAU;AAAA,MAC7D,EAAE,MAAsB;AAAA,IAC1B;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,iBAAa,KAAK,MAAM,IAAI;AAAA,EAC9B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,yDAAyD,UAAU;AAAA,MACnE,EAAE,MAAsB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,SAAS,kBAAkB,UAAU;AAC3C,MAAI,OAAO,mBAAmB,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,OAAO;AAItB,QAAM,WAAW,OAAO;AACxB,QAAM,UAAU,SAAS;AAGzB,QAAM,gBAAgB,oBAAI,IAA+C;AACzE,aAAW,KAAK,SAAS,OAAO;AAC9B,kBAAc,IAAI,EAAE,MAAM,CAAC;AAAA,EAC7B;AACA,MAAI,cAAc,SAAS,SAAS,MAAM,QAAQ;AAChD,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AAEA,QAAM,cAAc,oBAAI,IAAY;AACpC,aAAW,OAAO,OAAO,SAAS;AAChC,QAAI,YAAY,IAAI,IAAI,IAAI,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR,wDAAwD,IAAI,IAAI;AAAA,MAClE;AAAA,IACF;AACA,gBAAY,IAAI,IAAI,IAAI;AAAA,EAC1B;AAOA,QAAM,WAAW,MAAM,SAAS,OAAO,EAAE,MAAM,MAAM,OAAO;AAa5D,QAAM,kBAAkB,oBAAI,IAAoB;AAEhD,aAAW,OAAO,OAAO,SAAS;AAEhC,UAAM,QAAQ,cAAc,IAAI,IAAI,IAAI;AACxC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,2EAA2E,IAAI,IAAI;AAAA,MACrF;AAAA,IACF;AACA,UAAM,EAAE,QAAQ,MAAM,IAAI,aAAa,IAAI,OAAO;AAClD,QAAI,WAAW,MAAM,UAAU,UAAU,MAAM,OAAO;AACpD,YAAM,IAAI;AAAA,QACR,+CAA+C,IAAI,IAAI,qBAClC,MAAM,MAAM,UAAU,MAAM,KAAK,gBACtC,MAAM,UAAU,KAAK;AAAA,MACvC;AAAA,IACF;AAGA,QAAI,IAAI,KAAK,SAAS,IAAI,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,kGAAkG,IAAI,IAAI;AAAA,MAC5G;AAAA,IACF;AACA,UAAM,kBAAkB,KAAK,MAAM,UAAU,IAAI,IAAI;AACrD,QACE,IAAI,KAAK,WAAW,GAAG,KACvB,IAAI,KAAK,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,QAAQ,IAAI,KAC9C,gBAAgB,WAAW,IAAI,KAC/B,gBAAgB,WAAW,GAAG,GAC9B;AACA,YAAM,IAAI;AAAA,QACR,kDAAkD,IAAI,IAAI;AAAA,MAC5D;AAAA,IACF;AAGA,UAAM,YAAY,KAAK,KAAK,UAAU,iBAAiB,IAAI,IAAI,CAAC;AAChE,QAAI,CAAC,iBAAiB,UAAU,SAAS,GAAG;AAC1C,YAAM,IAAI;AAAA,QACR,kDAAkD,IAAI,IAAI;AAAA,MAC5D;AAAA,IACF;AAGA,UAAM,yBAAyB,UAAU,WAAW,IAAI,MAAM,cAAc;AAI5E,UAAM,cAAc,MAAM,MAAM,SAAS,EAAE,MAAM,MAAM,IAAI;AAC3D,QAAI,gBAAgB,QAAQ,YAAY,eAAe,GAAG;AACxD,YAAM,IAAI;AAAA,QACR,6EAA6E,IAAI,IAAI;AAAA,MACvF;AAAA,IACF;AAeA,UAAM,WAAW,UAAU,YAAY;AACvC,UAAM,kBAAkB,gBAAgB,IAAI,QAAQ;AACpD,QAAI,oBAAoB,QAAW;AACjC,YAAM,IAAI;AAAA,QACR,sFACM,eAAe,UAAU,IAAI,IAAI,kBAAkB,IAAI,IAAI;AAAA,MACnE;AAAA,IACF;AACA,oBAAgB,IAAI,UAAU,IAAI,IAAI;AAAA,EACxC;AAGA,aAAW,KAAK,SAAS,OAAO;AAC9B,QAAI,CAAC,YAAY,IAAI,EAAE,IAAI,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,2EAA2E,EAAE,IAAI;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AAMA,QAAM,SAAsC,CAAC;AAC7C,QAAM,UAAuC,CAAC;AAC9C,QAAM,YAA0C,CAAC;AAGjD,QAAM,gBAAgB,CAAC,GAAG,OAAO,OAAO,EAAE;AAAA,IAAK,CAAC,GAAG,MACjD,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EAC7B;AAEA,aAAW,OAAO,eAAe;AAC/B,UAAM,YAAY,KAAK,KAAK,UAAU,iBAAiB,IAAI,IAAI,CAAC;AAChE,UAAM,QAAQ,cAAc,IAAI,IAAI,IAAI;AAExC,UAAM,eAAe,MAAM,cAAc,SAAS;AAElD,QAAI,iBAAiB,MAAM;AAEzB,YAAM,MAAM,KAAK,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,YAAM,UAAU,WAAW,IAAI,SAAS,OAAO;AAC/C,aAAO,KAAK,EAAE,YAAY,IAAI,MAAM,YAAY,IAAI,MAAM,aAAa,MAAM,CAAC;AAC9E;AAAA,IACF;AAGA,UAAM,EAAE,QAAQ,YAAY,IAAI,aAAa,YAAY;AAEzD,QAAI,gBAAgB,MAAM,QAAQ;AAEhC,cAAQ,KAAK,EAAE,MAAM,IAAI,MAAM,QAAQ,YAAY,CAAC;AACpD;AAAA,IACF;AAGA,UAAM,EAAE,QAAQ,cAAc,IAAI,aAAa,IAAI,OAAO;AAC1D,cAAU,KAAK;AAAA,MACb,MAAM,IAAI;AAAA,MACV;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,iBAAiB,oBAAoB,iBAAiB,gBAAgB;AAExE,cAAQ,KAAK,EAAE,MAAM,IAAI,MAAM,QAAQ,WAAW,CAAC;AACnD;AAAA,IACF;AAGA,QAAI,cAAc;AAClB,QAAI,KAAK,cAAc,KAAK,WAAW,SAAS;AAE9C,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,QACL,kBAAkB,QAAQ,EAAE;AAAA,QAC5B;AAAA,MACF;AACA,oBAAc;AAAA,IAChB;AAEA,UAAM,UAAU,WAAW,IAAI,SAAS,OAAO;AAC/C,WAAO,KAAK,EAAE,YAAY,IAAI,MAAM,YAAY,IAAI,MAAM,YAAY,CAAC;AAAA,EACzE;AAGA,SAAO,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,cAAc,EAAE,UAAU,CAAC;AAC9D,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACnD,YAAU,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAErD,SAAO,EAAE,QAAQ,SAAS,WAAW,SAAS;AAChD;AAMA,eAAe,cAAc,SAAyC;AACpE,QAAM,KAAK,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,IAAI;AAC/C,MAAI,CAAC,MAAM,CAAC,GAAG,OAAO,EAAG,QAAO;AAChC,SAAO,SAAS,SAAS,OAAO;AAClC;","names":[]}
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-UXA5L2DZ.js";
5
5
  import {
6
6
  buildExtensionsBlockForConsolidation
7
- } from "./chunk-D2EFNQMY.js";
7
+ } from "./chunk-XW3W4PV4.js";
8
8
  import {
9
9
  runPostConsolidationMaterialize
10
- } from "./chunk-ANJOULTP.js";
10
+ } from "./chunk-C7AF236A.js";
11
11
  import "./chunk-LN4YGHTM.js";
12
12
  import "./chunk-JLNBQWZ2.js";
13
13
  import "./chunk-3UXOZBHV.js";
@@ -20,7 +20,7 @@ import "./chunk-L2EXJQJP.js";
20
20
  import "./chunk-7SI52C65.js";
21
21
  import "./chunk-RK6F44Y6.js";
22
22
  import "./chunk-HQ6NIBL6.js";
23
- import "./chunk-AX5O25EF.js";
23
+ import "./chunk-VH6EIKVS.js";
24
24
  import "./chunk-M7XQSUBB.js";
25
25
  import "./chunk-5UZXUTVO.js";
26
26
  import "./chunk-J6A3CX5N.js";
@@ -35,7 +35,7 @@ import "./chunk-DM2T26WE.js";
35
35
  import "./chunk-LDXUBPMO.js";
36
36
  import "./chunk-FVQJYWH7.js";
37
37
  import "./chunk-G7D6GZ5J.js";
38
- import "./chunk-ALEPI75L.js";
38
+ import "./chunk-VF4XKTX3.js";
39
39
  import "./chunk-RGMVMVMF.js";
40
40
  import {
41
41
  resolveCausalTrajectoryStoreDir
@@ -55,9 +55,10 @@ import {
55
55
  listJsonFiles,
56
56
  readJsonFile
57
57
  } from "./chunk-LPSF4OQH.js";
58
- import "./chunk-ILXTATKK.js";
58
+ import "./chunk-J2HSAU72.js";
59
59
  import "./chunk-A6XUJE5D.js";
60
60
  import "./chunk-P7FMDTKL.js";
61
+ import "./chunk-VS2IYZRU.js";
61
62
  import "./chunk-PZ5AY32C.js";
62
63
 
63
64
  // src/causal-consolidation.ts