claude-memory-layer 1.0.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 (127) hide show
  1. package/.claude-plugin/commands/memory-forget.md +42 -0
  2. package/.claude-plugin/commands/memory-history.md +34 -0
  3. package/.claude-plugin/commands/memory-import.md +56 -0
  4. package/.claude-plugin/commands/memory-list.md +37 -0
  5. package/.claude-plugin/commands/memory-search.md +36 -0
  6. package/.claude-plugin/commands/memory-stats.md +34 -0
  7. package/.claude-plugin/hooks.json +59 -0
  8. package/.claude-plugin/plugin.json +24 -0
  9. package/.history/package_20260201112328.json +45 -0
  10. package/.history/package_20260201113602.json +45 -0
  11. package/.history/package_20260201113713.json +45 -0
  12. package/.history/package_20260201114110.json +45 -0
  13. package/Memo.txt +558 -0
  14. package/README.md +520 -0
  15. package/context.md +636 -0
  16. package/dist/.claude-plugin/commands/memory-forget.md +42 -0
  17. package/dist/.claude-plugin/commands/memory-history.md +34 -0
  18. package/dist/.claude-plugin/commands/memory-import.md +56 -0
  19. package/dist/.claude-plugin/commands/memory-list.md +37 -0
  20. package/dist/.claude-plugin/commands/memory-search.md +36 -0
  21. package/dist/.claude-plugin/commands/memory-stats.md +34 -0
  22. package/dist/.claude-plugin/hooks.json +59 -0
  23. package/dist/.claude-plugin/plugin.json +24 -0
  24. package/dist/cli/index.js +3539 -0
  25. package/dist/cli/index.js.map +7 -0
  26. package/dist/core/index.js +4408 -0
  27. package/dist/core/index.js.map +7 -0
  28. package/dist/hooks/session-end.js +2971 -0
  29. package/dist/hooks/session-end.js.map +7 -0
  30. package/dist/hooks/session-start.js +2969 -0
  31. package/dist/hooks/session-start.js.map +7 -0
  32. package/dist/hooks/stop.js +3123 -0
  33. package/dist/hooks/stop.js.map +7 -0
  34. package/dist/hooks/user-prompt-submit.js +2960 -0
  35. package/dist/hooks/user-prompt-submit.js.map +7 -0
  36. package/dist/services/memory-service.js +2931 -0
  37. package/dist/services/memory-service.js.map +7 -0
  38. package/package.json +45 -0
  39. package/plan.md +1642 -0
  40. package/scripts/build.ts +102 -0
  41. package/spec.md +624 -0
  42. package/specs/citations-system/context.md +243 -0
  43. package/specs/citations-system/plan.md +495 -0
  44. package/specs/citations-system/spec.md +371 -0
  45. package/specs/endless-mode/context.md +305 -0
  46. package/specs/endless-mode/plan.md +620 -0
  47. package/specs/endless-mode/spec.md +455 -0
  48. package/specs/entity-edge-model/context.md +401 -0
  49. package/specs/entity-edge-model/plan.md +459 -0
  50. package/specs/entity-edge-model/spec.md +391 -0
  51. package/specs/evidence-aligner-v2/context.md +401 -0
  52. package/specs/evidence-aligner-v2/plan.md +303 -0
  53. package/specs/evidence-aligner-v2/spec.md +312 -0
  54. package/specs/mcp-desktop-integration/context.md +278 -0
  55. package/specs/mcp-desktop-integration/plan.md +550 -0
  56. package/specs/mcp-desktop-integration/spec.md +494 -0
  57. package/specs/post-tool-use-hook/context.md +319 -0
  58. package/specs/post-tool-use-hook/plan.md +469 -0
  59. package/specs/post-tool-use-hook/spec.md +364 -0
  60. package/specs/private-tags/context.md +288 -0
  61. package/specs/private-tags/plan.md +412 -0
  62. package/specs/private-tags/spec.md +345 -0
  63. package/specs/progressive-disclosure/context.md +346 -0
  64. package/specs/progressive-disclosure/plan.md +663 -0
  65. package/specs/progressive-disclosure/spec.md +415 -0
  66. package/specs/task-entity-system/context.md +297 -0
  67. package/specs/task-entity-system/plan.md +301 -0
  68. package/specs/task-entity-system/spec.md +314 -0
  69. package/specs/vector-outbox-v2/context.md +470 -0
  70. package/specs/vector-outbox-v2/plan.md +562 -0
  71. package/specs/vector-outbox-v2/spec.md +466 -0
  72. package/specs/web-viewer-ui/context.md +384 -0
  73. package/specs/web-viewer-ui/plan.md +797 -0
  74. package/specs/web-viewer-ui/spec.md +516 -0
  75. package/src/cli/index.ts +570 -0
  76. package/src/core/canonical-key.ts +186 -0
  77. package/src/core/citation-generator.ts +63 -0
  78. package/src/core/consolidated-store.ts +279 -0
  79. package/src/core/consolidation-worker.ts +384 -0
  80. package/src/core/context-formatter.ts +276 -0
  81. package/src/core/continuity-manager.ts +336 -0
  82. package/src/core/edge-repo.ts +324 -0
  83. package/src/core/embedder.ts +124 -0
  84. package/src/core/entity-repo.ts +342 -0
  85. package/src/core/event-store.ts +672 -0
  86. package/src/core/evidence-aligner.ts +635 -0
  87. package/src/core/graduation.ts +365 -0
  88. package/src/core/index.ts +32 -0
  89. package/src/core/matcher.ts +210 -0
  90. package/src/core/metadata-extractor.ts +203 -0
  91. package/src/core/privacy/filter.ts +179 -0
  92. package/src/core/privacy/index.ts +20 -0
  93. package/src/core/privacy/tag-parser.ts +145 -0
  94. package/src/core/progressive-retriever.ts +415 -0
  95. package/src/core/retriever.ts +235 -0
  96. package/src/core/task/blocker-resolver.ts +325 -0
  97. package/src/core/task/index.ts +9 -0
  98. package/src/core/task/task-matcher.ts +238 -0
  99. package/src/core/task/task-projector.ts +345 -0
  100. package/src/core/task/task-resolver.ts +414 -0
  101. package/src/core/types.ts +841 -0
  102. package/src/core/vector-outbox.ts +295 -0
  103. package/src/core/vector-store.ts +182 -0
  104. package/src/core/vector-worker.ts +488 -0
  105. package/src/core/working-set-store.ts +244 -0
  106. package/src/hooks/post-tool-use.ts +127 -0
  107. package/src/hooks/session-end.ts +78 -0
  108. package/src/hooks/session-start.ts +57 -0
  109. package/src/hooks/stop.ts +78 -0
  110. package/src/hooks/user-prompt-submit.ts +54 -0
  111. package/src/mcp/handlers.ts +212 -0
  112. package/src/mcp/index.ts +47 -0
  113. package/src/mcp/tools.ts +78 -0
  114. package/src/server/api/citations.ts +101 -0
  115. package/src/server/api/events.ts +101 -0
  116. package/src/server/api/index.ts +18 -0
  117. package/src/server/api/search.ts +98 -0
  118. package/src/server/api/sessions.ts +111 -0
  119. package/src/server/api/stats.ts +97 -0
  120. package/src/server/index.ts +91 -0
  121. package/src/services/memory-service.ts +626 -0
  122. package/src/services/session-history-importer.ts +367 -0
  123. package/tests/canonical-key.test.ts +101 -0
  124. package/tests/evidence-aligner.test.ts +152 -0
  125. package/tests/matcher.test.ts +112 -0
  126. package/tsconfig.json +24 -0
  127. package/vitest.config.ts +15 -0
@@ -0,0 +1,3539 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname } from 'path';
5
+ const require = createRequire(import.meta.url);
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ // src/cli/index.ts
10
+ import { Command } from "commander";
11
+
12
+ // src/services/memory-service.ts
13
+ import * as path from "path";
14
+ import * as os from "os";
15
+ import * as fs from "fs";
16
+ import * as crypto2 from "crypto";
17
+
18
+ // src/core/event-store.ts
19
+ import { Database } from "duckdb";
20
+ import { randomUUID } from "crypto";
21
+
22
+ // src/core/canonical-key.ts
23
+ import { createHash } from "crypto";
24
+ var MAX_KEY_LENGTH = 200;
25
+ function makeCanonicalKey(title, context) {
26
+ let normalized = title.normalize("NFKC");
27
+ normalized = normalized.toLowerCase();
28
+ normalized = normalized.replace(/[^\p{L}\p{N}\s]/gu, "");
29
+ normalized = normalized.replace(/\s+/g, " ").trim();
30
+ let key = normalized;
31
+ if (context?.project) {
32
+ key = `${context.project}::${key}`;
33
+ }
34
+ if (key.length > MAX_KEY_LENGTH) {
35
+ const hashSuffix = createHash("md5").update(key).digest("hex").slice(0, 8);
36
+ key = key.slice(0, MAX_KEY_LENGTH - 9) + "_" + hashSuffix;
37
+ }
38
+ return key;
39
+ }
40
+ function makeDedupeKey(content, sessionId) {
41
+ const contentHash = createHash("sha256").update(content).digest("hex");
42
+ return `${sessionId}:${contentHash}`;
43
+ }
44
+
45
+ // src/core/event-store.ts
46
+ var EventStore = class {
47
+ constructor(dbPath) {
48
+ this.dbPath = dbPath;
49
+ this.db = new Database(dbPath);
50
+ }
51
+ db;
52
+ initialized = false;
53
+ /**
54
+ * Initialize database schema
55
+ */
56
+ async initialize() {
57
+ if (this.initialized)
58
+ return;
59
+ await this.db.run(`
60
+ CREATE TABLE IF NOT EXISTS events (
61
+ id VARCHAR PRIMARY KEY,
62
+ event_type VARCHAR NOT NULL,
63
+ session_id VARCHAR NOT NULL,
64
+ timestamp TIMESTAMP NOT NULL,
65
+ content TEXT NOT NULL,
66
+ canonical_key VARCHAR NOT NULL,
67
+ dedupe_key VARCHAR UNIQUE,
68
+ metadata JSON
69
+ )
70
+ `);
71
+ await this.db.run(`
72
+ CREATE TABLE IF NOT EXISTS event_dedup (
73
+ dedupe_key VARCHAR PRIMARY KEY,
74
+ event_id VARCHAR NOT NULL,
75
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
76
+ )
77
+ `);
78
+ await this.db.run(`
79
+ CREATE TABLE IF NOT EXISTS sessions (
80
+ id VARCHAR PRIMARY KEY,
81
+ started_at TIMESTAMP NOT NULL,
82
+ ended_at TIMESTAMP,
83
+ project_path VARCHAR,
84
+ summary TEXT,
85
+ tags JSON
86
+ )
87
+ `);
88
+ await this.db.run(`
89
+ CREATE TABLE IF NOT EXISTS insights (
90
+ id VARCHAR PRIMARY KEY,
91
+ insight_type VARCHAR NOT NULL,
92
+ content TEXT NOT NULL,
93
+ canonical_key VARCHAR NOT NULL,
94
+ confidence FLOAT,
95
+ source_events JSON,
96
+ created_at TIMESTAMP,
97
+ last_updated TIMESTAMP
98
+ )
99
+ `);
100
+ await this.db.run(`
101
+ CREATE TABLE IF NOT EXISTS embedding_outbox (
102
+ id VARCHAR PRIMARY KEY,
103
+ event_id VARCHAR NOT NULL,
104
+ content TEXT NOT NULL,
105
+ status VARCHAR DEFAULT 'pending',
106
+ retry_count INT DEFAULT 0,
107
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
108
+ processed_at TIMESTAMP,
109
+ error_message TEXT
110
+ )
111
+ `);
112
+ await this.db.run(`
113
+ CREATE TABLE IF NOT EXISTS projection_offsets (
114
+ projection_name VARCHAR PRIMARY KEY,
115
+ last_event_id VARCHAR,
116
+ last_timestamp TIMESTAMP,
117
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
118
+ )
119
+ `);
120
+ await this.db.run(`
121
+ CREATE TABLE IF NOT EXISTS memory_levels (
122
+ event_id VARCHAR PRIMARY KEY,
123
+ level VARCHAR NOT NULL DEFAULT 'L0',
124
+ promoted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
125
+ )
126
+ `);
127
+ await this.db.run(`
128
+ CREATE TABLE IF NOT EXISTS entries (
129
+ entry_id VARCHAR PRIMARY KEY,
130
+ created_ts TIMESTAMP NOT NULL,
131
+ entry_type VARCHAR NOT NULL,
132
+ title VARCHAR NOT NULL,
133
+ content_json JSON NOT NULL,
134
+ stage VARCHAR NOT NULL DEFAULT 'raw',
135
+ status VARCHAR DEFAULT 'active',
136
+ superseded_by VARCHAR,
137
+ build_id VARCHAR,
138
+ evidence_json JSON,
139
+ canonical_key VARCHAR,
140
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
141
+ )
142
+ `);
143
+ await this.db.run(`
144
+ CREATE TABLE IF NOT EXISTS entities (
145
+ entity_id VARCHAR PRIMARY KEY,
146
+ entity_type VARCHAR NOT NULL,
147
+ canonical_key VARCHAR NOT NULL,
148
+ title VARCHAR NOT NULL,
149
+ stage VARCHAR NOT NULL DEFAULT 'raw',
150
+ status VARCHAR NOT NULL DEFAULT 'active',
151
+ current_json JSON NOT NULL,
152
+ title_norm VARCHAR,
153
+ search_text VARCHAR,
154
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
155
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
156
+ )
157
+ `);
158
+ await this.db.run(`
159
+ CREATE TABLE IF NOT EXISTS entity_aliases (
160
+ entity_type VARCHAR NOT NULL,
161
+ canonical_key VARCHAR NOT NULL,
162
+ entity_id VARCHAR NOT NULL,
163
+ is_primary BOOLEAN DEFAULT FALSE,
164
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
165
+ PRIMARY KEY(entity_type, canonical_key)
166
+ )
167
+ `);
168
+ await this.db.run(`
169
+ CREATE TABLE IF NOT EXISTS edges (
170
+ edge_id VARCHAR PRIMARY KEY,
171
+ src_type VARCHAR NOT NULL,
172
+ src_id VARCHAR NOT NULL,
173
+ rel_type VARCHAR NOT NULL,
174
+ dst_type VARCHAR NOT NULL,
175
+ dst_id VARCHAR NOT NULL,
176
+ meta_json JSON,
177
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
178
+ )
179
+ `);
180
+ await this.db.run(`
181
+ CREATE TABLE IF NOT EXISTS vector_outbox (
182
+ job_id VARCHAR PRIMARY KEY,
183
+ item_kind VARCHAR NOT NULL,
184
+ item_id VARCHAR NOT NULL,
185
+ embedding_version VARCHAR NOT NULL,
186
+ status VARCHAR NOT NULL DEFAULT 'pending',
187
+ retry_count INT DEFAULT 0,
188
+ error VARCHAR,
189
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
190
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
191
+ UNIQUE(item_kind, item_id, embedding_version)
192
+ )
193
+ `);
194
+ await this.db.run(`
195
+ CREATE TABLE IF NOT EXISTS build_runs (
196
+ build_id VARCHAR PRIMARY KEY,
197
+ started_at TIMESTAMP NOT NULL,
198
+ finished_at TIMESTAMP,
199
+ extractor_model VARCHAR NOT NULL,
200
+ extractor_prompt_hash VARCHAR NOT NULL,
201
+ embedder_model VARCHAR NOT NULL,
202
+ embedding_version VARCHAR NOT NULL,
203
+ idris_version VARCHAR NOT NULL,
204
+ schema_version VARCHAR NOT NULL,
205
+ status VARCHAR NOT NULL DEFAULT 'running',
206
+ error VARCHAR
207
+ )
208
+ `);
209
+ await this.db.run(`
210
+ CREATE TABLE IF NOT EXISTS pipeline_metrics (
211
+ id VARCHAR PRIMARY KEY,
212
+ ts TIMESTAMP NOT NULL,
213
+ stage VARCHAR NOT NULL,
214
+ latency_ms DOUBLE NOT NULL,
215
+ success BOOLEAN NOT NULL,
216
+ error VARCHAR,
217
+ session_id VARCHAR
218
+ )
219
+ `);
220
+ await this.db.run(`
221
+ CREATE TABLE IF NOT EXISTS working_set (
222
+ id VARCHAR PRIMARY KEY,
223
+ event_id VARCHAR NOT NULL,
224
+ added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
225
+ relevance_score FLOAT DEFAULT 1.0,
226
+ topics JSON,
227
+ expires_at TIMESTAMP
228
+ )
229
+ `);
230
+ await this.db.run(`
231
+ CREATE TABLE IF NOT EXISTS consolidated_memories (
232
+ memory_id VARCHAR PRIMARY KEY,
233
+ summary TEXT NOT NULL,
234
+ topics JSON,
235
+ source_events JSON,
236
+ confidence FLOAT DEFAULT 0.5,
237
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
238
+ accessed_at TIMESTAMP,
239
+ access_count INTEGER DEFAULT 0
240
+ )
241
+ `);
242
+ await this.db.run(`
243
+ CREATE TABLE IF NOT EXISTS continuity_log (
244
+ log_id VARCHAR PRIMARY KEY,
245
+ from_context_id VARCHAR,
246
+ to_context_id VARCHAR,
247
+ continuity_score FLOAT,
248
+ transition_type VARCHAR,
249
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
250
+ )
251
+ `);
252
+ await this.db.run(`
253
+ CREATE TABLE IF NOT EXISTS endless_config (
254
+ key VARCHAR PRIMARY KEY,
255
+ value JSON,
256
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
257
+ )
258
+ `);
259
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type)`);
260
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage)`);
261
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key)`);
262
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key)`);
263
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status)`);
264
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type)`);
265
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type)`);
266
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type)`);
267
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status)`);
268
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at)`);
269
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score DESC)`);
270
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence DESC)`);
271
+ await this.db.run(`CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at)`);
272
+ this.initialized = true;
273
+ }
274
+ /**
275
+ * Append event to store (AXIOMMIND Principle 2: Append-only)
276
+ * Returns existing event ID if duplicate (Principle 3: Idempotency)
277
+ */
278
+ async append(input) {
279
+ await this.initialize();
280
+ const canonicalKey = makeCanonicalKey(input.content);
281
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
282
+ const existing = await this.db.all(
283
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
284
+ [dedupeKey]
285
+ );
286
+ if (existing.length > 0) {
287
+ return {
288
+ success: true,
289
+ eventId: existing[0].event_id,
290
+ isDuplicate: true
291
+ };
292
+ }
293
+ const id = randomUUID();
294
+ const timestamp = input.timestamp.toISOString();
295
+ try {
296
+ await this.db.run(
297
+ `INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
298
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
299
+ [
300
+ id,
301
+ input.eventType,
302
+ input.sessionId,
303
+ timestamp,
304
+ input.content,
305
+ canonicalKey,
306
+ dedupeKey,
307
+ JSON.stringify(input.metadata || {})
308
+ ]
309
+ );
310
+ await this.db.run(
311
+ `INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)`,
312
+ [dedupeKey, id]
313
+ );
314
+ await this.db.run(
315
+ `INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')`,
316
+ [id]
317
+ );
318
+ return { success: true, eventId: id, isDuplicate: false };
319
+ } catch (error) {
320
+ return {
321
+ success: false,
322
+ error: error instanceof Error ? error.message : String(error)
323
+ };
324
+ }
325
+ }
326
+ /**
327
+ * Get events by session ID
328
+ */
329
+ async getSessionEvents(sessionId) {
330
+ await this.initialize();
331
+ const rows = await this.db.all(
332
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
333
+ [sessionId]
334
+ );
335
+ return rows.map(this.rowToEvent);
336
+ }
337
+ /**
338
+ * Get recent events
339
+ */
340
+ async getRecentEvents(limit = 100) {
341
+ await this.initialize();
342
+ const rows = await this.db.all(
343
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
344
+ [limit]
345
+ );
346
+ return rows.map(this.rowToEvent);
347
+ }
348
+ /**
349
+ * Get event by ID
350
+ */
351
+ async getEvent(id) {
352
+ await this.initialize();
353
+ const rows = await this.db.all(
354
+ `SELECT * FROM events WHERE id = ?`,
355
+ [id]
356
+ );
357
+ if (rows.length === 0)
358
+ return null;
359
+ return this.rowToEvent(rows[0]);
360
+ }
361
+ /**
362
+ * Create or update session
363
+ */
364
+ async upsertSession(session) {
365
+ await this.initialize();
366
+ const existing = await this.db.all(
367
+ `SELECT id FROM sessions WHERE id = ?`,
368
+ [session.id]
369
+ );
370
+ if (existing.length === 0) {
371
+ await this.db.run(
372
+ `INSERT INTO sessions (id, started_at, project_path, tags)
373
+ VALUES (?, ?, ?, ?)`,
374
+ [
375
+ session.id,
376
+ (session.startedAt || /* @__PURE__ */ new Date()).toISOString(),
377
+ session.projectPath || null,
378
+ JSON.stringify(session.tags || [])
379
+ ]
380
+ );
381
+ } else {
382
+ const updates = [];
383
+ const values = [];
384
+ if (session.endedAt) {
385
+ updates.push("ended_at = ?");
386
+ values.push(session.endedAt.toISOString());
387
+ }
388
+ if (session.summary) {
389
+ updates.push("summary = ?");
390
+ values.push(session.summary);
391
+ }
392
+ if (session.tags) {
393
+ updates.push("tags = ?");
394
+ values.push(JSON.stringify(session.tags));
395
+ }
396
+ if (updates.length > 0) {
397
+ values.push(session.id);
398
+ await this.db.run(
399
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
400
+ values
401
+ );
402
+ }
403
+ }
404
+ }
405
+ /**
406
+ * Get session by ID
407
+ */
408
+ async getSession(id) {
409
+ await this.initialize();
410
+ const rows = await this.db.all(
411
+ `SELECT * FROM sessions WHERE id = ?`,
412
+ [id]
413
+ );
414
+ if (rows.length === 0)
415
+ return null;
416
+ const row = rows[0];
417
+ return {
418
+ id: row.id,
419
+ startedAt: new Date(row.started_at),
420
+ endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
421
+ projectPath: row.project_path,
422
+ summary: row.summary,
423
+ tags: row.tags ? JSON.parse(row.tags) : void 0
424
+ };
425
+ }
426
+ /**
427
+ * Add to embedding outbox (Single-Writer Pattern)
428
+ */
429
+ async enqueueForEmbedding(eventId, content) {
430
+ await this.initialize();
431
+ const id = randomUUID();
432
+ await this.db.run(
433
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
434
+ VALUES (?, ?, ?, 'pending', 0)`,
435
+ [id, eventId, content]
436
+ );
437
+ return id;
438
+ }
439
+ /**
440
+ * Get pending outbox items
441
+ */
442
+ async getPendingOutboxItems(limit = 32) {
443
+ await this.initialize();
444
+ const rows = await this.db.all(
445
+ `UPDATE embedding_outbox
446
+ SET status = 'processing'
447
+ WHERE id IN (
448
+ SELECT id FROM embedding_outbox
449
+ WHERE status = 'pending'
450
+ ORDER BY created_at
451
+ LIMIT ?
452
+ )
453
+ RETURNING *`,
454
+ [limit]
455
+ );
456
+ return rows.map((row) => ({
457
+ id: row.id,
458
+ eventId: row.event_id,
459
+ content: row.content,
460
+ status: row.status,
461
+ retryCount: row.retry_count,
462
+ createdAt: new Date(row.created_at),
463
+ errorMessage: row.error_message
464
+ }));
465
+ }
466
+ /**
467
+ * Mark outbox items as done
468
+ */
469
+ async completeOutboxItems(ids) {
470
+ if (ids.length === 0)
471
+ return;
472
+ const placeholders = ids.map(() => "?").join(",");
473
+ await this.db.run(
474
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
475
+ ids
476
+ );
477
+ }
478
+ /**
479
+ * Mark outbox items as failed
480
+ */
481
+ async failOutboxItems(ids, error) {
482
+ if (ids.length === 0)
483
+ return;
484
+ const placeholders = ids.map(() => "?").join(",");
485
+ await this.db.run(
486
+ `UPDATE embedding_outbox
487
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
488
+ retry_count = retry_count + 1,
489
+ error_message = ?
490
+ WHERE id IN (${placeholders})`,
491
+ [error, ...ids]
492
+ );
493
+ }
494
+ /**
495
+ * Update memory level
496
+ */
497
+ async updateMemoryLevel(eventId, level) {
498
+ await this.initialize();
499
+ await this.db.run(
500
+ `UPDATE memory_levels SET level = ?, promoted_at = CURRENT_TIMESTAMP WHERE event_id = ?`,
501
+ [level, eventId]
502
+ );
503
+ }
504
+ /**
505
+ * Get memory level statistics
506
+ */
507
+ async getLevelStats() {
508
+ await this.initialize();
509
+ const rows = await this.db.all(
510
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
511
+ );
512
+ return rows;
513
+ }
514
+ // ============================================================
515
+ // Endless Mode Helper Methods
516
+ // ============================================================
517
+ /**
518
+ * Get database instance for Endless Mode stores
519
+ */
520
+ getDatabase() {
521
+ return this.db;
522
+ }
523
+ /**
524
+ * Get config value for endless mode
525
+ */
526
+ async getEndlessConfig(key) {
527
+ await this.initialize();
528
+ const rows = await this.db.all(
529
+ `SELECT value FROM endless_config WHERE key = ?`,
530
+ [key]
531
+ );
532
+ if (rows.length === 0)
533
+ return null;
534
+ return JSON.parse(rows[0].value);
535
+ }
536
+ /**
537
+ * Set config value for endless mode
538
+ */
539
+ async setEndlessConfig(key, value) {
540
+ await this.initialize();
541
+ await this.db.run(
542
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
543
+ VALUES (?, ?, CURRENT_TIMESTAMP)`,
544
+ [key, JSON.stringify(value)]
545
+ );
546
+ }
547
+ /**
548
+ * Get all sessions
549
+ */
550
+ async getAllSessions() {
551
+ await this.initialize();
552
+ const rows = await this.db.all(
553
+ `SELECT * FROM sessions ORDER BY started_at DESC`
554
+ );
555
+ return rows.map((row) => ({
556
+ id: row.id,
557
+ startedAt: new Date(row.started_at),
558
+ endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
559
+ projectPath: row.project_path,
560
+ summary: row.summary,
561
+ tags: row.tags ? JSON.parse(row.tags) : void 0
562
+ }));
563
+ }
564
+ /**
565
+ * Close database connection
566
+ */
567
+ async close() {
568
+ await this.db.close();
569
+ }
570
+ /**
571
+ * Convert database row to MemoryEvent
572
+ */
573
+ rowToEvent(row) {
574
+ return {
575
+ id: row.id,
576
+ eventType: row.event_type,
577
+ sessionId: row.session_id,
578
+ timestamp: new Date(row.timestamp),
579
+ content: row.content,
580
+ canonicalKey: row.canonical_key,
581
+ dedupeKey: row.dedupe_key,
582
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
583
+ };
584
+ }
585
+ };
586
+
587
+ // src/core/vector-store.ts
588
+ import * as lancedb from "@lancedb/lancedb";
589
+ var VectorStore = class {
590
+ constructor(dbPath) {
591
+ this.dbPath = dbPath;
592
+ }
593
+ db = null;
594
+ table = null;
595
+ tableName = "conversations";
596
+ /**
597
+ * Initialize LanceDB connection
598
+ */
599
+ async initialize() {
600
+ if (this.db)
601
+ return;
602
+ this.db = await lancedb.connect(this.dbPath);
603
+ try {
604
+ const tables = await this.db.tableNames();
605
+ if (tables.includes(this.tableName)) {
606
+ this.table = await this.db.openTable(this.tableName);
607
+ }
608
+ } catch {
609
+ this.table = null;
610
+ }
611
+ }
612
+ /**
613
+ * Add or update vector record
614
+ */
615
+ async upsert(record) {
616
+ await this.initialize();
617
+ if (!this.db) {
618
+ throw new Error("Database not initialized");
619
+ }
620
+ const data = {
621
+ id: record.id,
622
+ eventId: record.eventId,
623
+ sessionId: record.sessionId,
624
+ eventType: record.eventType,
625
+ content: record.content,
626
+ vector: record.vector,
627
+ timestamp: record.timestamp,
628
+ metadata: JSON.stringify(record.metadata || {})
629
+ };
630
+ if (!this.table) {
631
+ this.table = await this.db.createTable(this.tableName, [data]);
632
+ } else {
633
+ await this.table.add([data]);
634
+ }
635
+ }
636
+ /**
637
+ * Add multiple vector records in batch
638
+ */
639
+ async upsertBatch(records) {
640
+ if (records.length === 0)
641
+ return;
642
+ await this.initialize();
643
+ if (!this.db) {
644
+ throw new Error("Database not initialized");
645
+ }
646
+ const data = records.map((record) => ({
647
+ id: record.id,
648
+ eventId: record.eventId,
649
+ sessionId: record.sessionId,
650
+ eventType: record.eventType,
651
+ content: record.content,
652
+ vector: record.vector,
653
+ timestamp: record.timestamp,
654
+ metadata: JSON.stringify(record.metadata || {})
655
+ }));
656
+ if (!this.table) {
657
+ this.table = await this.db.createTable(this.tableName, data);
658
+ } else {
659
+ await this.table.add(data);
660
+ }
661
+ }
662
+ /**
663
+ * Search for similar vectors
664
+ */
665
+ async search(queryVector, options = {}) {
666
+ await this.initialize();
667
+ if (!this.table) {
668
+ return [];
669
+ }
670
+ const { limit = 5, minScore = 0.7, sessionId } = options;
671
+ let query = this.table.search(queryVector).limit(limit * 2);
672
+ if (sessionId) {
673
+ query = query.where(`sessionId = '${sessionId}'`);
674
+ }
675
+ const results = await query.toArray();
676
+ return results.filter((r) => {
677
+ const score = 1 - (r._distance || 0);
678
+ return score >= minScore;
679
+ }).slice(0, limit).map((r) => ({
680
+ id: r.id,
681
+ eventId: r.eventId,
682
+ content: r.content,
683
+ score: 1 - (r._distance || 0),
684
+ sessionId: r.sessionId,
685
+ eventType: r.eventType,
686
+ timestamp: r.timestamp
687
+ }));
688
+ }
689
+ /**
690
+ * Delete vector by event ID
691
+ */
692
+ async delete(eventId) {
693
+ if (!this.table)
694
+ return;
695
+ await this.table.delete(`eventId = '${eventId}'`);
696
+ }
697
+ /**
698
+ * Get total count of vectors
699
+ */
700
+ async count() {
701
+ if (!this.table)
702
+ return 0;
703
+ const result = await this.table.countRows();
704
+ return result;
705
+ }
706
+ /**
707
+ * Check if vector exists for event
708
+ */
709
+ async exists(eventId) {
710
+ if (!this.table)
711
+ return false;
712
+ const results = await this.table.search([]).where(`eventId = '${eventId}'`).limit(1).toArray();
713
+ return results.length > 0;
714
+ }
715
+ };
716
+
717
+ // src/core/embedder.ts
718
+ import { pipeline } from "@xenova/transformers";
719
+ var Embedder = class {
720
+ pipeline = null;
721
+ modelName;
722
+ initialized = false;
723
+ constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
724
+ this.modelName = modelName;
725
+ }
726
+ /**
727
+ * Initialize the embedding pipeline
728
+ */
729
+ async initialize() {
730
+ if (this.initialized)
731
+ return;
732
+ this.pipeline = await pipeline("feature-extraction", this.modelName);
733
+ this.initialized = true;
734
+ }
735
+ /**
736
+ * Generate embedding for a single text
737
+ */
738
+ async embed(text) {
739
+ await this.initialize();
740
+ if (!this.pipeline) {
741
+ throw new Error("Embedding pipeline not initialized");
742
+ }
743
+ const output = await this.pipeline(text, {
744
+ pooling: "mean",
745
+ normalize: true
746
+ });
747
+ const vector = Array.from(output.data);
748
+ return {
749
+ vector,
750
+ model: this.modelName,
751
+ dimensions: vector.length
752
+ };
753
+ }
754
+ /**
755
+ * Generate embeddings for multiple texts in batch
756
+ */
757
+ async embedBatch(texts) {
758
+ await this.initialize();
759
+ if (!this.pipeline) {
760
+ throw new Error("Embedding pipeline not initialized");
761
+ }
762
+ const results = [];
763
+ const batchSize = 32;
764
+ for (let i = 0; i < texts.length; i += batchSize) {
765
+ const batch = texts.slice(i, i + batchSize);
766
+ for (const text of batch) {
767
+ const output = await this.pipeline(text, {
768
+ pooling: "mean",
769
+ normalize: true
770
+ });
771
+ const vector = Array.from(output.data);
772
+ results.push({
773
+ vector,
774
+ model: this.modelName,
775
+ dimensions: vector.length
776
+ });
777
+ }
778
+ }
779
+ return results;
780
+ }
781
+ /**
782
+ * Get embedding dimensions for the current model
783
+ */
784
+ async getDimensions() {
785
+ const result = await this.embed("test");
786
+ return result.dimensions;
787
+ }
788
+ /**
789
+ * Check if embedder is ready
790
+ */
791
+ isReady() {
792
+ return this.initialized && this.pipeline !== null;
793
+ }
794
+ /**
795
+ * Get model name
796
+ */
797
+ getModelName() {
798
+ return this.modelName;
799
+ }
800
+ };
801
+ var defaultEmbedder = null;
802
+ function getDefaultEmbedder() {
803
+ if (!defaultEmbedder) {
804
+ defaultEmbedder = new Embedder();
805
+ }
806
+ return defaultEmbedder;
807
+ }
808
+
809
+ // src/core/vector-outbox.ts
810
+ var DEFAULT_CONFIG = {
811
+ embeddingVersion: "v1",
812
+ maxRetries: 3,
813
+ stuckThresholdMs: 5 * 60 * 1e3,
814
+ // 5 minutes
815
+ cleanupDays: 7
816
+ };
817
+
818
+ // src/core/vector-worker.ts
819
+ var DEFAULT_CONFIG2 = {
820
+ batchSize: 32,
821
+ pollIntervalMs: 1e3,
822
+ maxRetries: 3
823
+ };
824
+ var VectorWorker = class {
825
+ eventStore;
826
+ vectorStore;
827
+ embedder;
828
+ config;
829
+ running = false;
830
+ pollTimeout = null;
831
+ constructor(eventStore, vectorStore, embedder, config = {}) {
832
+ this.eventStore = eventStore;
833
+ this.vectorStore = vectorStore;
834
+ this.embedder = embedder;
835
+ this.config = { ...DEFAULT_CONFIG2, ...config };
836
+ }
837
+ /**
838
+ * Start the worker polling loop
839
+ */
840
+ start() {
841
+ if (this.running)
842
+ return;
843
+ this.running = true;
844
+ this.poll();
845
+ }
846
+ /**
847
+ * Stop the worker
848
+ */
849
+ stop() {
850
+ this.running = false;
851
+ if (this.pollTimeout) {
852
+ clearTimeout(this.pollTimeout);
853
+ this.pollTimeout = null;
854
+ }
855
+ }
856
+ /**
857
+ * Process a single batch of outbox items
858
+ */
859
+ async processBatch() {
860
+ const items = await this.eventStore.getPendingOutboxItems(this.config.batchSize);
861
+ if (items.length === 0) {
862
+ return 0;
863
+ }
864
+ const successful = [];
865
+ const failed = [];
866
+ try {
867
+ const embeddings = await this.embedder.embedBatch(items.map((i) => i.content));
868
+ const records = [];
869
+ for (let i = 0; i < items.length; i++) {
870
+ const item = items[i];
871
+ const embedding = embeddings[i];
872
+ const event = await this.eventStore.getEvent(item.eventId);
873
+ if (!event) {
874
+ failed.push(item.id);
875
+ continue;
876
+ }
877
+ records.push({
878
+ id: `vec_${item.id}`,
879
+ eventId: item.eventId,
880
+ sessionId: event.sessionId,
881
+ eventType: event.eventType,
882
+ content: item.content,
883
+ vector: embedding.vector,
884
+ timestamp: event.timestamp.toISOString(),
885
+ metadata: event.metadata
886
+ });
887
+ successful.push(item.id);
888
+ }
889
+ if (records.length > 0) {
890
+ await this.vectorStore.upsertBatch(records);
891
+ }
892
+ if (successful.length > 0) {
893
+ await this.eventStore.completeOutboxItems(successful);
894
+ }
895
+ if (failed.length > 0) {
896
+ await this.eventStore.failOutboxItems(failed, "Event not found");
897
+ }
898
+ return successful.length;
899
+ } catch (error) {
900
+ const allIds = items.map((i) => i.id);
901
+ const errorMessage = error instanceof Error ? error.message : String(error);
902
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
903
+ throw error;
904
+ }
905
+ }
906
+ /**
907
+ * Poll for new items
908
+ */
909
+ async poll() {
910
+ if (!this.running)
911
+ return;
912
+ try {
913
+ await this.processBatch();
914
+ } catch (error) {
915
+ console.error("Vector worker error:", error);
916
+ }
917
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
918
+ }
919
+ /**
920
+ * Process all pending items (blocking)
921
+ */
922
+ async processAll() {
923
+ let totalProcessed = 0;
924
+ let processed;
925
+ do {
926
+ processed = await this.processBatch();
927
+ totalProcessed += processed;
928
+ } while (processed > 0);
929
+ return totalProcessed;
930
+ }
931
+ /**
932
+ * Check if worker is running
933
+ */
934
+ isRunning() {
935
+ return this.running;
936
+ }
937
+ };
938
+ function createVectorWorker(eventStore, vectorStore, embedder, config) {
939
+ const worker = new VectorWorker(eventStore, vectorStore, embedder, config);
940
+ return worker;
941
+ }
942
+
943
+ // src/core/matcher.ts
944
+ var DEFAULT_CONFIG3 = {
945
+ weights: {
946
+ semanticSimilarity: 0.4,
947
+ ftsScore: 0.25,
948
+ recencyBonus: 0.2,
949
+ statusWeight: 0.15
950
+ },
951
+ minCombinedScore: 0.92,
952
+ minGap: 0.03,
953
+ suggestionThreshold: 0.75
954
+ };
955
+ var Matcher = class {
956
+ config;
957
+ constructor(config = {}) {
958
+ this.config = {
959
+ ...DEFAULT_CONFIG3,
960
+ ...config,
961
+ weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
962
+ };
963
+ }
964
+ /**
965
+ * Calculate combined score using AXIOMMIND weighted formula
966
+ */
967
+ calculateCombinedScore(semanticScore, ftsScore = 0, recencyDays = 0, isActive = true) {
968
+ const { weights } = this.config;
969
+ const recencyBonus = Math.max(0, 1 - recencyDays / 30);
970
+ const statusMultiplier = isActive ? 1 : 0.7;
971
+ const combinedScore = weights.semanticSimilarity * semanticScore + weights.ftsScore * ftsScore + weights.recencyBonus * recencyBonus + weights.statusWeight * statusMultiplier;
972
+ return Math.min(1, combinedScore);
973
+ }
974
+ /**
975
+ * Classify match confidence based on AXIOMMIND thresholds
976
+ */
977
+ classifyConfidence(topScore, secondScore) {
978
+ const { minCombinedScore, minGap, suggestionThreshold } = this.config;
979
+ const gap = secondScore !== null ? topScore - secondScore : Infinity;
980
+ if (topScore >= minCombinedScore && gap >= minGap) {
981
+ return "high";
982
+ }
983
+ if (topScore >= suggestionThreshold) {
984
+ return "suggested";
985
+ }
986
+ return "none";
987
+ }
988
+ /**
989
+ * Match search results to find best memory
990
+ */
991
+ matchSearchResults(results, getEventAge) {
992
+ if (results.length === 0) {
993
+ return {
994
+ match: null,
995
+ confidence: "none"
996
+ };
997
+ }
998
+ const scoredResults = results.map((result) => {
999
+ const ageDays = getEventAge(result.eventId);
1000
+ const combinedScore = this.calculateCombinedScore(
1001
+ result.score,
1002
+ 0,
1003
+ // FTS score - would need to be passed in
1004
+ ageDays,
1005
+ true
1006
+ // Assume active
1007
+ );
1008
+ return {
1009
+ result,
1010
+ combinedScore
1011
+ };
1012
+ });
1013
+ scoredResults.sort((a, b) => b.combinedScore - a.combinedScore);
1014
+ const topResult = scoredResults[0];
1015
+ const secondScore = scoredResults.length > 1 ? scoredResults[1].combinedScore : null;
1016
+ const confidence = this.classifyConfidence(topResult.combinedScore, secondScore);
1017
+ const match = {
1018
+ event: {
1019
+ id: topResult.result.eventId,
1020
+ eventType: topResult.result.eventType,
1021
+ sessionId: topResult.result.sessionId,
1022
+ timestamp: new Date(topResult.result.timestamp),
1023
+ content: topResult.result.content,
1024
+ canonicalKey: "",
1025
+ // Would need to be fetched
1026
+ dedupeKey: ""
1027
+ // Would need to be fetched
1028
+ },
1029
+ score: topResult.combinedScore
1030
+ };
1031
+ const gap = secondScore !== null ? topResult.combinedScore - secondScore : void 0;
1032
+ const alternatives = confidence === "suggested" ? scoredResults.slice(1, 4).map((sr) => ({
1033
+ event: {
1034
+ id: sr.result.eventId,
1035
+ eventType: sr.result.eventType,
1036
+ sessionId: sr.result.sessionId,
1037
+ timestamp: new Date(sr.result.timestamp),
1038
+ content: sr.result.content,
1039
+ canonicalKey: "",
1040
+ dedupeKey: ""
1041
+ },
1042
+ score: sr.combinedScore
1043
+ })) : void 0;
1044
+ return {
1045
+ match: confidence !== "none" ? match : null,
1046
+ confidence,
1047
+ gap,
1048
+ alternatives
1049
+ };
1050
+ }
1051
+ /**
1052
+ * Calculate days between two dates
1053
+ */
1054
+ static calculateAgeDays(timestamp) {
1055
+ const now = /* @__PURE__ */ new Date();
1056
+ const diffMs = now.getTime() - timestamp.getTime();
1057
+ return diffMs / (1e3 * 60 * 60 * 24);
1058
+ }
1059
+ /**
1060
+ * Get current configuration
1061
+ */
1062
+ getConfig() {
1063
+ return { ...this.config };
1064
+ }
1065
+ };
1066
+ var defaultMatcher = null;
1067
+ function getDefaultMatcher() {
1068
+ if (!defaultMatcher) {
1069
+ defaultMatcher = new Matcher();
1070
+ }
1071
+ return defaultMatcher;
1072
+ }
1073
+
1074
+ // src/core/retriever.ts
1075
+ var DEFAULT_OPTIONS = {
1076
+ topK: 5,
1077
+ minScore: 0.7,
1078
+ maxTokens: 2e3,
1079
+ includeSessionContext: true
1080
+ };
1081
+ var Retriever = class {
1082
+ eventStore;
1083
+ vectorStore;
1084
+ embedder;
1085
+ matcher;
1086
+ constructor(eventStore, vectorStore, embedder, matcher) {
1087
+ this.eventStore = eventStore;
1088
+ this.vectorStore = vectorStore;
1089
+ this.embedder = embedder;
1090
+ this.matcher = matcher;
1091
+ }
1092
+ /**
1093
+ * Retrieve relevant memories for a query
1094
+ */
1095
+ async retrieve(query, options = {}) {
1096
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1097
+ const queryEmbedding = await this.embedder.embed(query);
1098
+ const searchResults = await this.vectorStore.search(queryEmbedding.vector, {
1099
+ limit: opts.topK * 2,
1100
+ // Get extra for filtering
1101
+ minScore: opts.minScore,
1102
+ sessionId: opts.sessionId
1103
+ });
1104
+ const matchResult = this.matcher.matchSearchResults(
1105
+ searchResults,
1106
+ (eventId) => this.getEventAgeDays(eventId)
1107
+ );
1108
+ const memories = await this.enrichResults(searchResults.slice(0, opts.topK), opts);
1109
+ const context = this.buildContext(memories, opts.maxTokens);
1110
+ return {
1111
+ memories,
1112
+ matchResult,
1113
+ totalTokens: this.estimateTokens(context),
1114
+ context
1115
+ };
1116
+ }
1117
+ /**
1118
+ * Retrieve memories from a specific session
1119
+ */
1120
+ async retrieveFromSession(sessionId) {
1121
+ return this.eventStore.getSessionEvents(sessionId);
1122
+ }
1123
+ /**
1124
+ * Get recent memories across all sessions
1125
+ */
1126
+ async retrieveRecent(limit = 100) {
1127
+ return this.eventStore.getRecentEvents(limit);
1128
+ }
1129
+ /**
1130
+ * Enrich search results with full event data
1131
+ */
1132
+ async enrichResults(results, options) {
1133
+ const memories = [];
1134
+ for (const result of results) {
1135
+ const event = await this.eventStore.getEvent(result.eventId);
1136
+ if (!event)
1137
+ continue;
1138
+ let sessionContext;
1139
+ if (options.includeSessionContext) {
1140
+ sessionContext = await this.getSessionContext(event.sessionId, event.id);
1141
+ }
1142
+ memories.push({
1143
+ event,
1144
+ score: result.score,
1145
+ sessionContext
1146
+ });
1147
+ }
1148
+ return memories;
1149
+ }
1150
+ /**
1151
+ * Get surrounding context from the same session
1152
+ */
1153
+ async getSessionContext(sessionId, eventId) {
1154
+ const sessionEvents = await this.eventStore.getSessionEvents(sessionId);
1155
+ const eventIndex = sessionEvents.findIndex((e) => e.id === eventId);
1156
+ if (eventIndex === -1)
1157
+ return void 0;
1158
+ const start = Math.max(0, eventIndex - 1);
1159
+ const end = Math.min(sessionEvents.length, eventIndex + 2);
1160
+ const contextEvents = sessionEvents.slice(start, end);
1161
+ if (contextEvents.length <= 1)
1162
+ return void 0;
1163
+ return contextEvents.filter((e) => e.id !== eventId).map((e) => `[${e.eventType}]: ${e.content.slice(0, 200)}...`).join("\n");
1164
+ }
1165
+ /**
1166
+ * Build context string from memories (respecting token limit)
1167
+ */
1168
+ buildContext(memories, maxTokens) {
1169
+ const parts = [];
1170
+ let currentTokens = 0;
1171
+ for (const memory of memories) {
1172
+ const memoryText = this.formatMemory(memory);
1173
+ const memoryTokens = this.estimateTokens(memoryText);
1174
+ if (currentTokens + memoryTokens > maxTokens) {
1175
+ break;
1176
+ }
1177
+ parts.push(memoryText);
1178
+ currentTokens += memoryTokens;
1179
+ }
1180
+ if (parts.length === 0) {
1181
+ return "";
1182
+ }
1183
+ return `## Relevant Memories
1184
+
1185
+ ${parts.join("\n\n---\n\n")}`;
1186
+ }
1187
+ /**
1188
+ * Format a single memory for context
1189
+ */
1190
+ formatMemory(memory) {
1191
+ const { event, score, sessionContext } = memory;
1192
+ const date = event.timestamp.toISOString().split("T")[0];
1193
+ let text = `**${event.eventType}** (${date}, score: ${score.toFixed(2)})
1194
+ ${event.content}`;
1195
+ if (sessionContext) {
1196
+ text += `
1197
+
1198
+ _Context:_ ${sessionContext}`;
1199
+ }
1200
+ return text;
1201
+ }
1202
+ /**
1203
+ * Estimate token count (rough approximation)
1204
+ */
1205
+ estimateTokens(text) {
1206
+ return Math.ceil(text.length / 4);
1207
+ }
1208
+ /**
1209
+ * Get event age in days (for recency scoring)
1210
+ */
1211
+ getEventAgeDays(eventId) {
1212
+ return 0;
1213
+ }
1214
+ };
1215
+ function createRetriever(eventStore, vectorStore, embedder, matcher) {
1216
+ return new Retriever(eventStore, vectorStore, embedder, matcher);
1217
+ }
1218
+
1219
+ // src/core/graduation.ts
1220
+ var DEFAULT_CRITERIA = {
1221
+ L0toL1: {
1222
+ minAccessCount: 1,
1223
+ minConfidence: 0.5,
1224
+ minCrossSessionRefs: 0,
1225
+ maxAgeDays: 30
1226
+ },
1227
+ L1toL2: {
1228
+ minAccessCount: 3,
1229
+ minConfidence: 0.7,
1230
+ minCrossSessionRefs: 1,
1231
+ maxAgeDays: 60
1232
+ },
1233
+ L2toL3: {
1234
+ minAccessCount: 5,
1235
+ minConfidence: 0.85,
1236
+ minCrossSessionRefs: 2,
1237
+ maxAgeDays: 90
1238
+ },
1239
+ L3toL4: {
1240
+ minAccessCount: 10,
1241
+ minConfidence: 0.92,
1242
+ minCrossSessionRefs: 3,
1243
+ maxAgeDays: 180
1244
+ }
1245
+ };
1246
+ var GraduationPipeline = class {
1247
+ eventStore;
1248
+ criteria;
1249
+ metrics = /* @__PURE__ */ new Map();
1250
+ constructor(eventStore, criteria = {}) {
1251
+ this.eventStore = eventStore;
1252
+ this.criteria = {
1253
+ L0toL1: { ...DEFAULT_CRITERIA.L0toL1, ...criteria.L0toL1 },
1254
+ L1toL2: { ...DEFAULT_CRITERIA.L1toL2, ...criteria.L1toL2 },
1255
+ L2toL3: { ...DEFAULT_CRITERIA.L2toL3, ...criteria.L2toL3 },
1256
+ L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
1257
+ };
1258
+ }
1259
+ /**
1260
+ * Record an access to an event (used for graduation scoring)
1261
+ */
1262
+ recordAccess(eventId, fromSessionId, confidence = 1) {
1263
+ const existing = this.metrics.get(eventId);
1264
+ if (existing) {
1265
+ existing.accessCount++;
1266
+ existing.lastAccessed = /* @__PURE__ */ new Date();
1267
+ existing.confidence = Math.max(existing.confidence, confidence);
1268
+ } else {
1269
+ this.metrics.set(eventId, {
1270
+ eventId,
1271
+ accessCount: 1,
1272
+ lastAccessed: /* @__PURE__ */ new Date(),
1273
+ crossSessionRefs: 0,
1274
+ confidence
1275
+ });
1276
+ }
1277
+ }
1278
+ /**
1279
+ * Evaluate if an event should graduate to the next level
1280
+ */
1281
+ async evaluateGraduation(eventId, currentLevel) {
1282
+ const metrics = this.metrics.get(eventId);
1283
+ if (!metrics) {
1284
+ return {
1285
+ eventId,
1286
+ fromLevel: currentLevel,
1287
+ toLevel: currentLevel,
1288
+ success: false,
1289
+ reason: "No metrics available for event"
1290
+ };
1291
+ }
1292
+ const nextLevel = this.getNextLevel(currentLevel);
1293
+ if (!nextLevel) {
1294
+ return {
1295
+ eventId,
1296
+ fromLevel: currentLevel,
1297
+ toLevel: currentLevel,
1298
+ success: false,
1299
+ reason: "Already at maximum level"
1300
+ };
1301
+ }
1302
+ const criteria = this.getCriteria(currentLevel, nextLevel);
1303
+ const evaluation = this.checkCriteria(metrics, criteria);
1304
+ if (evaluation.passed) {
1305
+ await this.eventStore.updateMemoryLevel(eventId, nextLevel);
1306
+ return {
1307
+ eventId,
1308
+ fromLevel: currentLevel,
1309
+ toLevel: nextLevel,
1310
+ success: true
1311
+ };
1312
+ }
1313
+ return {
1314
+ eventId,
1315
+ fromLevel: currentLevel,
1316
+ toLevel: currentLevel,
1317
+ success: false,
1318
+ reason: evaluation.reason
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Run graduation evaluation for all events at a given level
1323
+ */
1324
+ async graduateBatch(level) {
1325
+ const results = [];
1326
+ for (const [eventId, metrics] of this.metrics) {
1327
+ const result = await this.evaluateGraduation(eventId, level);
1328
+ results.push(result);
1329
+ }
1330
+ return results;
1331
+ }
1332
+ /**
1333
+ * Extract insights from graduated events (L1+)
1334
+ */
1335
+ extractInsights(events) {
1336
+ const insights = [];
1337
+ const patterns = this.detectPatterns(events);
1338
+ for (const pattern of patterns) {
1339
+ insights.push({
1340
+ id: crypto.randomUUID(),
1341
+ insightType: "pattern",
1342
+ content: pattern.description,
1343
+ canonicalKey: pattern.key,
1344
+ confidence: pattern.confidence,
1345
+ sourceEvents: pattern.eventIds,
1346
+ createdAt: /* @__PURE__ */ new Date(),
1347
+ lastUpdated: /* @__PURE__ */ new Date()
1348
+ });
1349
+ }
1350
+ const preferences = this.detectPreferences(events);
1351
+ for (const pref of preferences) {
1352
+ insights.push({
1353
+ id: crypto.randomUUID(),
1354
+ insightType: "preference",
1355
+ content: pref.description,
1356
+ canonicalKey: pref.key,
1357
+ confidence: pref.confidence,
1358
+ sourceEvents: pref.eventIds,
1359
+ createdAt: /* @__PURE__ */ new Date(),
1360
+ lastUpdated: /* @__PURE__ */ new Date()
1361
+ });
1362
+ }
1363
+ return insights;
1364
+ }
1365
+ /**
1366
+ * Get the next level in the graduation pipeline
1367
+ */
1368
+ getNextLevel(current) {
1369
+ const levels = ["L0", "L1", "L2", "L3", "L4"];
1370
+ const currentIndex = levels.indexOf(current);
1371
+ if (currentIndex === -1 || currentIndex >= levels.length - 1) {
1372
+ return null;
1373
+ }
1374
+ return levels[currentIndex + 1];
1375
+ }
1376
+ /**
1377
+ * Get criteria for level transition
1378
+ */
1379
+ getCriteria(from, to) {
1380
+ const key = `${from}to${to}`;
1381
+ return this.criteria[key] || DEFAULT_CRITERIA.L0toL1;
1382
+ }
1383
+ /**
1384
+ * Check if metrics meet criteria
1385
+ */
1386
+ checkCriteria(metrics, criteria) {
1387
+ if (metrics.accessCount < criteria.minAccessCount) {
1388
+ return {
1389
+ passed: false,
1390
+ reason: `Access count ${metrics.accessCount} < ${criteria.minAccessCount}`
1391
+ };
1392
+ }
1393
+ if (metrics.confidence < criteria.minConfidence) {
1394
+ return {
1395
+ passed: false,
1396
+ reason: `Confidence ${metrics.confidence} < ${criteria.minConfidence}`
1397
+ };
1398
+ }
1399
+ if (metrics.crossSessionRefs < criteria.minCrossSessionRefs) {
1400
+ return {
1401
+ passed: false,
1402
+ reason: `Cross-session refs ${metrics.crossSessionRefs} < ${criteria.minCrossSessionRefs}`
1403
+ };
1404
+ }
1405
+ const ageDays = (Date.now() - metrics.lastAccessed.getTime()) / (1e3 * 60 * 60 * 24);
1406
+ if (ageDays > criteria.maxAgeDays) {
1407
+ return {
1408
+ passed: false,
1409
+ reason: `Event too old: ${ageDays.toFixed(1)} days > ${criteria.maxAgeDays}`
1410
+ };
1411
+ }
1412
+ return { passed: true };
1413
+ }
1414
+ /**
1415
+ * Detect patterns in events
1416
+ */
1417
+ detectPatterns(events) {
1418
+ const keyGroups = /* @__PURE__ */ new Map();
1419
+ for (const event of events) {
1420
+ const existing = keyGroups.get(event.canonicalKey) || [];
1421
+ existing.push(event);
1422
+ keyGroups.set(event.canonicalKey, existing);
1423
+ }
1424
+ const patterns = [];
1425
+ for (const [key, groupEvents] of keyGroups) {
1426
+ if (groupEvents.length >= 2) {
1427
+ patterns.push({
1428
+ key,
1429
+ description: `Repeated topic: ${key.slice(0, 50)}`,
1430
+ confidence: Math.min(1, groupEvents.length / 5),
1431
+ eventIds: groupEvents.map((e) => e.id)
1432
+ });
1433
+ }
1434
+ }
1435
+ return patterns;
1436
+ }
1437
+ /**
1438
+ * Detect user preferences from events
1439
+ */
1440
+ detectPreferences(events) {
1441
+ const preferenceKeywords = ["prefer", "like", "want", "always", "never", "favorite"];
1442
+ const preferences = [];
1443
+ for (const event of events) {
1444
+ if (event.eventType !== "user_prompt")
1445
+ continue;
1446
+ const lowerContent = event.content.toLowerCase();
1447
+ for (const keyword of preferenceKeywords) {
1448
+ if (lowerContent.includes(keyword)) {
1449
+ preferences.push({
1450
+ key: `preference_${keyword}_${event.id.slice(0, 8)}`,
1451
+ description: `User preference: ${event.content.slice(0, 100)}`,
1452
+ confidence: 0.7,
1453
+ eventIds: [event.id]
1454
+ });
1455
+ break;
1456
+ }
1457
+ }
1458
+ }
1459
+ return preferences;
1460
+ }
1461
+ /**
1462
+ * Get graduation statistics
1463
+ */
1464
+ async getStats() {
1465
+ return this.eventStore.getLevelStats();
1466
+ }
1467
+ };
1468
+ function createGraduationPipeline(eventStore) {
1469
+ return new GraduationPipeline(eventStore);
1470
+ }
1471
+
1472
+ // src/core/metadata-extractor.ts
1473
+ function createToolObservationEmbedding(toolName, metadata, success) {
1474
+ const parts = [];
1475
+ parts.push(`Tool: ${toolName}`);
1476
+ if (metadata.filePath) {
1477
+ parts.push(`File: ${metadata.filePath}`);
1478
+ }
1479
+ if (metadata.command) {
1480
+ parts.push(`Command: ${metadata.command}`);
1481
+ }
1482
+ if (metadata.pattern) {
1483
+ parts.push(`Pattern: ${metadata.pattern}`);
1484
+ }
1485
+ if (metadata.url) {
1486
+ try {
1487
+ const url = new URL(metadata.url);
1488
+ parts.push(`URL: ${url.hostname}`);
1489
+ } catch {
1490
+ }
1491
+ }
1492
+ parts.push(`Result: ${success ? "Success" : "Failed"}`);
1493
+ return parts.join("\n");
1494
+ }
1495
+
1496
+ // src/core/working-set-store.ts
1497
+ import { randomUUID as randomUUID2 } from "crypto";
1498
+ var WorkingSetStore = class {
1499
+ constructor(eventStore, config) {
1500
+ this.eventStore = eventStore;
1501
+ this.config = config;
1502
+ }
1503
+ get db() {
1504
+ return this.eventStore.getDatabase();
1505
+ }
1506
+ /**
1507
+ * Add an event to the working set
1508
+ */
1509
+ async add(eventId, relevanceScore = 1, topics) {
1510
+ const expiresAt = new Date(
1511
+ Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1e3
1512
+ );
1513
+ await this.db.run(
1514
+ `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
1515
+ VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
1516
+ [
1517
+ randomUUID2(),
1518
+ eventId,
1519
+ relevanceScore,
1520
+ JSON.stringify(topics || []),
1521
+ expiresAt.toISOString()
1522
+ ]
1523
+ );
1524
+ await this.enforceLimit();
1525
+ }
1526
+ /**
1527
+ * Get the current working set
1528
+ */
1529
+ async get() {
1530
+ await this.cleanup();
1531
+ const rows = await this.db.all(
1532
+ `SELECT ws.*, e.*
1533
+ FROM working_set ws
1534
+ JOIN events e ON ws.event_id = e.id
1535
+ ORDER BY ws.relevance_score DESC, ws.added_at DESC
1536
+ LIMIT ?`,
1537
+ [this.config.workingSet.maxEvents]
1538
+ );
1539
+ const events = rows.map((row) => ({
1540
+ id: row.id,
1541
+ eventType: row.event_type,
1542
+ sessionId: row.session_id,
1543
+ timestamp: new Date(row.timestamp),
1544
+ content: row.content,
1545
+ canonicalKey: row.canonical_key,
1546
+ dedupeKey: row.dedupe_key,
1547
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0
1548
+ }));
1549
+ return {
1550
+ recentEvents: events,
1551
+ lastActivity: events.length > 0 ? events[0].timestamp : /* @__PURE__ */ new Date(),
1552
+ continuityScore: await this.calculateContinuityScore()
1553
+ };
1554
+ }
1555
+ /**
1556
+ * Get working set items (metadata only)
1557
+ */
1558
+ async getItems() {
1559
+ const rows = await this.db.all(
1560
+ `SELECT * FROM working_set ORDER BY relevance_score DESC, added_at DESC`
1561
+ );
1562
+ return rows.map((row) => ({
1563
+ id: row.id,
1564
+ eventId: row.event_id,
1565
+ addedAt: new Date(row.added_at),
1566
+ relevanceScore: row.relevance_score,
1567
+ topics: row.topics ? JSON.parse(row.topics) : void 0,
1568
+ expiresAt: new Date(row.expires_at)
1569
+ }));
1570
+ }
1571
+ /**
1572
+ * Update relevance score for an event
1573
+ */
1574
+ async updateRelevance(eventId, score) {
1575
+ await this.db.run(
1576
+ `UPDATE working_set SET relevance_score = ? WHERE event_id = ?`,
1577
+ [score, eventId]
1578
+ );
1579
+ }
1580
+ /**
1581
+ * Prune specific events from working set (after consolidation)
1582
+ */
1583
+ async prune(eventIds) {
1584
+ if (eventIds.length === 0)
1585
+ return;
1586
+ const placeholders = eventIds.map(() => "?").join(",");
1587
+ await this.db.run(
1588
+ `DELETE FROM working_set WHERE event_id IN (${placeholders})`,
1589
+ eventIds
1590
+ );
1591
+ }
1592
+ /**
1593
+ * Get the count of items in working set
1594
+ */
1595
+ async count() {
1596
+ const result = await this.db.all(
1597
+ `SELECT COUNT(*) as count FROM working_set`
1598
+ );
1599
+ return result[0]?.count || 0;
1600
+ }
1601
+ /**
1602
+ * Clear the entire working set
1603
+ */
1604
+ async clear() {
1605
+ await this.db.run(`DELETE FROM working_set`);
1606
+ }
1607
+ /**
1608
+ * Check if an event is in the working set
1609
+ */
1610
+ async contains(eventId) {
1611
+ const result = await this.db.all(
1612
+ `SELECT COUNT(*) as count FROM working_set WHERE event_id = ?`,
1613
+ [eventId]
1614
+ );
1615
+ return (result[0]?.count || 0) > 0;
1616
+ }
1617
+ /**
1618
+ * Refresh expiration for an event (rehears al - keep relevant items longer)
1619
+ */
1620
+ async refresh(eventId) {
1621
+ const newExpiresAt = new Date(
1622
+ Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1e3
1623
+ );
1624
+ await this.db.run(
1625
+ `UPDATE working_set SET expires_at = ? WHERE event_id = ?`,
1626
+ [newExpiresAt.toISOString(), eventId]
1627
+ );
1628
+ }
1629
+ /**
1630
+ * Clean up expired items
1631
+ */
1632
+ async cleanup() {
1633
+ await this.db.run(
1634
+ `DELETE FROM working_set WHERE expires_at < datetime('now')`
1635
+ );
1636
+ }
1637
+ /**
1638
+ * Enforce the maximum size limit
1639
+ * Removes lowest relevance items when over limit
1640
+ */
1641
+ async enforceLimit() {
1642
+ const maxEvents = this.config.workingSet.maxEvents;
1643
+ const keepIds = await this.db.all(
1644
+ `SELECT id FROM working_set
1645
+ ORDER BY relevance_score DESC, added_at DESC
1646
+ LIMIT ?`,
1647
+ [maxEvents]
1648
+ );
1649
+ if (keepIds.length === 0)
1650
+ return;
1651
+ const keepIdList = keepIds.map((r) => r.id);
1652
+ const placeholders = keepIdList.map(() => "?").join(",");
1653
+ await this.db.run(
1654
+ `DELETE FROM working_set WHERE id NOT IN (${placeholders})`,
1655
+ keepIdList
1656
+ );
1657
+ }
1658
+ /**
1659
+ * Calculate continuity score based on recent context transitions
1660
+ */
1661
+ async calculateContinuityScore() {
1662
+ const result = await this.db.all(
1663
+ `SELECT AVG(continuity_score) as avg_score
1664
+ FROM continuity_log
1665
+ WHERE created_at > datetime('now', '-1 hour')`
1666
+ );
1667
+ return result[0]?.avg_score ?? 0.5;
1668
+ }
1669
+ /**
1670
+ * Get topics from current working set for context matching
1671
+ */
1672
+ async getActiveTopics() {
1673
+ const rows = await this.db.all(
1674
+ `SELECT topics FROM working_set WHERE topics IS NOT NULL`
1675
+ );
1676
+ const allTopics = /* @__PURE__ */ new Set();
1677
+ for (const row of rows) {
1678
+ const topics = JSON.parse(row.topics);
1679
+ topics.forEach((t) => allTopics.add(t));
1680
+ }
1681
+ return Array.from(allTopics);
1682
+ }
1683
+ };
1684
+ function createWorkingSetStore(eventStore, config) {
1685
+ return new WorkingSetStore(eventStore, config);
1686
+ }
1687
+
1688
+ // src/core/consolidated-store.ts
1689
+ import { randomUUID as randomUUID3 } from "crypto";
1690
+ var ConsolidatedStore = class {
1691
+ constructor(eventStore) {
1692
+ this.eventStore = eventStore;
1693
+ }
1694
+ get db() {
1695
+ return this.eventStore.getDatabase();
1696
+ }
1697
+ /**
1698
+ * Create a new consolidated memory
1699
+ */
1700
+ async create(input) {
1701
+ const memoryId = randomUUID3();
1702
+ await this.db.run(
1703
+ `INSERT INTO consolidated_memories
1704
+ (memory_id, summary, topics, source_events, confidence, created_at)
1705
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
1706
+ [
1707
+ memoryId,
1708
+ input.summary,
1709
+ JSON.stringify(input.topics),
1710
+ JSON.stringify(input.sourceEvents),
1711
+ input.confidence
1712
+ ]
1713
+ );
1714
+ return memoryId;
1715
+ }
1716
+ /**
1717
+ * Get a consolidated memory by ID
1718
+ */
1719
+ async get(memoryId) {
1720
+ const rows = await this.db.all(
1721
+ `SELECT * FROM consolidated_memories WHERE memory_id = ?`,
1722
+ [memoryId]
1723
+ );
1724
+ if (rows.length === 0)
1725
+ return null;
1726
+ return this.rowToMemory(rows[0]);
1727
+ }
1728
+ /**
1729
+ * Search consolidated memories by query (simple text search)
1730
+ */
1731
+ async search(query, options) {
1732
+ const topK = options?.topK || 5;
1733
+ const rows = await this.db.all(
1734
+ `SELECT * FROM consolidated_memories
1735
+ WHERE summary LIKE ?
1736
+ ORDER BY confidence DESC
1737
+ LIMIT ?`,
1738
+ [`%${query}%`, topK]
1739
+ );
1740
+ return rows.map(this.rowToMemory);
1741
+ }
1742
+ /**
1743
+ * Search by topics
1744
+ */
1745
+ async searchByTopics(topics, options) {
1746
+ const topK = options?.topK || 5;
1747
+ const topicConditions = topics.map(() => `topics LIKE ?`).join(" OR ");
1748
+ const topicParams = topics.map((t) => `%"${t}"%`);
1749
+ const rows = await this.db.all(
1750
+ `SELECT * FROM consolidated_memories
1751
+ WHERE ${topicConditions}
1752
+ ORDER BY confidence DESC
1753
+ LIMIT ?`,
1754
+ [...topicParams, topK]
1755
+ );
1756
+ return rows.map(this.rowToMemory);
1757
+ }
1758
+ /**
1759
+ * Get all consolidated memories ordered by confidence
1760
+ */
1761
+ async getAll(options) {
1762
+ const limit = options?.limit || 100;
1763
+ const rows = await this.db.all(
1764
+ `SELECT * FROM consolidated_memories
1765
+ ORDER BY confidence DESC, created_at DESC
1766
+ LIMIT ?`,
1767
+ [limit]
1768
+ );
1769
+ return rows.map(this.rowToMemory);
1770
+ }
1771
+ /**
1772
+ * Get recently created memories
1773
+ */
1774
+ async getRecent(options) {
1775
+ const limit = options?.limit || 10;
1776
+ const hours = options?.hours || 24;
1777
+ const rows = await this.db.all(
1778
+ `SELECT * FROM consolidated_memories
1779
+ WHERE created_at > datetime('now', '-${hours} hours')
1780
+ ORDER BY created_at DESC
1781
+ LIMIT ?`,
1782
+ [limit]
1783
+ );
1784
+ return rows.map(this.rowToMemory);
1785
+ }
1786
+ /**
1787
+ * Mark a memory as accessed (tracks usage for importance scoring)
1788
+ */
1789
+ async markAccessed(memoryId) {
1790
+ await this.db.run(
1791
+ `UPDATE consolidated_memories
1792
+ SET accessed_at = CURRENT_TIMESTAMP,
1793
+ access_count = access_count + 1
1794
+ WHERE memory_id = ?`,
1795
+ [memoryId]
1796
+ );
1797
+ }
1798
+ /**
1799
+ * Update confidence score for a memory
1800
+ */
1801
+ async updateConfidence(memoryId, confidence) {
1802
+ await this.db.run(
1803
+ `UPDATE consolidated_memories
1804
+ SET confidence = ?
1805
+ WHERE memory_id = ?`,
1806
+ [confidence, memoryId]
1807
+ );
1808
+ }
1809
+ /**
1810
+ * Delete a consolidated memory
1811
+ */
1812
+ async delete(memoryId) {
1813
+ await this.db.run(
1814
+ `DELETE FROM consolidated_memories WHERE memory_id = ?`,
1815
+ [memoryId]
1816
+ );
1817
+ }
1818
+ /**
1819
+ * Get count of consolidated memories
1820
+ */
1821
+ async count() {
1822
+ const result = await this.db.all(
1823
+ `SELECT COUNT(*) as count FROM consolidated_memories`
1824
+ );
1825
+ return result[0]?.count || 0;
1826
+ }
1827
+ /**
1828
+ * Get most accessed memories (for importance scoring)
1829
+ */
1830
+ async getMostAccessed(limit = 10) {
1831
+ const rows = await this.db.all(
1832
+ `SELECT * FROM consolidated_memories
1833
+ WHERE access_count > 0
1834
+ ORDER BY access_count DESC
1835
+ LIMIT ?`,
1836
+ [limit]
1837
+ );
1838
+ return rows.map(this.rowToMemory);
1839
+ }
1840
+ /**
1841
+ * Get statistics about consolidated memories
1842
+ */
1843
+ async getStats() {
1844
+ const total = await this.count();
1845
+ const avgResult = await this.db.all(
1846
+ `SELECT AVG(confidence) as avg FROM consolidated_memories`
1847
+ );
1848
+ const averageConfidence = avgResult[0]?.avg || 0;
1849
+ const recentResult = await this.db.all(
1850
+ `SELECT COUNT(*) as count FROM consolidated_memories
1851
+ WHERE created_at > datetime('now', '-24 hours')`
1852
+ );
1853
+ const recentCount = recentResult[0]?.count || 0;
1854
+ const allMemories = await this.getAll({ limit: 1e3 });
1855
+ const topicCounts = {};
1856
+ for (const memory of allMemories) {
1857
+ for (const topic of memory.topics) {
1858
+ topicCounts[topic] = (topicCounts[topic] || 0) + 1;
1859
+ }
1860
+ }
1861
+ return {
1862
+ total,
1863
+ averageConfidence,
1864
+ topicCounts,
1865
+ recentCount
1866
+ };
1867
+ }
1868
+ /**
1869
+ * Check if source events are already consolidated
1870
+ */
1871
+ async isAlreadyConsolidated(eventIds) {
1872
+ for (const eventId of eventIds) {
1873
+ const result = await this.db.all(
1874
+ `SELECT COUNT(*) as count FROM consolidated_memories
1875
+ WHERE source_events LIKE ?`,
1876
+ [`%"${eventId}"%`]
1877
+ );
1878
+ if ((result[0]?.count || 0) > 0)
1879
+ return true;
1880
+ }
1881
+ return false;
1882
+ }
1883
+ /**
1884
+ * Get the last consolidation time
1885
+ */
1886
+ async getLastConsolidationTime() {
1887
+ const result = await this.db.all(
1888
+ `SELECT created_at FROM consolidated_memories
1889
+ ORDER BY created_at DESC
1890
+ LIMIT 1`
1891
+ );
1892
+ if (result.length === 0)
1893
+ return null;
1894
+ return new Date(result[0].created_at);
1895
+ }
1896
+ /**
1897
+ * Convert database row to ConsolidatedMemory
1898
+ */
1899
+ rowToMemory(row) {
1900
+ return {
1901
+ memoryId: row.memory_id,
1902
+ summary: row.summary,
1903
+ topics: JSON.parse(row.topics || "[]"),
1904
+ sourceEvents: JSON.parse(row.source_events || "[]"),
1905
+ confidence: row.confidence,
1906
+ createdAt: new Date(row.created_at),
1907
+ accessedAt: row.accessed_at ? new Date(row.accessed_at) : void 0,
1908
+ accessCount: row.access_count || 0
1909
+ };
1910
+ }
1911
+ };
1912
+ function createConsolidatedStore(eventStore) {
1913
+ return new ConsolidatedStore(eventStore);
1914
+ }
1915
+
1916
+ // src/core/consolidation-worker.ts
1917
+ var ConsolidationWorker = class {
1918
+ constructor(workingSetStore, consolidatedStore, config) {
1919
+ this.workingSetStore = workingSetStore;
1920
+ this.consolidatedStore = consolidatedStore;
1921
+ this.config = config;
1922
+ }
1923
+ running = false;
1924
+ timeout = null;
1925
+ lastActivity = /* @__PURE__ */ new Date();
1926
+ /**
1927
+ * Start the consolidation worker
1928
+ */
1929
+ start() {
1930
+ if (this.running)
1931
+ return;
1932
+ this.running = true;
1933
+ this.scheduleNext();
1934
+ }
1935
+ /**
1936
+ * Stop the consolidation worker
1937
+ */
1938
+ stop() {
1939
+ this.running = false;
1940
+ if (this.timeout) {
1941
+ clearTimeout(this.timeout);
1942
+ this.timeout = null;
1943
+ }
1944
+ }
1945
+ /**
1946
+ * Record activity (resets idle timer)
1947
+ */
1948
+ recordActivity() {
1949
+ this.lastActivity = /* @__PURE__ */ new Date();
1950
+ }
1951
+ /**
1952
+ * Check if currently running
1953
+ */
1954
+ isRunning() {
1955
+ return this.running;
1956
+ }
1957
+ /**
1958
+ * Force a consolidation run (manual trigger)
1959
+ */
1960
+ async forceRun() {
1961
+ return await this.consolidate();
1962
+ }
1963
+ /**
1964
+ * Schedule the next consolidation check
1965
+ */
1966
+ scheduleNext() {
1967
+ if (!this.running)
1968
+ return;
1969
+ this.timeout = setTimeout(
1970
+ () => this.run(),
1971
+ this.config.consolidation.triggerIntervalMs
1972
+ );
1973
+ }
1974
+ /**
1975
+ * Run consolidation check
1976
+ */
1977
+ async run() {
1978
+ if (!this.running)
1979
+ return;
1980
+ try {
1981
+ await this.checkAndConsolidate();
1982
+ } catch (error) {
1983
+ console.error("Consolidation error:", error);
1984
+ }
1985
+ this.scheduleNext();
1986
+ }
1987
+ /**
1988
+ * Check conditions and consolidate if needed
1989
+ */
1990
+ async checkAndConsolidate() {
1991
+ const workingSet = await this.workingSetStore.get();
1992
+ if (!this.shouldConsolidate(workingSet)) {
1993
+ return;
1994
+ }
1995
+ await this.consolidate();
1996
+ }
1997
+ /**
1998
+ * Perform consolidation
1999
+ */
2000
+ async consolidate() {
2001
+ const workingSet = await this.workingSetStore.get();
2002
+ if (workingSet.recentEvents.length < 3) {
2003
+ return 0;
2004
+ }
2005
+ const groups = this.groupByTopic(workingSet.recentEvents);
2006
+ let consolidatedCount = 0;
2007
+ for (const group of groups) {
2008
+ if (group.events.length < 3)
2009
+ continue;
2010
+ const eventIds = group.events.map((e) => e.id);
2011
+ const alreadyConsolidated = await this.consolidatedStore.isAlreadyConsolidated(eventIds);
2012
+ if (alreadyConsolidated)
2013
+ continue;
2014
+ const summary = await this.summarize(group);
2015
+ await this.consolidatedStore.create({
2016
+ summary,
2017
+ topics: group.topics,
2018
+ sourceEvents: eventIds,
2019
+ confidence: this.calculateConfidence(group)
2020
+ });
2021
+ consolidatedCount++;
2022
+ }
2023
+ if (consolidatedCount > 0) {
2024
+ const consolidatedEventIds = groups.filter((g) => g.events.length >= 3).flatMap((g) => g.events.map((e) => e.id));
2025
+ const oldEventIds = consolidatedEventIds.filter((id) => {
2026
+ const event = workingSet.recentEvents.find((e) => e.id === id);
2027
+ if (!event)
2028
+ return false;
2029
+ const ageHours = (Date.now() - event.timestamp.getTime()) / (1e3 * 60 * 60);
2030
+ return ageHours > this.config.workingSet.timeWindowHours / 2;
2031
+ });
2032
+ if (oldEventIds.length > 0) {
2033
+ await this.workingSetStore.prune(oldEventIds);
2034
+ }
2035
+ }
2036
+ return consolidatedCount;
2037
+ }
2038
+ /**
2039
+ * Check if consolidation should run
2040
+ */
2041
+ shouldConsolidate(workingSet) {
2042
+ if (workingSet.recentEvents.length >= this.config.consolidation.triggerEventCount) {
2043
+ return true;
2044
+ }
2045
+ const idleTime = Date.now() - this.lastActivity.getTime();
2046
+ if (idleTime >= this.config.consolidation.triggerIdleMs) {
2047
+ return true;
2048
+ }
2049
+ return false;
2050
+ }
2051
+ /**
2052
+ * Group events by topic using simple keyword extraction
2053
+ */
2054
+ groupByTopic(events) {
2055
+ const groups = /* @__PURE__ */ new Map();
2056
+ for (const event of events) {
2057
+ const topics = this.extractTopics(event.content);
2058
+ for (const topic of topics) {
2059
+ if (!groups.has(topic)) {
2060
+ groups.set(topic, { topics: [topic], events: [] });
2061
+ }
2062
+ const group = groups.get(topic);
2063
+ if (!group.events.find((e) => e.id === event.id)) {
2064
+ group.events.push(event);
2065
+ }
2066
+ }
2067
+ }
2068
+ const mergedGroups = this.mergeOverlappingGroups(Array.from(groups.values()));
2069
+ return mergedGroups;
2070
+ }
2071
+ /**
2072
+ * Extract topics from content using simple keyword extraction
2073
+ */
2074
+ extractTopics(content) {
2075
+ const topics = [];
2076
+ const codePatterns = [
2077
+ /\b(function|class|interface|type|const|let|var)\s+(\w+)/gi,
2078
+ /\b(import|export)\s+.*?from\s+['"]([^'"]+)['"]/gi,
2079
+ /\bfile[:\s]+([^\s,]+)/gi
2080
+ ];
2081
+ for (const pattern of codePatterns) {
2082
+ let match;
2083
+ while ((match = pattern.exec(content)) !== null) {
2084
+ const keyword = match[2] || match[1];
2085
+ if (keyword && keyword.length > 2) {
2086
+ topics.push(keyword.toLowerCase());
2087
+ }
2088
+ }
2089
+ }
2090
+ const commonTerms = [
2091
+ "bug",
2092
+ "fix",
2093
+ "error",
2094
+ "issue",
2095
+ "feature",
2096
+ "test",
2097
+ "refactor",
2098
+ "implement",
2099
+ "add",
2100
+ "remove",
2101
+ "update",
2102
+ "change",
2103
+ "modify",
2104
+ "create",
2105
+ "delete"
2106
+ ];
2107
+ const contentLower = content.toLowerCase();
2108
+ for (const term of commonTerms) {
2109
+ if (contentLower.includes(term)) {
2110
+ topics.push(term);
2111
+ }
2112
+ }
2113
+ return [...new Set(topics)].slice(0, 5);
2114
+ }
2115
+ /**
2116
+ * Merge groups that have significant event overlap
2117
+ */
2118
+ mergeOverlappingGroups(groups) {
2119
+ const merged = [];
2120
+ for (const group of groups) {
2121
+ let foundMerge = false;
2122
+ for (const existing of merged) {
2123
+ const overlap = group.events.filter(
2124
+ (e) => existing.events.some((ex) => ex.id === e.id)
2125
+ );
2126
+ if (overlap.length > group.events.length / 2) {
2127
+ existing.topics = [.../* @__PURE__ */ new Set([...existing.topics, ...group.topics])];
2128
+ for (const event of group.events) {
2129
+ if (!existing.events.find((e) => e.id === event.id)) {
2130
+ existing.events.push(event);
2131
+ }
2132
+ }
2133
+ foundMerge = true;
2134
+ break;
2135
+ }
2136
+ }
2137
+ if (!foundMerge) {
2138
+ merged.push(group);
2139
+ }
2140
+ }
2141
+ return merged;
2142
+ }
2143
+ /**
2144
+ * Generate summary for a group of events
2145
+ * Rule-based extraction (no LLM by default)
2146
+ */
2147
+ async summarize(group) {
2148
+ if (this.config.consolidation.useLLMSummarization) {
2149
+ return this.ruleBasedSummary(group);
2150
+ }
2151
+ return this.ruleBasedSummary(group);
2152
+ }
2153
+ /**
2154
+ * Rule-based summary generation
2155
+ */
2156
+ ruleBasedSummary(group) {
2157
+ const keyPoints = [];
2158
+ for (const event of group.events.slice(0, 10)) {
2159
+ const keyPoint = this.extractKeyPoint(event.content);
2160
+ if (keyPoint) {
2161
+ keyPoints.push(keyPoint);
2162
+ }
2163
+ }
2164
+ const topicsStr = group.topics.slice(0, 3).join(", ");
2165
+ const summary = [
2166
+ `Topics: ${topicsStr}`,
2167
+ "",
2168
+ "Key points:",
2169
+ ...keyPoints.map((kp) => `- ${kp}`)
2170
+ ].join("\n");
2171
+ return summary;
2172
+ }
2173
+ /**
2174
+ * Extract key point from content
2175
+ */
2176
+ extractKeyPoint(content) {
2177
+ const sentences = content.split(/[.!?\n]+/).filter((s) => s.trim().length > 10);
2178
+ if (sentences.length === 0)
2179
+ return null;
2180
+ const firstSentence = sentences[0].trim();
2181
+ if (firstSentence.length > 100) {
2182
+ return firstSentence.slice(0, 100) + "...";
2183
+ }
2184
+ return firstSentence;
2185
+ }
2186
+ /**
2187
+ * Calculate confidence score for a group
2188
+ */
2189
+ calculateConfidence(group) {
2190
+ const eventScore = Math.min(group.events.length / 10, 1);
2191
+ const timeScore = this.calculateTimeProximity(group.events);
2192
+ const topicScore = Math.min(3 / group.topics.length, 1);
2193
+ return eventScore * 0.4 + timeScore * 0.4 + topicScore * 0.2;
2194
+ }
2195
+ /**
2196
+ * Calculate time proximity score
2197
+ */
2198
+ calculateTimeProximity(events) {
2199
+ if (events.length < 2)
2200
+ return 1;
2201
+ const timestamps = events.map((e) => e.timestamp.getTime()).sort((a, b) => a - b);
2202
+ const timeSpan = timestamps[timestamps.length - 1] - timestamps[0];
2203
+ const avgGap = timeSpan / (events.length - 1);
2204
+ const hourInMs = 60 * 60 * 1e3;
2205
+ return Math.max(0, 1 - avgGap / (24 * hourInMs));
2206
+ }
2207
+ };
2208
+ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
2209
+ return new ConsolidationWorker(workingSetStore, consolidatedStore, config);
2210
+ }
2211
+
2212
+ // src/core/continuity-manager.ts
2213
+ import { randomUUID as randomUUID4 } from "crypto";
2214
+ var ContinuityManager = class {
2215
+ constructor(eventStore, config) {
2216
+ this.eventStore = eventStore;
2217
+ this.config = config;
2218
+ }
2219
+ lastContext = null;
2220
+ get db() {
2221
+ return this.eventStore.getDatabase();
2222
+ }
2223
+ /**
2224
+ * Calculate continuity score between current and previous context
2225
+ */
2226
+ async calculateScore(currentContext, previousContext) {
2227
+ const prev = previousContext || this.lastContext;
2228
+ if (!prev) {
2229
+ this.lastContext = currentContext;
2230
+ return { score: 0.5, transitionType: "break" };
2231
+ }
2232
+ let score = 0;
2233
+ const topicOverlap = this.calculateOverlap(
2234
+ currentContext.topics,
2235
+ prev.topics
2236
+ );
2237
+ score += topicOverlap * 0.3;
2238
+ const fileOverlap = this.calculateOverlap(
2239
+ currentContext.files,
2240
+ prev.files
2241
+ );
2242
+ score += fileOverlap * 0.2;
2243
+ const timeDiff = currentContext.timestamp - prev.timestamp;
2244
+ const decayHours = this.config.continuity.topicDecayHours;
2245
+ const timeScore = Math.exp(-timeDiff / (decayHours * 36e5));
2246
+ score += timeScore * 0.3;
2247
+ const entityOverlap = this.calculateOverlap(
2248
+ currentContext.entities,
2249
+ prev.entities
2250
+ );
2251
+ score += entityOverlap * 0.2;
2252
+ const transitionType = this.determineTransitionType(score);
2253
+ await this.logTransition(currentContext, prev, score, transitionType);
2254
+ this.lastContext = currentContext;
2255
+ return { score, transitionType };
2256
+ }
2257
+ /**
2258
+ * Create a context snapshot from current state
2259
+ */
2260
+ createSnapshot(id, content, metadata) {
2261
+ return {
2262
+ id,
2263
+ timestamp: Date.now(),
2264
+ topics: this.extractTopics(content),
2265
+ files: metadata?.files || this.extractFiles(content),
2266
+ entities: metadata?.entities || this.extractEntities(content)
2267
+ };
2268
+ }
2269
+ /**
2270
+ * Get recent continuity logs
2271
+ */
2272
+ async getRecentLogs(limit = 10) {
2273
+ const rows = await this.db.all(
2274
+ `SELECT * FROM continuity_log
2275
+ ORDER BY created_at DESC
2276
+ LIMIT ?`,
2277
+ [limit]
2278
+ );
2279
+ return rows.map((row) => ({
2280
+ logId: row.log_id,
2281
+ fromContextId: row.from_context_id,
2282
+ toContextId: row.to_context_id,
2283
+ continuityScore: row.continuity_score,
2284
+ transitionType: row.transition_type,
2285
+ createdAt: new Date(row.created_at)
2286
+ }));
2287
+ }
2288
+ /**
2289
+ * Get average continuity score over time period
2290
+ */
2291
+ async getAverageScore(hours = 1) {
2292
+ const result = await this.db.all(
2293
+ `SELECT AVG(continuity_score) as avg_score
2294
+ FROM continuity_log
2295
+ WHERE created_at > datetime('now', '-${hours} hours')`
2296
+ );
2297
+ return result[0]?.avg_score ?? 0.5;
2298
+ }
2299
+ /**
2300
+ * Get transition type distribution
2301
+ */
2302
+ async getTransitionStats(hours = 24) {
2303
+ const rows = await this.db.all(
2304
+ `SELECT transition_type, COUNT(*) as count
2305
+ FROM continuity_log
2306
+ WHERE created_at > datetime('now', '-${hours} hours')
2307
+ GROUP BY transition_type`
2308
+ );
2309
+ const stats = {
2310
+ seamless: 0,
2311
+ topic_shift: 0,
2312
+ break: 0
2313
+ };
2314
+ for (const row of rows) {
2315
+ stats[row.transition_type] = row.count;
2316
+ }
2317
+ return stats;
2318
+ }
2319
+ /**
2320
+ * Clear old continuity logs
2321
+ */
2322
+ async cleanup(olderThanDays = 7) {
2323
+ const result = await this.db.all(
2324
+ `DELETE FROM continuity_log
2325
+ WHERE created_at < datetime('now', '-${olderThanDays} days')
2326
+ RETURNING COUNT(*) as changes`
2327
+ );
2328
+ return result[0]?.changes || 0;
2329
+ }
2330
+ /**
2331
+ * Calculate overlap between two arrays
2332
+ */
2333
+ calculateOverlap(a, b) {
2334
+ if (a.length === 0 || b.length === 0)
2335
+ return 0;
2336
+ const setA = new Set(a.map((s) => s.toLowerCase()));
2337
+ const setB = new Set(b.map((s) => s.toLowerCase()));
2338
+ const intersection = [...setA].filter((x) => setB.has(x));
2339
+ const union = /* @__PURE__ */ new Set([...setA, ...setB]);
2340
+ return intersection.length / union.size;
2341
+ }
2342
+ /**
2343
+ * Determine transition type based on score
2344
+ */
2345
+ determineTransitionType(score) {
2346
+ if (score >= this.config.continuity.minScoreForSeamless) {
2347
+ return "seamless";
2348
+ } else if (score >= 0.4) {
2349
+ return "topic_shift";
2350
+ } else {
2351
+ return "break";
2352
+ }
2353
+ }
2354
+ /**
2355
+ * Log a context transition
2356
+ */
2357
+ async logTransition(current, previous, score, type) {
2358
+ await this.db.run(
2359
+ `INSERT INTO continuity_log
2360
+ (log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
2361
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
2362
+ [randomUUID4(), previous.id, current.id, score, type]
2363
+ );
2364
+ }
2365
+ /**
2366
+ * Extract topics from content
2367
+ */
2368
+ extractTopics(content) {
2369
+ const topics = [];
2370
+ const contentLower = content.toLowerCase();
2371
+ const langPatterns = [
2372
+ { pattern: /typescript|\.ts\b/i, topic: "typescript" },
2373
+ { pattern: /javascript|\.js\b/i, topic: "javascript" },
2374
+ { pattern: /python|\.py\b/i, topic: "python" },
2375
+ { pattern: /rust|\.rs\b/i, topic: "rust" },
2376
+ { pattern: /go\b|golang/i, topic: "go" }
2377
+ ];
2378
+ for (const { pattern, topic } of langPatterns) {
2379
+ if (pattern.test(content)) {
2380
+ topics.push(topic);
2381
+ }
2382
+ }
2383
+ const devTopics = [
2384
+ "api",
2385
+ "database",
2386
+ "test",
2387
+ "bug",
2388
+ "feature",
2389
+ "refactor",
2390
+ "component",
2391
+ "function",
2392
+ "class",
2393
+ "module",
2394
+ "hook",
2395
+ "deploy",
2396
+ "build",
2397
+ "config",
2398
+ "docker",
2399
+ "git"
2400
+ ];
2401
+ for (const topic of devTopics) {
2402
+ if (contentLower.includes(topic)) {
2403
+ topics.push(topic);
2404
+ }
2405
+ }
2406
+ return [...new Set(topics)].slice(0, 10);
2407
+ }
2408
+ /**
2409
+ * Extract file paths from content
2410
+ */
2411
+ extractFiles(content) {
2412
+ const filePatterns = [
2413
+ /(?:^|\s)([a-zA-Z0-9_\-./]+\.[a-zA-Z0-9]+)(?:\s|$|:)/gm,
2414
+ /['"](\.?\/[^'"]+\.[a-zA-Z0-9]+)['"]/g,
2415
+ /file[:\s]+([^\s,]+)/gi
2416
+ ];
2417
+ const files = /* @__PURE__ */ new Set();
2418
+ for (const pattern of filePatterns) {
2419
+ let match;
2420
+ while ((match = pattern.exec(content)) !== null) {
2421
+ const file = match[1];
2422
+ if (file && file.length > 3 && file.length < 100) {
2423
+ if (!file.match(/^(https?:|mailto:|ftp:)/i)) {
2424
+ files.add(file);
2425
+ }
2426
+ }
2427
+ }
2428
+ }
2429
+ return Array.from(files).slice(0, 10);
2430
+ }
2431
+ /**
2432
+ * Extract entity names from content (functions, classes, variables)
2433
+ */
2434
+ extractEntities(content) {
2435
+ const entities = /* @__PURE__ */ new Set();
2436
+ const entityPatterns = [
2437
+ /\b(function|const|let|var|class|interface|type)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g,
2438
+ /\b([A-Z][a-zA-Z0-9_]*(?:Component|Service|Store|Manager|Handler|Factory|Provider))\b/g,
2439
+ /\b(use[A-Z][a-zA-Z0-9_]*)\b/g
2440
+ // React hooks
2441
+ ];
2442
+ for (const pattern of entityPatterns) {
2443
+ let match;
2444
+ while ((match = pattern.exec(content)) !== null) {
2445
+ const entity = match[2] || match[1];
2446
+ if (entity && entity.length > 2) {
2447
+ entities.add(entity);
2448
+ }
2449
+ }
2450
+ }
2451
+ return Array.from(entities).slice(0, 20);
2452
+ }
2453
+ /**
2454
+ * Reset the last context (for testing or manual reset)
2455
+ */
2456
+ resetLastContext() {
2457
+ this.lastContext = null;
2458
+ }
2459
+ /**
2460
+ * Get the last context snapshot
2461
+ */
2462
+ getLastContext() {
2463
+ return this.lastContext;
2464
+ }
2465
+ };
2466
+ function createContinuityManager(eventStore, config) {
2467
+ return new ContinuityManager(eventStore, config);
2468
+ }
2469
+
2470
+ // src/services/memory-service.ts
2471
+ var MemoryService = class {
2472
+ eventStore;
2473
+ vectorStore;
2474
+ embedder;
2475
+ matcher;
2476
+ retriever;
2477
+ graduation;
2478
+ vectorWorker = null;
2479
+ initialized = false;
2480
+ // Endless Mode components
2481
+ workingSetStore = null;
2482
+ consolidatedStore = null;
2483
+ consolidationWorker = null;
2484
+ continuityManager = null;
2485
+ endlessMode = "session";
2486
+ constructor(config) {
2487
+ const storagePath = this.expandPath(config.storagePath);
2488
+ if (!fs.existsSync(storagePath)) {
2489
+ fs.mkdirSync(storagePath, { recursive: true });
2490
+ }
2491
+ this.eventStore = new EventStore(path.join(storagePath, "events.duckdb"));
2492
+ this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
2493
+ this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
2494
+ this.matcher = getDefaultMatcher();
2495
+ this.retriever = createRetriever(
2496
+ this.eventStore,
2497
+ this.vectorStore,
2498
+ this.embedder,
2499
+ this.matcher
2500
+ );
2501
+ this.graduation = createGraduationPipeline(this.eventStore);
2502
+ }
2503
+ /**
2504
+ * Initialize all components
2505
+ */
2506
+ async initialize() {
2507
+ if (this.initialized)
2508
+ return;
2509
+ await this.eventStore.initialize();
2510
+ await this.vectorStore.initialize();
2511
+ await this.embedder.initialize();
2512
+ this.vectorWorker = createVectorWorker(
2513
+ this.eventStore,
2514
+ this.vectorStore,
2515
+ this.embedder
2516
+ );
2517
+ this.vectorWorker.start();
2518
+ const savedMode = await this.eventStore.getEndlessConfig("mode");
2519
+ if (savedMode === "endless") {
2520
+ this.endlessMode = "endless";
2521
+ await this.initializeEndlessMode();
2522
+ }
2523
+ this.initialized = true;
2524
+ }
2525
+ /**
2526
+ * Start a new session
2527
+ */
2528
+ async startSession(sessionId, projectPath) {
2529
+ await this.initialize();
2530
+ await this.eventStore.upsertSession({
2531
+ id: sessionId,
2532
+ startedAt: /* @__PURE__ */ new Date(),
2533
+ projectPath
2534
+ });
2535
+ }
2536
+ /**
2537
+ * End a session
2538
+ */
2539
+ async endSession(sessionId, summary) {
2540
+ await this.initialize();
2541
+ await this.eventStore.upsertSession({
2542
+ id: sessionId,
2543
+ endedAt: /* @__PURE__ */ new Date(),
2544
+ summary
2545
+ });
2546
+ }
2547
+ /**
2548
+ * Store a user prompt
2549
+ */
2550
+ async storeUserPrompt(sessionId, content, metadata) {
2551
+ await this.initialize();
2552
+ const result = await this.eventStore.append({
2553
+ eventType: "user_prompt",
2554
+ sessionId,
2555
+ timestamp: /* @__PURE__ */ new Date(),
2556
+ content,
2557
+ metadata
2558
+ });
2559
+ if (result.success && !result.isDuplicate) {
2560
+ await this.eventStore.enqueueForEmbedding(result.eventId, content);
2561
+ }
2562
+ return result;
2563
+ }
2564
+ /**
2565
+ * Store an agent response
2566
+ */
2567
+ async storeAgentResponse(sessionId, content, metadata) {
2568
+ await this.initialize();
2569
+ const result = await this.eventStore.append({
2570
+ eventType: "agent_response",
2571
+ sessionId,
2572
+ timestamp: /* @__PURE__ */ new Date(),
2573
+ content,
2574
+ metadata
2575
+ });
2576
+ if (result.success && !result.isDuplicate) {
2577
+ await this.eventStore.enqueueForEmbedding(result.eventId, content);
2578
+ }
2579
+ return result;
2580
+ }
2581
+ /**
2582
+ * Store a session summary
2583
+ */
2584
+ async storeSessionSummary(sessionId, summary) {
2585
+ await this.initialize();
2586
+ const result = await this.eventStore.append({
2587
+ eventType: "session_summary",
2588
+ sessionId,
2589
+ timestamp: /* @__PURE__ */ new Date(),
2590
+ content: summary
2591
+ });
2592
+ if (result.success && !result.isDuplicate) {
2593
+ await this.eventStore.enqueueForEmbedding(result.eventId, summary);
2594
+ }
2595
+ return result;
2596
+ }
2597
+ /**
2598
+ * Store a tool observation
2599
+ */
2600
+ async storeToolObservation(sessionId, payload) {
2601
+ await this.initialize();
2602
+ const content = JSON.stringify(payload);
2603
+ const result = await this.eventStore.append({
2604
+ eventType: "tool_observation",
2605
+ sessionId,
2606
+ timestamp: /* @__PURE__ */ new Date(),
2607
+ content,
2608
+ metadata: {
2609
+ toolName: payload.toolName,
2610
+ success: payload.success
2611
+ }
2612
+ });
2613
+ if (result.success && !result.isDuplicate) {
2614
+ const embeddingContent = createToolObservationEmbedding(
2615
+ payload.toolName,
2616
+ payload.metadata || {},
2617
+ payload.success
2618
+ );
2619
+ await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
2620
+ }
2621
+ return result;
2622
+ }
2623
+ /**
2624
+ * Retrieve relevant memories for a query
2625
+ */
2626
+ async retrieveMemories(query, options) {
2627
+ await this.initialize();
2628
+ if (this.vectorWorker) {
2629
+ await this.vectorWorker.processAll();
2630
+ }
2631
+ return this.retriever.retrieve(query, options);
2632
+ }
2633
+ /**
2634
+ * Get session history
2635
+ */
2636
+ async getSessionHistory(sessionId) {
2637
+ await this.initialize();
2638
+ return this.eventStore.getSessionEvents(sessionId);
2639
+ }
2640
+ /**
2641
+ * Get recent events
2642
+ */
2643
+ async getRecentEvents(limit = 100) {
2644
+ await this.initialize();
2645
+ return this.eventStore.getRecentEvents(limit);
2646
+ }
2647
+ /**
2648
+ * Get memory statistics
2649
+ */
2650
+ async getStats() {
2651
+ await this.initialize();
2652
+ const recentEvents = await this.eventStore.getRecentEvents(1e4);
2653
+ const vectorCount = await this.vectorStore.count();
2654
+ const levelStats = await this.graduation.getStats();
2655
+ return {
2656
+ totalEvents: recentEvents.length,
2657
+ vectorCount,
2658
+ levelStats
2659
+ };
2660
+ }
2661
+ /**
2662
+ * Process pending embeddings
2663
+ */
2664
+ async processPendingEmbeddings() {
2665
+ if (this.vectorWorker) {
2666
+ return this.vectorWorker.processAll();
2667
+ }
2668
+ return 0;
2669
+ }
2670
+ /**
2671
+ * Format retrieval results as context for Claude
2672
+ */
2673
+ formatAsContext(result) {
2674
+ if (!result.context) {
2675
+ return "";
2676
+ }
2677
+ const confidence = result.matchResult.confidence;
2678
+ let header = "";
2679
+ if (confidence === "high") {
2680
+ header = "\u{1F3AF} **High-confidence memory match found:**\n\n";
2681
+ } else if (confidence === "suggested") {
2682
+ header = "\u{1F4A1} **Suggested memories (may be relevant):**\n\n";
2683
+ }
2684
+ return header + result.context;
2685
+ }
2686
+ // ============================================================
2687
+ // Endless Mode Methods
2688
+ // ============================================================
2689
+ /**
2690
+ * Get the default endless mode config
2691
+ */
2692
+ getDefaultEndlessConfig() {
2693
+ return {
2694
+ enabled: true,
2695
+ workingSet: {
2696
+ maxEvents: 100,
2697
+ timeWindowHours: 24,
2698
+ minRelevanceScore: 0.5
2699
+ },
2700
+ consolidation: {
2701
+ triggerIntervalMs: 36e5,
2702
+ // 1 hour
2703
+ triggerEventCount: 100,
2704
+ triggerIdleMs: 18e5,
2705
+ // 30 minutes
2706
+ useLLMSummarization: false
2707
+ },
2708
+ continuity: {
2709
+ minScoreForSeamless: 0.7,
2710
+ topicDecayHours: 48
2711
+ }
2712
+ };
2713
+ }
2714
+ /**
2715
+ * Initialize Endless Mode components
2716
+ */
2717
+ async initializeEndlessMode() {
2718
+ const config = await this.getEndlessConfig();
2719
+ this.workingSetStore = createWorkingSetStore(this.eventStore, config);
2720
+ this.consolidatedStore = createConsolidatedStore(this.eventStore);
2721
+ this.consolidationWorker = createConsolidationWorker(
2722
+ this.workingSetStore,
2723
+ this.consolidatedStore,
2724
+ config
2725
+ );
2726
+ this.continuityManager = createContinuityManager(this.eventStore, config);
2727
+ this.consolidationWorker.start();
2728
+ }
2729
+ /**
2730
+ * Get Endless Mode configuration
2731
+ */
2732
+ async getEndlessConfig() {
2733
+ const savedConfig = await this.eventStore.getEndlessConfig("config");
2734
+ return savedConfig || this.getDefaultEndlessConfig();
2735
+ }
2736
+ /**
2737
+ * Set Endless Mode configuration
2738
+ */
2739
+ async setEndlessConfig(config) {
2740
+ const current = await this.getEndlessConfig();
2741
+ const merged = { ...current, ...config };
2742
+ await this.eventStore.setEndlessConfig("config", merged);
2743
+ }
2744
+ /**
2745
+ * Set memory mode (session or endless)
2746
+ */
2747
+ async setMode(mode) {
2748
+ await this.initialize();
2749
+ if (mode === this.endlessMode)
2750
+ return;
2751
+ this.endlessMode = mode;
2752
+ await this.eventStore.setEndlessConfig("mode", mode);
2753
+ if (mode === "endless") {
2754
+ await this.initializeEndlessMode();
2755
+ } else {
2756
+ if (this.consolidationWorker) {
2757
+ this.consolidationWorker.stop();
2758
+ this.consolidationWorker = null;
2759
+ }
2760
+ this.workingSetStore = null;
2761
+ this.consolidatedStore = null;
2762
+ this.continuityManager = null;
2763
+ }
2764
+ }
2765
+ /**
2766
+ * Get current memory mode
2767
+ */
2768
+ getMode() {
2769
+ return this.endlessMode;
2770
+ }
2771
+ /**
2772
+ * Check if endless mode is active
2773
+ */
2774
+ isEndlessModeActive() {
2775
+ return this.endlessMode === "endless";
2776
+ }
2777
+ /**
2778
+ * Add event to Working Set (Endless Mode)
2779
+ */
2780
+ async addToWorkingSet(eventId, relevanceScore) {
2781
+ if (!this.workingSetStore)
2782
+ return;
2783
+ await this.workingSetStore.add(eventId, relevanceScore);
2784
+ }
2785
+ /**
2786
+ * Get the current Working Set
2787
+ */
2788
+ async getWorkingSet() {
2789
+ if (!this.workingSetStore)
2790
+ return null;
2791
+ return this.workingSetStore.get();
2792
+ }
2793
+ /**
2794
+ * Search consolidated memories
2795
+ */
2796
+ async searchConsolidated(query, options) {
2797
+ if (!this.consolidatedStore)
2798
+ return [];
2799
+ return this.consolidatedStore.search(query, options);
2800
+ }
2801
+ /**
2802
+ * Get all consolidated memories
2803
+ */
2804
+ async getConsolidatedMemories(limit) {
2805
+ if (!this.consolidatedStore)
2806
+ return [];
2807
+ return this.consolidatedStore.getAll({ limit });
2808
+ }
2809
+ /**
2810
+ * Calculate continuity score for current context
2811
+ */
2812
+ async calculateContinuity(content, metadata) {
2813
+ if (!this.continuityManager)
2814
+ return null;
2815
+ const snapshot = this.continuityManager.createSnapshot(
2816
+ crypto2.randomUUID(),
2817
+ content,
2818
+ metadata
2819
+ );
2820
+ return this.continuityManager.calculateScore(snapshot);
2821
+ }
2822
+ /**
2823
+ * Record activity (for consolidation idle trigger)
2824
+ */
2825
+ recordActivity() {
2826
+ if (this.consolidationWorker) {
2827
+ this.consolidationWorker.recordActivity();
2828
+ }
2829
+ }
2830
+ /**
2831
+ * Force a consolidation run
2832
+ */
2833
+ async forceConsolidation() {
2834
+ if (!this.consolidationWorker)
2835
+ return 0;
2836
+ return this.consolidationWorker.forceRun();
2837
+ }
2838
+ /**
2839
+ * Get Endless Mode status
2840
+ */
2841
+ async getEndlessModeStatus() {
2842
+ await this.initialize();
2843
+ let workingSetSize = 0;
2844
+ let continuityScore = 0.5;
2845
+ let consolidatedCount = 0;
2846
+ let lastConsolidation = null;
2847
+ if (this.workingSetStore) {
2848
+ workingSetSize = await this.workingSetStore.count();
2849
+ const workingSet = await this.workingSetStore.get();
2850
+ continuityScore = workingSet.continuityScore;
2851
+ }
2852
+ if (this.consolidatedStore) {
2853
+ consolidatedCount = await this.consolidatedStore.count();
2854
+ lastConsolidation = await this.consolidatedStore.getLastConsolidationTime();
2855
+ }
2856
+ return {
2857
+ mode: this.endlessMode,
2858
+ workingSetSize,
2859
+ continuityScore,
2860
+ consolidatedCount,
2861
+ lastConsolidation
2862
+ };
2863
+ }
2864
+ /**
2865
+ * Format Endless Mode context for Claude
2866
+ */
2867
+ async formatEndlessContext(query) {
2868
+ if (!this.isEndlessModeActive()) {
2869
+ return "";
2870
+ }
2871
+ const workingSet = await this.getWorkingSet();
2872
+ const consolidated = await this.searchConsolidated(query, { topK: 3 });
2873
+ const continuity = await this.calculateContinuity(query);
2874
+ const parts = [];
2875
+ if (continuity) {
2876
+ const statusEmoji = continuity.transitionType === "seamless" ? "\u{1F517}" : continuity.transitionType === "topic_shift" ? "\u21AA\uFE0F" : "\u{1F195}";
2877
+ parts.push(`${statusEmoji} Context: ${continuity.transitionType} (score: ${continuity.score.toFixed(2)})`);
2878
+ }
2879
+ if (workingSet && workingSet.recentEvents.length > 0) {
2880
+ parts.push("\n## Recent Context (Working Set)");
2881
+ const recent = workingSet.recentEvents.slice(0, 5);
2882
+ for (const event of recent) {
2883
+ const preview = event.content.slice(0, 80) + (event.content.length > 80 ? "..." : "");
2884
+ const time = event.timestamp.toLocaleTimeString();
2885
+ parts.push(`- ${time} [${event.eventType}] ${preview}`);
2886
+ }
2887
+ }
2888
+ if (consolidated.length > 0) {
2889
+ parts.push("\n## Related Knowledge (Consolidated)");
2890
+ for (const memory of consolidated) {
2891
+ parts.push(`- ${memory.topics.slice(0, 3).join(", ")}: ${memory.summary.slice(0, 100)}...`);
2892
+ }
2893
+ }
2894
+ return parts.join("\n");
2895
+ }
2896
+ /**
2897
+ * Shutdown service
2898
+ */
2899
+ async shutdown() {
2900
+ if (this.consolidationWorker) {
2901
+ this.consolidationWorker.stop();
2902
+ }
2903
+ if (this.vectorWorker) {
2904
+ this.vectorWorker.stop();
2905
+ }
2906
+ await this.eventStore.close();
2907
+ }
2908
+ /**
2909
+ * Expand ~ to home directory
2910
+ */
2911
+ expandPath(p) {
2912
+ if (p.startsWith("~")) {
2913
+ return path.join(os.homedir(), p.slice(1));
2914
+ }
2915
+ return p;
2916
+ }
2917
+ };
2918
+ var defaultService = null;
2919
+ function getDefaultMemoryService() {
2920
+ if (!defaultService) {
2921
+ defaultService = new MemoryService({
2922
+ storagePath: "~/.claude-code/memory"
2923
+ });
2924
+ }
2925
+ return defaultService;
2926
+ }
2927
+
2928
+ // src/services/session-history-importer.ts
2929
+ import * as fs2 from "fs";
2930
+ import * as path2 from "path";
2931
+ import * as os2 from "os";
2932
+ import * as readline from "readline";
2933
+ var SessionHistoryImporter = class {
2934
+ memoryService;
2935
+ claudeDir;
2936
+ constructor(memoryService) {
2937
+ this.memoryService = memoryService;
2938
+ this.claudeDir = path2.join(os2.homedir(), ".claude");
2939
+ }
2940
+ /**
2941
+ * Import all sessions from a project
2942
+ */
2943
+ async importProject(projectPath, options = {}) {
2944
+ const result = {
2945
+ totalSessions: 0,
2946
+ totalMessages: 0,
2947
+ importedPrompts: 0,
2948
+ importedResponses: 0,
2949
+ skippedDuplicates: 0,
2950
+ errors: []
2951
+ };
2952
+ const projectDir = await this.findProjectDir(projectPath);
2953
+ if (!projectDir) {
2954
+ result.errors.push(`Project directory not found for: ${projectPath}`);
2955
+ return result;
2956
+ }
2957
+ const sessionFiles = await this.findSessionFiles(projectDir);
2958
+ result.totalSessions = sessionFiles.length;
2959
+ if (options.verbose) {
2960
+ console.log(`Found ${sessionFiles.length} session files in ${projectDir}`);
2961
+ }
2962
+ for (const sessionFile of sessionFiles) {
2963
+ try {
2964
+ const sessionResult = await this.importSessionFile(sessionFile, options);
2965
+ result.totalMessages += sessionResult.totalMessages;
2966
+ result.importedPrompts += sessionResult.importedPrompts;
2967
+ result.importedResponses += sessionResult.importedResponses;
2968
+ result.skippedDuplicates += sessionResult.skippedDuplicates;
2969
+ } catch (error) {
2970
+ result.errors.push(`Failed to import ${sessionFile}: ${error}`);
2971
+ }
2972
+ }
2973
+ return result;
2974
+ }
2975
+ /**
2976
+ * Import a specific session file
2977
+ */
2978
+ async importSessionFile(filePath, options = {}) {
2979
+ const result = {
2980
+ totalSessions: 1,
2981
+ totalMessages: 0,
2982
+ importedPrompts: 0,
2983
+ importedResponses: 0,
2984
+ skippedDuplicates: 0,
2985
+ errors: []
2986
+ };
2987
+ if (!fs2.existsSync(filePath)) {
2988
+ result.errors.push(`File not found: ${filePath}`);
2989
+ return result;
2990
+ }
2991
+ const sessionId = path2.basename(filePath, ".jsonl");
2992
+ await this.memoryService.startSession(sessionId, options.projectPath);
2993
+ const fileStream = fs2.createReadStream(filePath);
2994
+ const rl = readline.createInterface({
2995
+ input: fileStream,
2996
+ crlfDelay: Infinity
2997
+ });
2998
+ let lineCount = 0;
2999
+ const limit = options.limit || Infinity;
3000
+ for await (const line of rl) {
3001
+ if (lineCount >= limit)
3002
+ break;
3003
+ try {
3004
+ const entry = JSON.parse(line);
3005
+ result.totalMessages++;
3006
+ if (entry.type === "user" || entry.type === "assistant") {
3007
+ const content = this.extractContent(entry);
3008
+ if (!content)
3009
+ continue;
3010
+ if (entry.type === "user") {
3011
+ const appendResult = await this.memoryService.storeUserPrompt(
3012
+ sessionId,
3013
+ content,
3014
+ { importedFrom: filePath, originalTimestamp: entry.timestamp }
3015
+ );
3016
+ if (appendResult.isDuplicate) {
3017
+ result.skippedDuplicates++;
3018
+ } else {
3019
+ result.importedPrompts++;
3020
+ }
3021
+ } else if (entry.type === "assistant") {
3022
+ const truncatedContent = content.length > 5e3 ? content.slice(0, 5e3) + "...[truncated]" : content;
3023
+ const appendResult = await this.memoryService.storeAgentResponse(
3024
+ sessionId,
3025
+ truncatedContent,
3026
+ { importedFrom: filePath, originalTimestamp: entry.timestamp }
3027
+ );
3028
+ if (appendResult.isDuplicate) {
3029
+ result.skippedDuplicates++;
3030
+ } else {
3031
+ result.importedResponses++;
3032
+ }
3033
+ }
3034
+ lineCount++;
3035
+ }
3036
+ } catch (parseError) {
3037
+ result.errors.push(`Parse error on line: ${parseError}`);
3038
+ }
3039
+ }
3040
+ await this.memoryService.endSession(sessionId);
3041
+ if (options.verbose) {
3042
+ console.log(`Imported ${result.importedPrompts} prompts, ${result.importedResponses} responses from ${filePath}`);
3043
+ }
3044
+ return result;
3045
+ }
3046
+ /**
3047
+ * Import all sessions from all projects
3048
+ */
3049
+ async importAll(options = {}) {
3050
+ const result = {
3051
+ totalSessions: 0,
3052
+ totalMessages: 0,
3053
+ importedPrompts: 0,
3054
+ importedResponses: 0,
3055
+ skippedDuplicates: 0,
3056
+ errors: []
3057
+ };
3058
+ const projectsDir = path2.join(this.claudeDir, "projects");
3059
+ if (!fs2.existsSync(projectsDir)) {
3060
+ result.errors.push(`Projects directory not found: ${projectsDir}`);
3061
+ return result;
3062
+ }
3063
+ const projectDirs = fs2.readdirSync(projectsDir).map((name) => path2.join(projectsDir, name)).filter((p) => fs2.statSync(p).isDirectory());
3064
+ if (options.verbose) {
3065
+ console.log(`Found ${projectDirs.length} project directories`);
3066
+ }
3067
+ for (const projectDir of projectDirs) {
3068
+ try {
3069
+ const sessionFiles = await this.findSessionFiles(projectDir);
3070
+ for (const sessionFile of sessionFiles) {
3071
+ const sessionResult = await this.importSessionFile(sessionFile, options);
3072
+ result.totalSessions++;
3073
+ result.totalMessages += sessionResult.totalMessages;
3074
+ result.importedPrompts += sessionResult.importedPrompts;
3075
+ result.importedResponses += sessionResult.importedResponses;
3076
+ result.skippedDuplicates += sessionResult.skippedDuplicates;
3077
+ result.errors.push(...sessionResult.errors);
3078
+ }
3079
+ } catch (error) {
3080
+ result.errors.push(`Failed to process ${projectDir}: ${error}`);
3081
+ }
3082
+ }
3083
+ return result;
3084
+ }
3085
+ /**
3086
+ * Find project directory from project path
3087
+ */
3088
+ async findProjectDir(projectPath) {
3089
+ const projectsDir = path2.join(this.claudeDir, "projects");
3090
+ if (!fs2.existsSync(projectsDir)) {
3091
+ return null;
3092
+ }
3093
+ const projectDirs = fs2.readdirSync(projectsDir).map((name) => path2.join(projectsDir, name)).filter((p) => fs2.statSync(p).isDirectory());
3094
+ const normalizedPath = projectPath.replace(/\//g, "-").replace(/^-/, "");
3095
+ for (const dir of projectDirs) {
3096
+ const dirName = path2.basename(dir);
3097
+ if (dirName.includes(normalizedPath) || normalizedPath.includes(dirName)) {
3098
+ return dir;
3099
+ }
3100
+ }
3101
+ return projectDirs.length > 0 ? projectDirs[0] : null;
3102
+ }
3103
+ /**
3104
+ * Find all JSONL session files in a directory
3105
+ */
3106
+ async findSessionFiles(dir) {
3107
+ if (!fs2.existsSync(dir)) {
3108
+ return [];
3109
+ }
3110
+ return fs2.readdirSync(dir).filter((name) => name.endsWith(".jsonl")).map((name) => path2.join(dir, name)).filter((p) => fs2.statSync(p).isFile());
3111
+ }
3112
+ /**
3113
+ * Extract text content from Claude message
3114
+ */
3115
+ extractContent(entry) {
3116
+ if (!entry.message?.content) {
3117
+ return null;
3118
+ }
3119
+ const content = entry.message.content;
3120
+ if (typeof content === "string") {
3121
+ return content;
3122
+ }
3123
+ if (Array.isArray(content)) {
3124
+ const texts = content.filter((block) => block.type === "text" && block.text).map((block) => block.text);
3125
+ return texts.join("\n");
3126
+ }
3127
+ return null;
3128
+ }
3129
+ /**
3130
+ * List available sessions for import
3131
+ */
3132
+ async listAvailableSessions(projectPath) {
3133
+ const sessions = [];
3134
+ let projectDirs = [];
3135
+ if (projectPath) {
3136
+ const projectDir = await this.findProjectDir(projectPath);
3137
+ if (projectDir) {
3138
+ projectDirs = [projectDir];
3139
+ }
3140
+ } else {
3141
+ const projectsDir = path2.join(this.claudeDir, "projects");
3142
+ if (fs2.existsSync(projectsDir)) {
3143
+ projectDirs = fs2.readdirSync(projectsDir).map((name) => path2.join(projectsDir, name)).filter((p) => fs2.statSync(p).isDirectory());
3144
+ }
3145
+ }
3146
+ for (const projectDir of projectDirs) {
3147
+ const sessionFiles = await this.findSessionFiles(projectDir);
3148
+ for (const filePath of sessionFiles) {
3149
+ const stats = fs2.statSync(filePath);
3150
+ sessions.push({
3151
+ sessionId: path2.basename(filePath, ".jsonl"),
3152
+ filePath,
3153
+ size: stats.size,
3154
+ modifiedAt: stats.mtime
3155
+ });
3156
+ }
3157
+ }
3158
+ sessions.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
3159
+ return sessions;
3160
+ }
3161
+ };
3162
+ function createSessionHistoryImporter(memoryService) {
3163
+ return new SessionHistoryImporter(memoryService);
3164
+ }
3165
+
3166
+ // src/cli/index.ts
3167
+ var program = new Command();
3168
+ program.name("code-memory").description("Claude Code Memory Plugin CLI").version("1.0.0");
3169
+ program.command("search <query>").description("Search memories using semantic search").option("-k, --top-k <number>", "Number of results", "5").option("-s, --min-score <number>", "Minimum similarity score", "0.7").option("--session <id>", "Filter by session ID").action(async (query, options) => {
3170
+ const service = getDefaultMemoryService();
3171
+ try {
3172
+ const result = await service.retrieveMemories(query, {
3173
+ topK: parseInt(options.topK),
3174
+ minScore: parseFloat(options.minScore),
3175
+ sessionId: options.session
3176
+ });
3177
+ console.log("\n\u{1F4DA} Search Results\n");
3178
+ console.log(`Confidence: ${result.matchResult.confidence}`);
3179
+ console.log(`Total memories found: ${result.memories.length}
3180
+ `);
3181
+ for (const memory of result.memories) {
3182
+ const date = memory.event.timestamp.toISOString().split("T")[0];
3183
+ console.log(`---`);
3184
+ console.log(`\u{1F4CC} ${memory.event.eventType} (${date})`);
3185
+ console.log(` Score: ${memory.score.toFixed(3)}`);
3186
+ console.log(` Session: ${memory.event.sessionId.slice(0, 8)}...`);
3187
+ console.log(` Content: ${memory.event.content.slice(0, 200)}${memory.event.content.length > 200 ? "..." : ""}`);
3188
+ console.log("");
3189
+ }
3190
+ await service.shutdown();
3191
+ } catch (error) {
3192
+ console.error("Search failed:", error);
3193
+ process.exit(1);
3194
+ }
3195
+ });
3196
+ program.command("history").description("View conversation history").option("-l, --limit <number>", "Number of events", "20").option("--session <id>", "Filter by session ID").option("--type <type>", "Filter by event type").action(async (options) => {
3197
+ const service = getDefaultMemoryService();
3198
+ try {
3199
+ let events;
3200
+ if (options.session) {
3201
+ events = await service.getSessionHistory(options.session);
3202
+ } else {
3203
+ events = await service.getRecentEvents(parseInt(options.limit));
3204
+ }
3205
+ if (options.type) {
3206
+ events = events.filter((e) => e.eventType === options.type);
3207
+ }
3208
+ console.log("\n\u{1F4DC} Memory History\n");
3209
+ console.log(`Total events: ${events.length}
3210
+ `);
3211
+ for (const event of events.slice(0, parseInt(options.limit))) {
3212
+ const date = event.timestamp.toISOString();
3213
+ const icon = event.eventType === "user_prompt" ? "\u{1F464}" : event.eventType === "agent_response" ? "\u{1F916}" : "\u{1F4DD}";
3214
+ console.log(`${icon} [${date}] ${event.eventType}`);
3215
+ console.log(` Session: ${event.sessionId.slice(0, 8)}...`);
3216
+ console.log(` ${event.content.slice(0, 150)}${event.content.length > 150 ? "..." : ""}`);
3217
+ console.log("");
3218
+ }
3219
+ await service.shutdown();
3220
+ } catch (error) {
3221
+ console.error("History failed:", error);
3222
+ process.exit(1);
3223
+ }
3224
+ });
3225
+ program.command("stats").description("View memory statistics").action(async () => {
3226
+ const service = getDefaultMemoryService();
3227
+ try {
3228
+ const stats = await service.getStats();
3229
+ console.log("\n\u{1F4CA} Memory Statistics\n");
3230
+ console.log(`Total Events: ${stats.totalEvents}`);
3231
+ console.log(`Vector Count: ${stats.vectorCount}`);
3232
+ console.log("\nMemory Levels:");
3233
+ for (const level of stats.levelStats) {
3234
+ const bar = "\u2588".repeat(Math.min(20, Math.ceil(level.count / 10)));
3235
+ console.log(` ${level.level}: ${bar} ${level.count}`);
3236
+ }
3237
+ await service.shutdown();
3238
+ } catch (error) {
3239
+ console.error("Stats failed:", error);
3240
+ process.exit(1);
3241
+ }
3242
+ });
3243
+ program.command("forget [eventId]").description("Remove memories from storage").option("--session <id>", "Forget all events from a session").option("--before <date>", "Forget events before date (YYYY-MM-DD)").option("--confirm", "Skip confirmation").action(async (eventId, options) => {
3244
+ const service = getDefaultMemoryService();
3245
+ try {
3246
+ if (!eventId && !options.session && !options.before) {
3247
+ console.error("Please specify an event ID, --session, or --before option");
3248
+ process.exit(1);
3249
+ }
3250
+ if (!options.confirm) {
3251
+ console.log("\u26A0\uFE0F This will remove memories from storage.");
3252
+ console.log("Add --confirm to proceed.");
3253
+ process.exit(0);
3254
+ }
3255
+ console.log("\u{1F5D1}\uFE0F Forget functionality requires additional implementation.");
3256
+ console.log("Events are append-only; soft-delete markers would be added.");
3257
+ await service.shutdown();
3258
+ } catch (error) {
3259
+ console.error("Forget failed:", error);
3260
+ process.exit(1);
3261
+ }
3262
+ });
3263
+ program.command("process").description("Process pending embeddings").action(async () => {
3264
+ const service = getDefaultMemoryService();
3265
+ try {
3266
+ console.log("\u23F3 Processing pending embeddings...");
3267
+ const count = await service.processPendingEmbeddings();
3268
+ console.log(`\u2705 Processed ${count} embeddings`);
3269
+ await service.shutdown();
3270
+ } catch (error) {
3271
+ console.error("Process failed:", error);
3272
+ process.exit(1);
3273
+ }
3274
+ });
3275
+ program.command("import").description("Import existing Claude Code conversation history").option("-p, --project <path>", "Import from specific project path").option("-s, --session <file>", "Import specific session file (JSONL)").option("-a, --all", "Import all sessions from all projects").option("-l, --limit <number>", "Limit messages per session").option("-v, --verbose", "Show detailed progress").action(async (options) => {
3276
+ const service = getDefaultMemoryService();
3277
+ const importer = createSessionHistoryImporter(service);
3278
+ try {
3279
+ await service.initialize();
3280
+ let result;
3281
+ if (options.session) {
3282
+ console.log(`
3283
+ \u{1F4E5} Importing session: ${options.session}
3284
+ `);
3285
+ result = await importer.importSessionFile(options.session, {
3286
+ projectPath: options.project,
3287
+ limit: options.limit ? parseInt(options.limit) : void 0,
3288
+ verbose: options.verbose
3289
+ });
3290
+ } else if (options.project) {
3291
+ console.log(`
3292
+ \u{1F4E5} Importing project: ${options.project}
3293
+ `);
3294
+ result = await importer.importProject(options.project, {
3295
+ limit: options.limit ? parseInt(options.limit) : void 0,
3296
+ verbose: options.verbose
3297
+ });
3298
+ } else if (options.all) {
3299
+ console.log("\n\u{1F4E5} Importing all sessions from all projects\n");
3300
+ result = await importer.importAll({
3301
+ limit: options.limit ? parseInt(options.limit) : void 0,
3302
+ verbose: options.verbose
3303
+ });
3304
+ } else {
3305
+ const cwd = process.cwd();
3306
+ console.log(`
3307
+ \u{1F4E5} Importing sessions for current project: ${cwd}
3308
+ `);
3309
+ result = await importer.importProject(cwd, {
3310
+ projectPath: cwd,
3311
+ limit: options.limit ? parseInt(options.limit) : void 0,
3312
+ verbose: options.verbose
3313
+ });
3314
+ }
3315
+ console.log("\n\u23F3 Processing embeddings...");
3316
+ const embedCount = await service.processPendingEmbeddings();
3317
+ console.log("\n\u2705 Import Complete\n");
3318
+ console.log(`Sessions processed: ${result.totalSessions}`);
3319
+ console.log(`Total messages: ${result.totalMessages}`);
3320
+ console.log(`Imported prompts: ${result.importedPrompts}`);
3321
+ console.log(`Imported responses: ${result.importedResponses}`);
3322
+ console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
3323
+ console.log(`Embeddings processed: ${embedCount}`);
3324
+ if (result.errors.length > 0) {
3325
+ console.log(`
3326
+ \u26A0\uFE0F Errors (${result.errors.length}):`);
3327
+ for (const error of result.errors.slice(0, 5)) {
3328
+ console.log(` - ${error}`);
3329
+ }
3330
+ if (result.errors.length > 5) {
3331
+ console.log(` ... and ${result.errors.length - 5} more`);
3332
+ }
3333
+ }
3334
+ await service.shutdown();
3335
+ } catch (error) {
3336
+ console.error("Import failed:", error);
3337
+ process.exit(1);
3338
+ }
3339
+ });
3340
+ program.command("list").description("List available Claude Code sessions").option("-p, --project <path>", "Filter by project path").action(async (options) => {
3341
+ const service = getDefaultMemoryService();
3342
+ const importer = createSessionHistoryImporter(service);
3343
+ try {
3344
+ const sessions = await importer.listAvailableSessions(options.project);
3345
+ console.log("\n\u{1F4CB} Available Sessions\n");
3346
+ console.log(`Found ${sessions.length} session(s)
3347
+ `);
3348
+ for (const session of sessions.slice(0, 20)) {
3349
+ const date = session.modifiedAt.toISOString().split("T")[0];
3350
+ const sizeKB = (session.size / 1024).toFixed(1);
3351
+ console.log(`\u{1F4DD} ${session.sessionId.slice(0, 16)}...`);
3352
+ console.log(` Modified: ${date}`);
3353
+ console.log(` Size: ${sizeKB} KB`);
3354
+ console.log(` Path: ${session.filePath}`);
3355
+ console.log("");
3356
+ }
3357
+ if (sessions.length > 20) {
3358
+ console.log(`... and ${sessions.length - 20} more sessions`);
3359
+ }
3360
+ console.log('\nUse "code-memory import --session <path>" to import a specific session');
3361
+ } catch (error) {
3362
+ console.error("List failed:", error);
3363
+ process.exit(1);
3364
+ }
3365
+ });
3366
+ var endlessCmd = program.command("endless").description("Manage Endless Mode (biomimetic continuous memory)");
3367
+ endlessCmd.command("enable").description("Enable Endless Mode").action(async () => {
3368
+ const service = getDefaultMemoryService();
3369
+ try {
3370
+ await service.initialize();
3371
+ await service.setMode("endless");
3372
+ console.log("\n\u267E\uFE0F Endless Mode Enabled\n");
3373
+ console.log("Your conversations will now be continuously integrated");
3374
+ console.log("across session boundaries.\n");
3375
+ console.log("Features:");
3376
+ console.log(" - Working Set: Recent context kept active");
3377
+ console.log(" - Consolidation: Automatic memory integration");
3378
+ console.log(" - Continuity: Seamless context transitions\n");
3379
+ console.log('Use "code-memory endless status" to view current state');
3380
+ await service.shutdown();
3381
+ } catch (error) {
3382
+ console.error("Enable failed:", error);
3383
+ process.exit(1);
3384
+ }
3385
+ });
3386
+ endlessCmd.command("disable").description("Disable Endless Mode (return to Session Mode)").action(async () => {
3387
+ const service = getDefaultMemoryService();
3388
+ try {
3389
+ await service.initialize();
3390
+ await service.setMode("session");
3391
+ console.log("\n\u{1F4CB} Session Mode Enabled\n");
3392
+ console.log("Returned to traditional session-based memory.");
3393
+ console.log("Existing Endless Mode data is preserved for future use.");
3394
+ await service.shutdown();
3395
+ } catch (error) {
3396
+ console.error("Disable failed:", error);
3397
+ process.exit(1);
3398
+ }
3399
+ });
3400
+ endlessCmd.command("status").description("Show Endless Mode status").action(async () => {
3401
+ const service = getDefaultMemoryService();
3402
+ try {
3403
+ await service.initialize();
3404
+ const status = await service.getEndlessModeStatus();
3405
+ const modeIcon = status.mode === "endless" ? "\u267E\uFE0F" : "\u{1F4CB}";
3406
+ const modeName = status.mode === "endless" ? "Endless Mode" : "Session Mode";
3407
+ console.log(`
3408
+ ${modeIcon} ${modeName}
3409
+ `);
3410
+ if (status.mode === "endless") {
3411
+ const continuityBars = "\u2588".repeat(Math.round(status.continuityScore * 10));
3412
+ const continuityEmpty = "\u2591".repeat(10 - Math.round(status.continuityScore * 10));
3413
+ console.log("\u{1F4CA} Status:");
3414
+ console.log(` Working Set: ${status.workingSetSize} events`);
3415
+ console.log(` Continuity: [${continuityBars}${continuityEmpty}] ${(status.continuityScore * 100).toFixed(0)}%`);
3416
+ console.log(` Consolidated: ${status.consolidatedCount} memories`);
3417
+ if (status.lastConsolidation) {
3418
+ const ago = Math.round((Date.now() - status.lastConsolidation.getTime()) / 6e4);
3419
+ console.log(` Last Consolidation: ${ago} minutes ago`);
3420
+ } else {
3421
+ console.log(" Last Consolidation: Never");
3422
+ }
3423
+ } else {
3424
+ console.log("Endless Mode is disabled.");
3425
+ console.log('Use "code-memory endless enable" to activate.');
3426
+ }
3427
+ await service.shutdown();
3428
+ } catch (error) {
3429
+ console.error("Status failed:", error);
3430
+ process.exit(1);
3431
+ }
3432
+ });
3433
+ endlessCmd.command("consolidate").description("Manually trigger memory consolidation").action(async () => {
3434
+ const service = getDefaultMemoryService();
3435
+ try {
3436
+ await service.initialize();
3437
+ if (!service.isEndlessModeActive()) {
3438
+ console.log("\n\u26A0\uFE0F Endless Mode is not active");
3439
+ console.log('Use "code-memory endless enable" first');
3440
+ process.exit(1);
3441
+ }
3442
+ console.log("\n\u23F3 Running memory consolidation...");
3443
+ const count = await service.forceConsolidation();
3444
+ if (count > 0) {
3445
+ console.log(`
3446
+ \u2705 Consolidated ${count} memory group(s)`);
3447
+ } else {
3448
+ console.log("\n\u{1F4CB} No memories to consolidate");
3449
+ console.log("(Working set may not have enough events yet)");
3450
+ }
3451
+ await service.shutdown();
3452
+ } catch (error) {
3453
+ console.error("Consolidation failed:", error);
3454
+ process.exit(1);
3455
+ }
3456
+ });
3457
+ endlessCmd.command("working-set").alias("ws").description("View current working set").option("-l, --limit <number>", "Number of events to show", "10").action(async (options) => {
3458
+ const service = getDefaultMemoryService();
3459
+ try {
3460
+ await service.initialize();
3461
+ if (!service.isEndlessModeActive()) {
3462
+ console.log("\n\u26A0\uFE0F Endless Mode is not active");
3463
+ console.log('Use "code-memory endless enable" first');
3464
+ process.exit(1);
3465
+ }
3466
+ const workingSet = await service.getWorkingSet();
3467
+ if (!workingSet || workingSet.recentEvents.length === 0) {
3468
+ console.log("\n\u{1F4CB} Working Set is empty");
3469
+ console.log("Events will be added as you interact with Claude");
3470
+ process.exit(0);
3471
+ }
3472
+ console.log("\n\u{1F9E0} Working Set\n");
3473
+ console.log(`Total events: ${workingSet.recentEvents.length}`);
3474
+ console.log(`Continuity score: ${(workingSet.continuityScore * 100).toFixed(0)}%`);
3475
+ console.log(`Last activity: ${workingSet.lastActivity.toISOString()}
3476
+ `);
3477
+ const limit = parseInt(options.limit);
3478
+ const events = workingSet.recentEvents.slice(0, limit);
3479
+ for (const event of events) {
3480
+ const icon = event.eventType === "user_prompt" ? "\u{1F464}" : event.eventType === "agent_response" ? "\u{1F916}" : event.eventType === "tool_observation" ? "\u{1F527}" : "\u{1F4DD}";
3481
+ const time = event.timestamp.toLocaleTimeString();
3482
+ const preview = event.content.slice(0, 80) + (event.content.length > 80 ? "..." : "");
3483
+ console.log(`${icon} [${time}] ${event.eventType}`);
3484
+ console.log(` ${preview}`);
3485
+ console.log("");
3486
+ }
3487
+ if (workingSet.recentEvents.length > limit) {
3488
+ console.log(`... and ${workingSet.recentEvents.length - limit} more events`);
3489
+ }
3490
+ await service.shutdown();
3491
+ } catch (error) {
3492
+ console.error("Working set failed:", error);
3493
+ process.exit(1);
3494
+ }
3495
+ });
3496
+ endlessCmd.command("memories").description("View consolidated memories").option("-l, --limit <number>", "Number of memories to show", "10").option("-q, --query <text>", "Search consolidated memories").action(async (options) => {
3497
+ const service = getDefaultMemoryService();
3498
+ try {
3499
+ await service.initialize();
3500
+ let memories;
3501
+ if (options.query) {
3502
+ memories = await service.searchConsolidated(options.query, {
3503
+ topK: parseInt(options.limit)
3504
+ });
3505
+ console.log(`
3506
+ \u{1F50D} Searching for: "${options.query}"
3507
+ `);
3508
+ } else {
3509
+ memories = await service.getConsolidatedMemories(parseInt(options.limit));
3510
+ console.log("\n\u{1F4BE} Consolidated Memories\n");
3511
+ }
3512
+ if (memories.length === 0) {
3513
+ console.log("No consolidated memories found.");
3514
+ if (!service.isEndlessModeActive()) {
3515
+ console.log("Enable Endless Mode to start consolidating memories.");
3516
+ }
3517
+ process.exit(0);
3518
+ }
3519
+ console.log(`Showing ${memories.length} memory(ies)
3520
+ `);
3521
+ for (const memory of memories) {
3522
+ const date = memory.createdAt.toISOString().split("T")[0];
3523
+ const confidenceBars = "\u2588".repeat(Math.round(memory.confidence * 5));
3524
+ console.log(`\u{1F4DA} ${memory.topics.slice(0, 3).join(", ")}`);
3525
+ console.log(` Created: ${date}`);
3526
+ console.log(` Confidence: [${confidenceBars}] ${(memory.confidence * 100).toFixed(0)}%`);
3527
+ console.log(` Sources: ${memory.sourceEvents.length} events`);
3528
+ console.log(` Access count: ${memory.accessCount}`);
3529
+ console.log(` Summary: ${memory.summary.slice(0, 200)}${memory.summary.length > 200 ? "..." : ""}`);
3530
+ console.log("");
3531
+ }
3532
+ await service.shutdown();
3533
+ } catch (error) {
3534
+ console.error("Memories failed:", error);
3535
+ process.exit(1);
3536
+ }
3537
+ });
3538
+ program.parse();
3539
+ //# sourceMappingURL=index.js.map