@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 +314 -35
- package/dist/index.d.cts +118 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +118 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +309 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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 = [
|
|
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
|
-
`],
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
322
|
-
VALUES (?, ?, ?, ?, ?, 0, 0,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
538
|
+
const aggregates = this.#db.db.prepare(`SELECT COUNT(*) as total, SUM(pinned) as pinned FROM memories`).get() ?? {
|
|
384
539
|
total: 0,
|
|
385
|
-
|
|
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:
|
|
390
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|