@membank/core 0.9.3 → 0.10.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 +90 -817
- package/dist/index.d.cts +255 -58
- package/dist/index.d.mts +255 -58
- package/dist/index.mjs +90 -761
- package/package.json +5 -3
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,45 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import BetterSqlite3 from "better-sqlite3";
|
|
5
|
-
import * as sqliteVec from "sqlite-vec";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { pipeline } from "@huggingface/transformers";
|
|
8
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
-
import { execFile } from "node:child_process";
|
|
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
|
|
25
|
-
//#region src/db/errors.ts
|
|
26
|
-
var MembankError = class extends Error {
|
|
27
|
-
constructor(message, options) {
|
|
28
|
-
super(message, options);
|
|
29
|
-
this.name = "MembankError";
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
var DatabaseError = class extends MembankError {
|
|
33
|
-
constructor(message, options) {
|
|
34
|
-
super(message, options);
|
|
35
|
-
this.name = "DatabaseError";
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/db/manager.ts
|
|
40
|
-
const DEFAULT_DB_PATH = join(homedir(), ".membank", "memory.db");
|
|
41
|
-
const MIGRATIONS$1 = [
|
|
42
|
-
[1, `
|
|
1
|
+
import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r}from"node:fs";import{homedir as i}from"node:os";import{dirname as a,join as o}from"node:path";import{z as s}from"zod";import c from"better-sqlite3";import*as l from"sqlite-vec";import{EventEmitter as u}from"node:events";import{pipeline as d}from"@huggingface/transformers";import{createHash as f,randomUUID as p}from"node:crypto";import{execFile as ee}from"node:child_process";import{promisify as te}from"node:util";import{createSdkMcpServer as m,query as ne,tool as h}from"@anthropic-ai/claude-agent-sdk";function re(){let e=o(i(),`.membank`,`config.json`);try{let t=n(e,`utf8`);return JSON.parse(t)}catch{return null}}function ie(){return re()?.synthesis?.enabled===!0}const g=[`correction`,`preference`,`decision`,`learning`,`fact`],_=s.enum(g),v=s.array(s.string()),y=s.object({id:s.string(),name:s.string(),scopeHash:s.string(),createdAt:s.string(),updatedAt:s.string()}),b=s.enum([`similarity_dedup`]),x=s.object({id:s.string(),memoryId:s.string(),conflictingMemoryId:s.string().nullable(),similarity:s.number(),conflictContentSnapshot:s.string(),reason:b,createdAt:s.string(),resolvedAt:s.string().nullable()}),S=s.object({id:s.string(),memory_id:s.string(),conflicting_memory_id:s.string().nullable(),similarity:s.number(),conflict_content_snapshot:s.string(),reason:b,created_at:s.string(),resolved_at:s.string().nullable()}),C=s.object({id:s.string(),content:s.string(),type:_,tags:s.array(s.string()),projects:s.array(y),sourceHarness:s.string().nullable(),accessCount:s.number().int().nonnegative(),pinned:s.boolean(),reviewEvents:s.array(x),createdAt:s.string(),updatedAt:s.string()}),w=s.object({query:s.string().min(1),type:_.optional(),projectHash:s.string().optional(),limit:s.number().int().positive().optional(),includePinned:s.boolean().optional()}),T=s.object({content:s.string().min(1),type:_,tags:s.array(s.string()).optional(),projectScope:s.object({hash:s.string(),name:s.string()}).optional(),sourceHarness:s.string().optional()}),E=s.object({content:s.string().min(1).optional(),tags:s.array(s.string()).optional(),type:_.optional()}),D=s.object({id:s.string(),scope:s.string(),content:s.string(),sourceMemoryHash:s.string(),synthesizedAt:s.string(),expiresAt:s.string(),inFlightSince:s.string().nullable(),createdAt:s.string(),updatedAt:s.string()}),ae=s.object({stats:s.record(_,s.number()),pinnedGlobal:s.array(C),pinnedProject:s.array(C),synthesis:s.string().optional()}),O=s.object({id:s.string(),content:s.string(),type:s.string(),tags:s.string(),source:s.string().nullable(),access_count:s.number(),pinned:s.number(),created_at:s.string(),updated_at:s.string()}),k=s.object({id:s.string(),name:s.string(),scope_hash:s.string(),created_at:s.string(),updated_at:s.string()});function A(e,t,n=[]){return{id:e.id,content:e.content,type:_.parse(e.type),tags:v.parse(JSON.parse(e.tags)),projects:t,sourceHarness:e.source,accessCount:e.access_count,pinned:e.pinned!==0,reviewEvents:n,createdAt:e.created_at,updatedAt:e.updated_at}}function j(e){let t=S.parse(e);return{id:t.id,memoryId:t.memory_id,conflictingMemoryId:t.conflicting_memory_id,similarity:t.similarity,conflictContentSnapshot:t.conflict_content_snapshot,reason:t.reason,createdAt:t.created_at,resolvedAt:t.resolved_at}}function M(e){return{id:e.id,name:e.name,scopeHash:e.scope_hash,createdAt:e.created_at,updatedAt:e.updated_at}}var N=class extends Error{constructor(e,t){super(e,t),this.name=`MembankError`}},P=class extends N{constructor(e,t){super(e,t),this.name=`DatabaseError`}};const oe=o(i(),`.membank`,`memory.db`),F=[[1,`
|
|
43
2
|
CREATE TABLE IF NOT EXISTS memories (
|
|
44
3
|
id TEXT PRIMARY KEY,
|
|
45
4
|
content TEXT NOT NULL,
|
|
@@ -57,8 +16,7 @@ CREATE TABLE IF NOT EXISTS memories (
|
|
|
57
16
|
CREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(
|
|
58
17
|
embedding FLOAT[384]
|
|
59
18
|
);
|
|
60
|
-
`],
|
|
61
|
-
[2, `
|
|
19
|
+
`],[2,`
|
|
62
20
|
CREATE TABLE IF NOT EXISTS projects (
|
|
63
21
|
id TEXT PRIMARY KEY,
|
|
64
22
|
name TEXT NOT NULL,
|
|
@@ -91,8 +49,7 @@ JOIN projects p ON p.scope_hash = m.scope
|
|
|
91
49
|
WHERE m.scope != 'global';
|
|
92
50
|
|
|
93
51
|
ALTER TABLE memories DROP COLUMN scope;
|
|
94
|
-
`],
|
|
95
|
-
[3, `
|
|
52
|
+
`],[3,`
|
|
96
53
|
CREATE TABLE IF NOT EXISTS memory_review_events (
|
|
97
54
|
id TEXT PRIMARY KEY,
|
|
98
55
|
memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
@@ -108,8 +65,7 @@ CREATE INDEX IF NOT EXISTS idx_review_events_memory_open
|
|
|
108
65
|
ON memory_review_events(memory_id) WHERE resolved_at IS NULL;
|
|
109
66
|
|
|
110
67
|
ALTER TABLE memories DROP COLUMN needs_review;
|
|
111
|
-
`],
|
|
112
|
-
[4, `
|
|
68
|
+
`],[4,`
|
|
113
69
|
CREATE TABLE IF NOT EXISTS syntheses (
|
|
114
70
|
id TEXT PRIMARY KEY,
|
|
115
71
|
scope TEXT NOT NULL,
|
|
@@ -129,742 +85,115 @@ CREATE INDEX IF NOT EXISTS idx_syntheses_expires_at
|
|
|
129
85
|
|
|
130
86
|
CREATE INDEX IF NOT EXISTS idx_syntheses_scope_inflight
|
|
131
87
|
ON syntheses(scope) WHERE in_flight_since IS NOT NULL;
|
|
132
|
-
`]
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
88
|
+
`],[5,`
|
|
89
|
+
PRAGMA foreign_keys = OFF;
|
|
90
|
+
|
|
91
|
+
BEGIN;
|
|
92
|
+
|
|
93
|
+
-- Rescue associations from corrupt projects by relinking them to a valid project with the same name
|
|
94
|
+
INSERT OR IGNORE INTO memory_projects (memory_id, project_id)
|
|
95
|
+
SELECT mp.memory_id, (
|
|
96
|
+
SELECT p_good.id
|
|
97
|
+
FROM projects p_good
|
|
98
|
+
WHERE p_good.name = p_bad.name
|
|
99
|
+
AND length(p_good.scope_hash) = 16
|
|
100
|
+
AND trim(p_good.scope_hash, '0123456789abcdef') = ''
|
|
101
|
+
ORDER BY p_good.created_at
|
|
102
|
+
LIMIT 1
|
|
103
|
+
)
|
|
104
|
+
FROM memory_projects mp
|
|
105
|
+
JOIN projects p_bad ON p_bad.id = mp.project_id
|
|
106
|
+
WHERE (length(p_bad.scope_hash) != 16 OR trim(p_bad.scope_hash, '0123456789abcdef') != '')
|
|
107
|
+
AND EXISTS (
|
|
108
|
+
SELECT 1 FROM projects p_good
|
|
109
|
+
WHERE p_good.name = p_bad.name
|
|
110
|
+
AND length(p_good.scope_hash) = 16
|
|
111
|
+
AND trim(p_good.scope_hash, '0123456789abcdef') = ''
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
-- Drop all associations to corrupt projects (memories with no valid counterpart become global)
|
|
115
|
+
DELETE FROM memory_projects
|
|
116
|
+
WHERE project_id IN (
|
|
117
|
+
SELECT id FROM projects
|
|
118
|
+
WHERE length(scope_hash) != 16 OR trim(scope_hash, '0123456789abcdef') != ''
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
-- Delete corrupt projects
|
|
122
|
+
DELETE FROM projects
|
|
123
|
+
WHERE length(scope_hash) != 16 OR trim(scope_hash, '0123456789abcdef') != '';
|
|
124
|
+
|
|
125
|
+
-- Recreate projects table with CHECK constraint on scope_hash
|
|
126
|
+
-- SQLite requires table recreation to add CHECK constraints
|
|
127
|
+
DROP TABLE IF EXISTS projects_new;
|
|
128
|
+
|
|
129
|
+
CREATE TABLE projects_new (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
name TEXT NOT NULL,
|
|
132
|
+
scope_hash TEXT NOT NULL UNIQUE
|
|
133
|
+
CHECK(length(scope_hash) = 16 AND trim(scope_hash, '0123456789abcdef') = ''),
|
|
134
|
+
created_at TEXT NOT NULL,
|
|
135
|
+
updated_at TEXT NOT NULL
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
INSERT INTO projects_new SELECT * FROM projects;
|
|
139
|
+
|
|
140
|
+
DROP TABLE projects;
|
|
141
|
+
ALTER TABLE projects_new RENAME TO projects;
|
|
142
|
+
|
|
143
|
+
COMMIT;
|
|
144
|
+
|
|
145
|
+
PRAGMA foreign_keys = ON;
|
|
146
|
+
`]];var se=class e{#e;constructor(e){this.#e=e}static open(n){let r=n??oe;t(a(r),{recursive:!0});let i=new c(r);return e.#n(i,l.load)}static openInMemory(){return e.#t(l.load)}static _openInMemoryWithLoader(t){return e.#t(t)}static#t(t){let n=new c(`:memory:`);return e.#n(n,t)}static#n(t,n){try{n(t)}catch(e){throw new P(`Failed to load sqlite-vec extension`,{cause:e})}t.pragma(`journal_mode = WAL`),t.pragma(`foreign_keys = ON`);let r=new e(t);return r.#r(),r}#r(){this.#e.exec(`
|
|
170
147
|
CREATE TABLE IF NOT EXISTS meta (
|
|
171
148
|
key TEXT PRIMARY KEY,
|
|
172
149
|
value TEXT NOT NULL
|
|
173
150
|
);
|
|
174
|
-
`);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.#db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)").run(String(targetVersion));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
get db() {
|
|
183
|
-
return this.#db;
|
|
184
|
-
}
|
|
185
|
-
close() {
|
|
186
|
-
this.#db.close();
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/schemas.ts
|
|
191
|
-
const MEMORY_TYPE_VALUES = [
|
|
192
|
-
"correction",
|
|
193
|
-
"preference",
|
|
194
|
-
"decision",
|
|
195
|
-
"learning",
|
|
196
|
-
"fact"
|
|
197
|
-
];
|
|
198
|
-
const MemoryTypeSchema = z.enum(MEMORY_TYPE_VALUES);
|
|
199
|
-
const TagsJsonSchema = z.array(z.string());
|
|
200
|
-
const ProjectSchema = z.object({
|
|
201
|
-
id: z.string(),
|
|
202
|
-
name: z.string(),
|
|
203
|
-
scopeHash: z.string(),
|
|
204
|
-
createdAt: z.string(),
|
|
205
|
-
updatedAt: z.string()
|
|
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
|
-
});
|
|
228
|
-
const MemorySchema = z.object({
|
|
229
|
-
id: z.string(),
|
|
230
|
-
content: z.string(),
|
|
231
|
-
type: MemoryTypeSchema,
|
|
232
|
-
tags: z.array(z.string()),
|
|
233
|
-
projects: z.array(ProjectSchema),
|
|
234
|
-
sourceHarness: z.string().nullable(),
|
|
235
|
-
accessCount: z.number().int().nonnegative(),
|
|
236
|
-
pinned: z.boolean(),
|
|
237
|
-
reviewEvents: z.array(ReviewEventSchema),
|
|
238
|
-
createdAt: z.string(),
|
|
239
|
-
updatedAt: z.string()
|
|
240
|
-
});
|
|
241
|
-
const QueryOptionsSchema = z.object({
|
|
242
|
-
query: z.string().min(1),
|
|
243
|
-
type: MemoryTypeSchema.optional(),
|
|
244
|
-
projectHash: z.string().optional(),
|
|
245
|
-
limit: z.number().int().positive().optional(),
|
|
246
|
-
includePinned: z.boolean().optional()
|
|
247
|
-
});
|
|
248
|
-
const SaveOptionsSchema = z.object({
|
|
249
|
-
content: z.string().min(1),
|
|
250
|
-
type: MemoryTypeSchema,
|
|
251
|
-
tags: z.array(z.string()).optional(),
|
|
252
|
-
projectScope: z.object({
|
|
253
|
-
hash: z.string(),
|
|
254
|
-
name: z.string()
|
|
255
|
-
}).optional(),
|
|
256
|
-
sourceHarness: z.string().optional()
|
|
257
|
-
});
|
|
258
|
-
const MemoryPatchSchema = z.object({
|
|
259
|
-
content: z.string().min(1).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()
|
|
273
|
-
});
|
|
274
|
-
const SessionContextSchema = z.object({
|
|
275
|
-
stats: z.record(MemoryTypeSchema, z.number()),
|
|
276
|
-
pinnedGlobal: z.array(MemorySchema),
|
|
277
|
-
pinnedProject: z.array(MemorySchema),
|
|
278
|
-
synthesis: z.string().optional()
|
|
279
|
-
});
|
|
280
|
-
const MemoryRowSchema = z.object({
|
|
281
|
-
id: z.string(),
|
|
282
|
-
content: z.string(),
|
|
283
|
-
type: z.string(),
|
|
284
|
-
tags: z.string(),
|
|
285
|
-
source: z.string().nullable(),
|
|
286
|
-
access_count: z.number(),
|
|
287
|
-
pinned: z.number(),
|
|
288
|
-
created_at: z.string(),
|
|
289
|
-
updated_at: z.string()
|
|
290
|
-
});
|
|
291
|
-
const ProjectRowSchema = z.object({
|
|
292
|
-
id: z.string(),
|
|
293
|
-
name: z.string(),
|
|
294
|
-
scope_hash: z.string(),
|
|
295
|
-
created_at: z.string(),
|
|
296
|
-
updated_at: z.string()
|
|
297
|
-
});
|
|
298
|
-
//#endregion
|
|
299
|
-
//#region src/db/row-types.ts
|
|
300
|
-
function rowToMemory(row, projects, reviewEvents = []) {
|
|
301
|
-
return {
|
|
302
|
-
id: row.id,
|
|
303
|
-
content: row.content,
|
|
304
|
-
type: MemoryTypeSchema.parse(row.type),
|
|
305
|
-
tags: TagsJsonSchema.parse(JSON.parse(row.tags)),
|
|
306
|
-
projects,
|
|
307
|
-
sourceHarness: row.source,
|
|
308
|
-
accessCount: row.access_count,
|
|
309
|
-
pinned: row.pinned !== 0,
|
|
310
|
-
reviewEvents,
|
|
311
|
-
createdAt: row.created_at,
|
|
312
|
-
updatedAt: row.updated_at
|
|
313
|
-
};
|
|
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
|
-
}
|
|
328
|
-
function rowToProject(row) {
|
|
329
|
-
return {
|
|
330
|
-
id: row.id,
|
|
331
|
-
name: row.name,
|
|
332
|
-
scopeHash: row.scope_hash,
|
|
333
|
-
createdAt: row.created_at,
|
|
334
|
-
updatedAt: row.updated_at
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
//#endregion
|
|
338
|
-
//#region src/embedding/service.ts
|
|
339
|
-
var EmbeddingService = class {
|
|
340
|
-
modelCachePath;
|
|
341
|
-
onProgress;
|
|
342
|
-
pipelineInstance = null;
|
|
343
|
-
constructor(modelCachePath, onProgress) {
|
|
344
|
-
this.modelCachePath = modelCachePath ?? join(homedir(), ".membank", "models");
|
|
345
|
-
this.onProgress = onProgress;
|
|
346
|
-
}
|
|
347
|
-
async getPipeline() {
|
|
348
|
-
if (this.pipelineInstance === null) this.pipelineInstance = await pipeline("feature-extraction", "Xenova/bge-small-en-v1.5", {
|
|
349
|
-
cache_dir: this.modelCachePath,
|
|
350
|
-
progress_callback: this.onProgress
|
|
351
|
-
});
|
|
352
|
-
return this.pipelineInstance;
|
|
353
|
-
}
|
|
354
|
-
async embed(text) {
|
|
355
|
-
const flat = (await (await this.getPipeline())(text, {
|
|
356
|
-
pooling: "mean",
|
|
357
|
-
normalize: true
|
|
358
|
-
})).data;
|
|
359
|
-
return flat instanceof Float32Array ? flat : new Float32Array(flat);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
//#endregion
|
|
363
|
-
//#region src/memory/repository.ts
|
|
364
|
-
const PIN_BUDGET_THRESHOLD = 8e3;
|
|
365
|
-
var MemoryRepository = class {
|
|
366
|
-
#db;
|
|
367
|
-
#embedding;
|
|
368
|
-
#projects;
|
|
369
|
-
constructor(db, embeddingService, projects) {
|
|
370
|
-
this.#db = db;
|
|
371
|
-
this.#embedding = embeddingService;
|
|
372
|
-
this.#projects = projects;
|
|
373
|
-
}
|
|
374
|
-
async save(options) {
|
|
375
|
-
const { content, type, tags = [], projectScope, sourceHarness } = SaveOptionsSchema.parse(options);
|
|
376
|
-
const embedding = await this.#embedding.embed(content);
|
|
377
|
-
const embeddingBlob = Buffer.from(embedding.buffer);
|
|
378
|
-
let top;
|
|
379
|
-
if (projectScope !== void 0) top = this.#db.db.prepare(`SELECT m.rowid, m.*, (1 - vec_distance_cosine(e.embedding, ?)) AS similarity
|
|
151
|
+
`);let e=this.#e.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get(),t=e?Number.parseInt(e.value,10):0;for(let[e,n]of F)t<e&&(this.#e.exec(n),this.#e.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)`).run(String(e)))}get db(){return this.#e}close(){this.#e.close()}};const I=`Xenova/bge-small-en-v1.5`;var L=class extends Error{constructor(e,t){super(e,t),this.name=`ModelDownloadError`}};function ce(){return o(i(),`.membank`,`models`)}function R(t){if(!e(t))return!1;try{return r(t).length>0}catch{return!1}}var z=class extends u{modelPath;constructor(e){super(),this.modelPath=e??ce()}isAlreadyCached(){return R(this.modelPath)}get cachePath(){return this.modelPath}async download(){if(R(this.modelPath))return{skipped:!0};let e=Date.now(),t=0,n=e;try{await d(`feature-extraction`,I,{cache_dir:this.modelPath,progress_callback:e=>{if(e.status!==`progress`||e.total==null||e.loaded==null)return;let r=e.total,i=e.loaded,a=r>0?i/r*100:0,o=Date.now(),s=o-n,c=i-t,l=0;if(s>0&&c>0){let e=c/s;l=(r-i)/e/1e3}t=i,n=o;let u={totalBytes:r,downloadedBytes:i,percentage:a,estimatedSecondsRemaining:l};this.emit(`progress`,u)}})}catch(e){throw new L(`Failed to download model`,{cause:e})}return{skipped:!1}}},B=class{modelCachePath;onProgress;pipelineInstance=null;constructor(e,t){this.modelCachePath=e??o(i(),`.membank`,`models`),this.onProgress=t}async getPipeline(){return this.pipelineInstance===null&&(this.pipelineInstance=await d(`feature-extraction`,`Xenova/bge-small-en-v1.5`,{cache_dir:this.modelCachePath,progress_callback:this.onProgress})),this.pipelineInstance}async embed(e){let t=(await(await this.getPipeline())(e,{pooling:`mean`,normalize:!0})).data;return t instanceof Float32Array?t:new Float32Array(t)}};function V(e,t){return t.delete(e),Promise.resolve()}function H(e,t){t.resolveReviewEvents(e)}function U(e){return e>.92?`overwrite`:e>=.75?`flag`:`none`}async function W(e,t){let{content:n,type:r,tags:i=[],projectScope:a,sourceHarness:o}=T.parse(e),{repo:s,embedder:c}=t,l=await c.embed(n),[u]=s.findSimilar(l,r,a?.hash);if(u!==void 0){let e=U(u.similarity);if(e===`overwrite`)return s.overwrite(u.id,n,l);if(e===`flag`){let e=s.create({id:p(),content:n,type:r,tags:i,sourceHarness:o??null,embedding:l,projectScope:a});return s.createReviewEvent({memoryId:u.id,conflictingMemoryId:e.id,similarity:u.similarity,conflictContentSnapshot:n}),e}}return s.create({id:p(),content:n,type:r,tags:i,sourceHarness:o??null,embedding:l,projectScope:a})}async function G(e,t,n){let{repo:r,embedder:i}=n,a=E.parse(t),o=a.content===void 0?void 0:await i.embed(a.content);return r.update(e,a,o)}const K=8e3;function le(e){return e>=K}var q=class{#e;#t;constructor(e,t){this.#e=e,this.#t=t}findSimilar(e,t,n){let r=Buffer.from(e.buffer),i;return i=n===void 0?this.#e.db.prepare(`SELECT m.rowid, m.*, (1 - vec_distance_cosine(e.embedding, ?)) AS similarity
|
|
152
|
+
FROM memories m JOIN embeddings e ON e.rowid = m.rowid
|
|
153
|
+
WHERE m.type = ?
|
|
154
|
+
AND m.id NOT IN (SELECT memory_id FROM memory_projects)
|
|
155
|
+
ORDER BY similarity DESC LIMIT 1`).get(r,t):this.#e.db.prepare(`SELECT m.rowid, m.*, (1 - vec_distance_cosine(e.embedding, ?)) AS similarity
|
|
380
156
|
FROM memories m JOIN embeddings e ON e.rowid = m.rowid
|
|
381
157
|
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
382
158
|
JOIN projects p ON p.id = mp.project_id
|
|
383
159
|
WHERE m.type = ? AND p.scope_hash = ?
|
|
384
|
-
ORDER BY similarity DESC LIMIT 1`).get(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (top !== void 0 && top.similarity > .92) {
|
|
392
|
-
this.#db.db.prepare(`UPDATE memories SET content = ?, updated_at = ? WHERE id = ?`).run(content, now, top.id);
|
|
393
|
-
this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, top.rowid);
|
|
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) ?? []);
|
|
398
|
-
}
|
|
399
|
-
const id = randomUUID();
|
|
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);
|
|
405
|
-
this.#db.db.prepare(`INSERT INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`).run(embeddingBlob, id);
|
|
406
|
-
if (projectScope !== void 0) {
|
|
407
|
-
const project = this.#projects.upsertByHash(projectScope.hash, projectScope.name);
|
|
408
|
-
this.#projects.addAssociation(id, project.id);
|
|
409
|
-
}
|
|
410
|
-
return rowToMemory(MemoryRowSchema.parse(this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id)), this.#projects.getProjectsForMemories([id]).get(id) ?? [], []);
|
|
411
|
-
}
|
|
412
|
-
async update(id, patch) {
|
|
413
|
-
const { content, tags, type } = MemoryPatchSchema.parse(patch);
|
|
414
|
-
const existing = this.#db.db.prepare(`SELECT m.rowid, m.* FROM memories m WHERE m.id = ?`).get(id);
|
|
415
|
-
if (existing === void 0) throw new Error(`Memory not found: ${id}`);
|
|
416
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
417
|
-
const sets = ["updated_at = ?"];
|
|
418
|
-
const values = [now];
|
|
419
|
-
if (content !== void 0) {
|
|
420
|
-
sets.push("content = ?");
|
|
421
|
-
values.push(content);
|
|
422
|
-
}
|
|
423
|
-
if (tags !== void 0) {
|
|
424
|
-
sets.push("tags = ?");
|
|
425
|
-
values.push(JSON.stringify(tags));
|
|
426
|
-
}
|
|
427
|
-
if (type !== void 0) {
|
|
428
|
-
sets.push("type = ?");
|
|
429
|
-
values.push(type);
|
|
430
|
-
}
|
|
431
|
-
values.push(id);
|
|
432
|
-
this.#db.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
433
|
-
if (content !== void 0) {
|
|
434
|
-
const embedding = await this.#embedding.embed(content);
|
|
435
|
-
const embeddingBlob = Buffer.from(embedding.buffer);
|
|
436
|
-
this.#db.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(embeddingBlob, existing.rowid);
|
|
437
|
-
}
|
|
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) ?? []);
|
|
442
|
-
}
|
|
443
|
-
delete(id) {
|
|
444
|
-
const row = this.#db.db.prepare(`SELECT rowid FROM memories WHERE id = ?`).get(id);
|
|
445
|
-
if (row !== void 0) this.#db.db.prepare(`DELETE FROM embeddings WHERE rowid = ?`).run(row.rowid);
|
|
446
|
-
this.#db.db.prepare(`DELETE FROM memory_projects WHERE memory_id = ?`).run(id);
|
|
447
|
-
this.#db.db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
|
|
448
|
-
return Promise.resolve();
|
|
449
|
-
}
|
|
450
|
-
list(opts) {
|
|
451
|
-
const conditions = [];
|
|
452
|
-
const params = [];
|
|
453
|
-
if (opts?.type !== void 0) {
|
|
454
|
-
conditions.push("type = ?");
|
|
455
|
-
params.push(opts.type);
|
|
456
|
-
}
|
|
457
|
-
if (opts?.pinned === true) conditions.push("pinned = 1");
|
|
458
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
459
|
-
const rows = this.#db.db.prepare(`SELECT * FROM memories ${where} ORDER BY created_at DESC`).all(...params);
|
|
460
|
-
if (rows.length === 0) return [];
|
|
461
|
-
const ids = rows.map((r) => r.id);
|
|
462
|
-
const projectMap = this.#projects.getProjectsForMemories(ids);
|
|
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
|
|
160
|
+
ORDER BY similarity DESC LIMIT 1`).get(r,t,n),i===void 0?[]:[{id:i.id,similarity:i.similarity}]}create(e){let{id:t,content:n,type:r,tags:i,sourceHarness:a,embedding:o,projectScope:s}=e,c=new Date().toISOString(),l=Buffer.from(o.buffer);if(this.#e.db.prepare(`INSERT INTO memories (id, content, type, tags, source, access_count, pinned, created_at, updated_at)
|
|
161
|
+
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?)`).run(t,n,r,JSON.stringify(i),a,c,c),this.#e.db.prepare(`INSERT INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`).run(l,t),s!==void 0){let e=this.#t.upsertByHash(s.hash,s.name);this.#t.addAssociation(t,e.id)}return A(O.parse(this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(t)),this.#t.getProjectsForMemories([t]).get(t)??[])}overwrite(e,t,n){let r=new Date().toISOString(),i=Buffer.from(n.buffer);this.#e.db.prepare(`UPDATE memories SET content = ?, updated_at = ? WHERE id = ?`).run(t,r,e);let a=this.#e.db.prepare(`SELECT rowid FROM memories WHERE id = ?`).get(e)?.rowid;a!==void 0&&this.#e.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(i,a);let o=O.parse(this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(e)),s=this.#t.getProjectsForMemories([e]),c=this.#n([e]);return A(o,s.get(e)??[],c.get(e)??[])}findById(e){let t=this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(e);if(t===void 0)return;let n=this.#t.getProjectsForMemories([e]),r=this.#n([e]);return A(t,n.get(e)??[],r.get(e)??[])}update(e,t,n){let{content:r,tags:i,type:a}=E.parse(t),o=this.#e.db.prepare(`SELECT m.rowid, m.* FROM memories m WHERE m.id = ?`).get(e);if(o===void 0)throw Error(`Memory not found: ${e}`);let s=new Date().toISOString(),c=[`updated_at = ?`],l=[s];if(r!==void 0&&(c.push(`content = ?`),l.push(r)),i!==void 0&&(c.push(`tags = ?`),l.push(JSON.stringify(i))),a!==void 0&&(c.push(`type = ?`),l.push(a)),l.push(e),this.#e.db.prepare(`UPDATE memories SET ${c.join(`, `)} WHERE id = ?`).run(...l),n!==void 0){let e=Buffer.from(n.buffer);this.#e.db.prepare(`UPDATE embeddings SET embedding = ? WHERE rowid = ?`).run(e,o.rowid)}let u=O.parse(this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(e)),d=this.#t.getProjectsForMemories([e]),f=this.#n([e]);return A(u,d.get(e)??[],f.get(e)??[])}delete(e){let t=this.#e.db.prepare(`SELECT rowid FROM memories WHERE id = ?`).get(e);t!==void 0&&this.#e.db.prepare(`DELETE FROM embeddings WHERE rowid = ?`).run(t.rowid),this.#e.db.prepare(`DELETE FROM memory_projects WHERE memory_id = ?`).run(e),this.#e.db.prepare(`DELETE FROM memories WHERE id = ?`).run(e)}list(e){let t=[],n=[];e?.type!==void 0&&(t.push(`m.type = ?`),n.push(e.type)),e?.pinned===!0&&t.push(`m.pinned = 1`),e?.needsReview===!0&&t.push(`EXISTS (SELECT 1 FROM memory_review_events e WHERE e.memory_id = m.id AND e.resolved_at IS NULL)`),e?.projectId===`global`?t.push(`m.id NOT IN (SELECT memory_id FROM memory_projects)`):e?.projectId!==void 0&&(t.push(`m.id IN (SELECT memory_id FROM memory_projects WHERE project_id = ?)`),n.push(e.projectId));let r=t.length>0?`WHERE ${t.join(` AND `)}`:``,i=this.#e.db.prepare(`SELECT m.* FROM memories m ${r} ORDER BY m.created_at DESC`).all(...n);if(i.length===0)return[];let a=i.map(e=>e.id),o=this.#t.getProjectsForMemories(a),s=this.#n(a);return i.map(e=>A(e,o.get(e.id)??[],s.get(e.id)??[]))}listPinnedGlobal(){return this.#e.db.prepare(`SELECT * FROM memories
|
|
162
|
+
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
163
|
+
AND pinned = 1`).all().map(e=>A(e,[]))}listPinnedForProject(e){return this.#e.db.prepare(`SELECT m.* FROM memories m
|
|
164
|
+
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
165
|
+
JOIN projects p ON p.id = mp.project_id
|
|
166
|
+
WHERE p.scope_hash = ? AND m.pinned = 1`).all(e).map(e=>A(e,[]))}listFlagged(){let e=this.#e.db.prepare(`SELECT * FROM memories
|
|
468
167
|
WHERE EXISTS (
|
|
469
168
|
SELECT 1 FROM memory_review_events e
|
|
470
169
|
WHERE e.memory_id = memories.id AND e.resolved_at IS NULL
|
|
471
170
|
)
|
|
472
|
-
ORDER BY created_at DESC`).all();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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;
|
|
505
|
-
}
|
|
506
|
-
stats() {
|
|
507
|
-
const byType = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
|
|
508
|
-
const typeRows = this.#db.db.prepare(`SELECT type, COUNT(*) as count FROM memories GROUP BY type`).all();
|
|
509
|
-
for (const row of typeRows) {
|
|
510
|
-
const parsed = MemoryTypeSchema.safeParse(row.type);
|
|
511
|
-
if (parsed.success) byType[parsed.data] = row.count;
|
|
512
|
-
}
|
|
513
|
-
const aggregates = this.#db.db.prepare(`SELECT COUNT(*) as total, SUM(pinned) as pinned FROM memories`).get() ?? {
|
|
514
|
-
total: 0,
|
|
515
|
-
pinned: 0
|
|
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 };
|
|
518
|
-
return {
|
|
519
|
-
byType,
|
|
520
|
-
total: aggregates.total,
|
|
521
|
-
pinned: aggregates.pinned ?? 0,
|
|
522
|
-
needsReview: reviewRow.needsReview,
|
|
523
|
-
pinBudgetChars: this.getPinnedCharCount()
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
setPin(id, pinned) {
|
|
527
|
-
if (this.#db.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id) === void 0) throw new Error(`Memory not found: ${id}`);
|
|
528
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
529
|
-
this.#db.db.prepare(`UPDATE memories SET pinned = ?, updated_at = ? WHERE id = ?`).run(pinned ? 1 : 0, now, id);
|
|
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) ?? []);
|
|
534
|
-
}
|
|
535
|
-
incrementAccessCount(id) {
|
|
536
|
-
this.#db.db.prepare(`UPDATE memories SET access_count = access_count + 1 WHERE id = ?`).run(id);
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
//#endregion
|
|
540
|
-
//#region src/scope/resolver.ts
|
|
541
|
-
const execFileAsync = promisify(execFile);
|
|
542
|
-
function sha256Truncated(input) {
|
|
543
|
-
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
544
|
-
}
|
|
545
|
-
async function resolveProject() {
|
|
546
|
-
try {
|
|
547
|
-
const { stdout } = await execFileAsync("git", [
|
|
548
|
-
"remote",
|
|
549
|
-
"get-url",
|
|
550
|
-
"origin"
|
|
551
|
-
]);
|
|
552
|
-
const url = stdout.trim();
|
|
553
|
-
if (url) {
|
|
554
|
-
const hash = sha256Truncated(url);
|
|
555
|
-
return {
|
|
556
|
-
hash,
|
|
557
|
-
name: url.split("/").pop()?.replace(/\.git$/, "") ?? hash.slice(0, 8)
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
} catch {}
|
|
561
|
-
const cwd = process.cwd();
|
|
562
|
-
const hash = sha256Truncated(cwd);
|
|
563
|
-
return {
|
|
564
|
-
hash,
|
|
565
|
-
name: cwd.split(/[/\\]/).filter(Boolean).pop() ?? hash.slice(0, 8)
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
async function resolveScope() {
|
|
569
|
-
try {
|
|
570
|
-
const { stdout } = await execFileAsync("git", [
|
|
571
|
-
"remote",
|
|
572
|
-
"get-url",
|
|
573
|
-
"origin"
|
|
574
|
-
]);
|
|
575
|
-
const url = stdout.trim();
|
|
576
|
-
if (url) return sha256Truncated(url);
|
|
577
|
-
} catch {}
|
|
578
|
-
return sha256Truncated(process.cwd());
|
|
579
|
-
}
|
|
580
|
-
//#endregion
|
|
581
|
-
//#region src/migrations/index.ts
|
|
582
|
-
const MIGRATIONS = [{
|
|
583
|
-
name: "scope-to-projects",
|
|
584
|
-
description: "Rename the auto-migrated project for the current directory from its generic hash-derived name to the resolved repo/directory name."
|
|
585
|
-
}];
|
|
586
|
-
async function runScopeToProjectsMigration(projects) {
|
|
587
|
-
const resolved = await resolveProject();
|
|
588
|
-
const project = projects.getByHash(resolved.hash);
|
|
589
|
-
if (project === void 0) return null;
|
|
590
|
-
const oldName = project.name;
|
|
591
|
-
const memoryCount = projects.countMemories(project.id);
|
|
592
|
-
projects.rename(project.id, resolved.name);
|
|
593
|
-
return {
|
|
594
|
-
migration: "scope-to-projects",
|
|
595
|
-
oldName,
|
|
596
|
-
newName: resolved.name,
|
|
597
|
-
memoryCount
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
//#endregion
|
|
601
|
-
//#region src/project/repository.ts
|
|
602
|
-
var ProjectRepository = class {
|
|
603
|
-
#db;
|
|
604
|
-
constructor(db) {
|
|
605
|
-
this.#db = db;
|
|
606
|
-
}
|
|
607
|
-
upsertByHash(hash, name) {
|
|
608
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
609
|
-
const id = randomUUID();
|
|
610
|
-
this.#db.db.prepare(`INSERT OR IGNORE INTO projects (id, name, scope_hash, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`).run(id, name, hash, now, now);
|
|
611
|
-
return rowToProject(ProjectRowSchema.parse(this.#db.db.prepare(`SELECT * FROM projects WHERE scope_hash = ?`).get(hash)));
|
|
612
|
-
}
|
|
613
|
-
rename(id, name) {
|
|
614
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
615
|
-
this.#db.db.prepare(`UPDATE projects SET name = ?, updated_at = ? WHERE id = ?`).run(name, now, id);
|
|
616
|
-
const row = this.#db.db.prepare(`SELECT * FROM projects WHERE id = ?`).get(id);
|
|
617
|
-
if (row === void 0) throw new Error(`Project not found: ${id}`);
|
|
618
|
-
return rowToProject(row);
|
|
619
|
-
}
|
|
620
|
-
list() {
|
|
621
|
-
return this.#db.db.prepare(`SELECT * FROM projects ORDER BY name ASC`).all().map(rowToProject);
|
|
622
|
-
}
|
|
623
|
-
getByHash(hash) {
|
|
624
|
-
const row = this.#db.db.prepare(`SELECT * FROM projects WHERE scope_hash = ?`).get(hash);
|
|
625
|
-
return row !== void 0 ? rowToProject(row) : void 0;
|
|
626
|
-
}
|
|
627
|
-
getByName(name) {
|
|
628
|
-
const row = this.#db.db.prepare(`SELECT * FROM projects WHERE name = ? LIMIT 1`).get(name);
|
|
629
|
-
return row !== void 0 ? rowToProject(row) : void 0;
|
|
630
|
-
}
|
|
631
|
-
addAssociation(memoryId, projectId) {
|
|
632
|
-
this.#db.db.prepare(`INSERT OR IGNORE INTO memory_projects (memory_id, project_id) VALUES (?, ?)`).run(memoryId, projectId);
|
|
633
|
-
}
|
|
634
|
-
removeAssociation(memoryId, projectId) {
|
|
635
|
-
this.#db.db.prepare(`DELETE FROM memory_projects WHERE memory_id = ? AND project_id = ?`).run(memoryId, projectId);
|
|
636
|
-
}
|
|
637
|
-
countMemories(projectId) {
|
|
638
|
-
return this.#db.db.prepare(`SELECT COUNT(*) AS count FROM memory_projects WHERE project_id = ?`).get(projectId)?.count ?? 0;
|
|
639
|
-
}
|
|
640
|
-
getProjectsForMemories(memoryIds) {
|
|
641
|
-
if (memoryIds.length === 0) return /* @__PURE__ */ new Map();
|
|
642
|
-
const placeholders = memoryIds.map(() => "?").join(",");
|
|
643
|
-
const rows = this.#db.db.prepare(`SELECT p.*, mp.memory_id FROM projects p
|
|
171
|
+
ORDER BY created_at DESC`).all();if(e.length===0)return[];let t=e.map(e=>e.id),n=this.#t.getProjectsForMemories(t),r=this.#n(t,{unresolvedOnly:!0});return e.map(e=>A(e,n.get(e.id)??[],r.get(e.id)??[]))}listReviewEvents(e,t){let n=t?.unresolvedOnly===!0?`WHERE memory_id = ? AND resolved_at IS NULL`:`WHERE memory_id = ?`;return this.#e.db.prepare(`SELECT * FROM memory_review_events ${n} ORDER BY created_at DESC`).all(e).map(e=>j(S.parse(e)))}createReviewEvent(e){let t=new Date().toISOString();this.#e.db.prepare(`INSERT INTO memory_review_events
|
|
172
|
+
(id, memory_id, conflicting_memory_id, similarity, conflict_content_snapshot, reason, created_at)
|
|
173
|
+
VALUES (?, ?, ?, ?, ?, 'similarity_dedup', ?)`).run(p(),e.memoryId,e.conflictingMemoryId,e.similarity,e.conflictContentSnapshot,t)}resolveReviewEvents(e){let t=new Date().toISOString();this.#e.db.prepare(`UPDATE memory_review_events SET resolved_at = ? WHERE memory_id = ? AND resolved_at IS NULL`).run(t,e)}getPinnedCharCount(){return(this.#e.db.prepare(`SELECT COALESCE(SUM(LENGTH(content)), 0) as total FROM memories WHERE pinned = 1`).get()??{total:0}).total}stats(){let e=Object.fromEntries(g.map(e=>[e,0])),t=this.#e.db.prepare(`SELECT type, COUNT(*) as count FROM memories GROUP BY type`).all();for(let n of t){let t=_.safeParse(n.type);t.success&&(e[t.data]=n.count)}let n=this.#e.db.prepare(`SELECT COUNT(*) as total, SUM(pinned) as pinned FROM memories`).get()??{total:0,pinned:0},r=this.#e.db.prepare(`SELECT COUNT(DISTINCT memory_id) as needsReview FROM memory_review_events WHERE resolved_at IS NULL`).get()??{needsReview:0};return{byType:e,total:n.total,pinned:n.pinned??0,needsReview:r.needsReview,pinBudgetChars:this.getPinnedCharCount()}}setPin(e,t){if(this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(e)===void 0)throw Error(`Memory not found: ${e}`);let n=new Date().toISOString();this.#e.db.prepare(`UPDATE memories SET pinned = ?, updated_at = ? WHERE id = ?`).run(+!!t,n,e);let r=O.parse(this.#e.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(e)),i=this.#t.getProjectsForMemories([e]),a=this.#n([e]);return A(r,i.get(e)??[],a.get(e)??[])}incrementAccessCount(e){this.#e.db.prepare(`UPDATE memories SET access_count = access_count + 1 WHERE id = ?`).run(e)}exportAll(){return this.#e.db.prepare(`SELECT m.*, e.embedding FROM memories m LEFT JOIN embeddings e ON e.rowid = m.rowid ORDER BY m.created_at DESC`).all().map(e=>({id:e.id,content:e.content,type:_.parse(e.type),tags:v.parse(JSON.parse(e.tags)),sourceHarness:e.source,accessCount:e.access_count,pinned:e.pinned!==0,createdAt:e.created_at,updatedAt:e.updated_at,embedding:e.embedding===null?null:new Float32Array(e.embedding.buffer,e.embedding.byteOffset,e.embedding.byteLength/4)}))}importAll(e){let t=this.#e.db.prepare(`INSERT OR REPLACE INTO memories (id, content, type, tags, source, access_count, pinned, created_at, updated_at)
|
|
174
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),n=this.#e.db.prepare(`INSERT OR REPLACE INTO embeddings (rowid, embedding) SELECT m.rowid, ? FROM memories m WHERE m.id = ?`);this.#e.db.transaction(()=>{for(let r of e)t.run(r.id,r.content,r.type,JSON.stringify(r.tags),r.sourceHarness,r.accessCount,+!!r.pinned,r.createdAt,r.updatedAt),r.embedding!==null&&n.run(Buffer.from(r.embedding.buffer,r.embedding.byteOffset,r.embedding.byteLength),r.id)})()}#n(e,t){if(e.length===0)return new Map;let n=e.map(()=>`?`).join(`, `),r=t?.unresolvedOnly===!0?`AND resolved_at IS NULL`:``,i=this.#e.db.prepare(`SELECT * FROM memory_review_events
|
|
175
|
+
WHERE memory_id IN (${n}) ${r}
|
|
176
|
+
ORDER BY created_at DESC`).all(...e),a=new Map;for(let e of i){let t=j(S.parse(e)),n=a.get(t.memoryId)??[];n.push(t),a.set(t.memoryId,n)}return a}};function ue(e,t){return new q(e,t)}const J=te(ee);function Y(e){return f(`sha256`).update(e).digest(`hex`).slice(0,16)}async function X(){try{let{stdout:e}=await J(`git`,[`remote`,`get-url`,`origin`]),t=e.trim();if(t){let e=Y(t);return{hash:e,name:t.split(`/`).pop()?.replace(/\.git$/,``)??e.slice(0,8)}}}catch{}let e=process.cwd(),t=Y(e);return{hash:t,name:e.split(/[/\\]/).filter(Boolean).pop()??t.slice(0,8)}}async function de(){try{let{stdout:e}=await J(`git`,[`remote`,`get-url`,`origin`]),t=e.trim();if(t)return Y(t)}catch{}return Y(process.cwd())}const fe=[{name:`scope-to-projects`,description:`Rename the auto-migrated project for the current directory from its generic hash-derived name to the resolved repo/directory name.`}];async function pe(e){let t=await X(),n=e.getByHash(t.hash);if(n===void 0)return null;let r=n.name,i=e.countMemories(n.id);return e.rename(n.id,t.name),{migration:`scope-to-projects`,oldName:r,newName:t.name,memoryCount:i}}var me=class{#e;constructor(e){this.#e=e}upsertByHash(e,t){if(!/^[0-9a-f]{16}$/.test(e))throw Error(`Invalid scope hash "${e}": expected 16 lowercase hex characters`);let n=new Date().toISOString(),r=p();return this.#e.db.prepare(`INSERT OR IGNORE INTO projects (id, name, scope_hash, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`).run(r,t,e,n,n),M(k.parse(this.#e.db.prepare(`SELECT * FROM projects WHERE scope_hash = ?`).get(e)))}rename(e,t){let n=new Date().toISOString();this.#e.db.prepare(`UPDATE projects SET name = ?, updated_at = ? WHERE id = ?`).run(t,n,e);let r=this.#e.db.prepare(`SELECT * FROM projects WHERE id = ?`).get(e);if(r===void 0)throw Error(`Project not found: ${e}`);return M(r)}list(){return this.#e.db.prepare(`SELECT * FROM projects ORDER BY name ASC`).all().map(M)}getByHash(e){let t=this.#e.db.prepare(`SELECT * FROM projects WHERE scope_hash = ?`).get(e);return t===void 0?void 0:M(t)}getByName(e){let t=this.#e.db.prepare(`SELECT * FROM projects WHERE name = ? LIMIT 1`).get(e);return t===void 0?void 0:M(t)}addAssociation(e,t){this.#e.db.prepare(`INSERT OR IGNORE INTO memory_projects (memory_id, project_id) VALUES (?, ?)`).run(e,t)}removeAssociation(e,t){this.#e.db.prepare(`DELETE FROM memory_projects WHERE memory_id = ? AND project_id = ?`).run(e,t)}countMemories(e){return this.#e.db.prepare(`SELECT COUNT(*) AS count FROM memory_projects WHERE project_id = ?`).get(e)?.count??0}getProjectsForMemories(e){if(e.length===0)return new Map;let t=e.map(()=>`?`).join(`,`),n=this.#e.db.prepare(`SELECT p.*, mp.memory_id FROM projects p
|
|
644
177
|
JOIN memory_projects mp ON mp.project_id = p.id
|
|
645
|
-
WHERE mp.memory_id IN (${
|
|
646
|
-
const result = /* @__PURE__ */ new Map();
|
|
647
|
-
for (const row of rows) {
|
|
648
|
-
const list = result.get(row.memory_id) ?? [];
|
|
649
|
-
list.push(rowToProject(row));
|
|
650
|
-
result.set(row.memory_id, list);
|
|
651
|
-
}
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
//#endregion
|
|
656
|
-
//#region src/query/engine.ts
|
|
657
|
-
const TYPE_WEIGHTS = {
|
|
658
|
-
correction: 1,
|
|
659
|
-
preference: .8,
|
|
660
|
-
decision: .6,
|
|
661
|
-
learning: .4,
|
|
662
|
-
fact: .2
|
|
663
|
-
};
|
|
664
|
-
var QueryEngine = class {
|
|
665
|
-
#db;
|
|
666
|
-
#embedding;
|
|
667
|
-
#repo;
|
|
668
|
-
constructor(db, embeddingService, repo) {
|
|
669
|
-
this.#db = db;
|
|
670
|
-
this.#embedding = embeddingService;
|
|
671
|
-
this.#repo = repo;
|
|
672
|
-
}
|
|
673
|
-
async query(options) {
|
|
674
|
-
const { query, type, projectHash, limit = 10, includePinned } = QueryOptionsSchema.parse(options);
|
|
675
|
-
const queryEmbedding = await this.#embedding.embed(query);
|
|
676
|
-
const queryBlob = Buffer.from(queryEmbedding.buffer);
|
|
677
|
-
const whereClauses = [];
|
|
678
|
-
const params = [queryBlob];
|
|
679
|
-
let joinClause = "";
|
|
680
|
-
if (!includePinned) whereClauses.push("m.pinned = 0");
|
|
681
|
-
if (type !== void 0) {
|
|
682
|
-
whereClauses.push("m.type = ?");
|
|
683
|
-
params.push(type);
|
|
684
|
-
}
|
|
685
|
-
if (projectHash !== void 0) {
|
|
686
|
-
joinClause = "LEFT JOIN memory_projects mp ON mp.memory_id = m.id LEFT JOIN projects p ON p.id = mp.project_id";
|
|
687
|
-
whereClauses.push("p.scope_hash = ?");
|
|
688
|
-
params.push(projectHash);
|
|
689
|
-
}
|
|
690
|
-
const whereSQL = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
691
|
-
const sql = `
|
|
178
|
+
WHERE mp.memory_id IN (${t})`).all(...e),r=new Map;for(let e of n){let t=r.get(e.memory_id)??[];t.push(M(e)),r.set(e.memory_id,t)}return r}};function he(e){return new me(e)}const ge={correction:1,preference:.8,decision:.6,learning:.4,fact:.2};function _e(e,t,n){let r=ge[e.type],i=e.accessCount/(e.accessCount+10),a=1/(1+(n-new Date(e.updatedAt).getTime())/864e5),o=+!!e.pinned;return t*.4+r*.25+i*.2+a*.1+o*.05}async function Z(e,t){let{query:n,type:r,projectHash:i,limit:a=10,includePinned:o}=w.parse(e),s=await t.embedder.embed(n),c=Buffer.from(s.buffer),l=t.adapter.findByEmbedding(c,{type:r,projectHash:i,includePinned:o}),u=Date.now(),d=l.filter(e=>e.cosineSim>0).map(e=>{let{cosineSim:t,...n}=e;return{...n,score:_e(n,t,u)}});d.sort((e,t)=>t.score-e.score);let f=d.slice(0,a);for(let e of f)t.repo.incrementAccessCount(e.id);return f}var ve=class{#e;constructor(e){this.#e=e}findByEmbedding(e,t){let{type:n,projectHash:r,includePinned:i}=t,a=[],o=[e],s=``;i||a.push(`m.pinned = 0`),n!==void 0&&(a.push(`m.type = ?`),o.push(n)),r!==void 0&&(s=`LEFT JOIN memory_projects mp ON mp.memory_id = m.id LEFT JOIN projects p ON p.id = mp.project_id`,a.push(`p.scope_hash = ?`),o.push(r));let c=a.length>0?`WHERE ${a.join(` AND `)}`:``,l=`
|
|
692
179
|
SELECT m.*, (1 - vec_distance_cosine(e.embedding, ?)) AS cosine_sim
|
|
693
180
|
FROM memories m JOIN embeddings e ON e.rowid = m.rowid
|
|
694
|
-
${
|
|
695
|
-
${
|
|
696
|
-
`;
|
|
697
|
-
const rows = this.#db.db.prepare(sql).all(...params);
|
|
698
|
-
const now = Date.now();
|
|
699
|
-
const scored = rows.filter((row) => row.cosine_sim > 0).map((row) => {
|
|
700
|
-
const memory = rowToMemory(row, []);
|
|
701
|
-
const score = this.#computeScore(memory, row.cosine_sim, now);
|
|
702
|
-
return {
|
|
703
|
-
...memory,
|
|
704
|
-
score
|
|
705
|
-
};
|
|
706
|
-
});
|
|
707
|
-
scored.sort((a, b) => b.score - a.score);
|
|
708
|
-
const results = scored.slice(0, limit);
|
|
709
|
-
for (const result of results) this.#repo.incrementAccessCount(result.id);
|
|
710
|
-
return results;
|
|
711
|
-
}
|
|
712
|
-
#computeScore(memory, cosine_sim, now) {
|
|
713
|
-
const typeWeight = TYPE_WEIGHTS[memory.type];
|
|
714
|
-
const accessCountNorm = memory.accessCount / (memory.accessCount + 10);
|
|
715
|
-
const recencyNorm = 1 / (1 + (now - new Date(memory.updatedAt).getTime()) / 864e5);
|
|
716
|
-
const pinned = memory.pinned ? 1 : 0;
|
|
717
|
-
return cosine_sim * .4 + typeWeight * .25 + accessCountNorm * .2 + recencyNorm * .1 + pinned * .05;
|
|
718
|
-
}
|
|
719
|
-
};
|
|
720
|
-
//#endregion
|
|
721
|
-
//#region src/session/builder.ts
|
|
722
|
-
function listMemoryTypes() {
|
|
723
|
-
return [...MEMORY_TYPE_VALUES];
|
|
724
|
-
}
|
|
725
|
-
var SessionContextBuilder = class {
|
|
726
|
-
#db;
|
|
727
|
-
constructor(db) {
|
|
728
|
-
this.#db = db;
|
|
729
|
-
}
|
|
730
|
-
getSessionContext(projectHash, synthesis) {
|
|
731
|
-
const typeCounts = this.#db.db.prepare("SELECT type, COUNT(*) as count FROM memories GROUP BY type").all();
|
|
732
|
-
const stats = Object.fromEntries(MEMORY_TYPE_VALUES.map((t) => [t, 0]));
|
|
733
|
-
for (const row of typeCounts) {
|
|
734
|
-
const parsed = MemoryTypeSchema.safeParse(row.type);
|
|
735
|
-
if (parsed.success) stats[parsed.data] = row.count;
|
|
736
|
-
}
|
|
737
|
-
if (synthesis !== void 0 && synthesis.length > 0) return {
|
|
738
|
-
stats,
|
|
739
|
-
pinnedGlobal: [],
|
|
740
|
-
pinnedProject: [],
|
|
741
|
-
synthesis
|
|
742
|
-
};
|
|
743
|
-
return {
|
|
744
|
-
stats,
|
|
745
|
-
pinnedGlobal: this.#db.db.prepare(`SELECT * FROM memories
|
|
746
|
-
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
747
|
-
AND pinned = 1`).all().map((row) => rowToMemory(row, [])),
|
|
748
|
-
pinnedProject: this.#db.db.prepare(`SELECT m.* FROM memories m
|
|
749
|
-
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
750
|
-
JOIN projects p ON p.id = mp.project_id
|
|
751
|
-
WHERE p.scope_hash = ? AND m.pinned = 1`).all(projectHash).map((row) => rowToMemory(row, []))
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
//#endregion
|
|
756
|
-
//#region src/synthesis/repository.ts
|
|
757
|
-
function rowToSynthesis(row) {
|
|
758
|
-
return SynthesisSchema.parse({
|
|
759
|
-
id: row.id,
|
|
760
|
-
scope: row.scope,
|
|
761
|
-
content: row.content,
|
|
762
|
-
sourceMemoryHash: row.source_memory_hash,
|
|
763
|
-
synthesizedAt: row.synthesized_at,
|
|
764
|
-
expiresAt: row.expires_at,
|
|
765
|
-
inFlightSince: row.in_flight_since,
|
|
766
|
-
createdAt: row.created_at,
|
|
767
|
-
updatedAt: row.updated_at
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
const STALENESS_DAYS = 30;
|
|
771
|
-
var SynthesisRepository = class {
|
|
772
|
-
#db;
|
|
773
|
-
constructor(db) {
|
|
774
|
-
this.#db = db;
|
|
775
|
-
}
|
|
776
|
-
saveSynthesis(scope, content, sourceHash) {
|
|
777
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
778
|
-
const expiresAt = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
|
|
779
|
-
if (this.#db.db.prepare("SELECT id FROM syntheses WHERE scope = ?").get(scope) !== void 0) this.#db.db.prepare(`UPDATE syntheses
|
|
181
|
+
${s}
|
|
182
|
+
${c}
|
|
183
|
+
`;return this.#e.db.prepare(l).all(...o).map(e=>({...A(e,[]),cosineSim:e.cosine_sim}))}},ye=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}async query(e){return Z(e,{adapter:new ve(this.#e),repo:this.#n,embedder:this.#t})}};function Q(e,t){let n=t.repo.stats();return e.synthesis!==void 0&&e.synthesis.length>0?{stats:n.byType,pinnedGlobal:[],pinnedProject:[],synthesis:e.synthesis}:{stats:n.byType,pinnedGlobal:t.repo.listPinnedGlobal(),pinnedProject:t.repo.listPinnedForProject(e.projectHash)}}function be(){return[...g]}var xe=class{#e;constructor(e){this.#e=e}getSessionContext(e,t){return Q({projectHash:e,synthesis:t},{repo:this.#e})}},Se=class{#e;#t;#n;#r=new Set;#i=new Map;#a=!1;#o;#s=new Map;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}async init(){this.#e.clearStaleInFlight(this.#t.inFlightTimeoutMs??12e4),this.#e.expireStale();let e=this.#e.getExpiredOrDirtyScopes();for(let{scope:t}of e)this.#r.add(t);this.#a=!0,await this.#l()}shutdown(){this.#a=!1,this.#o!==void 0&&(clearTimeout(this.#o),this.#o=void 0);let e=[...this.#s.values()];return e.length===0?Promise.resolve():Promise.race([Promise.allSettled(e).then(()=>void 0),new Promise(e=>setTimeout(e,5e3))])}markDirty(e){this.#r.add(e)}#c(){if(!this.#a)return;let e=this.#t.debounceMs??45e3;this.#o=setTimeout(()=>{this.#l()},e)}async#l(){let e=[...this.#r];for(let t of e){let e=this.#t.inFlightTimeoutMs??12e4,n=this.#e.getSynthesis(t);if(n?.inFlightSince!==null&&n?.inFlightSince!==void 0){if(Date.now()-new Date(n.inFlightSince).getTime()<e)continue;this.#e.clearInFlight(t)}this.#r.delete(t);let r=this.#u(t).finally(()=>{this.#s.delete(t)});this.#s.set(t,r)}this.#c()}async#u(e){this.#e.markInFlight(e);try{let t=e===`global`?void 0:e,n=await this.#n.run(e,t),r=this.#e.computeSourceMemoryHash(e);this.#e.saveSynthesis(e,n,r),this.#i.delete(e)}catch(t){let n=(this.#i.get(e)??0)+1;this.#i.set(e,n);let r=Math.min(n,5),i=(this.#t.debounceMs??45e3)*r;process.stderr.write(`membank synthesis: error for scope=${e} failures=${n} backoff=${i}ms: ${t instanceof Error?t.message:String(t)}\n`),setTimeout(()=>{this.#r.add(e)},i),this.#e.clearInFlight(e)}}};async function Ce(e,t){let n=e===`global`?void 0:e;t.synthRepo.markInFlight(e);try{let[r,i]=await Promise.all([t.agentRunner.run(e,n),Promise.resolve(t.synthRepo.computeSourceMemoryHash(e))]);return t.synthRepo.saveSynthesis(e,r,i),r}catch(n){throw t.synthRepo.clearInFlight(e),n}}var we=class{#e;constructor(e,t){this.#e=e}async run(e,t){let n=m({name:`membank-synthesis-tools`,version:`1.0.0`,tools:[h(`query_memory`,`Search memories by semantic similarity`,{query:s.string().describe(`Search text`),limit:s.number().optional().describe(`Maximum results to return`),global:s.boolean().optional().describe(`Query global memories only when true, otherwise current project scope`)},async({query:e,limit:n,global:r})=>({content:[{type:`text`,text:await this.#e.queryMemory({query:e,limit:n,global:r,projectHash:t})}]}),{annotations:{readOnlyHint:!0}}),h(`get_memory_summary`,`Returns aggregate stats: total memories, counts by type, pinned count, review queue size`,{},async()=>({content:[{type:`text`,text:await this.#e.getMemorySummary()}]}),{annotations:{readOnlyHint:!0}})]}),r=`Synthesize the memories for ${e===`global`?`global (across all projects)`:`project scope: ${e}`}. Use get_memory_summary first to understand the overall state, then use query_memory to retrieve relevant memories (query with broad terms like "preferences", "corrections", "decisions", "key facts"). After gathering information, produce a concise synthesis of the most important things to remember about this user. Output only the synthesis text — no preamble, no metadata.`,i=Date.now(),a=Object.fromEntries(Object.entries(process.env).filter(e=>e[1]!==void 0)),o=ne({prompt:r,options:{model:`claude-haiku-4-5-20251001`,systemPrompt:`You are a memory synthesizer. Your job is to read the user's stored memories and produce a concise, well-structured summary of what's most important to remember about this user — their preferences, corrections, decisions, and key facts. Pinned memories are higher fidelity and should be weighted more heavily. Exclude transient or ephemeral details. Output plain text suitable for injection into an LLM context window. Be concise — target 200-400 words.`,mcpServers:{"membank-synthesis-tools":n},allowedTools:[`query_memory`,`get_memory_summary`],permissionMode:`bypassPermissions`,env:a}}),c=``;for await(let e of o)if(e.type===`result`)if(e.subtype===`success`)c=e.result;else{let t=`errors`in e&&Array.isArray(e.errors)?`: ${e.errors.join(`; `)}`:``;throw Error(`Synthesis agent failed: ${e.subtype}${t}`)}let l=Date.now()-i;if(process.stderr.write(`membank synthesis: scope=${e} duration=${l}ms\n`),c===``)throw Error(`Synthesis agent returned empty result`);return c}};function Te(e,t){return new we(e,t)}function $(e){return D.parse({id:e.id,scope:e.scope,content:e.content,sourceMemoryHash:e.source_memory_hash,synthesizedAt:e.synthesized_at,expiresAt:e.expires_at,inFlightSince:e.in_flight_since,createdAt:e.created_at,updatedAt:e.updated_at})}var Ee=class{#e;constructor(e){this.#e=e}saveSynthesis(e,t,n){let r=new Date().toISOString(),i=new Date(Date.now()+720*60*60*1e3).toISOString();if(this.#e.db.prepare(`SELECT id FROM syntheses WHERE scope = ?`).get(e)!==void 0)this.#e.db.prepare(`UPDATE syntheses
|
|
780
184
|
SET content = ?, source_memory_hash = ?, synthesized_at = ?, expires_at = ?,
|
|
781
185
|
in_flight_since = NULL, updated_at = ?
|
|
782
|
-
WHERE scope = ?`).run(
|
|
783
|
-
else {
|
|
784
|
-
const id = randomUUID();
|
|
785
|
-
this.#db.db.prepare(`INSERT INTO syntheses
|
|
186
|
+
WHERE scope = ?`).run(t,n,r,i,r,e);else{let a=p();this.#e.db.prepare(`INSERT INTO syntheses
|
|
786
187
|
(id, scope, content, source_memory_hash, synthesized_at, expires_at,
|
|
787
188
|
in_flight_since, created_at, updated_at)
|
|
788
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(
|
|
789
|
-
}
|
|
790
|
-
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
791
|
-
if (row === void 0) throw new Error(`Failed to save synthesis for scope: ${scope}`);
|
|
792
|
-
return rowToSynthesis(row);
|
|
793
|
-
}
|
|
794
|
-
getSynthesis(scope) {
|
|
795
|
-
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
796
|
-
return row !== void 0 ? rowToSynthesis(row) : void 0;
|
|
797
|
-
}
|
|
798
|
-
markInFlight(scope) {
|
|
799
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
800
|
-
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);
|
|
801
|
-
else {
|
|
802
|
-
const id = randomUUID();
|
|
803
|
-
const placeholder = "pending";
|
|
804
|
-
const future = new Date(Date.now() + STALENESS_DAYS * 24 * 60 * 60 * 1e3).toISOString();
|
|
805
|
-
this.#db.db.prepare(`INSERT INTO syntheses
|
|
189
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(a,e,t,n,r,i,r,r)}let a=this.#e.db.prepare(`SELECT * FROM syntheses WHERE scope = ?`).get(e);if(a===void 0)throw Error(`Failed to save synthesis for scope: ${e}`);return $(a)}getSynthesis(e){let t=this.#e.db.prepare(`SELECT * FROM syntheses WHERE scope = ?`).get(e);return t===void 0?void 0:$(t)}listAll(){return this.#e.db.prepare(`SELECT * FROM syntheses ORDER BY scope`).all().map($)}markInFlight(e){let t=new Date().toISOString();if(this.#e.db.prepare(`SELECT id FROM syntheses WHERE scope = ?`).get(e)!==void 0)this.#e.db.prepare(`UPDATE syntheses SET in_flight_since = ?, updated_at = ? WHERE scope = ?`).run(t,t,e);else{let n=p(),r=new Date(Date.now()+720*60*60*1e3).toISOString();this.#e.db.prepare(`INSERT INTO syntheses
|
|
806
190
|
(id, scope, content, source_memory_hash, synthesized_at, expires_at,
|
|
807
191
|
in_flight_since, created_at, updated_at)
|
|
808
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
clearInFlight(scope) {
|
|
812
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
813
|
-
this.#db.db.prepare("UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE scope = ?").run(now, scope);
|
|
814
|
-
}
|
|
815
|
-
clearStaleInFlight(thresholdMs) {
|
|
816
|
-
const cutoff = new Date(Date.now() - thresholdMs).toISOString();
|
|
817
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
818
|
-
this.#db.db.prepare("UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE in_flight_since IS NOT NULL AND in_flight_since < ?").run(now, cutoff);
|
|
819
|
-
}
|
|
820
|
-
computeSourceMemoryHash(scope) {
|
|
821
|
-
let contents;
|
|
822
|
-
if (scope === "global") contents = this.#db.db.prepare(`SELECT content FROM memories
|
|
192
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(n,e,`pending`,``,t,r,t,t,t)}}clearInFlight(e){let t=new Date().toISOString();this.#e.db.prepare(`UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE scope = ?`).run(t,e)}clearStaleInFlight(e){let t=new Date(Date.now()-e).toISOString(),n=new Date().toISOString();this.#e.db.prepare(`UPDATE syntheses SET in_flight_since = NULL, updated_at = ? WHERE in_flight_since IS NOT NULL AND in_flight_since < ?`).run(n,t)}computeSourceMemoryHash(e){let t;return t=e===`global`?this.#e.db.prepare(`SELECT content FROM memories
|
|
823
193
|
WHERE id NOT IN (SELECT memory_id FROM memory_projects)
|
|
824
|
-
ORDER BY id`).all()
|
|
825
|
-
else contents = this.#db.db.prepare(`SELECT m.content FROM memories m
|
|
194
|
+
ORDER BY id`).all():this.#e.db.prepare(`SELECT m.content FROM memories m
|
|
826
195
|
JOIN memory_projects mp ON mp.memory_id = m.id
|
|
827
196
|
JOIN projects p ON p.id = mp.project_id
|
|
828
197
|
WHERE p.scope_hash = ?
|
|
829
|
-
ORDER BY m.id`).all(scope);
|
|
830
|
-
return createHash("sha256").update(JSON.stringify(contents.map((r) => r.content))).digest("hex");
|
|
831
|
-
}
|
|
832
|
-
getExpiredOrDirtyScopes() {
|
|
833
|
-
const allScopes = this.getAllActiveScopes();
|
|
834
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
835
|
-
const results = [];
|
|
836
|
-
for (const scope of allScopes) {
|
|
837
|
-
const row = this.#db.db.prepare("SELECT * FROM syntheses WHERE scope = ?").get(scope);
|
|
838
|
-
if (row === void 0 || row.content === "pending" && row.source_memory_hash === "") {
|
|
839
|
-
results.push({
|
|
840
|
-
scope,
|
|
841
|
-
reason: "missing"
|
|
842
|
-
});
|
|
843
|
-
continue;
|
|
844
|
-
}
|
|
845
|
-
if (row.expires_at <= now) {
|
|
846
|
-
results.push({
|
|
847
|
-
scope,
|
|
848
|
-
reason: "expired"
|
|
849
|
-
});
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
if (this.computeSourceMemoryHash(scope) !== row.source_memory_hash) results.push({
|
|
853
|
-
scope,
|
|
854
|
-
reason: "dirty"
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
|
-
return results;
|
|
858
|
-
}
|
|
859
|
-
getAllActiveScopes() {
|
|
860
|
-
return ["global", ...this.#db.db.prepare("SELECT DISTINCT scope_hash FROM projects").all().map((r) => r.scope_hash)];
|
|
861
|
-
}
|
|
862
|
-
expireStale() {
|
|
863
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
864
|
-
this.#db.db.prepare("DELETE FROM syntheses WHERE expires_at < ?").run(now);
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
//#endregion
|
|
868
|
-
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 };
|
|
869
|
-
|
|
198
|
+
ORDER BY m.id`).all(e),f(`sha256`).update(JSON.stringify(t.map(e=>e.content))).digest(`hex`)}getExpiredOrDirtyScopes(){let e=this.getAllActiveScopes(),t=new Date().toISOString(),n=[];for(let r of e){let e=this.#e.db.prepare(`SELECT * FROM syntheses WHERE scope = ?`).get(r);if(e===void 0||e.content===`pending`&&e.source_memory_hash===``){n.push({scope:r,reason:`missing`});continue}if(e.expires_at<=t){n.push({scope:r,reason:`expired`});continue}this.computeSourceMemoryHash(r)!==e.source_memory_hash&&n.push({scope:r,reason:`dirty`})}return n}getAllActiveScopes(){return[`global`,...this.#e.db.prepare(`SELECT DISTINCT scope_hash FROM projects`).all().map(e=>e.scope_hash)]}expireStale(){let e=new Date().toISOString();this.#e.db.prepare(`DELETE FROM syntheses WHERE expires_at < ?`).run(e)}};function De(e){return new Ee(e)}export{P as DatabaseError,se as DatabaseManager,B as EmbeddingService,g as MEMORY_TYPE_VALUES,fe as MIGRATIONS,I as MODEL_NAME,N as MembankError,E as MemoryPatchSchema,O as MemoryRowSchema,C as MemorySchema,_ as MemoryTypeSchema,L as ModelDownloadError,z as ModelDownloader,K as PIN_BUDGET_THRESHOLD,k as ProjectRowSchema,y as ProjectSchema,ye as QueryEngine,w as QueryOptionsSchema,S as ReviewEventRowSchema,x as ReviewEventSchema,b as ReviewReasonSchema,T as SaveOptionsSchema,xe as SessionContextBuilder,ae as SessionContextSchema,q as SqliteMemoryRepository,Se as SynthesisEngine,D as SynthesisSchema,v as TagsJsonSchema,ue as createMemoryRepository,he as createProjectRepository,Te as createSynthesisAgentRunner,De as createSynthesisRepository,V as deleteMemory,Q as getSessionContext,le as isOverBudget,ie as isSynthesisEnabled,be as listMemoryTypes,Z as queryMemories,X as resolveProject,H as resolveReview,de as resolveScope,A as rowToMemory,M as rowToProject,pe as runScopeToProjectsMigration,Ce as runSynthesis,W as saveMemory,G as updateMemory};
|
|
870
199
|
//# sourceMappingURL=index.mjs.map
|