@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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import BetterSqlite3 from "better-sqlite3";
|
|
@@ -8,6 +8,20 @@ import { pipeline } from "@huggingface/transformers";
|
|
|
8
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
|
+
//#region src/config/loader.ts
|
|
12
|
+
function loadConfig() {
|
|
13
|
+
const configPath = join(homedir(), ".membank", "config.json");
|
|
14
|
+
try {
|
|
15
|
+
const raw = readFileSync(configPath, "utf8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isSynthesisEnabled() {
|
|
22
|
+
return loadConfig()?.synthesis?.enabled === true;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
11
25
|
//#region src/db/errors.ts
|
|
12
26
|
var MembankError = class extends Error {
|
|
13
27
|
constructor(message, options) {
|
|
@@ -24,7 +38,8 @@ var DatabaseError = class extends MembankError {
|
|
|
24
38
|
//#endregion
|
|
25
39
|
//#region src/db/manager.ts
|
|
26
40
|
const DEFAULT_DB_PATH = join(homedir(), ".membank", "memory.db");
|
|
27
|
-
const MIGRATIONS$1 = [
|
|
41
|
+
const MIGRATIONS$1 = [
|
|
42
|
+
[1, `
|
|
28
43
|
CREATE TABLE IF NOT EXISTS memories (
|
|
29
44
|
id TEXT PRIMARY KEY,
|
|
30
45
|
content TEXT NOT NULL,
|
|
@@ -42,7 +57,8 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
42
57
|
CREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(
|
|
43
58
|
embedding FLOAT[384]
|
|
44
59
|
);
|
|
45
|
-
`],
|
|
60
|
+
`],
|
|
61
|
+
[2, `
|
|
46
62
|
CREATE TABLE IF NOT EXISTS projects (
|
|
47
63
|
id TEXT PRIMARY KEY,
|
|
48
64
|
name TEXT NOT NULL,
|
|
@@ -75,7 +91,46 @@ JOIN projects p ON p.scope_hash = m.scope
|
|
|
75
91
|
WHERE m.scope != 'global';
|
|
76
92
|
|
|
77
93
|
ALTER TABLE memories DROP COLUMN scope;
|
|
78
|
-
`]
|
|
94
|
+
`],
|
|
95
|
+
[3, `
|
|
96
|
+
CREATE TABLE IF NOT EXISTS memory_review_events (
|
|
97
|
+
id TEXT PRIMARY KEY,
|
|
98
|
+
memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
99
|
+
conflicting_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
|
|
100
|
+
similarity REAL NOT NULL,
|
|
101
|
+
conflict_content_snapshot TEXT NOT NULL,
|
|
102
|
+
reason TEXT NOT NULL,
|
|
103
|
+
created_at TEXT NOT NULL,
|
|
104
|
+
resolved_at TEXT
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_review_events_memory_open
|
|
108
|
+
ON memory_review_events(memory_id) WHERE resolved_at IS NULL;
|
|
109
|
+
|
|
110
|
+
ALTER TABLE memories DROP COLUMN needs_review;
|
|
111
|
+
`],
|
|
112
|
+
[4, `
|
|
113
|
+
CREATE TABLE IF NOT EXISTS syntheses (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
scope TEXT NOT NULL,
|
|
116
|
+
content TEXT NOT NULL,
|
|
117
|
+
source_memory_hash TEXT NOT NULL,
|
|
118
|
+
synthesized_at TEXT NOT NULL,
|
|
119
|
+
expires_at TEXT NOT NULL,
|
|
120
|
+
in_flight_since TEXT,
|
|
121
|
+
created_at TEXT NOT NULL,
|
|
122
|
+
updated_at TEXT NOT NULL,
|
|
123
|
+
UNIQUE(scope),
|
|
124
|
+
CHECK(expires_at > synthesized_at)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_syntheses_expires_at
|
|
128
|
+
ON syntheses(expires_at);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_syntheses_scope_inflight
|
|
131
|
+
ON syntheses(scope) WHERE in_flight_since IS NOT NULL;
|
|
132
|
+
`]
|
|
133
|
+
];
|
|
79
134
|
var DatabaseManager = class DatabaseManager {
|
|
80
135
|
#db;
|
|
81
136
|
constructor(db) {
|
|
@@ -149,6 +204,27 @@ const ProjectSchema = z.object({
|
|
|
149
204
|
createdAt: z.string(),
|
|
150
205
|
updatedAt: z.string()
|
|
151
206
|
});
|
|
207
|
+
const ReviewReasonSchema = z.enum(["similarity_dedup"]);
|
|
208
|
+
const ReviewEventSchema = z.object({
|
|
209
|
+
id: z.string(),
|
|
210
|
+
memoryId: z.string(),
|
|
211
|
+
conflictingMemoryId: z.string().nullable(),
|
|
212
|
+
similarity: z.number(),
|
|
213
|
+
conflictContentSnapshot: z.string(),
|
|
214
|
+
reason: ReviewReasonSchema,
|
|
215
|
+
createdAt: z.string(),
|
|
216
|
+
resolvedAt: z.string().nullable()
|
|
217
|
+
});
|
|
218
|
+
const ReviewEventRowSchema = z.object({
|
|
219
|
+
id: z.string(),
|
|
220
|
+
memory_id: z.string(),
|
|
221
|
+
conflicting_memory_id: z.string().nullable(),
|
|
222
|
+
similarity: z.number(),
|
|
223
|
+
conflict_content_snapshot: z.string(),
|
|
224
|
+
reason: ReviewReasonSchema,
|
|
225
|
+
created_at: z.string(),
|
|
226
|
+
resolved_at: z.string().nullable()
|
|
227
|
+
});
|
|
152
228
|
const MemorySchema = z.object({
|
|
153
229
|
id: z.string(),
|
|
154
230
|
content: z.string(),
|
|
@@ -158,7 +234,7 @@ const MemorySchema = z.object({
|
|
|
158
234
|
sourceHarness: z.string().nullable(),
|
|
159
235
|
accessCount: z.number().int().nonnegative(),
|
|
160
236
|
pinned: z.boolean(),
|
|
161
|
-
|
|
237
|
+
reviewEvents: z.array(ReviewEventSchema),
|
|
162
238
|
createdAt: z.string(),
|
|
163
239
|
updatedAt: z.string()
|
|
164
240
|
});
|
|
@@ -181,12 +257,25 @@ const SaveOptionsSchema = z.object({
|
|
|
181
257
|
});
|
|
182
258
|
const MemoryPatchSchema = z.object({
|
|
183
259
|
content: z.string().min(1).optional(),
|
|
184
|
-
tags: z.array(z.string()).optional()
|
|
260
|
+
tags: z.array(z.string()).optional(),
|
|
261
|
+
type: MemoryTypeSchema.optional()
|
|
262
|
+
});
|
|
263
|
+
const SynthesisSchema = z.object({
|
|
264
|
+
id: z.string(),
|
|
265
|
+
scope: z.string(),
|
|
266
|
+
content: z.string(),
|
|
267
|
+
sourceMemoryHash: z.string(),
|
|
268
|
+
synthesizedAt: z.string(),
|
|
269
|
+
expiresAt: z.string(),
|
|
270
|
+
inFlightSince: z.string().nullable(),
|
|
271
|
+
createdAt: z.string(),
|
|
272
|
+
updatedAt: z.string()
|
|
185
273
|
});
|
|
186
274
|
const SessionContextSchema = z.object({
|
|
187
275
|
stats: z.record(MemoryTypeSchema, z.number()),
|
|
188
276
|
pinnedGlobal: z.array(MemorySchema),
|
|
189
|
-
pinnedProject: z.array(MemorySchema)
|
|
277
|
+
pinnedProject: z.array(MemorySchema),
|
|
278
|
+
synthesis: z.string().optional()
|
|
190
279
|
});
|
|
191
280
|
const MemoryRowSchema = z.object({
|
|
192
281
|
id: z.string(),
|
|
@@ -196,7 +285,6 @@ const MemoryRowSchema = z.object({
|
|
|
196
285
|
source: z.string().nullable(),
|
|
197
286
|
access_count: z.number(),
|
|
198
287
|
pinned: z.number(),
|
|
199
|
-
needs_review: z.number(),
|
|
200
288
|
created_at: z.string(),
|
|
201
289
|
updated_at: z.string()
|
|
202
290
|
});
|
|
@@ -209,7 +297,7 @@ const ProjectRowSchema = z.object({
|
|
|
209
297
|
});
|
|
210
298
|
//#endregion
|
|
211
299
|
//#region src/db/row-types.ts
|
|
212
|
-
function rowToMemory(row, projects) {
|
|
300
|
+
function rowToMemory(row, projects, reviewEvents = []) {
|
|
213
301
|
return {
|
|
214
302
|
id: row.id,
|
|
215
303
|
content: row.content,
|
|
@@ -219,11 +307,24 @@ function rowToMemory(row, projects) {
|
|
|
219
307
|
sourceHarness: row.source,
|
|
220
308
|
accessCount: row.access_count,
|
|
221
309
|
pinned: row.pinned !== 0,
|
|
222
|
-
|
|
310
|
+
reviewEvents,
|
|
223
311
|
createdAt: row.created_at,
|
|
224
312
|
updatedAt: row.updated_at
|
|
225
313
|
};
|
|
226
314
|
}
|
|
315
|
+
function rowToReviewEvent(row) {
|
|
316
|
+
const parsed = ReviewEventRowSchema.parse(row);
|
|
317
|
+
return {
|
|
318
|
+
id: parsed.id,
|
|
319
|
+
memoryId: parsed.memory_id,
|
|
320
|
+
conflictingMemoryId: parsed.conflicting_memory_id,
|
|
321
|
+
similarity: parsed.similarity,
|
|
322
|
+
conflictContentSnapshot: parsed.conflict_content_snapshot,
|
|
323
|
+
reason: parsed.reason,
|
|
324
|
+
createdAt: parsed.created_at,
|
|
325
|
+
resolvedAt: parsed.resolved_at
|
|
326
|
+
};
|
|
327
|
+
}
|
|
227
328
|
function rowToProject(row) {
|
|
228
329
|
return {
|
|
229
330
|
id: row.id,
|
|
@@ -260,6 +361,7 @@ var EmbeddingService = class {
|
|
|
260
361
|
};
|
|
261
362
|
//#endregion
|
|
262
363
|
//#region src/memory/repository.ts
|
|
364
|
+
const PIN_BUDGET_THRESHOLD = 8e3;
|
|
263
365
|
var MemoryRepository = class {
|
|
264
366
|
#db;
|
|
265
367
|
#embedding;
|
|
@@ -289,21 +391,26 @@ var MemoryRepository = class {
|
|
|
289
391
|
if (top !== void 0 && top.similarity > .92) {
|
|
290
392
|
this.#db.db.prepare(`UPDATE memories SET content = ?, updated_at = ? WHERE id = ?`).run(content, now, top.id);
|
|
291
393
|
this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, top.rowid);
|
|
292
|
-
|
|
394
|
+
const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(top.id));
|
|
395
|
+
const projectMap = this.#projects.getProjectsForMemories([top.id]);
|
|
396
|
+
const events = this.#getEventsForMemories([top.id]);
|
|
397
|
+
return rowToMemory(updated, projectMap.get(top.id) ?? [], events.get(top.id) ?? []);
|
|
293
398
|
}
|
|
294
|
-
if (top !== void 0 && top.similarity >= .75) this.#db.db.prepare(`UPDATE memories SET needs_review = 1 WHERE id = ?`).run(top.id);
|
|
295
399
|
const id = randomUUID();
|
|
296
|
-
this.#db.db.prepare(`INSERT INTO memories (id, content, type, tags, source, access_count, pinned,
|
|
297
|
-
VALUES (?, ?, ?, ?, ?, 0, 0,
|
|
400
|
+
this.#db.db.prepare(`INSERT INTO memories (id, content, type, tags, source, access_count, pinned, created_at, updated_at)
|
|
401
|
+
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)`).run(id, content, type, JSON.stringify(tags), sourceHarness ?? null, now, now);
|
|
402
|
+
if (top !== void 0 && top.similarity >= .75) this.#db.db.prepare(`INSERT INTO memory_review_events
|
|
403
|
+
(id, memory_id, conflicting_memory_id, similarity, conflict_content_snapshot, reason, created_at)
|
|
404
|
+
VALUES (?, ?, ?, ?, ?, 'similarity_dedup', ?)`).run(randomUUID(), top.id, id, top.similarity, content, now);
|
|
298
405
|
this.#db.db.prepare(`INSERT INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`).run(embeddingBlob, id);
|
|
299
406
|
if (projectScope !== void 0) {
|
|
300
407
|
const project = this.#projects.upsertByHash(projectScope.hash, projectScope.name);
|
|
301
408
|
this.#projects.addAssociation(id, project.id);
|
|
302
409
|
}
|
|
303
|
-
return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? []);
|
|
410
|
+
return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? [], []);
|
|
304
411
|
}
|
|
305
412
|
async update(id, patch) {
|
|
306
|
-
const { content, tags } = MemoryPatchSchema.parse(patch);
|
|
413
|
+
const { content, tags, type } = MemoryPatchSchema.parse(patch);
|
|
307
414
|
const existing = this.#db.db.prepare(`SELECT m.rowid, m.* FROM memories m WHERE m.id = ?`).get(id);
|
|
308
415
|
if (existing === void 0) throw new Error(`Memory not found: ${id}`);
|
|
309
416
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -317,6 +424,10 @@ var MemoryRepository = class {
|
|
|
317
424
|
sets.push("tags = ?");
|
|
318
425
|
values.push(JSON.stringify(tags));
|
|
319
426
|
}
|
|
427
|
+
if (type !== void 0) {
|
|
428
|
+
sets.push("type = ?");
|
|
429
|
+
values.push(type);
|
|
430
|
+
}
|
|
320
431
|
values.push(id);
|
|
321
432
|
this.#db.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
322
433
|
if (content !== void 0) {
|
|
@@ -324,7 +435,10 @@ var MemoryRepository = class {
|
|
|
324
435
|
const embeddingBlob = Buffer.from(embedding.buffer);
|
|
325
436
|
this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, existing.rowid);
|
|
326
437
|
}
|
|
327
|
-
|
|
438
|
+
const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id));
|
|
439
|
+
const projectMap = this.#projects.getProjectsForMemories([id]);
|
|
440
|
+
const events = this.#getEventsForMemories([id]);
|
|
441
|
+
return rowToMemory(updated, projectMap.get(id) ?? [], events.get(id) ?? []);
|
|
328
442
|
}
|
|
329
443
|
delete(id) {
|
|
330
444
|
const row = this.#db.db.prepare(`SELECT rowid FROM memories WHERE id = ?`).get(id);
|
|
@@ -346,7 +460,48 @@ var MemoryRepository = class {
|
|
|
346
460
|
if (rows.length === 0) return [];
|
|
347
461
|
const ids = rows.map((r) => r.id);
|
|
348
462
|
const projectMap = this.#projects.getProjectsForMemories(ids);
|
|
349
|
-
|
|
463
|
+
const eventMap = this.#getEventsForMemories(ids);
|
|
464
|
+
return rows.map((row) => rowToMemory(row, projectMap.get(row.id) ?? [], eventMap.get(row.id) ?? []));
|
|
465
|
+
}
|
|
466
|
+
listFlagged() {
|
|
467
|
+
const rows = this.#db.db.prepare(`SELECT * FROM memories
|
|
468
|
+
WHERE EXISTS (
|
|
469
|
+
SELECT 1 FROM memory_review_events e
|
|
470
|
+
WHERE e.memory_id = memories.id AND e.resolved_at IS NULL
|
|
471
|
+
)
|
|
472
|
+
ORDER BY created_at DESC`).all();
|
|
473
|
+
if (rows.length === 0) return [];
|
|
474
|
+
const ids = rows.map((r) => r.id);
|
|
475
|
+
const projectMap = this.#projects.getProjectsForMemories(ids);
|
|
476
|
+
const eventMap = this.#getEventsForMemories(ids, { unresolvedOnly: true });
|
|
477
|
+
return rows.map((row) => rowToMemory(row, projectMap.get(row.id) ?? [], eventMap.get(row.id) ?? []));
|
|
478
|
+
}
|
|
479
|
+
listReviewEvents(memoryId, opts) {
|
|
480
|
+
const where = opts?.unresolvedOnly === true ? "WHERE memory_id = ? AND resolved_at IS NULL" : "WHERE memory_id = ?";
|
|
481
|
+
return this.#db.db.prepare(`SELECT * FROM memory_review_events ${where} ORDER BY created_at DESC`).all(memoryId).map((r) => rowToReviewEvent(ReviewEventRowSchema.parse(r)));
|
|
482
|
+
}
|
|
483
|
+
resolveReviewEvents(memoryId) {
|
|
484
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
485
|
+
this.#db.db.prepare(`UPDATE memory_review_events SET resolved_at = ? WHERE memory_id = ? AND resolved_at IS NULL`).run(now, memoryId);
|
|
486
|
+
}
|
|
487
|
+
#getEventsForMemories(ids, opts) {
|
|
488
|
+
if (ids.length === 0) return /* @__PURE__ */ new Map();
|
|
489
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
490
|
+
const unresolvedClause = opts?.unresolvedOnly === true ? "AND resolved_at IS NULL" : "";
|
|
491
|
+
const rows = this.#db.db.prepare(`SELECT * FROM memory_review_events
|
|
492
|
+
WHERE memory_id IN (${placeholders}) ${unresolvedClause}
|
|
493
|
+
ORDER BY created_at DESC`).all(...ids);
|
|
494
|
+
const map = /* @__PURE__ */ new Map();
|
|
495
|
+
for (const row of rows) {
|
|
496
|
+
const event = rowToReviewEvent(ReviewEventRowSchema.parse(row));
|
|
497
|
+
const existing = map.get(event.memoryId) ?? [];
|
|
498
|
+
existing.push(event);
|
|
499
|
+
map.set(event.memoryId, existing);
|
|
500
|
+
}
|
|
501
|
+
return map;
|
|
502
|
+
}
|
|
503
|
+
getPinnedCharCount() {
|
|
504
|
+
return (this.#db.db.prepare(`SELECT COALESCE(SUM(LENGTH(content)), 0) as total FROM memories WHERE pinned = 1`).get() ?? { total: 0 }).total;
|
|
350
505
|
}
|
|
351
506
|
stats() {
|
|
352
507
|
const byType = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
|
|
@@ -355,21 +510,27 @@ var MemoryRepository = class {
|
|
|
355
510
|
const parsed = MemoryTypeSchema.safeParse(row.type);
|
|
356
511
|
if (parsed.success) byType[parsed.data] = row.count;
|
|
357
512
|
}
|
|
358
|
-
const
|
|
513
|
+
const aggregates = this.#db.db.prepare(`SELECT COUNT(*) as total, SUM(pinned) as pinned FROM memories`).get() ?? {
|
|
359
514
|
total: 0,
|
|
360
|
-
|
|
515
|
+
pinned: 0
|
|
361
516
|
};
|
|
517
|
+
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 };
|
|
362
518
|
return {
|
|
363
519
|
byType,
|
|
364
|
-
total:
|
|
365
|
-
|
|
520
|
+
total: aggregates.total,
|
|
521
|
+
pinned: aggregates.pinned ?? 0,
|
|
522
|
+
needsReview: reviewRow.needsReview,
|
|
523
|
+
pinBudgetChars: this.getPinnedCharCount()
|
|
366
524
|
};
|
|
367
525
|
}
|
|
368
526
|
setPin(id, pinned) {
|
|
369
527
|
if (this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id) === void 0) throw new Error(`Memory not found: ${id}`);
|
|
370
528
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
371
529
|
this.#db.db.prepare(`UPDATE memories SET pinned = ?, updated_at = ? WHERE id = ?`).run(pinned ? 1 : 0, now, id);
|
|
372
|
-
|
|
530
|
+
const updated = MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id));
|
|
531
|
+
const projectMap = this.#projects.getProjectsForMemories([id]);
|
|
532
|
+
const events = this.#getEventsForMemories([id]);
|
|
533
|
+
return rowToMemory(updated, projectMap.get(id) ?? [], events.get(id) ?? []);
|
|
373
534
|
}
|
|
374
535
|
incrementAccessCount(id) {
|
|
375
536
|
this.#db.db.prepare(`UPDATE memories SET access_count = access_count + 1 WHERE id = ?`).run(id);
|
|
@@ -533,7 +694,7 @@ var QueryEngine = class {
|
|
|
533
694
|
const now = Date.now();
|
|
534
695
|
const scored = rows.filter((row) => row.cosine_sim > 0).map((row) => {
|
|
535
696
|
const memory = rowToMemory(row, []);
|
|
536
|
-
const score = this.#computeScore(memory, now);
|
|
697
|
+
const score = this.#computeScore(memory, row.cosine_sim, now);
|
|
537
698
|
return {
|
|
538
699
|
...memory,
|
|
539
700
|
score
|
|
@@ -544,12 +705,12 @@ var QueryEngine = class {
|
|
|
544
705
|
for (const result of results) this.#repo.incrementAccessCount(result.id);
|
|
545
706
|
return results;
|
|
546
707
|
}
|
|
547
|
-
#computeScore(memory, now) {
|
|
708
|
+
#computeScore(memory, cosine_sim, now) {
|
|
548
709
|
const typeWeight = TYPE_WEIGHTS[memory.type];
|
|
549
710
|
const accessCountNorm = memory.accessCount / (memory.accessCount + 10);
|
|
550
711
|
const recencyNorm = 1 / (1 + (now - new Date(memory.updatedAt).getTime()) / 864e5);
|
|
551
712
|
const pinned = memory.pinned ? 1 : 0;
|
|
552
|
-
return
|
|
713
|
+
return cosine_sim * .4 + typeWeight * .25 + accessCountNorm * .2 + recencyNorm * .1 + pinned * .05;
|
|
553
714
|
}
|
|
554
715
|
};
|
|
555
716
|
//#endregion
|
|
@@ -562,28 +723,139 @@ var SessionContextBuilder = class {
|
|
|
562
723
|
constructor(db) {
|
|
563
724
|
this.#db = db;
|
|
564
725
|
}
|
|
565
|
-
getSessionContext(projectHash) {
|
|
566
|
-
const pinnedGlobal = this.#db.db.prepare(`SELECT * FROM memories
|
|
567
|
-
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
568
|
-
AND pinned = 1`).all().map((row) => rowToMemory(row, []));
|
|
569
|
-
const pinnedProject = this.#db.db.prepare(`SELECT m.* FROM memories m
|
|
570
|
-
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
571
|
-
JOIN projects p ON p.id = mp.project_id
|
|
572
|
-
WHERE p.scope_hash = ? AND m.pinned = 1`).all(projectHash).map((row) => rowToMemory(row, []));
|
|
726
|
+
getSessionContext(projectHash, synthesis) {
|
|
573
727
|
const typeCounts = this.#db.db.prepare("SELECT type, COUNT(*) as count FROM memories GROUP BY type").all();
|
|
574
728
|
const stats = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
|
|
575
729
|
for (const row of typeCounts) {
|
|
576
730
|
const parsed = MemoryTypeSchema.safeParse(row.type);
|
|
577
731
|
if (parsed.success) stats[parsed.data] = row.count;
|
|
578
732
|
}
|
|
733
|
+
if (synthesis !== void 0 && synthesis.length > 0) return {
|
|
734
|
+
stats,
|
|
735
|
+
pinnedGlobal: [],
|
|
736
|
+
pinnedProject: [],
|
|
737
|
+
synthesis
|
|
738
|
+
};
|
|
579
739
|
return {
|
|
580
740
|
stats,
|
|
581
|
-
pinnedGlobal
|
|
582
|
-
|
|
741
|
+
pinnedGlobal: this.#db.db.prepare(`SELECT * FROM memories
|
|
742
|
+
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
743
|
+
AND pinned = 1`).all().map((row) => rowToMemory(row, [])),
|
|
744
|
+
pinnedProject: this.#db.db.prepare(`SELECT m.* FROM memories m
|
|
745
|
+
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
746
|
+
JOIN projects p ON p.id = mp.project_id
|
|
747
|
+
WHERE p.scope_hash = ? AND m.pinned = 1`).all(projectHash).map((row) => rowToMemory(row, []))
|
|
583
748
|
};
|
|
584
749
|
}
|
|
585
750
|
};
|
|
586
751
|
//#endregion
|
|
587
|
-
|
|
752
|
+
//#region src/synthesis/repository.ts
|
|
753
|
+
function rowToSynthesis(row) {
|
|
754
|
+
return SynthesisSchema.parse({
|
|
755
|
+
id: row.id,
|
|
756
|
+
scope: row.scope,
|
|
757
|
+
content: row.content,
|
|
758
|
+
sourceMemoryHash: row.source_memory_hash,
|
|
759
|
+
synthesizedAt: row.synthesized_at,
|
|
760
|
+
expiresAt: row.expires_at,
|
|
761
|
+
inFlightSince: row.in_flight_since,
|
|
762
|
+
createdAt: row.created_at,
|
|
763
|
+
updatedAt: row.updated_at
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const STALENESS_DAYS = 30;
|
|
767
|
+
var SynthesisRepository = class {
|
|
768
|
+
#db;
|
|
769
|
+
constructor(db) {
|
|
770
|
+
this.#db = db;
|
|
771
|
+
}
|
|
772
|
+
saveSynthesis(scope, content, sourceHash) {
|
|
773
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
774
|
+
const expiresAt = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
|
|
775
|
+
if (this.#db.db.prepare("SELECT id FROM syntheses WHERE scope = ?").get(scope) !== void 0) this.#db.db.prepare(`UPDATE syntheses
|
|
776
|
+
SET content = ?, source_memory_hash = ?, synthesized_at = ?, expires_at = ?,
|
|
777
|
+
in_flight_since = NULL, updated_at = ?
|
|
778
|
+
WHERE scope = ?`).run(content, sourceHash, now, expiresAt, now, scope);
|
|
779
|
+
else {
|
|
780
|
+
const id = randomUUID();
|
|
781
|
+
this.#db.db.prepare(`INSERT INTO syntheses
|
|
782
|
+
(id, scope, content, source_memory_hash, synthesized_at, expires_at,
|
|
783
|
+
in_flight_since, created_at, updated_at)
|
|
784
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(id, scope, content, sourceHash, now, expiresAt, now, now);
|
|
785
|
+
}
|
|
786
|
+
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
787
|
+
if (row === void 0) throw new Error(`Failed to save synthesis for scope: ${scope}`);
|
|
788
|
+
return rowToSynthesis(row);
|
|
789
|
+
}
|
|
790
|
+
getSynthesis(scope) {
|
|
791
|
+
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
792
|
+
return row !== void 0 ? rowToSynthesis(row) : void 0;
|
|
793
|
+
}
|
|
794
|
+
markInFlight(scope) {
|
|
795
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
796
|
+
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);
|
|
797
|
+
else {
|
|
798
|
+
const id = randomUUID();
|
|
799
|
+
const placeholder = "pending";
|
|
800
|
+
const future = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
|
|
801
|
+
this.#db.db.prepare(`INSERT INTO syntheses
|
|
802
|
+
(id, scope, content, source_memory_hash, synthesized_at, expires_at,
|
|
803
|
+
in_flight_since, created_at, updated_at)
|
|
804
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, scope, placeholder, "", now, future, now, now, now);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
clearInFlight(scope) {
|
|
808
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
809
|
+
this.#db.db.prepare("UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE scope = ?").run(now, scope);
|
|
810
|
+
}
|
|
811
|
+
computeSourceMemoryHash(scope) {
|
|
812
|
+
let contents;
|
|
813
|
+
if (scope === "global") contents = this.#db.db.prepare(`SELECT content FROM memories
|
|
814
|
+
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
815
|
+
ORDER BY id`).all();
|
|
816
|
+
else contents = this.#db.db.prepare(`SELECT m.content FROM memories m
|
|
817
|
+
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
818
|
+
JOIN projects p ON p.id = mp.project_id
|
|
819
|
+
WHERE p.scope_hash = ?
|
|
820
|
+
ORDER BY m.id`).all(scope);
|
|
821
|
+
return createHash("sha256").update(JSON.stringify(contents.map((r) => r.content))).digest("hex");
|
|
822
|
+
}
|
|
823
|
+
getExpiredOrDirtyScopes() {
|
|
824
|
+
const allScopes = this.getAllActiveScopes();
|
|
825
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
826
|
+
const results = [];
|
|
827
|
+
for (const scope of allScopes) {
|
|
828
|
+
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
829
|
+
if (row === void 0 || row.content === "pending" && row.source_memory_hash === "") {
|
|
830
|
+
results.push({
|
|
831
|
+
scope,
|
|
832
|
+
reason: "missing"
|
|
833
|
+
});
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (row.expires_at <= now) {
|
|
837
|
+
results.push({
|
|
838
|
+
scope,
|
|
839
|
+
reason: "expired"
|
|
840
|
+
});
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (this.computeSourceMemoryHash(scope) !== row.source_memory_hash) results.push({
|
|
844
|
+
scope,
|
|
845
|
+
reason: "dirty"
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return results;
|
|
849
|
+
}
|
|
850
|
+
getAllActiveScopes() {
|
|
851
|
+
return ["global", ...this.#db.db.prepare("SELECT DISTINCT scope_hash FROM projects").all().map((r) => r.scope_hash)];
|
|
852
|
+
}
|
|
853
|
+
expireStale() {
|
|
854
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
855
|
+
this.#db.db.prepare("DELETE FROM syntheses WHERE expires_at < ?").run(now);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
//#endregion
|
|
859
|
+
export { DatabaseError, DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MIGRATIONS, MembankError, MemoryPatchSchema, MemoryRepository, MemoryRowSchema, MemorySchema, MemoryTypeSchema, PIN_BUDGET_THRESHOLD, ProjectRepository, ProjectRowSchema, ProjectSchema, QueryEngine, QueryOptionsSchema, ReviewEventRowSchema, ReviewEventSchema, ReviewReasonSchema, SaveOptionsSchema, SessionContextBuilder, SessionContextSchema, SynthesisRepository, SynthesisSchema, TagsJsonSchema, isSynthesisEnabled, listMemoryTypes, resolveProject, resolveScope, rowToMemory, rowToProject, runScopeToProjectsMigration };
|
|
588
860
|
|
|
589
861
|
//# sourceMappingURL=index.mjs.map
|