agent-recall-core 3.4.31 → 3.4.36

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 (270) hide show
  1. package/README.md +121 -441
  2. package/dist/digest/store.d.ts.map +1 -1
  3. package/dist/digest/store.js +4 -3
  4. package/dist/digest/store.js.map +1 -1
  5. package/dist/display/board-render.d.ts +61 -0
  6. package/dist/display/board-render.d.ts.map +1 -0
  7. package/dist/display/board-render.js +223 -0
  8. package/dist/display/board-render.js.map +1 -0
  9. package/dist/helpers/alignment-patterns.d.ts +10 -0
  10. package/dist/helpers/alignment-patterns.d.ts.map +1 -1
  11. package/dist/helpers/alignment-patterns.js +5 -1
  12. package/dist/helpers/alignment-patterns.js.map +1 -1
  13. package/dist/helpers/blind-spots.d.ts +86 -0
  14. package/dist/helpers/blind-spots.d.ts.map +1 -0
  15. package/dist/helpers/blind-spots.js +205 -0
  16. package/dist/helpers/blind-spots.js.map +1 -0
  17. package/dist/helpers/conflict-scan.d.ts +10 -0
  18. package/dist/helpers/conflict-scan.d.ts.map +1 -1
  19. package/dist/helpers/conflict-scan.js +3 -3
  20. package/dist/helpers/conflict-scan.js.map +1 -1
  21. package/dist/helpers/semantic-match.d.ts +82 -0
  22. package/dist/helpers/semantic-match.d.ts.map +1 -0
  23. package/dist/helpers/semantic-match.js +229 -0
  24. package/dist/helpers/semantic-match.js.map +1 -0
  25. package/dist/index.d.ts +57 -17
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +60 -14
  28. package/dist/index.js.map +1 -1
  29. package/dist/palace/awareness.d.ts +30 -0
  30. package/dist/palace/awareness.d.ts.map +1 -1
  31. package/dist/palace/awareness.js +65 -2
  32. package/dist/palace/awareness.js.map +1 -1
  33. package/dist/palace/consolidate.d.ts.map +1 -1
  34. package/dist/palace/consolidate.js +17 -0
  35. package/dist/palace/consolidate.js.map +1 -1
  36. package/dist/palace/decay-pass.d.ts +46 -0
  37. package/dist/palace/decay-pass.d.ts.map +1 -0
  38. package/dist/palace/decay-pass.js +87 -0
  39. package/dist/palace/decay-pass.js.map +1 -0
  40. package/dist/palace/insights-index.d.ts.map +1 -1
  41. package/dist/palace/insights-index.js +4 -2
  42. package/dist/palace/insights-index.js.map +1 -1
  43. package/dist/palace/skills.d.ts +41 -2
  44. package/dist/palace/skills.d.ts.map +1 -1
  45. package/dist/palace/skills.js +106 -4
  46. package/dist/palace/skills.js.map +1 -1
  47. package/dist/prompts/consolidation-prompt.d.ts +25 -0
  48. package/dist/prompts/consolidation-prompt.d.ts.map +1 -0
  49. package/dist/prompts/consolidation-prompt.js +91 -0
  50. package/dist/prompts/consolidation-prompt.js.map +1 -0
  51. package/dist/storage/ab-experiment.d.ts +184 -0
  52. package/dist/storage/ab-experiment.d.ts.map +1 -0
  53. package/dist/storage/ab-experiment.js +294 -0
  54. package/dist/storage/ab-experiment.js.map +1 -0
  55. package/dist/storage/archive-prune.d.ts +56 -0
  56. package/dist/storage/archive-prune.d.ts.map +1 -0
  57. package/dist/storage/archive-prune.js +118 -0
  58. package/dist/storage/archive-prune.js.map +1 -0
  59. package/dist/storage/archive-write.d.ts +38 -0
  60. package/dist/storage/archive-write.d.ts.map +1 -0
  61. package/dist/storage/archive-write.js +98 -0
  62. package/dist/storage/archive-write.js.map +1 -0
  63. package/dist/storage/blind-spots-store.d.ts +30 -0
  64. package/dist/storage/blind-spots-store.d.ts.map +1 -0
  65. package/dist/storage/blind-spots-store.js +91 -0
  66. package/dist/storage/blind-spots-store.js.map +1 -0
  67. package/dist/storage/capture-router.d.ts +60 -0
  68. package/dist/storage/capture-router.d.ts.map +1 -0
  69. package/dist/storage/capture-router.js +133 -0
  70. package/dist/storage/capture-router.js.map +1 -0
  71. package/dist/storage/classification.d.ts +36 -0
  72. package/dist/storage/classification.d.ts.map +1 -0
  73. package/dist/storage/classification.js +78 -0
  74. package/dist/storage/classification.js.map +1 -0
  75. package/dist/storage/consolidation-queue.d.ts +39 -0
  76. package/dist/storage/consolidation-queue.d.ts.map +1 -0
  77. package/dist/storage/consolidation-queue.js +120 -0
  78. package/dist/storage/consolidation-queue.js.map +1 -0
  79. package/dist/storage/content-guard.d.ts +74 -0
  80. package/dist/storage/content-guard.d.ts.map +1 -0
  81. package/dist/storage/content-guard.js +179 -0
  82. package/dist/storage/content-guard.js.map +1 -0
  83. package/dist/storage/corrections.d.ts +329 -22
  84. package/dist/storage/corrections.d.ts.map +1 -1
  85. package/dist/storage/corrections.js +788 -58
  86. package/dist/storage/corrections.js.map +1 -1
  87. package/dist/storage/durable-intent.d.ts +49 -0
  88. package/dist/storage/durable-intent.d.ts.map +1 -0
  89. package/dist/storage/durable-intent.js +124 -0
  90. package/dist/storage/durable-intent.js.map +1 -0
  91. package/dist/storage/filelock.d.ts +6 -0
  92. package/dist/storage/filelock.d.ts.map +1 -1
  93. package/dist/storage/filelock.js +6 -1
  94. package/dist/storage/filelock.js.map +1 -1
  95. package/dist/storage/memory-protocol.d.ts +19 -0
  96. package/dist/storage/memory-protocol.d.ts.map +1 -0
  97. package/dist/storage/memory-protocol.js +101 -0
  98. package/dist/storage/memory-protocol.js.map +1 -0
  99. package/dist/storage/paths.d.ts +23 -0
  100. package/dist/storage/paths.d.ts.map +1 -1
  101. package/dist/storage/paths.js +39 -0
  102. package/dist/storage/paths.js.map +1 -1
  103. package/dist/storage/retention.d.ts +23 -0
  104. package/dist/storage/retention.d.ts.map +1 -0
  105. package/dist/storage/retention.js +48 -0
  106. package/dist/storage/retention.js.map +1 -0
  107. package/dist/storage/session.d.ts +2 -2
  108. package/dist/storage/session.d.ts.map +1 -1
  109. package/dist/storage/session.js +11 -1
  110. package/dist/storage/session.js.map +1 -1
  111. package/dist/supabase/config.d.ts +26 -0
  112. package/dist/supabase/config.d.ts.map +1 -1
  113. package/dist/supabase/config.js +11 -0
  114. package/dist/supabase/config.js.map +1 -1
  115. package/dist/supabase/recall-backend.d.ts +1 -0
  116. package/dist/supabase/recall-backend.d.ts.map +1 -1
  117. package/dist/supabase/recall-backend.js +13 -13
  118. package/dist/supabase/recall-backend.js.map +1 -1
  119. package/dist/supabase/sync.d.ts +1 -1
  120. package/dist/supabase/sync.d.ts.map +1 -1
  121. package/dist/supabase/sync.js +85 -3
  122. package/dist/supabase/sync.js.map +1 -1
  123. package/dist/tools-logic/bootstrap.d.ts +15 -0
  124. package/dist/tools-logic/bootstrap.d.ts.map +1 -1
  125. package/dist/tools-logic/bootstrap.js +238 -47
  126. package/dist/tools-logic/bootstrap.js.map +1 -1
  127. package/dist/tools-logic/brief.d.ts +84 -0
  128. package/dist/tools-logic/brief.d.ts.map +1 -0
  129. package/dist/tools-logic/brief.js +193 -0
  130. package/dist/tools-logic/brief.js.map +1 -0
  131. package/dist/tools-logic/check-action.d.ts +9 -0
  132. package/dist/tools-logic/check-action.d.ts.map +1 -1
  133. package/dist/tools-logic/check-action.js +61 -3
  134. package/dist/tools-logic/check-action.js.map +1 -1
  135. package/dist/tools-logic/check.d.ts +7 -0
  136. package/dist/tools-logic/check.d.ts.map +1 -1
  137. package/dist/tools-logic/check.js +24 -2
  138. package/dist/tools-logic/check.js.map +1 -1
  139. package/dist/tools-logic/confidence.d.ts +50 -0
  140. package/dist/tools-logic/confidence.d.ts.map +1 -0
  141. package/dist/tools-logic/confidence.js +70 -0
  142. package/dist/tools-logic/confidence.js.map +1 -0
  143. package/dist/tools-logic/drill-down.d.ts +37 -0
  144. package/dist/tools-logic/drill-down.d.ts.map +1 -0
  145. package/dist/tools-logic/drill-down.js +63 -0
  146. package/dist/tools-logic/drill-down.js.map +1 -0
  147. package/dist/tools-logic/export-corrections.d.ts +63 -0
  148. package/dist/tools-logic/export-corrections.d.ts.map +1 -0
  149. package/dist/tools-logic/export-corrections.js +124 -0
  150. package/dist/tools-logic/export-corrections.js.map +1 -0
  151. package/dist/tools-logic/journal-capture.d.ts.map +1 -1
  152. package/dist/tools-logic/journal-capture.js +4 -1
  153. package/dist/tools-logic/journal-capture.js.map +1 -1
  154. package/dist/tools-logic/journal-write.d.ts.map +1 -1
  155. package/dist/tools-logic/journal-write.js +3 -2
  156. package/dist/tools-logic/journal-write.js.map +1 -1
  157. package/dist/tools-logic/local-archive-backend.d.ts +33 -0
  158. package/dist/tools-logic/local-archive-backend.d.ts.map +1 -0
  159. package/dist/tools-logic/local-archive-backend.js +124 -0
  160. package/dist/tools-logic/local-archive-backend.js.map +1 -0
  161. package/dist/tools-logic/memory-backend.d.ts +74 -0
  162. package/dist/tools-logic/memory-backend.d.ts.map +1 -0
  163. package/dist/tools-logic/memory-backend.js +172 -0
  164. package/dist/tools-logic/memory-backend.js.map +1 -0
  165. package/dist/tools-logic/memory-query.d.ts +10 -3
  166. package/dist/tools-logic/memory-query.d.ts.map +1 -1
  167. package/dist/tools-logic/memory-query.js +31 -7
  168. package/dist/tools-logic/memory-query.js.map +1 -1
  169. package/dist/tools-logic/mirror-builder.d.ts +115 -0
  170. package/dist/tools-logic/mirror-builder.d.ts.map +1 -0
  171. package/dist/tools-logic/mirror-builder.js +348 -0
  172. package/dist/tools-logic/mirror-builder.js.map +1 -0
  173. package/dist/tools-logic/palace-write.d.ts.map +1 -1
  174. package/dist/tools-logic/palace-write.js +3 -2
  175. package/dist/tools-logic/palace-write.js.map +1 -1
  176. package/dist/tools-logic/pipeline-close.d.ts.map +1 -1
  177. package/dist/tools-logic/pipeline-close.js +2 -1
  178. package/dist/tools-logic/pipeline-close.js.map +1 -1
  179. package/dist/tools-logic/pipeline-open.d.ts.map +1 -1
  180. package/dist/tools-logic/pipeline-open.js +2 -1
  181. package/dist/tools-logic/pipeline-open.js.map +1 -1
  182. package/dist/tools-logic/predict-correction.d.ts +47 -0
  183. package/dist/tools-logic/predict-correction.d.ts.map +1 -0
  184. package/dist/tools-logic/predict-correction.js +137 -0
  185. package/dist/tools-logic/predict-correction.js.map +1 -0
  186. package/dist/tools-logic/prior-builder.d.ts +27 -0
  187. package/dist/tools-logic/prior-builder.d.ts.map +1 -0
  188. package/dist/tools-logic/prior-builder.js +90 -0
  189. package/dist/tools-logic/prior-builder.js.map +1 -0
  190. package/dist/tools-logic/recognition-builder.d.ts +122 -0
  191. package/dist/tools-logic/recognition-builder.d.ts.map +1 -0
  192. package/dist/tools-logic/recognition-builder.js +289 -0
  193. package/dist/tools-logic/recognition-builder.js.map +1 -0
  194. package/dist/tools-logic/safety-consolidation.d.ts +104 -0
  195. package/dist/tools-logic/safety-consolidation.d.ts.map +1 -0
  196. package/dist/tools-logic/safety-consolidation.js +256 -0
  197. package/dist/tools-logic/safety-consolidation.js.map +1 -0
  198. package/dist/tools-logic/session-end-reflect.d.ts +18 -0
  199. package/dist/tools-logic/session-end-reflect.d.ts.map +1 -1
  200. package/dist/tools-logic/session-end-reflect.js +69 -30
  201. package/dist/tools-logic/session-end-reflect.js.map +1 -1
  202. package/dist/tools-logic/session-end.d.ts +18 -0
  203. package/dist/tools-logic/session-end.d.ts.map +1 -1
  204. package/dist/tools-logic/session-end.js +189 -34
  205. package/dist/tools-logic/session-end.js.map +1 -1
  206. package/dist/tools-logic/session-start-lite.d.ts +2 -0
  207. package/dist/tools-logic/session-start-lite.d.ts.map +1 -1
  208. package/dist/tools-logic/session-start-lite.js +11 -2
  209. package/dist/tools-logic/session-start-lite.js.map +1 -1
  210. package/dist/tools-logic/session-start.d.ts +81 -2
  211. package/dist/tools-logic/session-start.d.ts.map +1 -1
  212. package/dist/tools-logic/session-start.js +357 -40
  213. package/dist/tools-logic/session-start.js.map +1 -1
  214. package/dist/tools-logic/skill-propose.d.ts +27 -0
  215. package/dist/tools-logic/skill-propose.d.ts.map +1 -0
  216. package/dist/tools-logic/skill-propose.js +66 -0
  217. package/dist/tools-logic/skill-propose.js.map +1 -0
  218. package/dist/tools-logic/skill-recall.d.ts +5 -0
  219. package/dist/tools-logic/skill-recall.d.ts.map +1 -1
  220. package/dist/tools-logic/skill-recall.js +14 -1
  221. package/dist/tools-logic/skill-recall.js.map +1 -1
  222. package/dist/tools-logic/smart-recall.d.ts +17 -0
  223. package/dist/tools-logic/smart-recall.d.ts.map +1 -1
  224. package/dist/tools-logic/smart-recall.js +51 -16
  225. package/dist/tools-logic/smart-recall.js.map +1 -1
  226. package/dist/tools-logic/smart-remember.js +8 -9
  227. package/dist/tools-logic/smart-remember.js.map +1 -1
  228. package/dist/tools-logic/store-doctor.d.ts +71 -0
  229. package/dist/tools-logic/store-doctor.d.ts.map +1 -0
  230. package/dist/tools-logic/store-doctor.js +387 -0
  231. package/dist/tools-logic/store-doctor.js.map +1 -0
  232. package/dist/tools-logic/store-repair.d.ts +64 -0
  233. package/dist/tools-logic/store-repair.d.ts.map +1 -0
  234. package/dist/tools-logic/store-repair.js +243 -0
  235. package/dist/tools-logic/store-repair.js.map +1 -0
  236. package/dist/tools-logic/supersession.d.ts +34 -0
  237. package/dist/tools-logic/supersession.d.ts.map +1 -0
  238. package/dist/tools-logic/supersession.js +103 -0
  239. package/dist/tools-logic/supersession.js.map +1 -0
  240. package/dist/types.d.ts +7 -1
  241. package/dist/types.d.ts.map +1 -1
  242. package/dist/types.js +1 -1
  243. package/dist/vector/embedding.d.ts +8 -0
  244. package/dist/vector/embedding.d.ts.map +1 -1
  245. package/dist/vector/embedding.js +9 -1
  246. package/dist/vector/embedding.js.map +1 -1
  247. package/dist/vector/local-vector-backend.d.ts.map +1 -1
  248. package/dist/vector/local-vector-backend.js +14 -17
  249. package/dist/vector/local-vector-backend.js.map +1 -1
  250. package/package.json +2 -2
  251. package/dist/relevance/injector.d.ts +0 -51
  252. package/dist/relevance/injector.d.ts.map +0 -1
  253. package/dist/relevance/injector.js +0 -121
  254. package/dist/relevance/injector.js.map +0 -1
  255. package/dist/relevance/precision.d.ts +0 -109
  256. package/dist/relevance/precision.d.ts.map +0 -1
  257. package/dist/relevance/precision.js +0 -279
  258. package/dist/relevance/precision.js.map +0 -1
  259. package/dist/relevance/promoter.d.ts +0 -60
  260. package/dist/relevance/promoter.d.ts.map +0 -1
  261. package/dist/relevance/promoter.js +0 -336
  262. package/dist/relevance/promoter.js.map +0 -1
  263. package/dist/relevance/stager.d.ts +0 -61
  264. package/dist/relevance/stager.d.ts.map +0 -1
  265. package/dist/relevance/stager.js +0 -251
  266. package/dist/relevance/stager.js.map +0 -1
  267. package/dist/relevance/surfacer.d.ts +0 -34
  268. package/dist/relevance/surfacer.d.ts.map +0 -1
  269. package/dist/relevance/surfacer.js +0 -333
  270. package/dist/relevance/surfacer.js.map +0 -1
@@ -0,0 +1,294 @@
1
+ /**
2
+ * ab-experiment.ts — A/B injection switch for the correction-uplift experiment.
3
+ *
4
+ * DESIGN RATIONALE (C4, 2026-07-03)
5
+ * ─────────────────────────────────
6
+ * GOAL: measure whether injecting corrections at session_start reduces the rate
7
+ * at which the same correction recurs. The manipulated variable is the
8
+ * FULL correction-derived surface of the session_start payload. Capture,
9
+ * journaling, and session_end outcome recording stay ON in both arms —
10
+ * we A/B the injection EFFECT, not the capture pipeline.
11
+ *
12
+ * OFF-arm semantics (orchestrator ruling 2026-07-03): "this agent has no
13
+ * correction memory today". ALL correction-derived surfaces are absent or
14
+ * empty in an OFF payload:
15
+ * corrections → [] · watch_for → [] · predicted_risks → absent ·
16
+ * blind_spots → [] · mirror_available → absent · alignment → null ·
17
+ * recognition.person → absent (tendencies derive from blind spots).
18
+ * Additionally NO "retrieved" outcomes are recorded in OFF sessions —
19
+ * recording retrieval for rules the agent never saw would corrupt the
20
+ * precision KPI and the experiment itself.
21
+ *
22
+ * Insights, rooms, and captures (journal lineage) are intentionally NOT
23
+ * manipulated in v1 and stay in BOTH arms: v1 measures corrections only.
24
+ * If the experiment detects uplift, a v2 can vary insights too. This
25
+ * choice is recorded here so it cannot be silently reversed.
26
+ *
27
+ * OPT-IN DEFAULT: the experiment is DISABLED by default (AR_AB_ENABLED is not
28
+ * set). All sessions get full injection as today. The experiment owner
29
+ * sets AR_AB_ENABLED=1 when ready to start accumulating data. This is a
30
+ * hard requirement: the OFF arm degrades real user sessions, so we never
31
+ * run it without explicit intent.
32
+ *
33
+ * ARM ASSIGNMENT (deterministic, not random):
34
+ * hash( project + local-date + session_ordinal ) mod 2
35
+ * → 0 = ON, 1 = OFF
36
+ * The session_ordinal is the count of existing _ab_arms.jsonl rows for this
37
+ * project (0-indexed). Combined with project+date, this produces a balanced,
38
+ * non-predictable-by-time-of-day alternating pattern. Math.random is banned
39
+ * (repo rule). The hash is SHA-256 truncated to the first 8 hex digits.
40
+ *
41
+ * ESCAPE HATCH: AR_AB_FORCE=on|off overrides arm assignment for demos or
42
+ * emergencies. Forced sessions are flagged { forced: true } in the ledger
43
+ * and EXCLUDED from the outcome comparison in ab-report.mjs.
44
+ * AR_AB_FORCE without AR_AB_ENABLED=1 is a LOUD no-op: one stderr warning,
45
+ * no arm, no ledger row, injection unchanged (warnForcedWithoutEnabled).
46
+ *
47
+ * LEDGER: append-only <project>/corrections/_ab_arms.jsonl — TWO row kinds:
48
+ * assignment row (written by assignArm, counters zeroed):
49
+ * { ts, project, arm, forced, session_key, injected_count: 0, payload_tokens: 0 }
50
+ * result row (written by logABResult after the payload is built):
51
+ * { ts, kind: "result", session_key, injected_count, payload_tokens }
52
+ * readABArms merges result rows onto their assignment rows by session_key.
53
+ * NOTHING is ever rewritten in place — the append-only invariant is physical,
54
+ * not just conventional, so two concurrent same-project sessions can never
55
+ * clobber each other's counter fill (each appends its own result row).
56
+ *
57
+ * PAYLOAD SIGNAL: session_start result carries ab_arm: "on"|"off" so the
58
+ * transcript records which arm the session ran. The terse formatter appends
59
+ * a quiet trailing marker rather than a banner — we must NOT prime the agent
60
+ * to behave differently depending on the arm (experimenter effect).
61
+ */
62
+ import * as crypto from "node:crypto";
63
+ import * as fs from "node:fs";
64
+ import * as path from "node:path";
65
+ import { getRoot } from "../types.js";
66
+ // ── Path helper ───────────────────────────────────────────────────────────────
67
+ function abArmsPath(project) {
68
+ // Reuse the corrections dir path — share the directory for co-location.
69
+ const safe = (project || "unnamed")
70
+ .replace(/[^a-zA-Z0-9_\-]/g, "-")
71
+ .replace(/^-+|-+$/g, "")
72
+ .slice(0, 100) || "unnamed";
73
+ const root = getRoot();
74
+ const resolved = path.join(root, "projects", safe, "corrections");
75
+ const rootSep = root.endsWith(path.sep) ? root : root + path.sep;
76
+ if (!resolved.startsWith(rootSep)) {
77
+ throw new Error(`Invalid project (path escape): ${project}`);
78
+ }
79
+ return path.join(resolved, "_ab_arms.jsonl");
80
+ }
81
+ // ── Ordinal counter ───────────────────────────────────────────────────────────
82
+ /**
83
+ * Count existing ASSIGNMENT rows for this project (0-based ordinal for the
84
+ * next session). Result rows (kind:"result") are NOT counted — they are
85
+ * counter fills, not sessions. Returns 0 if the file does not exist or is
86
+ * unreadable. Never throws.
87
+ */
88
+ function countExistingRows(project) {
89
+ try {
90
+ const p = abArmsPath(project);
91
+ if (!fs.existsSync(p))
92
+ return 0;
93
+ const raw = fs.readFileSync(p, "utf-8");
94
+ let count = 0;
95
+ for (const line of raw.split("\n")) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed)
98
+ continue;
99
+ try {
100
+ const row = JSON.parse(trimmed);
101
+ if (row.kind !== "result")
102
+ count++;
103
+ }
104
+ catch {
105
+ // Malformed line — count it as an assignment slot so ordinals stay
106
+ // monotonic (never reuse a possibly-taken ordinal).
107
+ count++;
108
+ }
109
+ }
110
+ return count;
111
+ }
112
+ catch {
113
+ return 0;
114
+ }
115
+ }
116
+ // ── Deterministic arm assignment ──────────────────────────────────────────────
117
+ /**
118
+ * computeArm(project, localDate, ordinal) → "on" | "off"
119
+ *
120
+ * SHA-256( project + "|" + localDate + "|" + ordinal ) → first 8 hex digits
121
+ * as uint32, mod 2 → 0 = "on", 1 = "off".
122
+ *
123
+ * Properties:
124
+ * - Deterministic: same inputs always yield the same arm (safe to re-run).
125
+ * - Balanced: over 100 synthetic sessions, yields ~50 ON / 50 OFF.
126
+ * - Not time-predictable: ordinal + project, not clock-parity, controls arm.
127
+ * - Zero Math.random.
128
+ *
129
+ * Exported for unit tests (pure function, no side effects).
130
+ */
131
+ export function computeArm(project, localDate, ordinal) {
132
+ const input = `${project}|${localDate}|${ordinal}`;
133
+ const hex = crypto.createHash("sha256").update(input).digest("hex");
134
+ // Use first 8 hex chars (32 bits) to get a uint32, then mod 2.
135
+ const uint32 = parseInt(hex.slice(0, 8), 16);
136
+ return uint32 % 2 === 0 ? "on" : "off";
137
+ }
138
+ // ── Experiment enabled check ──────────────────────────────────────────────────
139
+ /**
140
+ * isExperimentEnabled() → true only when AR_AB_ENABLED=1.
141
+ *
142
+ * Default: false — all sessions get full injection (no degradation without
143
+ * explicit opt-in). The experiment owner sets AR_AB_ENABLED=1 when ready to
144
+ * start accumulating discordant-pair data.
145
+ */
146
+ export function isExperimentEnabled() {
147
+ return process.env["AR_AB_ENABLED"] === "1";
148
+ }
149
+ // ── Forced override ───────────────────────────────────────────────────────────
150
+ /**
151
+ * getForcedArm() → "on" | "off" | null
152
+ *
153
+ * Reads AR_AB_FORCE env var. Validated to "on" or "off"; any other value
154
+ * is silently ignored (null = no override). Forced sessions are flagged in
155
+ * the ledger and excluded from ab-report comparisons.
156
+ */
157
+ export function getForcedArm() {
158
+ const v = process.env["AR_AB_FORCE"];
159
+ if (v === "on" || v === "off")
160
+ return v;
161
+ return null;
162
+ }
163
+ /**
164
+ * warnForcedWithoutEnabled() → true if it warned.
165
+ *
166
+ * Orchestrator ruling 2026-07-03: AR_AB_FORCE without AR_AB_ENABLED=1 is a
167
+ * LOUD no-op — one stderr warning, no arm assignment, no ledger row, injection
168
+ * unchanged. Called by session_start on the disabled path so a misconfigured
169
+ * demo/emergency override never fails silently.
170
+ */
171
+ export function warnForcedWithoutEnabled() {
172
+ if (getForcedArm() !== null && !isExperimentEnabled()) {
173
+ process.stderr.write("AR_AB_FORCE is set but AR_AB_ENABLED=1 is not — force ignored, experiment disabled\n");
174
+ return true;
175
+ }
176
+ return false;
177
+ }
178
+ /**
179
+ * assignArm(project) → { arm, forced, session_key }
180
+ *
181
+ * Side effects:
182
+ * - Appends an incomplete ledger row to _ab_arms.jsonl (injected_count and
183
+ * payload_tokens are 0 at assignment time — they are filled by logABResult
184
+ * after session_start resolves its payload).
185
+ * - Creates the corrections directory if it does not exist.
186
+ *
187
+ * Never throws: if the ledger write fails, the assignment still returns a
188
+ * valid arm so session_start can proceed. Errors are swallowed silently —
189
+ * a ledger gap is better than a broken orientation.
190
+ *
191
+ * Called only when isExperimentEnabled() is true.
192
+ */
193
+ export function assignArm(project) {
194
+ const forced = getForcedArm();
195
+ const localDate = new Date().toLocaleDateString("sv"); // YYYY-MM-DD, local TZ
196
+ const ordinal = countExistingRows(project);
197
+ const arm = forced ?? computeArm(project, localDate, ordinal);
198
+ const session_key = `${project}/${localDate}/${ordinal}`;
199
+ // Append ledger row (with zeroed counters; logABResult fills them later).
200
+ try {
201
+ const p = abArmsPath(project);
202
+ fs.mkdirSync(path.dirname(p), { recursive: true });
203
+ const row = {
204
+ ts: new Date().toISOString(),
205
+ project,
206
+ arm,
207
+ forced: forced !== null,
208
+ session_key,
209
+ injected_count: 0, // filled by logABResult
210
+ payload_tokens: 0, // filled by logABResult
211
+ };
212
+ fs.appendFileSync(p, JSON.stringify(row) + "\n", "utf-8");
213
+ }
214
+ catch {
215
+ // Ledger write failure must never break session_start.
216
+ }
217
+ return { arm, forced: forced !== null, session_key };
218
+ }
219
+ /**
220
+ * logABResult(project, session_key, injected_count, payload_tokens)
221
+ *
222
+ * APPENDS a result row keyed by session_key — never rewrites the ledger.
223
+ * The previous in-place last-row rewrite had a race: two concurrent sessions
224
+ * of the same project could zero each other's counter fill (review CRITICAL,
225
+ * fixed per orchestrator ruling 2026-07-03). With append-only result rows,
226
+ * each session's fill lands regardless of interleaving; readABArms merges
227
+ * result rows onto assignment rows by session_key (last result wins).
228
+ *
229
+ * No-ops when the ledger file does not exist (no assignment ever happened —
230
+ * a result row without its assignment row would be an orphan). Never throws.
231
+ */
232
+ export function logABResult(project, session_key, injected_count, payload_tokens) {
233
+ try {
234
+ const p = abArmsPath(project);
235
+ if (!fs.existsSync(p))
236
+ return;
237
+ const row = {
238
+ ts: new Date().toISOString(),
239
+ kind: "result",
240
+ session_key,
241
+ injected_count,
242
+ payload_tokens,
243
+ };
244
+ fs.appendFileSync(p, JSON.stringify(row) + "\n", "utf-8");
245
+ }
246
+ catch {
247
+ // Silent — never break session_start.
248
+ }
249
+ }
250
+ // ── Read ledger (for ab-report.mjs) ──────────────────────────────────────────
251
+ /**
252
+ * readABArms(project) → ABLedgerRow[]
253
+ *
254
+ * Reads _ab_arms.jsonl for a project and MERGES result rows onto their
255
+ * assignment rows by session_key (last result row wins when duplicates exist).
256
+ * Returns assignment rows only — result rows are counter fills, not sessions.
257
+ * Malformed lines are silently skipped (never throws). Returns [] when the
258
+ * file does not exist.
259
+ */
260
+ export function readABArms(project) {
261
+ try {
262
+ const p = abArmsPath(project);
263
+ if (!fs.existsSync(p))
264
+ return [];
265
+ const raw = fs.readFileSync(p, "utf-8");
266
+ const assignments = [];
267
+ const results = new Map(); // session_key → last result row
268
+ for (const line of raw.split("\n")) {
269
+ const trimmed = line.trim();
270
+ if (!trimmed)
271
+ continue;
272
+ try {
273
+ const row = JSON.parse(trimmed);
274
+ if (row.kind === "result") {
275
+ results.set(row.session_key, row);
276
+ }
277
+ else {
278
+ assignments.push(row);
279
+ }
280
+ }
281
+ catch { /* skip malformed */ }
282
+ }
283
+ return assignments.map((a) => {
284
+ const r = results.get(a.session_key);
285
+ return r
286
+ ? { ...a, injected_count: r.injected_count, payload_tokens: r.payload_tokens }
287
+ : a;
288
+ });
289
+ }
290
+ catch {
291
+ return [];
292
+ }
293
+ }
294
+ //# sourceMappingURL=ab-experiment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ab-experiment.js","sourceRoot":"","sources":["../../src/storage/ab-experiment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * archive-prune.ts — retention pass for the lossless raw archive tier (Wave 2).
3
+ *
4
+ * Companion to archive-write.ts. Bounds ~/.agent-recall growth by gzipping (or
5
+ * removing) raw session segments under journal/archive/raw/ that are BOTH:
6
+ * (a) older than `olderThanDays` (by file mtime), AND
7
+ * (b) already CONSUMED — i.e. their mtime is at or before
8
+ * journal/archive/raw/.consumed.json `lastConsumedAt`.
9
+ *
10
+ * "Consumed" (single agreed definition, shared with advanceConsumeMarker in
11
+ * safety-consolidation.ts): a raw segment whose content has been folded into the
12
+ * palace by the login-free, regex journal→palace consolidation. The marker is
13
+ * advanced monotonically to `now − retentionWindow` by advanceConsumeMarker on
14
+ * the login-free safety pass (the optional dreaming loop MAY also advance it, but
15
+ * is no longer required to — that was the original Wave-5 plan, superseded).
16
+ *
17
+ * The consumed gate is load-bearing: while `lastConsumedAt` is null (nothing
18
+ * advanced the marker yet) this pass prunes NOTHING — we never discard raw bytes
19
+ * that have not been distilled into the palace.
20
+ *
21
+ * Mirrors journal-archive.ts's older_than_days / dry-run shape. Unlike
22
+ * archive-write.ts (which must never throw inside the Stop turn), this is an
23
+ * explicitly-invoked maintenance op: it surfaces no error to the caller but
24
+ * skips any single file it cannot process rather than aborting the whole pass.
25
+ */
26
+ export interface PruneRawArchiveOptions {
27
+ /** Segments whose mtime is older than this many days are prune candidates. Default 30. */
28
+ olderThanDays?: number;
29
+ /** When true, report what WOULD be pruned but write/delete nothing. Default false. */
30
+ dryRun?: boolean;
31
+ /** "gzip" (default) compresses to `<file>.gz` then removes the original; "remove" deletes outright. */
32
+ mode?: "gzip" | "remove";
33
+ }
34
+ export interface PruneRawArchiveResult {
35
+ /** The journal/archive/raw directory inspected. */
36
+ dir: string;
37
+ /** Raw `.md` segments examined. */
38
+ scanned: number;
39
+ /** Segments gzipped (0 in dryRun). */
40
+ gzipped: number;
41
+ /** Segments removed outright (0 in dryRun; only when mode === "remove"). */
42
+ removed: number;
43
+ /** Candidates matching BOTH gates (old AND consumed). Equals gzipped+removed when !dryRun and no per-file error. */
44
+ eligible: number;
45
+ /** Segments kept: too new, or not yet consumed. */
46
+ kept: number;
47
+ dryRun: boolean;
48
+ /** `lastConsumedAt` from .consumed.json; null when nothing is consumed yet (⇒ nothing pruned). */
49
+ consumedThrough: string | null;
50
+ }
51
+ /**
52
+ * Gzip-or-remove consumed, aged raw archive segments to bound disk growth.
53
+ * Safe by construction: with no consume marker advanced, it prunes nothing.
54
+ */
55
+ export declare function pruneRawArchive(project: string, options?: PruneRawArchiveOptions): PruneRawArchiveResult;
56
+ //# sourceMappingURL=archive-prune.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-prune.d.ts","sourceRoot":"","sources":["../../src/storage/archive-prune.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAeH,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,oHAAoH;IACpH,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,kGAAkG;IAClG,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAqFvB"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * archive-prune.ts — retention pass for the lossless raw archive tier (Wave 2).
3
+ *
4
+ * Companion to archive-write.ts. Bounds ~/.agent-recall growth by gzipping (or
5
+ * removing) raw session segments under journal/archive/raw/ that are BOTH:
6
+ * (a) older than `olderThanDays` (by file mtime), AND
7
+ * (b) already CONSUMED — i.e. their mtime is at or before
8
+ * journal/archive/raw/.consumed.json `lastConsumedAt`.
9
+ *
10
+ * "Consumed" (single agreed definition, shared with advanceConsumeMarker in
11
+ * safety-consolidation.ts): a raw segment whose content has been folded into the
12
+ * palace by the login-free, regex journal→palace consolidation. The marker is
13
+ * advanced monotonically to `now − retentionWindow` by advanceConsumeMarker on
14
+ * the login-free safety pass (the optional dreaming loop MAY also advance it, but
15
+ * is no longer required to — that was the original Wave-5 plan, superseded).
16
+ *
17
+ * The consumed gate is load-bearing: while `lastConsumedAt` is null (nothing
18
+ * advanced the marker yet) this pass prunes NOTHING — we never discard raw bytes
19
+ * that have not been distilled into the palace.
20
+ *
21
+ * Mirrors journal-archive.ts's older_than_days / dry-run shape. Unlike
22
+ * archive-write.ts (which must never throw inside the Stop turn), this is an
23
+ * explicitly-invoked maintenance op: it surfaces no error to the caller but
24
+ * skips any single file it cannot process rather than aborting the whole pass.
25
+ */
26
+ import * as fs from "node:fs";
27
+ import * as path from "node:path";
28
+ import { gzipSync } from "node:zlib";
29
+ import { archiveRawDir, sanitizeSlug } from "./paths.js";
30
+ import { readJsonSafe } from "./fs-utils.js";
31
+ const DAY_MS = 86_400_000;
32
+ // Raw segment filenames are `<YYYY-MM-DD>--<sid>.md` (see archive-write.ts).
33
+ // This deliberately excludes `.consumed.json`, `index.md`, and already-gzipped
34
+ // `*.md.gz` segments.
35
+ const RAW_SEGMENT_RE = /^\d{4}-\d{2}-\d{2}--.+\.md$/;
36
+ /**
37
+ * Gzip-or-remove consumed, aged raw archive segments to bound disk growth.
38
+ * Safe by construction: with no consume marker advanced, it prunes nothing.
39
+ */
40
+ export function pruneRawArchive(project, options = {}) {
41
+ const olderThanDays = options.olderThanDays ?? 30;
42
+ const dryRun = options.dryRun ?? false;
43
+ const mode = options.mode ?? "gzip";
44
+ const slug = sanitizeSlug(project);
45
+ const dir = archiveRawDir(slug);
46
+ const result = {
47
+ dir,
48
+ scanned: 0,
49
+ gzipped: 0,
50
+ removed: 0,
51
+ eligible: 0,
52
+ kept: 0,
53
+ dryRun,
54
+ consumedThrough: null,
55
+ };
56
+ if (!fs.existsSync(dir))
57
+ return result;
58
+ // Consume marker: only prune what the dreaming loop has already distilled.
59
+ const marker = readJsonSafe(path.join(dir, ".consumed.json"));
60
+ const lastConsumedAt = marker?.lastConsumedAt ?? null;
61
+ result.consumedThrough = lastConsumedAt;
62
+ // Guard: nothing consumed yet → never discard undistilled raw bytes.
63
+ const consumedCutoffMs = lastConsumedAt ? new Date(lastConsumedAt).getTime() : null;
64
+ const ageCutoffMs = Date.now() - olderThanDays * DAY_MS;
65
+ let entries;
66
+ try {
67
+ entries = fs.readdirSync(dir);
68
+ }
69
+ catch {
70
+ return result;
71
+ }
72
+ for (const name of entries) {
73
+ if (!RAW_SEGMENT_RE.test(name))
74
+ continue;
75
+ result.scanned++;
76
+ const full = path.join(dir, name);
77
+ let mtimeMs;
78
+ try {
79
+ mtimeMs = fs.statSync(full).mtimeMs;
80
+ }
81
+ catch {
82
+ continue; // vanished mid-pass; ignore
83
+ }
84
+ // Inclusive boundaries (deterministic; the consumed gate uses the exact
85
+ // `mtime <= lastConsumedAt` contract from the plan).
86
+ const isOld = mtimeMs <= ageCutoffMs;
87
+ const isConsumed = consumedCutoffMs !== null && mtimeMs <= consumedCutoffMs;
88
+ if (!(isOld && isConsumed)) {
89
+ result.kept++;
90
+ continue;
91
+ }
92
+ result.eligible++;
93
+ if (dryRun)
94
+ continue; // report only; touch nothing
95
+ try {
96
+ if (mode === "gzip") {
97
+ const gzPath = full + ".gz";
98
+ if (!fs.existsSync(gzPath)) {
99
+ const raw = fs.readFileSync(full);
100
+ const tmp = gzPath + ".tmp." + process.pid;
101
+ fs.writeFileSync(tmp, gzipSync(raw));
102
+ fs.renameSync(tmp, gzPath);
103
+ }
104
+ fs.unlinkSync(full);
105
+ result.gzipped++;
106
+ }
107
+ else {
108
+ fs.unlinkSync(full);
109
+ result.removed++;
110
+ }
111
+ }
112
+ catch {
113
+ // A single failed file must not abort the pass; it simply is not counted.
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ //# sourceMappingURL=archive-prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-prune.js","sourceRoot":"","sources":["../../src/storage/archive-prune.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * archive-write.ts — the lossless, mechanical, judgment-free verbatim tier
3
+ * (Wave 2). Written on EVERY session end, BEFORE any capture/summary logic, so
4
+ * a session can never be "lost" just because nothing was captured.
5
+ *
6
+ * Hard invariants (privacy + safety):
7
+ * - LOCAL-ONLY. This module MUST NOT import journal-write or syncToSupabase.
8
+ * The raw tier never leaves the machine. (Verified by a structural test.)
9
+ * - NEVER throws to the caller. Any failure returns { path:"", bytes:0 } so a
10
+ * crash here can never break the Stop turn.
11
+ * - sessionId is UNTRUSTED (arrives from hook stdin) → MUST pass through
12
+ * sanitizeSlug before any path.join (MCP-security rule).
13
+ * - Idempotent on the session UUID: a second call for the same session is a
14
+ * no-op (returns bytes:0) and never overwrites the original verbatim bytes.
15
+ */
16
+ export interface ArchiveSessionInput {
17
+ project: string;
18
+ /** UNTRUSTED — sanitized before use. The dedup key (session UUID). */
19
+ sessionId: string;
20
+ /** The transcript file path, recorded in frontmatter for provenance only. */
21
+ transcriptPath?: string;
22
+ /** The verbatim transcript bytes (head+tail). Written as-is, no truncation. */
23
+ rawTranscript: string;
24
+ /** Optional one-line human summary (first user message), for the index line. */
25
+ summary?: string;
26
+ }
27
+ export interface ArchiveSessionResult {
28
+ /** Absolute path of the written (or pre-existing) raw file; "" on failure. */
29
+ path: string;
30
+ /** Bytes of rawTranscript written; 0 if idempotent no-op or on failure. */
31
+ bytes: number;
32
+ }
33
+ /**
34
+ * Write a verbatim, lossless dump of a session under journal/archive/raw/.
35
+ * Mechanical and judgment-free: no min-length gate, no summarization, no sync.
36
+ */
37
+ export declare function archiveSession(input: ArchiveSessionInput): ArchiveSessionResult;
38
+ //# sourceMappingURL=archive-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-write.d.ts","sourceRoot":"","sources":["../../src/storage/archive-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf;AAsCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAmD/E"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * archive-write.ts — the lossless, mechanical, judgment-free verbatim tier
3
+ * (Wave 2). Written on EVERY session end, BEFORE any capture/summary logic, so
4
+ * a session can never be "lost" just because nothing was captured.
5
+ *
6
+ * Hard invariants (privacy + safety):
7
+ * - LOCAL-ONLY. This module MUST NOT import journal-write or syncToSupabase.
8
+ * The raw tier never leaves the machine. (Verified by a structural test.)
9
+ * - NEVER throws to the caller. Any failure returns { path:"", bytes:0 } so a
10
+ * crash here can never break the Stop turn.
11
+ * - sessionId is UNTRUSTED (arrives from hook stdin) → MUST pass through
12
+ * sanitizeSlug before any path.join (MCP-security rule).
13
+ * - Idempotent on the session UUID: a second call for the same session is a
14
+ * no-op (returns bytes:0) and never overwrites the original verbatim bytes.
15
+ */
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+ import { archiveRawDir, sanitizeSlug } from "./paths.js";
19
+ import { ensureDir, todayISO, writeJsonAtomic } from "./fs-utils.js";
20
+ import { writeMemoryProtocol } from "./memory-protocol.js";
21
+ function buildFrontmatter(meta) {
22
+ const lines = [
23
+ "---",
24
+ `project: ${meta.project}`,
25
+ `sessionId: ${meta.sessionId}`,
26
+ `savedAt: ${meta.savedAt}`,
27
+ `source: ${meta.source}`,
28
+ ];
29
+ if (meta.transcriptPath) {
30
+ // JSON-encode to neutralize any newline/colon in the untrusted path.
31
+ lines.push(`transcriptPath: ${JSON.stringify(meta.transcriptPath)}`);
32
+ }
33
+ lines.push("---", "");
34
+ return lines.join("\n");
35
+ }
36
+ /**
37
+ * Append a single line to journal/archive/index.md. Best-effort; swallows errors.
38
+ */
39
+ function appendIndexLine(slug, line) {
40
+ try {
41
+ // archiveRawDir = .../journal/archive/raw ; index.md lives one level up.
42
+ const indexPath = path.join(path.dirname(archiveRawDir(slug)), "index.md");
43
+ ensureDir(path.dirname(indexPath));
44
+ fs.appendFileSync(indexPath, line + "\n", "utf-8");
45
+ }
46
+ catch {
47
+ // index is a convenience; failure must not abort the archive write.
48
+ }
49
+ }
50
+ /**
51
+ * Write a verbatim, lossless dump of a session under journal/archive/raw/.
52
+ * Mechanical and judgment-free: no min-length gate, no summarization, no sync.
53
+ */
54
+ export function archiveSession(input) {
55
+ try {
56
+ if (typeof input.rawTranscript !== "string") {
57
+ // Defensive: a non-string body would throw on .length / write below.
58
+ return { path: "", bytes: 0 };
59
+ }
60
+ const slug = sanitizeSlug(input.project); // also hardens the project name
61
+ const sid = sanitizeSlug(input.sessionId); // UNTRUSTED → sanitize first
62
+ const dir = archiveRawDir(slug);
63
+ ensureDir(dir);
64
+ const dest = path.join(dir, `${todayISO()}--${sid}.md`);
65
+ // Idempotent on the session UUID: never overwrite an existing dump.
66
+ if (fs.existsSync(dest)) {
67
+ return { path: dest, bytes: 0 };
68
+ }
69
+ const savedAt = new Date().toISOString();
70
+ const frontmatter = buildFrontmatter({
71
+ project: slug,
72
+ sessionId: sid,
73
+ savedAt,
74
+ source: "hook-archive",
75
+ transcriptPath: input.transcriptPath,
76
+ });
77
+ // Atomic write: tmp + rename so a partial dump never appears as complete.
78
+ const tmp = dest + ".tmp." + process.pid;
79
+ fs.writeFileSync(tmp, frontmatter + input.rawTranscript, "utf-8");
80
+ fs.renameSync(tmp, dest);
81
+ // One append-only index line.
82
+ const summary = (input.summary ?? "").replace(/\s+/g, " ").trim().slice(0, 200);
83
+ appendIndexLine(slug, `${todayISO()} ${sid} ${summary}`.trimEnd());
84
+ // Seed the consume marker if absent (the dreaming loop advances it).
85
+ const consumed = path.join(dir, ".consumed.json");
86
+ if (!fs.existsSync(consumed)) {
87
+ writeJsonAtomic(consumed, { lastConsumedOffset: 0, lastConsumedAt: null });
88
+ }
89
+ // Write the self-describing protocol doc once.
90
+ writeMemoryProtocol(slug);
91
+ return { path: dest, bytes: input.rawTranscript.length };
92
+ }
93
+ catch {
94
+ // NEVER throw into the Stop turn.
95
+ return { path: "", bytes: 0 };
96
+ }
97
+ }
98
+ //# sourceMappingURL=archive-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-write.js","sourceRoot":"","sources":["../../src/storage/archive-write.ts"],"names":[],"mappings":"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"}