claude-memory-layer 1.0.0 → 1.0.1

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 (37) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.history/package_20260201114632.json +46 -0
  3. package/dist/cli/index.js +360 -154
  4. package/dist/cli/index.js.map +4 -4
  5. package/dist/core/index.js +337 -161
  6. package/dist/core/index.js.map +3 -3
  7. package/dist/hooks/session-end.js +320 -130
  8. package/dist/hooks/session-end.js.map +4 -4
  9. package/dist/hooks/session-start.js +331 -138
  10. package/dist/hooks/session-start.js.map +4 -4
  11. package/dist/hooks/stop.js +320 -130
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +320 -130
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/services/memory-service.js +349 -128
  16. package/dist/services/memory-service.js.map +4 -4
  17. package/package.json +2 -1
  18. package/src/cli/index.ts +84 -23
  19. package/src/core/consolidated-store.ts +33 -18
  20. package/src/core/continuity-manager.ts +12 -7
  21. package/src/core/db-wrapper.ts +112 -0
  22. package/src/core/edge-repo.ts +22 -13
  23. package/src/core/entity-repo.ts +23 -14
  24. package/src/core/event-store.ts +98 -72
  25. package/src/core/task/blocker-resolver.ts +17 -9
  26. package/src/core/task/task-matcher.ts +8 -6
  27. package/src/core/task/task-projector.ts +29 -16
  28. package/src/core/task/task-resolver.ts +17 -9
  29. package/src/core/vector-outbox.ts +29 -16
  30. package/src/core/vector-store.ts +23 -12
  31. package/src/core/vector-worker.ts +7 -4
  32. package/src/core/working-set-store.ts +31 -18
  33. package/src/hooks/session-end.ts +3 -2
  34. package/src/hooks/session-start.ts +12 -8
  35. package/src/hooks/stop.ts +3 -2
  36. package/src/hooks/user-prompt-submit.ts +3 -2
  37. package/src/services/memory-service.ts +158 -6
@@ -3,7 +3,7 @@
3
3
  * AXIOMMIND: Incremental processing with offset tracking
4
4
  */
5
5
 
6
- import { Database } from 'duckdb';
6
+ import { dbRun, dbAll, toDate, type Database } from '../db-wrapper.js';
7
7
  import { randomUUID } from 'crypto';
8
8
  import type { BlockerMode, BlockerRef } from '../types.js';
9
9
 
@@ -37,7 +37,8 @@ export class TaskProjector {
37
37
  * Get current projection offset
38
38
  */
39
39
  async getOffset(): Promise<ProjectionOffset> {
40
- const rows = await this.db.all<Array<Record<string, unknown>>>(
40
+ const rows = await dbAll<Record<string, unknown>>(
41
+ this.db,
41
42
  `SELECT last_event_id, last_timestamp
42
43
  FROM projection_offsets
43
44
  WHERE projection_name = ?`,
@@ -60,7 +61,8 @@ export class TaskProjector {
60
61
  * Update projection offset
61
62
  */
62
63
  private async updateOffset(eventId: string, timestamp: Date): Promise<void> {
63
- await this.db.run(
64
+ await dbRun(
65
+ this.db,
64
66
  `INSERT INTO projection_offsets (projection_name, last_event_id, last_timestamp, updated_at)
65
67
  VALUES (?, ?, ?, CURRENT_TIMESTAMP)
66
68
  ON CONFLICT (projection_name) DO UPDATE SET
@@ -97,13 +99,13 @@ export class TaskProjector {
97
99
  query += ` ORDER BY timestamp ASC, id ASC LIMIT ?`;
98
100
  params.push(limit);
99
101
 
100
- const rows = await this.db.all<Array<Record<string, unknown>>>(query, params);
102
+ const rows = await dbAll<Record<string, unknown>>(this.db, query, params);
101
103
 
102
104
  return rows.map(row => ({
103
105
  id: row.id as string,
104
106
  eventType: row.event_type as string,
105
107
  sessionId: row.session_id as string,
106
- timestamp: new Date(row.timestamp as string),
108
+ timestamp: toDate(row.timestamp),
107
109
  content: typeof row.content === 'string'
108
110
  ? JSON.parse(row.content)
109
111
  : row.content as Record<string, unknown>
@@ -184,14 +186,16 @@ export class TaskProjector {
184
186
 
185
187
  // If status changed to 'done', remove all blocked_by edges
186
188
  if (toStatus === 'done') {
187
- await this.db.run(
189
+ await dbRun(
190
+ this.db,
188
191
  `DELETE FROM edges
189
192
  WHERE src_id = ? AND rel_type IN ('blocked_by', 'blocked_by_suggested')`,
190
193
  [taskId]
191
194
  );
192
195
 
193
196
  // Clear blockers cache in entity
194
- await this.db.run(
197
+ await dbRun(
198
+ this.db,
195
199
  `UPDATE entities
196
200
  SET current_json = json_remove(json_remove(current_json, '$.blockers'), '$.blockerSuggestions'),
197
201
  updated_at = CURRENT_TIMESTAMP
@@ -213,7 +217,8 @@ export class TaskProjector {
213
217
 
214
218
  if (mode === 'replace') {
215
219
  // Delete existing blocked_by edges
216
- await this.db.run(
220
+ await dbRun(
221
+ this.db,
217
222
  `DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by'`,
218
223
  [taskId]
219
224
  );
@@ -225,7 +230,8 @@ export class TaskProjector {
225
230
 
226
231
  // Update entity cache
227
232
  const blockerIds = blockers.map(b => b.entityId);
228
- await this.db.run(
233
+ await dbRun(
234
+ this.db,
229
235
  `UPDATE entities
230
236
  SET current_json = json_set(current_json, '$.blockers', ?),
231
237
  updated_at = CURRENT_TIMESTAMP
@@ -236,7 +242,8 @@ export class TaskProjector {
236
242
  } else {
237
243
  // mode === 'suggest'
238
244
  // Delete existing suggested edges
239
- await this.db.run(
245
+ await dbRun(
246
+ this.db,
240
247
  `DELETE FROM edges WHERE src_id = ? AND rel_type = 'blocked_by_suggested'`,
241
248
  [taskId]
242
249
  );
@@ -248,7 +255,8 @@ export class TaskProjector {
248
255
 
249
256
  // Update entity cache (suggestions)
250
257
  const suggestionIds = blockers.map(b => b.entityId);
251
- await this.db.run(
258
+ await dbRun(
259
+ this.db,
252
260
  `UPDATE entities
253
261
  SET current_json = json_set(current_json, '$.blockerSuggestions', ?),
254
262
  updated_at = CURRENT_TIMESTAMP
@@ -268,7 +276,8 @@ export class TaskProjector {
268
276
  ): Promise<void> {
269
277
  const edgeId = randomUUID();
270
278
 
271
- await this.db.run(
279
+ await dbRun(
280
+ this.db,
272
281
  `INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json, created_at)
273
282
  VALUES (?, 'entity', ?, ?, 'entity', ?, ?, CURRENT_TIMESTAMP)
274
283
  ON CONFLICT DO NOTHING`,
@@ -297,7 +306,8 @@ export class TaskProjector {
297
306
  };
298
307
 
299
308
  // Update condition entity
300
- await this.db.run(
309
+ await dbRun(
310
+ this.db,
301
311
  `UPDATE entities
302
312
  SET current_json = json_set(json_set(current_json, '$.resolved', true), '$.resolvedTo', ?),
303
313
  updated_at = CURRENT_TIMESTAMP
@@ -315,7 +325,8 @@ export class TaskProjector {
315
325
  const jobId = randomUUID();
316
326
  const embeddingVersion = 'v1'; // Should come from config
317
327
 
318
- await this.db.run(
328
+ await dbRun(
329
+ this.db,
319
330
  `INSERT INTO vector_outbox (job_id, item_kind, item_id, embedding_version, status, retry_count)
320
331
  VALUES (?, ?, ?, ?, 'pending', 0)
321
332
  ON CONFLICT (item_kind, item_id, embedding_version) DO NOTHING`,
@@ -329,12 +340,14 @@ export class TaskProjector {
329
340
  */
330
341
  async rebuild(): Promise<number> {
331
342
  // Clear task-related edges
332
- await this.db.run(
343
+ await dbRun(
344
+ this.db,
333
345
  `DELETE FROM edges WHERE rel_type IN ('blocked_by', 'blocked_by_suggested', 'resolves_to')`
334
346
  );
335
347
 
336
348
  // Reset offset
337
- await this.db.run(
349
+ await dbRun(
350
+ this.db,
338
351
  `DELETE FROM projection_offsets WHERE projection_name = ?`,
339
352
  [PROJECTOR_NAME]
340
353
  );
@@ -3,7 +3,7 @@
3
3
  * AXIOMMIND: Task state via event fold, no direct updates
4
4
  */
5
5
 
6
- import { Database } from 'duckdb';
6
+ import { dbRun, dbAll, type Database } from '../db-wrapper.js';
7
7
  import { randomUUID } from 'crypto';
8
8
  import type {
9
9
  Entity,
@@ -154,7 +154,8 @@ export class TaskResolver {
154
154
  };
155
155
 
156
156
  // Insert entity
157
- await this.db.run(
157
+ await dbRun(
158
+ this.db,
158
159
  `INSERT INTO entities (
159
160
  entity_id, entity_type, canonical_key, title, stage, status,
160
161
  current_json, title_norm, search_text, created_at, updated_at
@@ -175,7 +176,8 @@ export class TaskResolver {
175
176
  );
176
177
 
177
178
  // Create alias
178
- await this.db.run(
179
+ await dbRun(
180
+ this.db,
179
181
  `INSERT INTO entity_aliases (entity_type, canonical_key, entity_id, is_primary)
180
182
  VALUES (?, ?, ?, TRUE)
181
183
  ON CONFLICT (entity_type, canonical_key) DO NOTHING`,
@@ -245,7 +247,8 @@ export class TaskResolver {
245
247
  });
246
248
 
247
249
  // Update entity (projector will do this, but we update for immediate consistency)
248
- await this.db.run(
250
+ await dbRun(
251
+ this.db,
249
252
  `UPDATE entities
250
253
  SET current_json = json_set(current_json, '$.status', ?),
251
254
  updated_at = ?
@@ -278,7 +281,8 @@ export class TaskResolver {
278
281
  });
279
282
 
280
283
  // Update entity
281
- await this.db.run(
284
+ await dbRun(
285
+ this.db,
282
286
  `UPDATE entities
283
287
  SET current_json = json_set(current_json, '$.priority', ?),
284
288
  updated_at = ?
@@ -348,7 +352,8 @@ export class TaskResolver {
348
352
  );
349
353
 
350
354
  // Check for duplicate
351
- const existing = await this.db.all<Array<{ event_id: string }>>(
355
+ const existing = await dbAll<{ event_id: string }>(
356
+ this.db,
352
357
  `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
353
358
  [dedupeKey]
354
359
  );
@@ -358,7 +363,8 @@ export class TaskResolver {
358
363
  }
359
364
 
360
365
  // Insert event
361
- await this.db.run(
366
+ await dbRun(
367
+ this.db,
362
368
  `INSERT INTO events (
363
369
  id, event_type, session_id, timestamp, content,
364
370
  canonical_key, dedupe_key, metadata
@@ -376,7 +382,8 @@ export class TaskResolver {
376
382
  );
377
383
 
378
384
  // Insert dedup record
379
- await this.db.run(
385
+ await dbRun(
386
+ this.db,
380
387
  `INSERT INTO event_dedup (dedupe_key, event_id)
381
388
  VALUES (?, ?)
382
389
  ON CONFLICT DO NOTHING`,
@@ -402,7 +409,8 @@ export class TaskResolver {
402
409
  });
403
410
 
404
411
  // Create resolves_to edge
405
- await this.db.run(
412
+ await dbRun(
413
+ this.db,
406
414
  `INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
407
415
  VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
408
416
  ON CONFLICT DO NOTHING`,
@@ -3,7 +3,7 @@
3
3
  * AXIOMMIND Principle 6: DuckDB → outbox → LanceDB unidirectional flow
4
4
  */
5
5
 
6
- import { Database } from 'duckdb';
6
+ import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
7
7
  import { randomUUID } from 'crypto';
8
8
  import type {
9
9
  OutboxJob,
@@ -56,7 +56,8 @@ export class VectorOutbox {
56
56
  const jobId = randomUUID();
57
57
  const now = new Date().toISOString();
58
58
 
59
- await this.db.run(
59
+ await dbRun(
60
+ this.db,
60
61
  `INSERT INTO vector_outbox (
61
62
  job_id, item_kind, item_id, embedding_version, status, retry_count, created_at, updated_at
62
63
  ) VALUES (?, ?, ?, ?, 'pending', 0, ?, ?)
@@ -74,7 +75,8 @@ export class VectorOutbox {
74
75
  const now = new Date().toISOString();
75
76
 
76
77
  // Atomic claim using UPDATE RETURNING
77
- const rows = await this.db.all<Array<Record<string, unknown>>>(
78
+ const rows = await dbAll<Record<string, unknown>>(
79
+ this.db,
78
80
  `UPDATE vector_outbox
79
81
  SET status = 'processing', updated_at = ?
80
82
  WHERE job_id IN (
@@ -94,7 +96,8 @@ export class VectorOutbox {
94
96
  * Mark job as done
95
97
  */
96
98
  async markDone(jobId: string): Promise<void> {
97
- await this.db.run(
99
+ await dbRun(
100
+ this.db,
98
101
  `UPDATE vector_outbox
99
102
  SET status = 'done', updated_at = ?
100
103
  WHERE job_id = ?`,
@@ -109,7 +112,8 @@ export class VectorOutbox {
109
112
  const now = new Date().toISOString();
110
113
 
111
114
  // Check retry count
112
- const rows = await this.db.all<Array<{ retry_count: number }>>(
115
+ const rows = await dbAll<{ retry_count: number }>(
116
+ this.db,
113
117
  `SELECT retry_count FROM vector_outbox WHERE job_id = ?`,
114
118
  [jobId]
115
119
  );
@@ -121,7 +125,8 @@ export class VectorOutbox {
121
125
  ? 'failed'
122
126
  : 'pending'; // Will retry
123
127
 
124
- await this.db.run(
128
+ await dbRun(
129
+ this.db,
125
130
  `UPDATE vector_outbox
126
131
  SET status = ?, error = ?, retry_count = retry_count + 1, updated_at = ?
127
132
  WHERE job_id = ?`,
@@ -133,7 +138,8 @@ export class VectorOutbox {
133
138
  * Get job by ID
134
139
  */
135
140
  async getJob(jobId: string): Promise<OutboxJob | null> {
136
- const rows = await this.db.all<Array<Record<string, unknown>>>(
141
+ const rows = await dbAll<Record<string, unknown>>(
142
+ this.db,
137
143
  `SELECT * FROM vector_outbox WHERE job_id = ?`,
138
144
  [jobId]
139
145
  );
@@ -146,7 +152,8 @@ export class VectorOutbox {
146
152
  * Get jobs by status
147
153
  */
148
154
  async getJobsByStatus(status: OutboxStatus, limit: number = 100): Promise<OutboxJob[]> {
149
- const rows = await this.db.all<Array<Record<string, unknown>>>(
155
+ const rows = await dbAll<Record<string, unknown>>(
156
+ this.db,
150
157
  `SELECT * FROM vector_outbox
151
158
  WHERE status = ?
152
159
  ORDER BY created_at ASC
@@ -165,7 +172,8 @@ export class VectorOutbox {
165
172
  const stuckThreshold = new Date(now.getTime() - this.config.stuckThresholdMs);
166
173
 
167
174
  // Recover stuck processing jobs
168
- const recoveredResult = await this.db.run(
175
+ await dbRun(
176
+ this.db,
169
177
  `UPDATE vector_outbox
170
178
  SET status = 'pending', updated_at = ?
171
179
  WHERE status = 'processing'
@@ -174,7 +182,8 @@ export class VectorOutbox {
174
182
  );
175
183
 
176
184
  // Retry failed jobs that haven't exceeded max retries
177
- const retriedResult = await this.db.run(
185
+ await dbRun(
186
+ this.db,
178
187
  `UPDATE vector_outbox
179
188
  SET status = 'pending', updated_at = ?
180
189
  WHERE status = 'failed'
@@ -183,7 +192,8 @@ export class VectorOutbox {
183
192
  );
184
193
 
185
194
  // Get counts (DuckDB doesn't return affected rows easily)
186
- const recoveredRows = await this.db.all<Array<{ count: number }>>(
195
+ const recoveredRows = await dbAll<{ count: number }>(
196
+ this.db,
187
197
  `SELECT COUNT(*) as count FROM vector_outbox
188
198
  WHERE status = 'pending' AND updated_at = ?`,
189
199
  [now.toISOString()]
@@ -202,7 +212,8 @@ export class VectorOutbox {
202
212
  const threshold = new Date();
203
213
  threshold.setDate(threshold.getDate() - this.config.cleanupDays);
204
214
 
205
- await this.db.run(
215
+ await dbRun(
216
+ this.db,
206
217
  `DELETE FROM vector_outbox
207
218
  WHERE status = 'done'
208
219
  AND updated_at < ?`,
@@ -216,13 +227,15 @@ export class VectorOutbox {
216
227
  * Get metrics
217
228
  */
218
229
  async getMetrics(): Promise<OutboxMetrics> {
219
- const statusCounts = await this.db.all<Array<{ status: string; count: number }>>(
230
+ const statusCounts = await dbAll<{ status: string; count: number }>(
231
+ this.db,
220
232
  `SELECT status, COUNT(*) as count
221
233
  FROM vector_outbox
222
234
  GROUP BY status`
223
235
  );
224
236
 
225
- const oldestPending = await this.db.all<Array<{ created_at: string }>>(
237
+ const oldestPending = await dbAll<{ created_at: string }>(
238
+ this.db,
226
239
  `SELECT created_at FROM vector_outbox
227
240
  WHERE status = 'pending'
228
241
  ORDER BY created_at ASC
@@ -288,8 +301,8 @@ export class VectorOutbox {
288
301
  status: row.status as OutboxStatus,
289
302
  retryCount: row.retry_count as number,
290
303
  error: row.error as string | undefined,
291
- createdAt: new Date(row.created_at as string),
292
- updatedAt: new Date(row.updated_at as string)
304
+ createdAt: toDate(row.created_at),
305
+ updatedAt: toDate(row.updated_at)
293
306
  };
294
307
  }
295
308
  }
@@ -121,7 +121,11 @@ export class VectorStore {
121
121
 
122
122
  const { limit = 5, minScore = 0.7, sessionId } = options;
123
123
 
124
- let query = this.table.search(queryVector).limit(limit * 2); // Get more for filtering
124
+ // Use cosine distance for semantic similarity
125
+ let query = this.table
126
+ .search(queryVector)
127
+ .distanceType('cosine')
128
+ .limit(limit * 2); // Get more for filtering
125
129
 
126
130
  // Apply session filter if specified
127
131
  if (sessionId) {
@@ -132,20 +136,27 @@ export class VectorStore {
132
136
 
133
137
  return results
134
138
  .filter(r => {
135
- // Convert distance to score (assuming cosine distance)
136
- const score = 1 - (r._distance || 0);
139
+ // Convert cosine distance to similarity score
140
+ // Cosine distance ranges from 0 (identical) to 2 (opposite)
141
+ // Score = 1 - (distance / 2) gives range [0, 1]
142
+ const distance = r._distance || 0;
143
+ const score = 1 - (distance / 2);
137
144
  return score >= minScore;
138
145
  })
139
146
  .slice(0, limit)
140
- .map(r => ({
141
- id: r.id as string,
142
- eventId: r.eventId as string,
143
- content: r.content as string,
144
- score: 1 - (r._distance || 0),
145
- sessionId: r.sessionId as string,
146
- eventType: r.eventType as string,
147
- timestamp: r.timestamp as string
148
- }));
147
+ .map(r => {
148
+ const distance = r._distance || 0;
149
+ const score = 1 - (distance / 2);
150
+ return {
151
+ id: r.id as string,
152
+ eventId: r.eventId as string,
153
+ content: r.content as string,
154
+ score,
155
+ sessionId: r.sessionId as string,
156
+ eventType: r.eventType as string,
157
+ timestamp: r.timestamp as string
158
+ };
159
+ });
149
160
  }
150
161
 
151
162
  /**
@@ -186,7 +186,7 @@ export function createVectorWorker(
186
186
  // Vector Worker V2 - Extended for Task Entity System
187
187
  // ============================================================
188
188
 
189
- import { Database } from 'duckdb';
189
+ import { dbAll, type Database } from './db-wrapper.js';
190
190
  import { VectorOutbox } from './vector-outbox.js';
191
191
  import type { OutboxJob, OutboxItemKind } from './types.js';
192
192
 
@@ -240,7 +240,8 @@ export class DefaultContentProvider implements ContentProvider {
240
240
  content: string;
241
241
  metadata: Record<string, unknown>;
242
242
  } | null> {
243
- const rows = await this.db.all<Array<Record<string, unknown>>>(
243
+ const rows = await dbAll<Record<string, unknown>>(
244
+ this.db,
244
245
  `SELECT title, content_json, entry_type FROM entries WHERE entry_id = ?`,
245
246
  [entryId]
246
247
  );
@@ -265,7 +266,8 @@ export class DefaultContentProvider implements ContentProvider {
265
266
  content: string;
266
267
  metadata: Record<string, unknown>;
267
268
  } | null> {
268
- const rows = await this.db.all<Array<Record<string, unknown>>>(
269
+ const rows = await dbAll<Record<string, unknown>>(
270
+ this.db,
269
271
  `SELECT title, search_text, current_json FROM entities
270
272
  WHERE entity_id = ? AND entity_type = 'task'`,
271
273
  [taskId]
@@ -287,7 +289,8 @@ export class DefaultContentProvider implements ContentProvider {
287
289
  content: string;
288
290
  metadata: Record<string, unknown>;
289
291
  } | null> {
290
- const rows = await this.db.all<Array<Record<string, unknown>>>(
292
+ const rows = await dbAll<Record<string, unknown>>(
293
+ this.db,
291
294
  `SELECT content, event_type, session_id FROM events WHERE id = ?`,
292
295
  [eventId]
293
296
  );
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { randomUUID } from 'crypto';
8
- import { Database } from 'duckdb';
8
+ import { dbRun, dbAll, toDate, type Database } from './db-wrapper.js';
9
9
  import type {
10
10
  MemoryEvent,
11
11
  EndlessModeConfig,
@@ -32,7 +32,8 @@ export class WorkingSetStore {
32
32
  Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1000
33
33
  );
34
34
 
35
- await this.db.run(
35
+ await dbRun(
36
+ this.db,
36
37
  `INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
37
38
  VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
38
39
  [
@@ -56,7 +57,8 @@ export class WorkingSetStore {
56
57
  await this.cleanup();
57
58
 
58
59
  // Get working set items with their events
59
- const rows = await this.db.all<Array<Record<string, unknown>>>(
60
+ const rows = await dbAll<Record<string, unknown>>(
61
+ this.db,
60
62
  `SELECT ws.*, e.*
61
63
  FROM working_set ws
62
64
  JOIN events e ON ws.event_id = e.id
@@ -69,7 +71,7 @@ export class WorkingSetStore {
69
71
  id: row.id as string,
70
72
  eventType: row.event_type as 'user_prompt' | 'agent_response' | 'session_summary' | 'tool_observation',
71
73
  sessionId: row.session_id as string,
72
- timestamp: new Date(row.timestamp as string),
74
+ timestamp: toDate(row.timestamp),
73
75
  content: row.content as string,
74
76
  canonicalKey: row.canonical_key as string,
75
77
  dedupeKey: row.dedupe_key as string,
@@ -87,17 +89,18 @@ export class WorkingSetStore {
87
89
  * Get working set items (metadata only)
88
90
  */
89
91
  async getItems(): Promise<WorkingSetItem[]> {
90
- const rows = await this.db.all<Array<Record<string, unknown>>>(
92
+ const rows = await dbAll<Record<string, unknown>>(
93
+ this.db,
91
94
  `SELECT * FROM working_set ORDER BY relevance_score DESC, added_at DESC`
92
95
  );
93
96
 
94
97
  return rows.map(row => ({
95
98
  id: row.id as string,
96
99
  eventId: row.event_id as string,
97
- addedAt: new Date(row.added_at as string),
100
+ addedAt: toDate(row.added_at),
98
101
  relevanceScore: row.relevance_score as number,
99
102
  topics: row.topics ? JSON.parse(row.topics as string) : undefined,
100
- expiresAt: new Date(row.expires_at as string)
103
+ expiresAt: toDate(row.expires_at)
101
104
  }));
102
105
  }
103
106
 
@@ -105,7 +108,8 @@ export class WorkingSetStore {
105
108
  * Update relevance score for an event
106
109
  */
107
110
  async updateRelevance(eventId: string, score: number): Promise<void> {
108
- await this.db.run(
111
+ await dbRun(
112
+ this.db,
109
113
  `UPDATE working_set SET relevance_score = ? WHERE event_id = ?`,
110
114
  [score, eventId]
111
115
  );
@@ -118,7 +122,8 @@ export class WorkingSetStore {
118
122
  if (eventIds.length === 0) return;
119
123
 
120
124
  const placeholders = eventIds.map(() => '?').join(',');
121
- await this.db.run(
125
+ await dbRun(
126
+ this.db,
122
127
  `DELETE FROM working_set WHERE event_id IN (${placeholders})`,
123
128
  eventIds
124
129
  );
@@ -128,7 +133,8 @@ export class WorkingSetStore {
128
133
  * Get the count of items in working set
129
134
  */
130
135
  async count(): Promise<number> {
131
- const result = await this.db.all<Array<{ count: number }>>(
136
+ const result = await dbAll<{ count: number }>(
137
+ this.db,
132
138
  `SELECT COUNT(*) as count FROM working_set`
133
139
  );
134
140
  return result[0]?.count || 0;
@@ -138,14 +144,15 @@ export class WorkingSetStore {
138
144
  * Clear the entire working set
139
145
  */
140
146
  async clear(): Promise<void> {
141
- await this.db.run(`DELETE FROM working_set`);
147
+ await dbRun(this.db, `DELETE FROM working_set`);
142
148
  }
143
149
 
144
150
  /**
145
151
  * Check if an event is in the working set
146
152
  */
147
153
  async contains(eventId: string): Promise<boolean> {
148
- const result = await this.db.all<Array<{ count: number }>>(
154
+ const result = await dbAll<{ count: number }>(
155
+ this.db,
149
156
  `SELECT COUNT(*) as count FROM working_set WHERE event_id = ?`,
150
157
  [eventId]
151
158
  );
@@ -160,7 +167,8 @@ export class WorkingSetStore {
160
167
  Date.now() + this.config.workingSet.timeWindowHours * 60 * 60 * 1000
161
168
  );
162
169
 
163
- await this.db.run(
170
+ await dbRun(
171
+ this.db,
164
172
  `UPDATE working_set SET expires_at = ? WHERE event_id = ?`,
165
173
  [newExpiresAt.toISOString(), eventId]
166
174
  );
@@ -170,7 +178,8 @@ export class WorkingSetStore {
170
178
  * Clean up expired items
171
179
  */
172
180
  private async cleanup(): Promise<void> {
173
- await this.db.run(
181
+ await dbRun(
182
+ this.db,
174
183
  `DELETE FROM working_set WHERE expires_at < datetime('now')`
175
184
  );
176
185
  }
@@ -183,7 +192,8 @@ export class WorkingSetStore {
183
192
  const maxEvents = this.config.workingSet.maxEvents;
184
193
 
185
194
  // Get IDs to keep (highest relevance, most recent)
186
- const keepIds = await this.db.all<Array<{ id: string }>>(
195
+ const keepIds = await dbAll<{ id: string }>(
196
+ this.db,
187
197
  `SELECT id FROM working_set
188
198
  ORDER BY relevance_score DESC, added_at DESC
189
199
  LIMIT ?`,
@@ -196,7 +206,8 @@ export class WorkingSetStore {
196
206
  const placeholders = keepIdList.map(() => '?').join(',');
197
207
 
198
208
  // Delete everything not in the keep list
199
- await this.db.run(
209
+ await dbRun(
210
+ this.db,
200
211
  `DELETE FROM working_set WHERE id NOT IN (${placeholders})`,
201
212
  keepIdList
202
213
  );
@@ -206,7 +217,8 @@ export class WorkingSetStore {
206
217
  * Calculate continuity score based on recent context transitions
207
218
  */
208
219
  private async calculateContinuityScore(): Promise<number> {
209
- const result = await this.db.all<Array<{ avg_score: number | null }>>(
220
+ const result = await dbAll<{ avg_score: number | null }>(
221
+ this.db,
210
222
  `SELECT AVG(continuity_score) as avg_score
211
223
  FROM continuity_log
212
224
  WHERE created_at > datetime('now', '-1 hour')`
@@ -219,7 +231,8 @@ export class WorkingSetStore {
219
231
  * Get topics from current working set for context matching
220
232
  */
221
233
  async getActiveTopics(): Promise<string[]> {
222
- const rows = await this.db.all<Array<{ topics: string }>>(
234
+ const rows = await dbAll<{ topics: string }>(
235
+ this.db,
223
236
  `SELECT topics FROM working_set WHERE topics IS NOT NULL`
224
237
  );
225
238
 
@@ -4,7 +4,7 @@
4
4
  * Called when session ends - generates and stores session summary
5
5
  */
6
6
 
7
- import { getDefaultMemoryService } from '../services/memory-service.js';
7
+ import { getMemoryServiceForSession } from '../services/memory-service.js';
8
8
  import type { SessionEndInput } from '../core/types.js';
9
9
 
10
10
  async function main(): Promise<void> {
@@ -12,7 +12,8 @@ async function main(): Promise<void> {
12
12
  const inputData = await readStdin();
13
13
  const input: SessionEndInput = JSON.parse(inputData);
14
14
 
15
- const memoryService = getDefaultMemoryService();
15
+ // Get project-specific memory service via session lookup
16
+ const memoryService = getMemoryServiceForSession(input.session_id);
16
17
 
17
18
  try {
18
19
  // Get session history