elepha 0.1.0

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 (290) hide show
  1. package/LICENSE.md +375 -0
  2. package/README.md +135 -0
  3. package/bin/elepha.js +2 -0
  4. package/dist/adapters/base.js +600 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/claude-code.js +260 -0
  7. package/dist/adapters/claude-code.js.map +1 -0
  8. package/dist/adapters/codex.js +499 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/discriminators.js +39 -0
  11. package/dist/adapters/discriminators.js.map +1 -0
  12. package/dist/adapters/index.js +6 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/adapters/internal-command.js +3 -0
  15. package/dist/adapters/internal-command.js.map +1 -0
  16. package/dist/cli/backup-wizard.js +97 -0
  17. package/dist/cli/backup-wizard.js.map +1 -0
  18. package/dist/cli/capture-service.js +80 -0
  19. package/dist/cli/capture-service.js.map +1 -0
  20. package/dist/cli/commands/backfills.js +336 -0
  21. package/dist/cli/commands/backfills.js.map +1 -0
  22. package/dist/cli/commands/backup.js +234 -0
  23. package/dist/cli/commands/backup.js.map +1 -0
  24. package/dist/cli/commands/config.js +66 -0
  25. package/dist/cli/commands/config.js.map +1 -0
  26. package/dist/cli/commands/consent.js +237 -0
  27. package/dist/cli/commands/consent.js.map +1 -0
  28. package/dist/cli/commands/daemon-control.js +76 -0
  29. package/dist/cli/commands/daemon-control.js.map +1 -0
  30. package/dist/cli/commands/doctor.js +31 -0
  31. package/dist/cli/commands/doctor.js.map +1 -0
  32. package/dist/cli/commands/hook.js +24 -0
  33. package/dist/cli/commands/hook.js.map +1 -0
  34. package/dist/cli/commands/import.js +611 -0
  35. package/dist/cli/commands/import.js.map +1 -0
  36. package/dist/cli/commands/init.js +19 -0
  37. package/dist/cli/commands/init.js.map +1 -0
  38. package/dist/cli/commands/inspect.js +54 -0
  39. package/dist/cli/commands/inspect.js.map +1 -0
  40. package/dist/cli/commands/install.js +49 -0
  41. package/dist/cli/commands/install.js.map +1 -0
  42. package/dist/cli/commands/internal.js +16 -0
  43. package/dist/cli/commands/internal.js.map +1 -0
  44. package/dist/cli/commands/mcp.js +12 -0
  45. package/dist/cli/commands/mcp.js.map +1 -0
  46. package/dist/cli/commands/projects.js +26 -0
  47. package/dist/cli/commands/projects.js.map +1 -0
  48. package/dist/cli/commands/purge.js +216 -0
  49. package/dist/cli/commands/purge.js.map +1 -0
  50. package/dist/cli/commands/reingest.js +80 -0
  51. package/dist/cli/commands/reingest.js.map +1 -0
  52. package/dist/cli/commands/rekey.js +58 -0
  53. package/dist/cli/commands/rekey.js.map +1 -0
  54. package/dist/cli/commands/restore.js +376 -0
  55. package/dist/cli/commands/restore.js.map +1 -0
  56. package/dist/cli/commands/rollup.js +130 -0
  57. package/dist/cli/commands/rollup.js.map +1 -0
  58. package/dist/cli/commands/sanitize.js +72 -0
  59. package/dist/cli/commands/sanitize.js.map +1 -0
  60. package/dist/cli/commands/segment.js +153 -0
  61. package/dist/cli/commands/segment.js.map +1 -0
  62. package/dist/cli/commands/self-update.js +26 -0
  63. package/dist/cli/commands/self-update.js.map +1 -0
  64. package/dist/cli/commands/start.js +53 -0
  65. package/dist/cli/commands/start.js.map +1 -0
  66. package/dist/cli/commands/stats.js +42 -0
  67. package/dist/cli/commands/stats.js.map +1 -0
  68. package/dist/cli/commands/status.js +73 -0
  69. package/dist/cli/commands/status.js.map +1 -0
  70. package/dist/cli/commands/uninstall.js +23 -0
  71. package/dist/cli/commands/uninstall.js.map +1 -0
  72. package/dist/cli/config-wizard.js +104 -0
  73. package/dist/cli/config-wizard.js.map +1 -0
  74. package/dist/cli/destructive-op.js +32 -0
  75. package/dist/cli/destructive-op.js.map +1 -0
  76. package/dist/cli/import-wizard.js +89 -0
  77. package/dist/cli/import-wizard.js.map +1 -0
  78. package/dist/cli/index.js +74 -0
  79. package/dist/cli/index.js.map +1 -0
  80. package/dist/cli/init-wizard.js +80 -0
  81. package/dist/cli/init-wizard.js.map +1 -0
  82. package/dist/cli/init.js +159 -0
  83. package/dist/cli/init.js.map +1 -0
  84. package/dist/cli/launcher-probe.js +71 -0
  85. package/dist/cli/launcher-probe.js.map +1 -0
  86. package/dist/cli/project-path.js +16 -0
  87. package/dist/cli/project-path.js.map +1 -0
  88. package/dist/cli/purge-wizard.js +200 -0
  89. package/dist/cli/purge-wizard.js.map +1 -0
  90. package/dist/cli/restore-wizard.js +101 -0
  91. package/dist/cli/restore-wizard.js.map +1 -0
  92. package/dist/cli/shared.js +128 -0
  93. package/dist/cli/shared.js.map +1 -0
  94. package/dist/cli/status.js +22 -0
  95. package/dist/cli/status.js.map +1 -0
  96. package/dist/cli/wordmark.js +15 -0
  97. package/dist/cli/wordmark.js.map +1 -0
  98. package/dist/config/constants.js +115 -0
  99. package/dist/config/constants.js.map +1 -0
  100. package/dist/config/env.js +168 -0
  101. package/dist/config/env.js.map +1 -0
  102. package/dist/config/memory-config.js +63 -0
  103. package/dist/config/memory-config.js.map +1 -0
  104. package/dist/config/paths.js +343 -0
  105. package/dist/config/paths.js.map +1 -0
  106. package/dist/config/settings.js +132 -0
  107. package/dist/config/settings.js.map +1 -0
  108. package/dist/config/version.js +12 -0
  109. package/dist/config/version.js.map +1 -0
  110. package/dist/daemon/failure-window.js +38 -0
  111. package/dist/daemon/failure-window.js.map +1 -0
  112. package/dist/daemon/heartbeat.js +40 -0
  113. package/dist/daemon/heartbeat.js.map +1 -0
  114. package/dist/daemon/index.js +873 -0
  115. package/dist/daemon/index.js.map +1 -0
  116. package/dist/daemon/log-rotation.js +27 -0
  117. package/dist/daemon/log-rotation.js.map +1 -0
  118. package/dist/daemon/readability-guard.js +89 -0
  119. package/dist/daemon/readability-guard.js.map +1 -0
  120. package/dist/daemon/rollup-service.js +239 -0
  121. package/dist/daemon/rollup-service.js.map +1 -0
  122. package/dist/daemon/update-check.js +87 -0
  123. package/dist/daemon/update-check.js.map +1 -0
  124. package/dist/daemon/work-queue.js +28 -0
  125. package/dist/daemon/work-queue.js.map +1 -0
  126. package/dist/discovery/session-projects.js +242 -0
  127. package/dist/discovery/session-projects.js.map +1 -0
  128. package/dist/hooks/codex-trust.js +80 -0
  129. package/dist/hooks/codex-trust.js.map +1 -0
  130. package/dist/hooks/common.js +81 -0
  131. package/dist/hooks/common.js.map +1 -0
  132. package/dist/hooks/hook-log.js +38 -0
  133. package/dist/hooks/hook-log.js.map +1 -0
  134. package/dist/hooks/installer.js +200 -0
  135. package/dist/hooks/installer.js.map +1 -0
  136. package/dist/hooks/session-start.js +321 -0
  137. package/dist/hooks/session-start.js.map +1 -0
  138. package/dist/hooks/user-prompt-submit.js +248 -0
  139. package/dist/hooks/user-prompt-submit.js.map +1 -0
  140. package/dist/install/binary.js +72 -0
  141. package/dist/install/binary.js.map +1 -0
  142. package/dist/install/config-file.js +119 -0
  143. package/dist/install/config-file.js.map +1 -0
  144. package/dist/install/daemon-health.js +48 -0
  145. package/dist/install/daemon-health.js.map +1 -0
  146. package/dist/install/daemon-service.js +309 -0
  147. package/dist/install/daemon-service.js.map +1 -0
  148. package/dist/install/doctor.js +180 -0
  149. package/dist/install/doctor.js.map +1 -0
  150. package/dist/install/health-checks.js +90 -0
  151. package/dist/install/health-checks.js.map +1 -0
  152. package/dist/install/installer.js +411 -0
  153. package/dist/install/installer.js.map +1 -0
  154. package/dist/install/launchd-backend.js +261 -0
  155. package/dist/install/launchd-backend.js.map +1 -0
  156. package/dist/install/launcher.js +224 -0
  157. package/dist/install/launcher.js.map +1 -0
  158. package/dist/install/markers.js +14 -0
  159. package/dist/install/markers.js.map +1 -0
  160. package/dist/install/platform.js +29 -0
  161. package/dist/install/platform.js.map +1 -0
  162. package/dist/install/present-tools.js +16 -0
  163. package/dist/install/present-tools.js.map +1 -0
  164. package/dist/install/self-update.js +120 -0
  165. package/dist/install/self-update.js.map +1 -0
  166. package/dist/install/service-backend.js +36 -0
  167. package/dist/install/service-backend.js.map +1 -0
  168. package/dist/install/service-manifest.js +36 -0
  169. package/dist/install/service-manifest.js.map +1 -0
  170. package/dist/install/status.js +58 -0
  171. package/dist/install/status.js.map +1 -0
  172. package/dist/install/systemd-backend.js +225 -0
  173. package/dist/install/systemd-backend.js.map +1 -0
  174. package/dist/markers.js +5 -0
  175. package/dist/markers.js.map +1 -0
  176. package/dist/mcp/installer.js +131 -0
  177. package/dist/mcp/installer.js.map +1 -0
  178. package/dist/mcp/server.js +81 -0
  179. package/dist/mcp/server.js.map +1 -0
  180. package/dist/mcp/tools/get-context.js +3 -0
  181. package/dist/mcp/tools/get-context.js.map +1 -0
  182. package/dist/mcp/tools/list-projects.js +3 -0
  183. package/dist/mcp/tools/list-projects.js.map +1 -0
  184. package/dist/mcp/tools/save-memory.js +3 -0
  185. package/dist/mcp/tools/save-memory.js.map +1 -0
  186. package/dist/mcp/tools.js +288 -0
  187. package/dist/mcp/tools.js.map +1 -0
  188. package/dist/rendering/raw-turn-renderer.js +85 -0
  189. package/dist/rendering/raw-turn-renderer.js.map +1 -0
  190. package/dist/security/file-permissions.js +15 -0
  191. package/dist/security/file-permissions.js.map +1 -0
  192. package/dist/security/provider-transcript.js +74 -0
  193. package/dist/security/provider-transcript.js.map +1 -0
  194. package/dist/security/sanitize.js +210 -0
  195. package/dist/security/sanitize.js.map +1 -0
  196. package/dist/security/self-ingestion.js +47 -0
  197. package/dist/security/self-ingestion.js.map +1 -0
  198. package/dist/security/sentinel.js +19 -0
  199. package/dist/security/sentinel.js.map +1 -0
  200. package/dist/security/subprocess-allowlist.js +421 -0
  201. package/dist/security/subprocess-allowlist.js.map +1 -0
  202. package/dist/serving/instructions.js +33 -0
  203. package/dist/serving/instructions.js.map +1 -0
  204. package/dist/serving/lexical-recall.js +249 -0
  205. package/dist/serving/lexical-recall.js.map +1 -0
  206. package/dist/serving/session-reader.js +302 -0
  207. package/dist/serving/session-reader.js.map +1 -0
  208. package/dist/storage/backfill-runner.js +34 -0
  209. package/dist/storage/backfill-runner.js.map +1 -0
  210. package/dist/storage/backup.js +46 -0
  211. package/dist/storage/backup.js.map +1 -0
  212. package/dist/storage/candidate-validator.js +77 -0
  213. package/dist/storage/candidate-validator.js.map +1 -0
  214. package/dist/storage/consent-store.js +198 -0
  215. package/dist/storage/consent-store.js.map +1 -0
  216. package/dist/storage/custom-title-backfill.js +68 -0
  217. package/dist/storage/custom-title-backfill.js.map +1 -0
  218. package/dist/storage/db.js +312 -0
  219. package/dist/storage/db.js.map +1 -0
  220. package/dist/storage/external-agent-import-purge.js +182 -0
  221. package/dist/storage/external-agent-import-purge.js.map +1 -0
  222. package/dist/storage/first-prompt-search-backfill.js +76 -0
  223. package/dist/storage/first-prompt-search-backfill.js.map +1 -0
  224. package/dist/storage/first-prompt-search.js +7 -0
  225. package/dist/storage/first-prompt-search.js.map +1 -0
  226. package/dist/storage/injection-store.js +37 -0
  227. package/dist/storage/injection-store.js.map +1 -0
  228. package/dist/storage/memory-store.js +428 -0
  229. package/dist/storage/memory-store.js.map +1 -0
  230. package/dist/storage/project-resolver.js +294 -0
  231. package/dist/storage/project-resolver.js.map +1 -0
  232. package/dist/storage/project-store.js +104 -0
  233. package/dist/storage/project-store.js.map +1 -0
  234. package/dist/storage/rendered-chars-backfill.js +85 -0
  235. package/dist/storage/rendered-chars-backfill.js.map +1 -0
  236. package/dist/storage/resegmentation.js +582 -0
  237. package/dist/storage/resegmentation.js.map +1 -0
  238. package/dist/storage/rollup-store.js +246 -0
  239. package/dist/storage/rollup-store.js.map +1 -0
  240. package/dist/storage/root-commit-backfill.js +32 -0
  241. package/dist/storage/root-commit-backfill.js.map +1 -0
  242. package/dist/storage/sanitize-backfill.js +184 -0
  243. package/dist/storage/sanitize-backfill.js.map +1 -0
  244. package/dist/storage/segmentation.js +87 -0
  245. package/dist/storage/segmentation.js.map +1 -0
  246. package/dist/storage/session-fields-backfill.js +196 -0
  247. package/dist/storage/session-fields-backfill.js.map +1 -0
  248. package/dist/storage/session-read-model.js +130 -0
  249. package/dist/storage/session-read-model.js.map +1 -0
  250. package/dist/storage/session-store.js +200 -0
  251. package/dist/storage/session-store.js.map +1 -0
  252. package/dist/storage/session-title-backfill.js +75 -0
  253. package/dist/storage/session-title-backfill.js.map +1 -0
  254. package/dist/storage/session-title.js +71 -0
  255. package/dist/storage/session-title.js.map +1 -0
  256. package/dist/storage/shown-session-list-store.js +33 -0
  257. package/dist/storage/shown-session-list-store.js.map +1 -0
  258. package/dist/storage/stats.js +30 -0
  259. package/dist/storage/stats.js.map +1 -0
  260. package/dist/storage/turn-store.js +202 -0
  261. package/dist/storage/turn-store.js.map +1 -0
  262. package/dist/storage/ulid.js +17 -0
  263. package/dist/storage/ulid.js.map +1 -0
  264. package/dist/summarizer/anthropic-call.js +76 -0
  265. package/dist/summarizer/anthropic-call.js.map +1 -0
  266. package/dist/summarizer/call-log.js +120 -0
  267. package/dist/summarizer/call-log.js.map +1 -0
  268. package/dist/summarizer/haiku-provider.js +136 -0
  269. package/dist/summarizer/haiku-provider.js.map +1 -0
  270. package/dist/summarizer/pricing.js +8 -0
  271. package/dist/summarizer/pricing.js.map +1 -0
  272. package/dist/summarizer/prompt-cache.js +6 -0
  273. package/dist/summarizer/prompt-cache.js.map +1 -0
  274. package/dist/summarizer/prompt.js +47 -0
  275. package/dist/summarizer/prompt.js.map +1 -0
  276. package/dist/summarizer/provider-config.js +25 -0
  277. package/dist/summarizer/provider-config.js.map +1 -0
  278. package/dist/summarizer/rollup-prompt.js +225 -0
  279. package/dist/summarizer/rollup-prompt.js.map +1 -0
  280. package/dist/summarizer/rollup-provider.js +224 -0
  281. package/dist/summarizer/rollup-provider.js.map +1 -0
  282. package/dist/types/index.js +10 -0
  283. package/dist/types/index.js.map +1 -0
  284. package/dist/util/error.js +5 -0
  285. package/dist/util/error.js.map +1 -0
  286. package/dist/util/fs.js +97 -0
  287. package/dist/util/fs.js.map +1 -0
  288. package/dist/util/relative-time.js +12 -0
  289. package/dist/util/relative-time.js.map +1 -0
  290. package/package.json +81 -0
@@ -0,0 +1,224 @@
1
+ // Haiku-backed session rollup generation. Same defensive-parsing contract as
2
+ // the per-turn summarizer (see haiku-provider.ts): the model wraps JSON in
3
+ // markdown fences regardless of instructions, so output is extracted, not
4
+ // trusted.
5
+ import Anthropic from '@anthropic-ai/sdk';
6
+ import { z } from 'zod';
7
+ import { DECISION_PROVENANCE_OVERLAP_THRESHOLD, DEFAULT_ANTHROPIC_MODEL, ROLLUP_MAX_TOKENS, ROLLUP_RETRY_MAX_TOKENS, } from '../config/constants.js';
8
+ import { callAnthropicJson, extractJsonCandidate } from './anthropic-call.js';
9
+ import { SummarizerCallLog } from './call-log.js';
10
+ import { cacheableSystemPrompt } from './prompt-cache.js';
11
+ import { buildRollupMergeUserContent, buildRollupUserContent, ROLLUP_MERGE_SYSTEM_PROMPT, ROLLUP_SYSTEM_PROMPT, } from './rollup-prompt.js';
12
+ /**
13
+ * `turn_index` arrives as a STRING from this model - `"turn_index": "0"` -
14
+ * despite the prompt asking for a number, in fixed-sampling runs, reproducibly.
15
+ *
16
+ * The first version of this field was `z.number().int().optional()`, whose
17
+ * comment claimed optionality meant "a model that ignores the instruction
18
+ * still parses". It does not: `optional()` tolerates an ABSENT key, not a
19
+ * wrong-typed one, so every rollup failed to parse and a rebuild died on
20
+ * its first three sessions. Textbook instance of this codebase's own
21
+ * "format instructions in a prompt are a suggestion, not a contract" - added
22
+ * while fixing a different instance of the same rule.
23
+ */
24
+ function coerceTurnIndex(value) {
25
+ if (typeof value === 'number' && Number.isInteger(value)) {
26
+ return value;
27
+ }
28
+ if (typeof value === 'string' && /^-?\d+$/.test(value.trim())) {
29
+ return Number(value.trim());
30
+ }
31
+ return undefined;
32
+ }
33
+ /**
34
+ * One decision, or undefined if it is unusable.
35
+ *
36
+ * Nothing here can fail the enclosing rollup. A malformed or hollow decision
37
+ * costs that decision; it must never cost the title, the summary, the pending
38
+ * items and every other decision in the session, which is what a strict
39
+ * element schema inside `z.array()` does.
40
+ */
41
+ function coerceDecision(raw) {
42
+ if (!raw || typeof raw !== 'object') {
43
+ return undefined;
44
+ }
45
+ const rec = raw;
46
+ const what = typeof rec.what === 'string' ? rec.what.trim() : '';
47
+ // A decision with no reason is dropped, per the prompt contract ("omit
48
+ // that decision entirely rather than emitting a hollow one"). Counted and
49
+ // reported by the caller rather than dropped silently - see
50
+ // RollupResult.droppedDecisions.
51
+ const why = typeof rec.why === 'string' ? rec.why.trim() : '';
52
+ if (what === '' || why === '') {
53
+ return undefined;
54
+ }
55
+ return { what, why, turn_index: coerceTurnIndex(rec.turn_index) };
56
+ }
57
+ const rollupSchema = z.object({
58
+ title: z.string().min(1),
59
+ summary: z.string().min(1),
60
+ decisions: z.array(z.unknown()),
61
+ pending_items: z.array(z.unknown()),
62
+ });
63
+ const EMPTY_ROLLUP = { title: '', summary: '', decisions: [], pending_items: [], droppedDecisions: 0 };
64
+ function tryParse(candidate) {
65
+ try {
66
+ const result = rollupSchema.safeParse(JSON.parse(candidate));
67
+ if (!result.success) {
68
+ return undefined;
69
+ }
70
+ const decisions = [];
71
+ let droppedDecisions = 0;
72
+ for (const raw of result.data.decisions) {
73
+ const decision = coerceDecision(raw);
74
+ if (decision) {
75
+ decisions.push(decision);
76
+ }
77
+ else {
78
+ droppedDecisions++;
79
+ }
80
+ }
81
+ return {
82
+ title: result.data.title,
83
+ summary: result.data.summary,
84
+ decisions,
85
+ pending_items: result.data.pending_items.filter((p) => typeof p === 'string' && p.trim() !== ''),
86
+ droppedDecisions,
87
+ };
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ export function parseRollup(raw) {
94
+ for (const candidate of extractJsonCandidate(raw)) {
95
+ const parsed = tryParse(candidate);
96
+ if (parsed) {
97
+ return parsed;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+ const STOPWORDS = new Set('the a an to for and or with by as at is are was were be been it its this that from into use used using in on of'.split(' '));
103
+ function tokens(s) {
104
+ return new Set(s
105
+ .toLowerCase()
106
+ .replace(/[^a-z0-9_./-]+/g, ' ')
107
+ .split(/\s+/)
108
+ .filter((t) => t.length > 2 && !STOPWORDS.has(t)));
109
+ }
110
+ function overlap(a, b) {
111
+ if (a.size === 0 || b.size === 0) {
112
+ return 0;
113
+ }
114
+ let shared = 0;
115
+ for (const t of a) {
116
+ if (b.has(t)) {
117
+ shared++;
118
+ }
119
+ }
120
+ return shared / Math.min(a.size, b.size);
121
+ }
122
+ /**
123
+ * Assigns each returned decision a turn of origin, IN CODE.
124
+ *
125
+ * The prompt asks for `turn_index`, but the whole reason this exists is that
126
+ * the merge model does not reliably follow ordering instructions - trusting it
127
+ * to self-report provenance would rebuild the bug one level up. So:
128
+ *
129
+ * 1. Accept the model's `turn_index` only when it names a turn that was
130
+ * actually in this batch. Anything else is discarded silently-as-data, not
131
+ * silently-as-failure: the next steps still produce a value.
132
+ * 2. Otherwise, match the decision text against the batch's own per-turn
133
+ * decision strings and take the best-overlapping turn.
134
+ * 3. Otherwise, fall back to the batch's LAST turn index. The decision came
135
+ * out of this batch, so its true origin is somewhere in it; biasing the
136
+ * fallback newest keeps an unplaceable decision visible in the brief
137
+ * instead of sinking it to the bottom, which is the failure mode being
138
+ * fixed. Over-attributing recency is recoverable; under-attributing it is
139
+ * the bug.
140
+ */
141
+ export function attributeDecisions(decisions, batch) {
142
+ if (batch.length === 0) {
143
+ return decisions.map((d) => ({ what: d.what, why: d.why }));
144
+ }
145
+ const byIndex = new Map(batch.map((t) => [t.turnIndex, t]));
146
+ const lastTurn = batch[batch.length - 1];
147
+ const turnTokens = batch.map((t) => ({ turn: t, toks: tokens(t.decisions.join(' ')) }));
148
+ return decisions.map((d) => {
149
+ const claimed = d.turn_index !== undefined ? byIndex.get(d.turn_index) : undefined;
150
+ if (claimed) {
151
+ return { what: d.what, why: d.why, turnIndex: claimed.turnIndex, at: claimed.startedAt };
152
+ }
153
+ const dt = tokens(d.what);
154
+ let best;
155
+ for (const candidate of turnTokens) {
156
+ const score = overlap(dt, candidate.toks);
157
+ if (score >= DECISION_PROVENANCE_OVERLAP_THRESHOLD && (best === undefined || score > best.score)) {
158
+ best = { turn: candidate.turn, score };
159
+ }
160
+ }
161
+ const resolved = best?.turn ?? lastTurn;
162
+ return { what: d.what, why: d.why, turnIndex: resolved.turnIndex, at: resolved.startedAt };
163
+ });
164
+ }
165
+ export class HaikuRollupProvider {
166
+ client;
167
+ model;
168
+ callLog;
169
+ constructor(options = {}) {
170
+ this.client = new Anthropic(options.apiKey ? { apiKey: options.apiKey } : {});
171
+ this.model = options.model ?? DEFAULT_ANTHROPIC_MODEL;
172
+ this.callLog = options.callLog ?? new SummarizerCallLog();
173
+ }
174
+ rollup(turns) {
175
+ if (turns.length === 0) {
176
+ return Promise.resolve({ output: EMPTY_ROLLUP, status: 'empty_turn' });
177
+ }
178
+ return this.call(ROLLUP_SYSTEM_PROMPT, buildRollupUserContent(turns));
179
+ }
180
+ merge(previous, newTurns) {
181
+ if (newTurns.length === 0) {
182
+ return Promise.resolve({ output: EMPTY_ROLLUP, status: 'empty_turn' });
183
+ }
184
+ return this.call(ROLLUP_MERGE_SYSTEM_PROMPT, buildRollupMergeUserContent(previous, newTurns));
185
+ }
186
+ /**
187
+ * One rollup request, retried ONCE at a larger output budget if the first
188
+ * attempt ran out of room mid-JSON.
189
+ *
190
+ * A same-budget retry risks the same truncation, so it is pure waste.
191
+ * The only thing worth changing is the budget - the input was never the
192
+ * problem. (A malformed-but-complete response is a different failure and
193
+ * is not retried here: rollups are recomputed from turn rows on the next
194
+ * pass, and the watermark deliberately does not advance on failure.)
195
+ */
196
+ async call(system, userContent) {
197
+ const first = await this.callOnce(system, userContent, ROLLUP_MAX_TOKENS, 1);
198
+ if (first.status === 'ok' || !first.truncated) {
199
+ return { output: first.output, status: first.status };
200
+ }
201
+ const retried = await this.callOnce(system, userContent, ROLLUP_RETRY_MAX_TOKENS, 2);
202
+ return { output: retried.output, status: retried.status };
203
+ }
204
+ async callOnce(system, userContent, maxTokens, attempt) {
205
+ // A session rollup is far larger than a per-turn summary: a 17-turn session
206
+ // produced enough decisions to blow straight through 1024 tokens and truncate
207
+ // mid-JSON, which surfaced only as an unexplained parse_error. Sized with
208
+ // headroom, and truncation is now reported distinctly rather than looking like
209
+ // a malformed response.
210
+ const result = await callAnthropicJson({
211
+ client: this.client,
212
+ model: this.model,
213
+ callLog: this.callLog,
214
+ system: cacheableSystemPrompt(system),
215
+ user: userContent,
216
+ maxTokens,
217
+ parse: parseRollup,
218
+ job: 'rollup_merge',
219
+ attempt,
220
+ });
221
+ return { output: result.output ?? EMPTY_ROLLUP, status: result.status, truncated: result.truncated };
222
+ }
223
+ }
224
+ //# sourceMappingURL=rollup-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup-provider.js","sourceRoot":"","sources":["../../src/summarizer/rollup-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ // Shared types: SessionTurn, ToolName, etc.
2
+ export const TOOL_METADATA = {
3
+ 'claude-code': { displayName: 'Claude Code' },
4
+ codex: { displayName: 'Codex' },
5
+ };
6
+ export const SUPPORTED_TOOLS = Object.keys(TOOL_METADATA);
7
+ export function isToolName(value) {
8
+ return SUPPORTED_TOOLS.includes(value);
9
+ }
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAI5C,MAAM,CAAC,MAAM,aAAa,GAAG;IACzB,aAAa,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE;IAC7C,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;CACzB,CAAC;AAIX,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAe,CAAC;AAExE,MAAM,UAAU,UAAU,CAAC,KAAc;IACrC,OAAO,eAAe,CAAC,QAAQ,CAAC,KAAiB,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Returns a display-safe message from an unknown thrown value. */
2
+ export function errorMessage(error) {
3
+ return error instanceof Error ? error.message : String(error);
4
+ }
5
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/util/error.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,KAAc;IACvC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,97 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { PRIVATE_DIR_MODE } from '../config/constants.js';
5
+ /** Creates a directory and ensures it is private even when it already existed. */
6
+ export function ensurePrivateDir(dir) {
7
+ mkdirSync(dir, { recursive: true });
8
+ chmodSync(dir, PRIVATE_DIR_MODE);
9
+ }
10
+ /** Creates missing directories and hardens only the directories created by this call. */
11
+ export function ensureCreatedDirsPrivate(dir) {
12
+ const firstCreated = mkdirSync(dir, { recursive: true });
13
+ if (firstCreated === undefined) {
14
+ return;
15
+ }
16
+ const firstCreatedPath = path.resolve(firstCreated);
17
+ let current = path.resolve(dir);
18
+ while (true) {
19
+ chmodSync(current, PRIVATE_DIR_MODE);
20
+ if (current === firstCreatedPath) {
21
+ return;
22
+ }
23
+ current = path.dirname(current);
24
+ }
25
+ }
26
+ /** Applies a restrictive mode to an already-written private file. */
27
+ export function setPrivateFileMode(file, mode) {
28
+ chmodSync(file, mode);
29
+ }
30
+ /** Atomically replace a file without replacing a symlink that points at it. */
31
+ export function atomicWrite(file, text, mode) {
32
+ const target = (() => {
33
+ try {
34
+ return lstatSync(file).isSymbolicLink() ? realpathSync(file) : file;
35
+ }
36
+ catch (error) {
37
+ if (error.code === 'ENOENT') {
38
+ return file;
39
+ }
40
+ throw error;
41
+ }
42
+ })();
43
+ mkdirSync(path.dirname(target), { recursive: true });
44
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
45
+ writeFileSync(temporary, text, { mode });
46
+ renameSync(temporary, target);
47
+ chmodSync(target, mode);
48
+ }
49
+ /** Atomically replaces a file with a byte-for-byte private copy without treating binary data as text. */
50
+ export function atomicCopyPrivateFile(source, destination, mode) {
51
+ const target = (() => {
52
+ try {
53
+ return lstatSync(destination).isSymbolicLink() ? realpathSync(destination) : destination;
54
+ }
55
+ catch (error) {
56
+ if (error.code === 'ENOENT') {
57
+ return destination;
58
+ }
59
+ throw error;
60
+ }
61
+ })();
62
+ ensurePrivateDir(path.dirname(target));
63
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
64
+ copyFileSync(source, temporary);
65
+ chmodSync(temporary, mode);
66
+ renameSync(temporary, target);
67
+ chmodSync(target, mode);
68
+ }
69
+ /** Read JSON only when the complete file can be read and parsed. */
70
+ export function readJson(file) {
71
+ try {
72
+ return JSON.parse(readFileSync(file, 'utf8'));
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ }
78
+ /** Create the parent directory and write a newline-terminated JSON file. */
79
+ export function writeJson(file, value, mode) {
80
+ mkdirSync(path.dirname(file), { recursive: true });
81
+ writeFileSync(file, `${JSON.stringify(value)}\n`, { mode });
82
+ }
83
+ /** Lists regular files without turning a missing optional directory into an error. */
84
+ export function listRegularFiles(directory) {
85
+ try {
86
+ return readdirSync(directory, { withFileTypes: true })
87
+ .filter((entry) => entry.isFile())
88
+ .map((entry) => path.join(directory, entry.name));
89
+ }
90
+ catch (error) {
91
+ if (error.code === 'ENOENT') {
92
+ return [];
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { DAY_MS } from '../config/constants.js';
2
+ export function relativeTime(iso, now) {
3
+ const age = Math.max(0, now - Date.parse(iso));
4
+ if (age < 60 * 60 * 1000) {
5
+ return `${Math.floor(age / 60000)}m ago`;
6
+ }
7
+ if (age < DAY_MS) {
8
+ return `${Math.floor(age / (60 * 60 * 1000))}h ago`;
9
+ }
10
+ return `${Math.floor(age / DAY_MS)}d ago`;
11
+ }
12
+ //# sourceMappingURL=relative-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../../src/util/relative-time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEhD,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,GAAW;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,IAAI,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;QACvB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC;IAC7C,CAAC;IACD,IAAI,GAAG,GAAG,MAAM,EAAE,CAAC;QACf,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC;IACxD,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;AAC9C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "elepha",
3
+ "version": "0.1.0",
4
+ "description": "Local memory layer for developers switching between AI coding CLIs",
5
+ "keywords": [
6
+ "claude-code",
7
+ "codex",
8
+ "coding-agent",
9
+ "ai-memory",
10
+ "agent-memory",
11
+ "agent-memory",
12
+ "session-memory",
13
+ "session-history",
14
+ "context-management",
15
+ "developer-tools",
16
+ "cli",
17
+ "local-first"
18
+ ],
19
+ "homepage": "https://github.com/elepha-app/elepha#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/elepha-app/elepha/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/elepha-app/elepha.git"
26
+ },
27
+ "license": "MPL-2.0",
28
+ "type": "module",
29
+ "bin": {
30
+ "elepha": "./bin/elepha.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "bin"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "prepublishOnly": "npm run build",
44
+ "build": "tsc -p tsconfig.json",
45
+ "dev": "tsx watch src/cli/index.ts",
46
+ "start": "node dist/cli/index.js",
47
+ "test": "vitest run",
48
+ "typecheck": "tsc --noEmit",
49
+ "typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
50
+ "changeset": "changeset",
51
+ "release": "changeset version && changeset publish",
52
+ "format-lint": "biome check --write .",
53
+ "format:check": "biome check ."
54
+ },
55
+ "dependencies": {
56
+ "@anthropic-ai/sdk": "^0.122.0",
57
+ "@clack/prompts": "^1.7.0",
58
+ "@modelcontextprotocol/sdk": "^1.30.0",
59
+ "better-sqlite3": "^13.0.3",
60
+ "chokidar": "^5.0.0",
61
+ "commander": "^15.0.0",
62
+ "smol-toml": "^1.8.0",
63
+ "zod": "^4.5.2"
64
+ },
65
+ "devDependencies": {
66
+ "@biomejs/biome": "2.5.11",
67
+ "@changesets/cli": "^3.0.1",
68
+ "@commitlint/cli": "^21.2.2",
69
+ "@commitlint/config-conventional": "^21.2.2",
70
+ "@types/better-sqlite3": "^9.6.0",
71
+ "@types/node": "^26.4.0",
72
+ "tsx": "^4.23.12",
73
+ "typescript": "^7.0.2",
74
+ "vitest": "^4.1.11"
75
+ },
76
+ "allowScripts": {
77
+ "better-sqlite3@13.0.3": true,
78
+ "esbuild@0.28.2": true,
79
+ "fsevents@2.3.3": true
80
+ }
81
+ }