@membank/core 0.7.0 → 0.9.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.
package/dist/index.cjs CHANGED
@@ -33,6 +33,20 @@ let _huggingface_transformers = require("@huggingface/transformers");
33
33
  let node_crypto = require("node:crypto");
34
34
  let node_child_process = require("node:child_process");
35
35
  let node_util = require("node:util");
36
+ //#region src/config/loader.ts
37
+ function loadConfig() {
38
+ const configPath = (0, node_path.join)((0, node_os.homedir)(), ".membank", "config.json");
39
+ try {
40
+ const raw = (0, node_fs.readFileSync)(configPath, "utf8");
41
+ return JSON.parse(raw);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ function isSynthesisEnabled() {
47
+ return loadConfig()?.synthesis?.enabled === true;
48
+ }
49
+ //#endregion
36
50
  //#region src/db/errors.ts
37
51
  var MembankError = class extends Error {
38
52
  constructor(message, options) {
@@ -49,7 +63,8 @@ var DatabaseError = class extends MembankError {
49
63
  //#endregion
50
64
  //#region src/db/manager.ts
51
65
  const DEFAULT_DB_PATH = (0, node_path.join)((0, node_os.homedir)(), ".membank", "memory.db");
52
- const MIGRATIONS$1 = [[1, `
66
+ const MIGRATIONS$1 = [
67
+ [1, `
53
68
  CREATE TABLE IF NOT EXISTS memories (
54
69
  id TEXT PRIMARY KEY,
55
70
  content TEXT NOT NULL,
@@ -67,7 +82,8 @@ CREATE TABLE IF NOT EXISTS memories (
67
82
  CREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(
68
83
  embedding FLOAT[384]
69
84
  );
70
- `], [2, `
85
+ `],
86
+ [2, `
71
87
  CREATE TABLE IF NOT EXISTS projects (
72
88
  id TEXT PRIMARY KEY,
73
89
  name TEXT NOT NULL,
@@ -100,7 +116,46 @@ JOIN projects p ON p.scope_hash = m.scope
100
116
  WHERE m.scope != 'global';
101
117
 
102
118
  ALTER TABLE memories DROP COLUMN scope;
103
- `]];
119
+ `],
120
+ [3, `
121
+ CREATE TABLE IF NOT EXISTS memory_review_events (
122
+ id TEXT PRIMARY KEY,
123
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
124
+ conflicting_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
125
+ similarity REAL NOT NULL,
126
+ conflict_content_snapshot TEXT NOT NULL,
127
+ reason TEXT NOT NULL,
128
+ created_at TEXT NOT NULL,
129
+ resolved_at TEXT
130
+ );
131
+
132
+ CREATE INDEX IF NOT EXISTS idx_review_events_memory_open
133
+ ON memory_review_events(memory_id) WHERE resolved_at IS NULL;
134
+
135
+ ALTER TABLE memories DROP COLUMN needs_review;
136
+ `],
137
+ [4, `
138
+ CREATE TABLE IF NOT EXISTS syntheses (
139
+ id TEXT PRIMARY KEY,
140
+ scope TEXT NOT NULL,
141
+ content TEXT NOT NULL,
142
+ source_memory_hash TEXT NOT NULL,
143
+ synthesized_at TEXT NOT NULL,
144
+ expires_at TEXT NOT NULL,
145
+ in_flight_since TEXT,
146
+ created_at TEXT NOT NULL,
147
+ updated_at TEXT NOT NULL,
148
+ UNIQUE(scope),
149
+ CHECK(expires_at > synthesized_at)
150
+ );
151
+
152
+ CREATE INDEX IF NOT EXISTS idx_syntheses_expires_at
153
+ ON syntheses(expires_at);
154
+
155
+ CREATE INDEX IF NOT EXISTS idx_syntheses_scope_inflight
156
+ ON syntheses(scope) WHERE in_flight_since IS NOT NULL;
157
+ `]
158
+ ];
104
159
  var DatabaseManager = class DatabaseManager {
105
160
  #db;
106
161
  constructor(db) {
@@ -174,6 +229,27 @@ const ProjectSchema = zod.z.object({
174
229
  createdAt: zod.z.string(),
175
230
  updatedAt: zod.z.string()
176
231
  });
232
+ const ReviewReasonSchema = zod.z.enum(["similarity_dedup"]);
233
+ const ReviewEventSchema = zod.z.object({
234
+ id: zod.z.string(),
235
+ memoryId: zod.z.string(),
236
+ conflictingMemoryId: zod.z.string().nullable(),
237
+ similarity: zod.z.number(),
238
+ conflictContentSnapshot: zod.z.string(),
239
+ reason: ReviewReasonSchema,
240
+ createdAt: zod.z.string(),
241
+ resolvedAt: zod.z.string().nullable()
242
+ });
243
+ const ReviewEventRowSchema = zod.z.object({
244
+ id: zod.z.string(),
245
+ memory_id: zod.z.string(),
246
+ conflicting_memory_id: zod.z.string().nullable(),
247
+ similarity: zod.z.number(),
248
+ conflict_content_snapshot: zod.z.string(),
249
+ reason: ReviewReasonSchema,
250
+ created_at: zod.z.string(),
251
+ resolved_at: zod.z.string().nullable()
252
+ });
177
253
  const MemorySchema = zod.z.object({
178
254
  id: zod.z.string(),
179
255
  content: zod.z.string(),
@@ -183,7 +259,7 @@ const MemorySchema = zod.z.object({
183
259
  sourceHarness: zod.z.string().nullable(),
184
260
  accessCount: zod.z.number().int().nonnegative(),
185
261
  pinned: zod.z.boolean(),
186
- needsReview: zod.z.boolean(),
262
+ reviewEvents: zod.z.array(ReviewEventSchema),
187
263
  createdAt: zod.z.string(),
188
264
  updatedAt: zod.z.string()
189
265
  });
@@ -206,12 +282,25 @@ const SaveOptionsSchema = zod.z.object({
206
282
  });
207
283
  const MemoryPatchSchema = zod.z.object({
208
284
  content: zod.z.string().min(1).optional(),
209
- tags: zod.z.array(zod.z.string()).optional()
285
+ tags: zod.z.array(zod.z.string()).optional(),
286
+ type: MemoryTypeSchema.optional()
287
+ });
288
+ const SynthesisSchema = zod.z.object({
289
+ id: zod.z.string(),
290
+ scope: zod.z.string(),
291
+ content: zod.z.string(),
292
+ sourceMemoryHash: zod.z.string(),
293
+ synthesizedAt: zod.z.string(),
294
+ expiresAt: zod.z.string(),
295
+ inFlightSince: zod.z.string().nullable(),
296
+ createdAt: zod.z.string(),
297
+ updatedAt: zod.z.string()
210
298
  });
211
299
  const SessionContextSchema = zod.z.object({
212
300
  stats: zod.z.record(MemoryTypeSchema, zod.z.number()),
213
301
  pinnedGlobal: zod.z.array(MemorySchema),
214
- pinnedProject: zod.z.array(MemorySchema)
302
+ pinnedProject: zod.z.array(MemorySchema),
303
+ synthesis: zod.z.string().optional()
215
304
  });
216
305
  const MemoryRowSchema = zod.z.object({
217
306
  id: zod.z.string(),
@@ -221,7 +310,6 @@ const MemoryRowSchema = zod.z.object({
221
310
  source: zod.z.string().nullable(),
222
311
  access_count: zod.z.number(),
223
312
  pinned: zod.z.number(),
224
- needs_review: zod.z.number(),
225
313
  created_at: zod.z.string(),
226
314
  updated_at: zod.z.string()
227
315
  });
@@ -234,7 +322,7 @@ const ProjectRowSchema = zod.z.object({
234
322
  });
235
323
  //#endregion
236
324
  //#region src/db/row-types.ts
237
- function rowToMemory(row, projects) {
325
+ function rowToMemory(row, projects, reviewEvents = []) {
238
326
  return {
239
327
  id: row.id,
240
328
  content: row.content,
@@ -244,11 +332,24 @@ function rowToMemory(row, projects) {
244
332
  sourceHarness: row.source,
245
333
  accessCount: row.access_count,
246
334
  pinned: row.pinned !== 0,
247
- needsReview: row.needs_review !== 0,
335
+ reviewEvents,
248
336
  createdAt: row.created_at,
249
337
  updatedAt: row.updated_at
250
338
  };
251
339
  }
340
+ function rowToReviewEvent(row) {
341
+ const parsed = ReviewEventRowSchema.parse(row);
342
+ return {
343
+ id: parsed.id,
344
+ memoryId: parsed.memory_id,
345
+ conflictingMemoryId: parsed.conflicting_memory_id,
346
+ similarity: parsed.similarity,
347
+ conflictContentSnapshot: parsed.conflict_content_snapshot,
348
+ reason: parsed.reason,
349
+ createdAt: parsed.created_at,
350
+ resolvedAt: parsed.resolved_at
351
+ };
352
+ }
252
353
  function rowToProject(row) {
253
354
  return {
254
355
  id: row.id,
@@ -285,6 +386,7 @@ var EmbeddingService = class {
285
386
  };
286
387
  //#endregion
287
388
  //#region src/memory/repository.ts
389
+ const PIN_BUDGET_THRESHOLD = 8e3;
288
390
  var MemoryRepository = class {
289
391
  #db;
290
392
  #embedding;
@@ -314,21 +416,26 @@ var MemoryRepository = class {
314
416
  if (top !== void 0 && top.similarity > .92) {
315
417
  this.#db.db.prepare(`UPDATE memories SET content = ?, updated_at = ? WHERE id = ?`).run(content, now, top.id);
316
418
  this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, top.rowid);
317
- return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(top.id)), this.#projects.getProjectsForMemories([top.id]).get(top.id) ?? []);
419
+ const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(top.id));
420
+ const projectMap = this.#projects.getProjectsForMemories([top.id]);
421
+ const events = this.#getEventsForMemories([top.id]);
422
+ return rowToMemory(updated, projectMap.get(top.id) ?? [], events.get(top.id) ?? []);
318
423
  }
319
- if (top !== void 0 && top.similarity >= .75) this.#db.db.prepare(`UPDATE memories SET needs_review = 1 WHERE id = ?`).run(top.id);
320
424
  const id = (0, node_crypto.randomUUID)();
321
- this.#db.db.prepare(`INSERT INTO memories (id, content, type, tags, source, access_count, pinned, needs_review, created_at, updated_at)
322
- VALUES (?, ?, ?, ?, ?, 0, 0, 0, ?, ?)`).run(id, content, type, JSON.stringify(tags), sourceHarness ?? null, now, now);
425
+ this.#db.db.prepare(`INSERT INTO memories (id, content, type, tags, source, access_count, pinned, created_at, updated_at)
426
+ VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)`).run(id, content, type, JSON.stringify(tags), sourceHarness ?? null, now, now);
427
+ if (top !== void 0 && top.similarity >= .75) this.#db.db.prepare(`INSERT INTO memory_review_events
428
+ (id, memory_id, conflicting_memory_id, similarity, conflict_content_snapshot, reason, created_at)
429
+ VALUES (?, ?, ?, ?, ?, 'similarity_dedup', ?)`).run((0, node_crypto.randomUUID)(), top.id, id, top.similarity, content, now);
323
430
  this.#db.db.prepare(`INSERT INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`).run(embeddingBlob, id);
324
431
  if (projectScope !== void 0) {
325
432
  const project = this.#projects.upsertByHash(projectScope.hash, projectScope.name);
326
433
  this.#projects.addAssociation(id, project.id);
327
434
  }
328
- return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? []);
435
+ return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? [], []);
329
436
  }
330
437
  async update(id, patch) {
331
- const { content, tags } = MemoryPatchSchema.parse(patch);
438
+ const { content, tags, type } = MemoryPatchSchema.parse(patch);
332
439
  const existing = this.#db.db.prepare(`SELECT m.rowid, m.* FROM memories m WHERE m.id = ?`).get(id);
333
440
  if (existing === void 0) throw new Error(`Memory not found: ${id}`);
334
441
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -342,6 +449,10 @@ var MemoryRepository = class {
342
449
  sets.push("tags = ?");
343
450
  values.push(JSON.stringify(tags));
344
451
  }
452
+ if (type !== void 0) {
453
+ sets.push("type = ?");
454
+ values.push(type);
455
+ }
345
456
  values.push(id);
346
457
  this.#db.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
347
458
  if (content !== void 0) {
@@ -349,7 +460,10 @@ var MemoryRepository = class {
349
460
  const embeddingBlob = Buffer.from(embedding.buffer);
350
461
  this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, existing.rowid);
351
462
  }
352
- return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? []);
463
+ const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id));
464
+ const projectMap = this.#projects.getProjectsForMemories([id]);
465
+ const events = this.#getEventsForMemories([id]);
466
+ return rowToMemory(updated, projectMap.get(id) ?? [], events.get(id) ?? []);
353
467
  }
354
468
  delete(id) {
355
469
  const row = this.#db.db.prepare(`SELECT rowid FROM memories WHERE id = ?`).get(id);
@@ -371,7 +485,48 @@ var MemoryRepository = class {
371
485
  if (rows.length === 0) return [];
372
486
  const ids = rows.map((r) => r.id);
373
487
  const projectMap = this.#projects.getProjectsForMemories(ids);
374
- return rows.map((row) => rowToMemory(row, projectMap.get(row.id) ?? []));
488
+ const eventMap = this.#getEventsForMemories(ids);
489
+ return rows.map((row) => rowToMemory(row, projectMap.get(row.id) ?? [], eventMap.get(row.id) ?? []));
490
+ }
491
+ listFlagged() {
492
+ const rows = this.#db.db.prepare(`SELECT * FROM memories
493
+ WHERE EXISTS (
494
+ SELECT 1 FROM memory_review_events e
495
+ WHERE e.memory_id = memories.id AND e.resolved_at IS NULL
496
+ )
497
+ ORDER BY created_at DESC`).all();
498
+ if (rows.length === 0) return [];
499
+ const ids = rows.map((r) => r.id);
500
+ const projectMap = this.#projects.getProjectsForMemories(ids);
501
+ const eventMap = this.#getEventsForMemories(ids, { unresolvedOnly: true });
502
+ return rows.map((row) => rowToMemory(row, projectMap.get(row.id) ?? [], eventMap.get(row.id) ?? []));
503
+ }
504
+ listReviewEvents(memoryId, opts) {
505
+ const where = opts?.unresolvedOnly === true ? "WHERE memory_id = ? AND resolved_at IS NULL" : "WHERE memory_id = ?";
506
+ return this.#db.db.prepare(`SELECT * FROM memory_review_events ${where} ORDER BY created_at DESC`).all(memoryId).map((r) => rowToReviewEvent(ReviewEventRowSchema.parse(r)));
507
+ }
508
+ resolveReviewEvents(memoryId) {
509
+ const now = (/* @__PURE__ */ new Date()).toISOString();
510
+ this.#db.db.prepare(`UPDATE memory_review_events SET resolved_at = ? WHERE memory_id = ? AND resolved_at IS NULL`).run(now, memoryId);
511
+ }
512
+ #getEventsForMemories(ids, opts) {
513
+ if (ids.length === 0) return /* @__PURE__ */ new Map();
514
+ const placeholders = ids.map(() => "?").join(", ");
515
+ const unresolvedClause = opts?.unresolvedOnly === true ? "AND resolved_at IS NULL" : "";
516
+ const rows = this.#db.db.prepare(`SELECT * FROM memory_review_events
517
+ WHERE memory_id IN (${placeholders}) ${unresolvedClause}
518
+ ORDER BY created_at DESC`).all(...ids);
519
+ const map = /* @__PURE__ */ new Map();
520
+ for (const row of rows) {
521
+ const event = rowToReviewEvent(ReviewEventRowSchema.parse(row));
522
+ const existing = map.get(event.memoryId) ?? [];
523
+ existing.push(event);
524
+ map.set(event.memoryId, existing);
525
+ }
526
+ return map;
527
+ }
528
+ getPinnedCharCount() {
529
+ return (this.#db.db.prepare(`SELECT COALESCE(SUM(LENGTH(content)), 0) as total FROM memories WHERE pinned = 1`).get() ?? { total: 0 }).total;
375
530
  }
376
531
  stats() {
377
532
  const byType = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
@@ -380,21 +535,27 @@ var MemoryRepository = class {
380
535
  const parsed = MemoryTypeSchema.safeParse(row.type);
381
536
  if (parsed.success) byType[parsed.data] = row.count;
382
537
  }
383
- const totals = this.#db.db.prepare(`SELECT COUNT(*) as total, SUM(needs_review) as needsReview FROM memories`).get() ?? {
538
+ const aggregates = this.#db.db.prepare(`SELECT COUNT(*) as total, SUM(pinned) as pinned FROM memories`).get() ?? {
384
539
  total: 0,
385
- needsReview: 0
540
+ pinned: 0
386
541
  };
542
+ const reviewRow = this.#db.db.prepare(`SELECT COUNT(DISTINCT memory_id) as needsReview FROM memory_review_events WHERE resolved_at IS NULL`).get() ?? { needsReview: 0 };
387
543
  return {
388
544
  byType,
389
- total: totals.total,
390
- needsReview: totals.needsReview ?? 0
545
+ total: aggregates.total,
546
+ pinned: aggregates.pinned ?? 0,
547
+ needsReview: reviewRow.needsReview,
548
+ pinBudgetChars: this.getPinnedCharCount()
391
549
  };
392
550
  }
393
551
  setPin(id, pinned) {
394
552
  if (this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id) === void 0) throw new Error(`Memory not found: ${id}`);
395
553
  const now = (/* @__PURE__ */ new Date()).toISOString();
396
554
  this.#db.db.prepare(`UPDATE memories SET pinned = ?, updated_at = ? WHERE id = ?`).run(pinned ? 1 : 0, now, id);
397
- return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? []);
555
+ const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id));
556
+ const projectMap = this.#projects.getProjectsForMemories([id]);
557
+ const events = this.#getEventsForMemories([id]);
558
+ return rowToMemory(updated, projectMap.get(id) ?? [], events.get(id) ?? []);
398
559
  }
399
560
  incrementAccessCount(id) {
400
561
  this.#db.db.prepare(`UPDATE memories SET access_count = access_count + 1 WHERE id = ?`).run(id);
@@ -558,7 +719,7 @@ var QueryEngine = class {
558
719
  const now = Date.now();
559
720
  const scored = rows.filter((row) => row.cosine_sim > 0).map((row) => {
560
721
  const memory = rowToMemory(row, []);
561
- const score = this.#computeScore(memory, now);
722
+ const score = this.#computeScore(memory, row.cosine_sim, now);
562
723
  return {
563
724
  ...memory,
564
725
  score
@@ -569,12 +730,12 @@ var QueryEngine = class {
569
730
  for (const result of results) this.#repo.incrementAccessCount(result.id);
570
731
  return results;
571
732
  }
572
- #computeScore(memory, now) {
733
+ #computeScore(memory, cosine_sim, now) {
573
734
  const typeWeight = TYPE_WEIGHTS[memory.type];
574
735
  const accessCountNorm = memory.accessCount / (memory.accessCount + 10);
575
736
  const recencyNorm = 1 / (1 + (now - new Date(memory.updatedAt).getTime()) / 864e5);
576
737
  const pinned = memory.pinned ? 1 : 0;
577
- return typeWeight * .4 + accessCountNorm * .3 + recencyNorm * .2 + pinned * .1;
738
+ return cosine_sim * .4 + typeWeight * .25 + accessCountNorm * .2 + recencyNorm * .1 + pinned * .05;
578
739
  }
579
740
  };
580
741
  //#endregion
@@ -587,28 +748,139 @@ var SessionContextBuilder = class {
587
748
  constructor(db) {
588
749
  this.#db = db;
589
750
  }
590
- getSessionContext(projectHash) {
591
- const pinnedGlobal = this.#db.db.prepare(`SELECT * FROM memories
592
- WHERE id NOT IN (SELECT memory_id FROM memory_projects)
593
- AND pinned = 1`).all().map((row) => rowToMemory(row, []));
594
- const pinnedProject = this.#db.db.prepare(`SELECT m.* FROM memories m
595
- JOIN memory_projects mp ON mp.memory_id = m.id
596
- JOIN projects p ON p.id = mp.project_id
597
- WHERE p.scope_hash = ? AND m.pinned = 1`).all(projectHash).map((row) => rowToMemory(row, []));
751
+ getSessionContext(projectHash, synthesis) {
598
752
  const typeCounts = this.#db.db.prepare("SELECT type, COUNT(*) as count FROM memories GROUP BY type").all();
599
753
  const stats = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
600
754
  for (const row of typeCounts) {
601
755
  const parsed = MemoryTypeSchema.safeParse(row.type);
602
756
  if (parsed.success) stats[parsed.data] = row.count;
603
757
  }
758
+ if (synthesis !== void 0 && synthesis.length > 0) return {
759
+ stats,
760
+ pinnedGlobal: [],
761
+ pinnedProject: [],
762
+ synthesis
763
+ };
604
764
  return {
605
765
  stats,
606
- pinnedGlobal,
607
- pinnedProject
766
+ pinnedGlobal: this.#db.db.prepare(`SELECT * FROM memories
767
+ WHERE id NOT IN (SELECT memory_id FROM memory_projects)
768
+ AND pinned = 1`).all().map((row) => rowToMemory(row, [])),
769
+ pinnedProject: this.#db.db.prepare(`SELECT m.* FROM memories m
770
+ JOIN memory_projects mp ON mp.memory_id = m.id
771
+ JOIN projects p ON p.id = mp.project_id
772
+ WHERE p.scope_hash = ? AND m.pinned = 1`).all(projectHash).map((row) => rowToMemory(row, []))
608
773
  };
609
774
  }
610
775
  };
611
776
  //#endregion
777
+ //#region src/synthesis/repository.ts
778
+ function rowToSynthesis(row) {
779
+ return SynthesisSchema.parse({
780
+ id: row.id,
781
+ scope: row.scope,
782
+ content: row.content,
783
+ sourceMemoryHash: row.source_memory_hash,
784
+ synthesizedAt: row.synthesized_at,
785
+ expiresAt: row.expires_at,
786
+ inFlightSince: row.in_flight_since,
787
+ createdAt: row.created_at,
788
+ updatedAt: row.updated_at
789
+ });
790
+ }
791
+ const STALENESS_DAYS = 30;
792
+ var SynthesisRepository = class {
793
+ #db;
794
+ constructor(db) {
795
+ this.#db = db;
796
+ }
797
+ saveSynthesis(scope, content, sourceHash) {
798
+ const now = (/* @__PURE__ */ new Date()).toISOString();
799
+ const expiresAt = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
800
+ if (this.#db.db.prepare("SELECT id FROM syntheses WHERE scope = ?").get(scope) !== void 0) this.#db.db.prepare(`UPDATE syntheses
801
+ SET content = ?, source_memory_hash = ?, synthesized_at = ?, expires_at = ?,
802
+ in_flight_since = NULL, updated_at = ?
803
+ WHERE scope = ?`).run(content, sourceHash, now, expiresAt, now, scope);
804
+ else {
805
+ const id = (0, node_crypto.randomUUID)();
806
+ this.#db.db.prepare(`INSERT INTO syntheses
807
+ (id, scope, content, source_memory_hash, synthesized_at, expires_at,
808
+ in_flight_since, created_at, updated_at)
809
+ VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(id, scope, content, sourceHash, now, expiresAt, now, now);
810
+ }
811
+ const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
812
+ if (row === void 0) throw new Error(`Failed to save synthesis for scope: ${scope}`);
813
+ return rowToSynthesis(row);
814
+ }
815
+ getSynthesis(scope) {
816
+ const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
817
+ return row !== void 0 ? rowToSynthesis(row) : void 0;
818
+ }
819
+ markInFlight(scope) {
820
+ const now = (/* @__PURE__ */ new Date()).toISOString();
821
+ if (this.#db.db.prepare("SELECT id FROM syntheses WHERE scope = ?").get(scope) !== void 0) this.#db.db.prepare("UPDATE syntheses SET in_flight_since = ?, updated_at = ? WHERE scope = ?").run(now, now, scope);
822
+ else {
823
+ const id = (0, node_crypto.randomUUID)();
824
+ const placeholder = "pending";
825
+ const future = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
826
+ this.#db.db.prepare(`INSERT INTO syntheses
827
+ (id, scope, content, source_memory_hash, synthesized_at, expires_at,
828
+ in_flight_since, created_at, updated_at)
829
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, scope, placeholder, "", now, future, now, now, now);
830
+ }
831
+ }
832
+ clearInFlight(scope) {
833
+ const now = (/* @__PURE__ */ new Date()).toISOString();
834
+ this.#db.db.prepare("UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE scope = ?").run(now, scope);
835
+ }
836
+ computeSourceMemoryHash(scope) {
837
+ let contents;
838
+ if (scope === "global") contents = this.#db.db.prepare(`SELECT content FROM memories
839
+ WHERE id NOT IN (SELECT memory_id FROM memory_projects)
840
+ ORDER BY id`).all();
841
+ else contents = this.#db.db.prepare(`SELECT m.content FROM memories m
842
+ JOIN memory_projects mp ON mp.memory_id = m.id
843
+ JOIN projects p ON p.id = mp.project_id
844
+ WHERE p.scope_hash = ?
845
+ ORDER BY m.id`).all(scope);
846
+ return (0, node_crypto.createHash)("sha256").update(JSON.stringify(contents.map((r) => r.content))).digest("hex");
847
+ }
848
+ getExpiredOrDirtyScopes() {
849
+ const allScopes = this.getAllActiveScopes();
850
+ const now = (/* @__PURE__ */ new Date()).toISOString();
851
+ const results = [];
852
+ for (const scope of allScopes) {
853
+ const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
854
+ if (row === void 0 || row.content === "pending" && row.source_memory_hash === "") {
855
+ results.push({
856
+ scope,
857
+ reason: "missing"
858
+ });
859
+ continue;
860
+ }
861
+ if (row.expires_at <= now) {
862
+ results.push({
863
+ scope,
864
+ reason: "expired"
865
+ });
866
+ continue;
867
+ }
868
+ if (this.computeSourceMemoryHash(scope) !== row.source_memory_hash) results.push({
869
+ scope,
870
+ reason: "dirty"
871
+ });
872
+ }
873
+ return results;
874
+ }
875
+ getAllActiveScopes() {
876
+ return ["global", ...this.#db.db.prepare("SELECT DISTINCT scope_hash FROM projects").all().map((r) => r.scope_hash)];
877
+ }
878
+ expireStale() {
879
+ const now = (/* @__PURE__ */ new Date()).toISOString();
880
+ this.#db.db.prepare("DELETE FROM syntheses WHERE expires_at < ?").run(now);
881
+ }
882
+ };
883
+ //#endregion
612
884
  exports.DatabaseError = DatabaseError;
613
885
  exports.DatabaseManager = DatabaseManager;
614
886
  exports.EmbeddingService = EmbeddingService;
@@ -620,15 +892,22 @@ exports.MemoryRepository = MemoryRepository;
620
892
  exports.MemoryRowSchema = MemoryRowSchema;
621
893
  exports.MemorySchema = MemorySchema;
622
894
  exports.MemoryTypeSchema = MemoryTypeSchema;
895
+ exports.PIN_BUDGET_THRESHOLD = PIN_BUDGET_THRESHOLD;
623
896
  exports.ProjectRepository = ProjectRepository;
624
897
  exports.ProjectRowSchema = ProjectRowSchema;
625
898
  exports.ProjectSchema = ProjectSchema;
626
899
  exports.QueryEngine = QueryEngine;
627
900
  exports.QueryOptionsSchema = QueryOptionsSchema;
901
+ exports.ReviewEventRowSchema = ReviewEventRowSchema;
902
+ exports.ReviewEventSchema = ReviewEventSchema;
903
+ exports.ReviewReasonSchema = ReviewReasonSchema;
628
904
  exports.SaveOptionsSchema = SaveOptionsSchema;
629
905
  exports.SessionContextBuilder = SessionContextBuilder;
630
906
  exports.SessionContextSchema = SessionContextSchema;
907
+ exports.SynthesisRepository = SynthesisRepository;
908
+ exports.SynthesisSchema = SynthesisSchema;
631
909
  exports.TagsJsonSchema = TagsJsonSchema;
910
+ exports.isSynthesisEnabled = isSynthesisEnabled;
632
911
  exports.listMemoryTypes = listMemoryTypes;
633
912
  exports.resolveProject = resolveProject;
634
913
  exports.resolveScope = resolveScope;