@cohaku/cli 0.2.6

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.
@@ -0,0 +1,1136 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+
3
+ // src/shared/engine.ts
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import { execSync } from "child_process";
7
+
8
+ // ../core/src/storage/sqlite-driver.ts
9
+ import Database from "better-sqlite3";
10
+ import * as sqliteVec from "sqlite-vec";
11
+
12
+ // ../shared/src/constants.ts
13
+ var DEFAULT_WEIGHTS = {
14
+ semantic: 0.4,
15
+ bm25: 0.25,
16
+ recency: 0.2,
17
+ salience: 0.15
18
+ };
19
+ var MEMORY_LAYER_PRIORITY = {
20
+ rule: 10,
21
+ working: 5,
22
+ long_term: 1
23
+ };
24
+ var DEFAULT_DECAY_RATE = 0.01;
25
+ var DEFAULT_EMBEDDING_DIMENSIONS = 384;
26
+
27
+ // ../core/src/storage/schema.ts
28
+ var SCHEMA_VERSION = 2;
29
+ var CREATE_TABLES = `
30
+ -- Memory (3-layer)
31
+ CREATE TABLE IF NOT EXISTS memories (
32
+ id TEXT PRIMARY KEY,
33
+ project_id TEXT,
34
+ content TEXT NOT NULL,
35
+ layer TEXT NOT NULL DEFAULT 'long_term',
36
+ type TEXT NOT NULL DEFAULT 'note',
37
+ priority INTEGER NOT NULL DEFAULT 1,
38
+ importance REAL NOT NULL DEFAULT 0.5,
39
+ tags TEXT,
40
+ expires_at TEXT,
41
+ last_accessed_at TEXT NOT NULL,
42
+ created_at TEXT NOT NULL,
43
+ updated_at TEXT NOT NULL,
44
+ deleted_at TEXT
45
+ );
46
+
47
+ -- FTS5 virtual table (BM25 search)
48
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
49
+ content,
50
+ tags,
51
+ content='memories',
52
+ content_rowid='rowid',
53
+ tokenize='porter'
54
+ );
55
+
56
+ -- FTS5 sync triggers
57
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
58
+ INSERT INTO memories_fts(rowid, content, tags)
59
+ VALUES (new.rowid, new.content, new.tags);
60
+ END;
61
+
62
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
63
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags)
64
+ VALUES ('delete', old.rowid, old.content, old.tags);
65
+ END;
66
+
67
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
68
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags)
69
+ VALUES ('delete', old.rowid, old.content, old.tags);
70
+ INSERT INTO memories_fts(rowid, content, tags)
71
+ VALUES (new.rowid, new.content, new.tags);
72
+ END;
73
+
74
+ -- Entity nodes
75
+ CREATE TABLE IF NOT EXISTS nodes (
76
+ id TEXT PRIMARY KEY,
77
+ project_id TEXT,
78
+ name TEXT NOT NULL,
79
+ name_embedding BLOB,
80
+ summary TEXT,
81
+ entity_type TEXT,
82
+ created_at TEXT NOT NULL,
83
+ updated_at TEXT NOT NULL
84
+ );
85
+
86
+ -- Edges (bi-temporal)
87
+ CREATE TABLE IF NOT EXISTS edges (
88
+ id TEXT PRIMARY KEY,
89
+ project_id TEXT,
90
+ source_id TEXT NOT NULL REFERENCES nodes(id),
91
+ target_id TEXT NOT NULL REFERENCES nodes(id),
92
+ relation TEXT NOT NULL,
93
+ fact TEXT,
94
+ fact_embedding BLOB,
95
+ created_at TEXT NOT NULL,
96
+ expired_at TEXT,
97
+ valid_at TEXT,
98
+ invalid_at TEXT
99
+ );
100
+
101
+ -- Episodes
102
+ CREATE TABLE IF NOT EXISTS episodes (
103
+ id TEXT PRIMARY KEY,
104
+ project_id TEXT,
105
+ content TEXT NOT NULL,
106
+ content_type TEXT,
107
+ source TEXT,
108
+ created_at TEXT NOT NULL
109
+ );
110
+
111
+ -- Episode references
112
+ CREATE TABLE IF NOT EXISTS episode_refs (
113
+ episode_id TEXT REFERENCES episodes(id),
114
+ ref_type TEXT,
115
+ ref_id TEXT,
116
+ PRIMARY KEY (episode_id, ref_type, ref_id)
117
+ );
118
+
119
+ -- Sessions
120
+ CREATE TABLE IF NOT EXISTS sessions (
121
+ id TEXT PRIMARY KEY,
122
+ project_id TEXT,
123
+ status TEXT NOT NULL DEFAULT 'active',
124
+ started_at TEXT NOT NULL,
125
+ ended_at TEXT,
126
+ checkpoint_count INTEGER NOT NULL DEFAULT 0,
127
+ working_memory_snapshot TEXT,
128
+ metadata TEXT
129
+ );
130
+
131
+ -- Config
132
+ CREATE TABLE IF NOT EXISTS config (
133
+ key TEXT PRIMARY KEY,
134
+ value TEXT NOT NULL
135
+ );
136
+
137
+ -- Indexes
138
+ CREATE INDEX IF NOT EXISTS idx_memories_layer ON memories(layer);
139
+ CREATE INDEX IF NOT EXISTS idx_memories_deleted ON memories(deleted_at);
140
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
141
+ CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_id);
142
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
143
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
144
+ CREATE INDEX IF NOT EXISTS idx_edges_expired ON edges(expired_at);
145
+ CREATE INDEX IF NOT EXISTS idx_edges_project ON edges(project_id);
146
+ CREATE INDEX IF NOT EXISTS idx_episodes_project ON episodes(project_id);
147
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
148
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
149
+ `;
150
+ var MIGRATE_V1_TO_V2 = `
151
+ ALTER TABLE memories ADD COLUMN project_id TEXT;
152
+ ALTER TABLE nodes ADD COLUMN project_id TEXT;
153
+ ALTER TABLE edges ADD COLUMN project_id TEXT;
154
+ ALTER TABLE episodes ADD COLUMN project_id TEXT;
155
+ ALTER TABLE sessions ADD COLUMN project_id TEXT;
156
+
157
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
158
+ CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_id);
159
+ CREATE INDEX IF NOT EXISTS idx_edges_project ON edges(project_id);
160
+ CREATE INDEX IF NOT EXISTS idx_episodes_project ON episodes(project_id);
161
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
162
+ `;
163
+ function createVecTables(dimensions) {
164
+ return `
165
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(
166
+ memory_id TEXT PRIMARY KEY,
167
+ embedding float[${dimensions}]
168
+ );
169
+
170
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_nodes USING vec0(
171
+ node_id TEXT PRIMARY KEY,
172
+ embedding float[${dimensions}]
173
+ );
174
+
175
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_edges USING vec0(
176
+ edge_id TEXT PRIMARY KEY,
177
+ embedding float[${dimensions}]
178
+ );
179
+ `;
180
+ }
181
+
182
+ // ../core/src/utils/temporal.ts
183
+ function nowISO() {
184
+ return (/* @__PURE__ */ new Date()).toISOString();
185
+ }
186
+ function toVecBlob(embedding) {
187
+ return Buffer.from(new Float32Array(embedding).buffer);
188
+ }
189
+
190
+ // ../core/src/storage/sqlite-driver.ts
191
+ var SqliteDriver = class {
192
+ constructor(dbPath, dimensions = DEFAULT_EMBEDDING_DIMENSIONS) {
193
+ this.dbPath = dbPath;
194
+ this.dimensions = dimensions;
195
+ }
196
+ db;
197
+ async initialize() {
198
+ this.db = new Database(this.dbPath);
199
+ this.db.pragma("journal_mode = WAL");
200
+ this.db.pragma("foreign_keys = ON");
201
+ sqliteVec.load(this.db);
202
+ this.db.exec("CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
203
+ const existing = this.db.prepare("SELECT value FROM config WHERE key = ?").get("schema_version");
204
+ const currentVersion = existing ? Number(existing.value) : 0;
205
+ if (currentVersion >= 1 && currentVersion < 2) {
206
+ for (const stmt of MIGRATE_V1_TO_V2.split(";").filter((s) => s.trim())) {
207
+ this.db.exec(stmt);
208
+ }
209
+ }
210
+ this.db.exec(CREATE_TABLES);
211
+ const vecSQL = createVecTables(this.dimensions);
212
+ for (const stmt of vecSQL.split(";").filter((s) => s.trim())) {
213
+ this.db.exec(stmt);
214
+ }
215
+ this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run("schema_version", String(SCHEMA_VERSION));
216
+ }
217
+ close() {
218
+ this.db.close();
219
+ }
220
+ // ── Project Filter Helper ──
221
+ buildProjectFilter(projectIds, tableAlias) {
222
+ if (!projectIds) return { clause: "", params: [] };
223
+ const hasNull = projectIds.includes(null);
224
+ const nonNull = projectIds.filter((id) => id !== null);
225
+ const conditions = [];
226
+ const params = [];
227
+ if (hasNull) {
228
+ conditions.push(`${tableAlias}.project_id IS NULL`);
229
+ }
230
+ if (nonNull.length > 0) {
231
+ conditions.push(`${tableAlias}.project_id IN (${nonNull.map(() => "?").join(", ")})`);
232
+ params.push(...nonNull);
233
+ }
234
+ if (conditions.length === 0) return { clause: "", params: [] };
235
+ return { clause: `AND (${conditions.join(" OR ")})`, params };
236
+ }
237
+ // ── Memory ──
238
+ async addMemory(memory) {
239
+ const tagsStr = memory.tags ? JSON.stringify(memory.tags) : null;
240
+ this.db.transaction(() => {
241
+ this.db.prepare(`
242
+ INSERT INTO memories (id, project_id, content, layer, type, priority, importance, tags, expires_at, last_accessed_at, created_at, updated_at, deleted_at)
243
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
244
+ `).run(
245
+ memory.id,
246
+ memory.projectId ?? null,
247
+ memory.content,
248
+ memory.layer,
249
+ memory.type,
250
+ memory.priority,
251
+ memory.importance,
252
+ tagsStr,
253
+ memory.expiresAt ?? null,
254
+ memory.lastAccessedAt,
255
+ memory.createdAt,
256
+ memory.updatedAt,
257
+ memory.deletedAt ?? null
258
+ );
259
+ if (memory.embedding) {
260
+ this.db.prepare(`
261
+ INSERT INTO vec_memories (memory_id, embedding) VALUES (?, ?)
262
+ `).run(memory.id, toVecBlob(memory.embedding));
263
+ }
264
+ })();
265
+ }
266
+ async getMemory(id) {
267
+ const row = this.db.prepare("SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL").get(id);
268
+ return row ? this.rowToMemory(row) : null;
269
+ }
270
+ async updateMemory(id, input) {
271
+ const sets = ["updated_at = ?"];
272
+ const values = [(/* @__PURE__ */ new Date()).toISOString()];
273
+ if (input.content !== void 0) {
274
+ sets.push("content = ?");
275
+ values.push(input.content);
276
+ }
277
+ if (input.layer !== void 0) {
278
+ sets.push("layer = ?");
279
+ values.push(input.layer);
280
+ }
281
+ if (input.type !== void 0) {
282
+ sets.push("type = ?");
283
+ values.push(input.type);
284
+ }
285
+ if (input.importance !== void 0) {
286
+ sets.push("importance = ?");
287
+ values.push(input.importance);
288
+ }
289
+ if (input.tags !== void 0) {
290
+ sets.push("tags = ?");
291
+ values.push(JSON.stringify(input.tags));
292
+ }
293
+ if (input.expiresAt !== void 0) {
294
+ sets.push("expires_at = ?");
295
+ values.push(input.expiresAt);
296
+ }
297
+ values.push(id);
298
+ this.db.prepare(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`).run(...values);
299
+ }
300
+ async updateMemoryEmbedding(id, embedding) {
301
+ this.db.transaction(() => {
302
+ this.db.prepare("DELETE FROM vec_memories WHERE memory_id = ?").run(id);
303
+ this.db.prepare(
304
+ "INSERT INTO vec_memories (memory_id, embedding) VALUES (?, ?)"
305
+ ).run(id, toVecBlob(embedding));
306
+ })();
307
+ }
308
+ async deleteMemory(id) {
309
+ this.db.prepare("UPDATE memories SET deleted_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
310
+ }
311
+ async searchMemories(embedding, limit, projectIds) {
312
+ const { clause, params } = this.buildProjectFilter(projectIds, "m");
313
+ const rows = this.db.prepare(`
314
+ SELECT m.*, v.distance
315
+ FROM vec_memories v
316
+ INNER JOIN memories m ON m.id = v.memory_id
317
+ WHERE v.embedding MATCH ?
318
+ AND m.deleted_at IS NULL
319
+ AND k = ?
320
+ ${clause}
321
+ `).all(toVecBlob(embedding), limit, ...params);
322
+ return rows.map((row) => ({
323
+ memory: this.rowToMemory(row),
324
+ distance: row.distance
325
+ }));
326
+ }
327
+ async fullTextSearch(query, limit, projectIds) {
328
+ const { clause, params } = this.buildProjectFilter(projectIds, "m");
329
+ const rows = this.db.prepare(`
330
+ SELECT m.*, fts.rank
331
+ FROM memories_fts fts
332
+ INNER JOIN memories m ON m.rowid = fts.rowid
333
+ WHERE memories_fts MATCH ?
334
+ AND m.deleted_at IS NULL
335
+ ${clause}
336
+ ORDER BY fts.rank
337
+ LIMIT ?
338
+ `).all(query, ...params, limit);
339
+ return rows.map((row) => ({
340
+ memory: this.rowToMemory(row),
341
+ rank: row.rank
342
+ }));
343
+ }
344
+ async findSimilarMemories(embedding, threshold, limit, projectIds) {
345
+ const results = await this.searchMemories(embedding, limit * 3, projectIds);
346
+ return results.filter((r) => r.distance <= threshold).slice(0, limit);
347
+ }
348
+ async listMemories(limit, projectIds) {
349
+ const now = (/* @__PURE__ */ new Date()).toISOString();
350
+ const { clause, params } = this.buildProjectFilter(projectIds, "memories");
351
+ const rows = this.db.prepare(`
352
+ SELECT * FROM memories
353
+ WHERE deleted_at IS NULL
354
+ AND (expires_at IS NULL OR expires_at > ?)
355
+ ${clause}
356
+ ORDER BY updated_at DESC
357
+ LIMIT ?
358
+ `).all(now, ...params, limit);
359
+ return rows.map((row) => this.rowToMemory(row));
360
+ }
361
+ async listNodes(limit, projectIds) {
362
+ const { clause, params } = this.buildProjectFilter(projectIds, "nodes");
363
+ const rows = this.db.prepare(`
364
+ SELECT * FROM nodes WHERE 1=1 ${clause} ORDER BY updated_at DESC LIMIT ?
365
+ `).all(...params, limit);
366
+ return rows.map((row) => this.rowToNode(row));
367
+ }
368
+ async listEdges(limit, includeExpired, projectIds) {
369
+ const expiredClause = includeExpired ? "" : "AND expired_at IS NULL";
370
+ const { clause, params } = this.buildProjectFilter(projectIds, "edges");
371
+ const rows = this.db.prepare(`
372
+ SELECT * FROM edges WHERE 1=1 ${expiredClause} ${clause} ORDER BY created_at DESC LIMIT ?
373
+ `).all(...params, limit);
374
+ return rows.map((row) => this.rowToEdge(row));
375
+ }
376
+ async listMemoriesByLayer(layer, limit, projectIds) {
377
+ const now = (/* @__PURE__ */ new Date()).toISOString();
378
+ const { clause, params } = this.buildProjectFilter(projectIds, "memories");
379
+ const rows = this.db.prepare(`
380
+ SELECT * FROM memories
381
+ WHERE layer = ?
382
+ AND deleted_at IS NULL
383
+ AND (expires_at IS NULL OR expires_at > ?)
384
+ ${clause}
385
+ ORDER BY updated_at DESC
386
+ LIMIT ?
387
+ `).all(layer, now, ...params, limit);
388
+ return rows.map((row) => this.rowToMemory(row));
389
+ }
390
+ // ── Nodes ──
391
+ async upsertNode(node) {
392
+ this.db.transaction(() => {
393
+ this.db.prepare(`
394
+ INSERT INTO nodes (id, project_id, name, summary, entity_type, created_at, updated_at)
395
+ VALUES (?, ?, ?, ?, ?, ?, ?)
396
+ ON CONFLICT(id) DO UPDATE SET
397
+ name = excluded.name,
398
+ summary = excluded.summary,
399
+ entity_type = excluded.entity_type,
400
+ updated_at = excluded.updated_at
401
+ `).run(node.id, node.projectId ?? null, node.name, node.summary ?? null, node.entityType ?? null, node.createdAt, node.updatedAt);
402
+ if (node.nameEmbedding) {
403
+ this.db.prepare(`
404
+ DELETE FROM vec_nodes WHERE node_id = ?
405
+ `).run(node.id);
406
+ this.db.prepare(`
407
+ INSERT INTO vec_nodes (node_id, embedding) VALUES (?, ?)
408
+ `).run(node.id, toVecBlob(node.nameEmbedding));
409
+ }
410
+ })();
411
+ }
412
+ async getNode(id) {
413
+ const row = this.db.prepare("SELECT * FROM nodes WHERE id = ?").get(id);
414
+ return row ? this.rowToNode(row) : null;
415
+ }
416
+ async searchNodes(embedding, limit, projectIds) {
417
+ const { clause, params } = this.buildProjectFilter(projectIds, "n");
418
+ const rows = this.db.prepare(`
419
+ SELECT n.*
420
+ FROM vec_nodes v
421
+ INNER JOIN nodes n ON n.id = v.node_id
422
+ WHERE v.embedding MATCH ?
423
+ AND k = ?
424
+ ${clause}
425
+ `).all(toVecBlob(embedding), limit, ...params);
426
+ return rows.map((row) => this.rowToNode(row));
427
+ }
428
+ // ── Edges ──
429
+ async upsertEdge(edge) {
430
+ this.db.transaction(() => {
431
+ this.db.prepare(`
432
+ INSERT INTO edges (id, project_id, source_id, target_id, relation, fact, created_at, expired_at, valid_at, invalid_at)
433
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
434
+ ON CONFLICT(id) DO UPDATE SET
435
+ relation = excluded.relation,
436
+ fact = excluded.fact,
437
+ expired_at = excluded.expired_at,
438
+ valid_at = excluded.valid_at,
439
+ invalid_at = excluded.invalid_at
440
+ `).run(
441
+ edge.id,
442
+ edge.projectId ?? null,
443
+ edge.sourceId,
444
+ edge.targetId,
445
+ edge.relation,
446
+ edge.fact ?? null,
447
+ edge.createdAt,
448
+ edge.expiredAt ?? null,
449
+ edge.validAt ?? null,
450
+ edge.invalidAt ?? null
451
+ );
452
+ if (edge.factEmbedding) {
453
+ this.db.prepare(`
454
+ DELETE FROM vec_edges WHERE edge_id = ?
455
+ `).run(edge.id);
456
+ this.db.prepare(`
457
+ INSERT INTO vec_edges (edge_id, embedding) VALUES (?, ?)
458
+ `).run(edge.id, toVecBlob(edge.factEmbedding));
459
+ }
460
+ })();
461
+ }
462
+ async getEdge(id) {
463
+ const row = this.db.prepare("SELECT * FROM edges WHERE id = ?").get(id);
464
+ return row ? this.rowToEdge(row) : null;
465
+ }
466
+ async invalidateEdge(id, expiredAt, invalidAt) {
467
+ this.db.prepare("UPDATE edges SET expired_at = ?, invalid_at = ? WHERE id = ?").run(expiredAt, invalidAt, id);
468
+ }
469
+ async searchEdges(embedding, filters, limit, projectIds) {
470
+ let whereClause = "1=1";
471
+ const params = [toVecBlob(embedding), limit];
472
+ if (filters.excludeExpired !== false) {
473
+ whereClause += " AND e.expired_at IS NULL";
474
+ }
475
+ if (filters.asOf) {
476
+ whereClause += " AND (e.valid_at IS NULL OR e.valid_at <= ?)";
477
+ params.push(filters.asOf);
478
+ whereClause += " AND (e.invalid_at IS NULL OR e.invalid_at > ?)";
479
+ params.push(filters.asOf);
480
+ }
481
+ const { clause: projectClause, params: projectParams } = this.buildProjectFilter(projectIds, "e");
482
+ whereClause += ` ${projectClause}`;
483
+ params.push(...projectParams);
484
+ const rows = this.db.prepare(`
485
+ SELECT e.*
486
+ FROM vec_edges v
487
+ INNER JOIN edges e ON e.id = v.edge_id
488
+ WHERE v.embedding MATCH ?
489
+ AND k = ?
490
+ AND ${whereClause}
491
+ `).all(...params);
492
+ return rows.map((row) => this.rowToEdge(row));
493
+ }
494
+ // ── Graph Traversal ──
495
+ async getEdgesForNode(nodeId) {
496
+ const rows = this.db.prepare(`
497
+ SELECT * FROM edges
498
+ WHERE (source_id = ? OR target_id = ?) AND expired_at IS NULL
499
+ `).all(nodeId, nodeId);
500
+ return rows.map((row) => this.rowToEdge(row));
501
+ }
502
+ async getNeighbors(nodeId, depth) {
503
+ if (depth < 1) return [];
504
+ const oneHop = this.db.prepare(`
505
+ SELECT DISTINCT n.* FROM nodes n
506
+ INNER JOIN edges e ON (e.source_id = ? AND e.target_id = n.id)
507
+ OR (e.target_id = ? AND e.source_id = n.id)
508
+ WHERE e.expired_at IS NULL
509
+ `).all(nodeId, nodeId);
510
+ if (depth === 1) {
511
+ return oneHop.map((row) => this.rowToNode(row));
512
+ }
513
+ const visited = /* @__PURE__ */ new Set([nodeId]);
514
+ const results = [];
515
+ for (const row of oneHop) {
516
+ visited.add(row.id);
517
+ results.push(this.rowToNode(row));
518
+ }
519
+ for (const node of [...results]) {
520
+ const twoHop = this.db.prepare(`
521
+ SELECT DISTINCT n.* FROM nodes n
522
+ INNER JOIN edges e ON (e.source_id = ? AND e.target_id = n.id)
523
+ OR (e.target_id = ? AND e.source_id = n.id)
524
+ WHERE e.expired_at IS NULL
525
+ `).all(node.id, node.id);
526
+ for (const row of twoHop) {
527
+ if (!visited.has(row.id)) {
528
+ visited.add(row.id);
529
+ results.push(this.rowToNode(row));
530
+ }
531
+ }
532
+ }
533
+ return results;
534
+ }
535
+ // ── Episodes ──
536
+ async addEpisode(episode) {
537
+ this.db.prepare(`
538
+ INSERT INTO episodes (id, project_id, content, content_type, source, created_at)
539
+ VALUES (?, ?, ?, ?, ?, ?)
540
+ `).run(episode.id, episode.projectId ?? null, episode.content, episode.contentType ?? null, episode.source ?? null, episode.createdAt);
541
+ }
542
+ async addEpisodeRef(ref) {
543
+ this.db.prepare(`
544
+ INSERT OR IGNORE INTO episode_refs (episode_id, ref_type, ref_id)
545
+ VALUES (?, ?, ?)
546
+ `).run(ref.episodeId, ref.refType, ref.refId);
547
+ }
548
+ async getEpisode(id) {
549
+ const row = this.db.prepare("SELECT * FROM episodes WHERE id = ?").get(id);
550
+ return row ? this.rowToEpisode(row) : null;
551
+ }
552
+ async listEpisodes(limit, projectIds) {
553
+ const { clause, params } = this.buildProjectFilter(projectIds, "episodes");
554
+ const rows = this.db.prepare(`SELECT * FROM episodes WHERE 1=1 ${clause} ORDER BY created_at DESC LIMIT ?`).all(...params, limit);
555
+ return rows.map((row) => this.rowToEpisode(row));
556
+ }
557
+ async getEpisodeRefs(episodeId) {
558
+ const rows = this.db.prepare("SELECT * FROM episode_refs WHERE episode_id = ?").all(episodeId);
559
+ return rows.map((row) => ({
560
+ episodeId: row.episode_id,
561
+ refType: row.ref_type,
562
+ refId: row.ref_id
563
+ }));
564
+ }
565
+ // ── Sessions ──
566
+ async addSession(session) {
567
+ this.db.prepare(`
568
+ INSERT INTO sessions (id, project_id, status, started_at, ended_at, checkpoint_count, working_memory_snapshot, metadata)
569
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
570
+ `).run(
571
+ session.id,
572
+ session.projectId ?? null,
573
+ session.status,
574
+ session.startedAt,
575
+ session.endedAt ?? null,
576
+ session.checkpointCount,
577
+ session.workingMemorySnapshot ?? null,
578
+ session.metadata ?? null
579
+ );
580
+ }
581
+ async updateSession(id, updates) {
582
+ const sets = [];
583
+ const values = [];
584
+ if (updates.status !== void 0) {
585
+ sets.push("status = ?");
586
+ values.push(updates.status);
587
+ }
588
+ if (updates.endedAt !== void 0) {
589
+ sets.push("ended_at = ?");
590
+ values.push(updates.endedAt);
591
+ }
592
+ if (updates.checkpointCount !== void 0) {
593
+ sets.push("checkpoint_count = ?");
594
+ values.push(updates.checkpointCount);
595
+ }
596
+ if (updates.workingMemorySnapshot !== void 0) {
597
+ sets.push("working_memory_snapshot = ?");
598
+ values.push(updates.workingMemorySnapshot);
599
+ }
600
+ if (updates.metadata !== void 0) {
601
+ sets.push("metadata = ?");
602
+ values.push(updates.metadata);
603
+ }
604
+ if (sets.length === 0) return;
605
+ values.push(id);
606
+ this.db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values);
607
+ }
608
+ async getSession(id) {
609
+ const row = this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
610
+ return row ? this.rowToSession(row) : null;
611
+ }
612
+ async listSessions(limit, projectIds) {
613
+ const { clause, params } = this.buildProjectFilter(projectIds, "sessions");
614
+ const rows = this.db.prepare(`SELECT * FROM sessions WHERE 1=1 ${clause} ORDER BY started_at DESC LIMIT ?`).all(...params, limit);
615
+ return rows.map((row) => this.rowToSession(row));
616
+ }
617
+ // ── Config ──
618
+ async getConfig(key) {
619
+ const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
620
+ return row?.value ?? null;
621
+ }
622
+ async setConfig(key, value) {
623
+ this.db.prepare("INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
624
+ }
625
+ // ── Row Converters ──
626
+ rowToMemory(row) {
627
+ return {
628
+ id: row.id,
629
+ projectId: row.project_id ?? void 0,
630
+ content: row.content,
631
+ layer: row.layer,
632
+ type: row.type,
633
+ priority: row.priority,
634
+ importance: row.importance,
635
+ tags: row.tags ? JSON.parse(row.tags) : void 0,
636
+ expiresAt: row.expires_at ?? void 0,
637
+ lastAccessedAt: row.last_accessed_at,
638
+ createdAt: row.created_at,
639
+ updatedAt: row.updated_at,
640
+ deletedAt: row.deleted_at ?? void 0
641
+ };
642
+ }
643
+ rowToNode(row) {
644
+ return {
645
+ id: row.id,
646
+ projectId: row.project_id ?? void 0,
647
+ name: row.name,
648
+ summary: row.summary ?? void 0,
649
+ entityType: row.entity_type ?? void 0,
650
+ createdAt: row.created_at,
651
+ updatedAt: row.updated_at
652
+ };
653
+ }
654
+ rowToEdge(row) {
655
+ return {
656
+ id: row.id,
657
+ projectId: row.project_id ?? void 0,
658
+ sourceId: row.source_id,
659
+ targetId: row.target_id,
660
+ relation: row.relation,
661
+ fact: row.fact ?? void 0,
662
+ createdAt: row.created_at,
663
+ expiredAt: row.expired_at ?? void 0,
664
+ validAt: row.valid_at ?? void 0,
665
+ invalidAt: row.invalid_at ?? void 0
666
+ };
667
+ }
668
+ rowToEpisode(row) {
669
+ return {
670
+ id: row.id,
671
+ projectId: row.project_id ?? void 0,
672
+ content: row.content,
673
+ contentType: row.content_type ?? void 0,
674
+ source: row.source ?? void 0,
675
+ createdAt: row.created_at
676
+ };
677
+ }
678
+ rowToSession(row) {
679
+ return {
680
+ id: row.id,
681
+ projectId: row.project_id ?? void 0,
682
+ status: row.status,
683
+ startedAt: row.started_at,
684
+ endedAt: row.ended_at ?? void 0,
685
+ checkpointCount: row.checkpoint_count,
686
+ workingMemorySnapshot: row.working_memory_snapshot ?? void 0,
687
+ metadata: row.metadata ?? void 0
688
+ };
689
+ }
690
+ };
691
+
692
+ // ../core/src/embedding/local-embedding.ts
693
+ var LocalEmbedding = class {
694
+ constructor(cacheDir) {
695
+ this.cacheDir = cacheDir;
696
+ }
697
+ dimensions = 384;
698
+ pipe = null;
699
+ initPromise = null;
700
+ async getPipeline() {
701
+ if (this.pipe) return this.pipe;
702
+ if (!this.initPromise) {
703
+ this.initPromise = (async () => {
704
+ const { pipeline, env } = await import("@xenova/transformers");
705
+ if (this.cacheDir) {
706
+ env.cacheDir = this.cacheDir;
707
+ }
708
+ const pipe = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
709
+ return pipe;
710
+ })();
711
+ }
712
+ this.pipe = await this.initPromise;
713
+ return this.pipe;
714
+ }
715
+ async embed(text) {
716
+ const pipe = await this.getPipeline();
717
+ const result = await pipe(text, { pooling: "mean", normalize: true });
718
+ return Array.from(result.data).slice(0, this.dimensions);
719
+ }
720
+ async embedBatch(texts) {
721
+ return Promise.all(texts.map((t) => this.embed(t)));
722
+ }
723
+ };
724
+
725
+ // ../core/src/engine/memory-engine.ts
726
+ import { mkdirSync } from "fs";
727
+ import { dirname } from "path";
728
+
729
+ // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
730
+ import { webcrypto as crypto } from "crypto";
731
+
732
+ // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
733
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
734
+
735
+ // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
736
+ var POOL_SIZE_MULTIPLIER = 128;
737
+ var pool;
738
+ var poolOffset;
739
+ function fillPool(bytes) {
740
+ if (!pool || pool.length < bytes) {
741
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
742
+ crypto.getRandomValues(pool);
743
+ poolOffset = 0;
744
+ } else if (poolOffset + bytes > pool.length) {
745
+ crypto.getRandomValues(pool);
746
+ poolOffset = 0;
747
+ }
748
+ poolOffset += bytes;
749
+ }
750
+ function nanoid(size = 21) {
751
+ fillPool(size |= 0);
752
+ let id = "";
753
+ for (let i = poolOffset - size; i < poolOffset; i++) {
754
+ id += urlAlphabet[pool[i] & 63];
755
+ }
756
+ return id;
757
+ }
758
+
759
+ // ../core/src/utils/id.ts
760
+ function generateId() {
761
+ return nanoid();
762
+ }
763
+
764
+ // ../core/src/engine/scoring.ts
765
+ function decay(importance, lastAccessedAt, rate = 0.01) {
766
+ const ageHours = (Date.now() - new Date(lastAccessedAt).getTime()) / 36e5;
767
+ return importance * Math.exp(-rate * ageHours);
768
+ }
769
+ function recencyScore(timestamp, halfLifeHours = 24) {
770
+ const ageHours = (Date.now() - new Date(timestamp).getTime()) / 36e5;
771
+ return Math.exp(-Math.LN2 / halfLifeHours * ageHours);
772
+ }
773
+ function hybridScore(semantic, bm25, recency, salience, weights) {
774
+ return semantic * weights.semantic + bm25 * weights.bm25 + recency * weights.recency + salience * weights.salience;
775
+ }
776
+ function normalizeBm25(rank) {
777
+ return 1 / (1 + Math.abs(rank));
778
+ }
779
+ function distanceToSimilarity(distance) {
780
+ return 1 / (1 + distance);
781
+ }
782
+
783
+ // ../core/src/engine/context.ts
784
+ async function getContext(storage, options = {}) {
785
+ const { maxMemories = 50, projectIds } = options;
786
+ const rules = await storage.listMemoriesByLayer("rule", maxMemories, projectIds);
787
+ const remaining1 = maxMemories - rules.length;
788
+ const working = remaining1 > 0 ? await storage.listMemoriesByLayer("working", Math.ceil(remaining1 * 0.6), projectIds) : [];
789
+ const remaining2 = maxMemories - rules.length - working.length;
790
+ const longTerm = remaining2 > 0 ? await storage.listMemoriesByLayer("long_term", remaining2, projectIds) : [];
791
+ const memories = [...rules, ...working, ...longTerm];
792
+ const totalCount = memories.length;
793
+ return {
794
+ memories,
795
+ totalCount,
796
+ truncated: totalCount >= maxMemories
797
+ };
798
+ }
799
+
800
+ // ../core/src/engine/memory-engine.ts
801
+ var MemoryEngine = class {
802
+ storage;
803
+ embedding;
804
+ weights;
805
+ decayRate;
806
+ defaultProjectId;
807
+ constructor(config) {
808
+ mkdirSync(dirname(config.dbPath), { recursive: true });
809
+ this.storage = new SqliteDriver(config.dbPath);
810
+ this.embedding = new LocalEmbedding(config.embeddingCacheDir);
811
+ this.weights = { ...DEFAULT_WEIGHTS, ...config.weights };
812
+ this.decayRate = config.decayRate ?? DEFAULT_DECAY_RATE;
813
+ this.defaultProjectId = config.defaultProjectId;
814
+ }
815
+ async initialize() {
816
+ await this.storage.initialize();
817
+ }
818
+ close() {
819
+ this.storage.close();
820
+ }
821
+ // ── Scope Helpers ──
822
+ resolveProjectId(scope) {
823
+ if (scope === "global") return void 0;
824
+ return this.defaultProjectId;
825
+ }
826
+ resolveProjectFilter(scope) {
827
+ if (scope === "all") return void 0;
828
+ if (scope === "global") return [null];
829
+ if (this.defaultProjectId) return [this.defaultProjectId, null];
830
+ return void 0;
831
+ }
832
+ // ── Memory ──
833
+ async addMemory(input) {
834
+ const now = nowISO();
835
+ const layer = input.layer ?? "long_term";
836
+ const memory = {
837
+ id: generateId(),
838
+ projectId: this.resolveProjectId(input.scope),
839
+ content: input.content,
840
+ layer,
841
+ type: input.type ?? "note",
842
+ priority: MEMORY_LAYER_PRIORITY[layer] ?? 1,
843
+ importance: input.importance ?? 0.5,
844
+ tags: input.tags,
845
+ embedding: await this.embedding.embed(input.content),
846
+ expiresAt: input.expiresAt,
847
+ lastAccessedAt: now,
848
+ createdAt: now,
849
+ updatedAt: now
850
+ };
851
+ await this.storage.addMemory(memory);
852
+ return memory;
853
+ }
854
+ async searchMemories(query) {
855
+ const limit = query.limit ?? 10;
856
+ const weights = { ...this.weights, ...query.weights };
857
+ const projectIds = this.resolveProjectFilter(query.scope);
858
+ const queryEmbedding = query.embedding ?? await this.embedding.embed(query.text);
859
+ const semanticResults = await this.storage.searchMemories(queryEmbedding, limit * 2, projectIds);
860
+ let bm25Results = [];
861
+ try {
862
+ bm25Results = await this.storage.fullTextSearch(query.text, limit * 2, projectIds);
863
+ } catch {
864
+ }
865
+ const candidateMap = /* @__PURE__ */ new Map();
866
+ for (const { memory } of semanticResults) candidateMap.set(memory.id, memory);
867
+ for (const { memory } of bm25Results) candidateMap.set(memory.id, memory);
868
+ const semanticScoreMap = /* @__PURE__ */ new Map();
869
+ for (const { memory, distance } of semanticResults) {
870
+ semanticScoreMap.set(memory.id, distanceToSimilarity(distance));
871
+ }
872
+ const bm25ScoreMap = /* @__PURE__ */ new Map();
873
+ for (const { memory, rank } of bm25Results) {
874
+ bm25ScoreMap.set(memory.id, normalizeBm25(rank));
875
+ }
876
+ const layerFilter = query.layers ? new Set(query.layers) : null;
877
+ const results = [];
878
+ for (const [id, memory] of candidateMap) {
879
+ if (layerFilter && !layerFilter.has(memory.layer)) continue;
880
+ if (memory.expiresAt && new Date(memory.expiresAt) < /* @__PURE__ */ new Date()) continue;
881
+ const semantic = semanticScoreMap.get(id) ?? 0;
882
+ const bm25 = bm25ScoreMap.get(id) ?? 0;
883
+ const rec = recencyScore(memory.lastAccessedAt);
884
+ const sal = decay(memory.importance, memory.lastAccessedAt, this.decayRate);
885
+ const score = hybridScore(semantic, bm25, rec, sal, weights);
886
+ results.push({
887
+ id: memory.id,
888
+ type: "memory",
889
+ content: memory.content,
890
+ score,
891
+ scoreBreakdown: { semantic, bm25, recency: rec, salience: sal }
892
+ });
893
+ }
894
+ results.sort((a, b) => b.score - a.score);
895
+ return results.slice(0, limit);
896
+ }
897
+ async updateMemory(id, input) {
898
+ await this.storage.updateMemory(id, input);
899
+ if (input.content !== void 0) {
900
+ const newEmbedding = await this.embedding.embed(input.content);
901
+ await this.storage.updateMemoryEmbedding(id, newEmbedding);
902
+ }
903
+ }
904
+ async deleteMemory(id) {
905
+ await this.storage.deleteMemory(id);
906
+ }
907
+ async getMemory(id) {
908
+ return this.storage.getMemory(id);
909
+ }
910
+ async listMemories(options) {
911
+ const limit = options?.limit ?? 100;
912
+ const projectIds = this.resolveProjectFilter(options?.scope);
913
+ if (options?.layer) {
914
+ return this.storage.listMemoriesByLayer(options.layer, limit, projectIds);
915
+ }
916
+ return this.storage.listMemories(limit, projectIds);
917
+ }
918
+ async listEntities(options) {
919
+ const limit = options?.limit ?? 100;
920
+ const projectIds = this.resolveProjectFilter(options?.scope);
921
+ return this.storage.listNodes(limit, projectIds);
922
+ }
923
+ async listEdges(options) {
924
+ const limit = options?.limit ?? 100;
925
+ const projectIds = this.resolveProjectFilter(options?.scope);
926
+ return this.storage.listEdges(limit, options?.includeExpired ?? false, projectIds);
927
+ }
928
+ // ── Deduplication & Consolidation ──
929
+ async findDuplicates(memoryId, threshold = 0.3) {
930
+ const target = await this.storage.getMemory(memoryId);
931
+ if (!target) throw new Error(`Memory not found: ${memoryId}`);
932
+ const embedding = target.embedding ?? await this.embedding.embed(target.content);
933
+ const projectIds = this.resolveProjectFilter("project");
934
+ const similar = await this.storage.findSimilarMemories(embedding, threshold, 20, projectIds);
935
+ return similar.filter((r) => r.memory.id !== memoryId);
936
+ }
937
+ async consolidateMemories(sourceIds, mergedContent, layer) {
938
+ const merged = await this.addMemory({
939
+ content: mergedContent,
940
+ layer: layer ?? "long_term",
941
+ type: "note"
942
+ });
943
+ for (const id of sourceIds) {
944
+ await this.storage.deleteMemory(id);
945
+ }
946
+ return merged;
947
+ }
948
+ // ── Graph ──
949
+ async addEntity(input) {
950
+ const now = nowISO();
951
+ const node = {
952
+ id: generateId(),
953
+ projectId: this.resolveProjectId(input.scope),
954
+ name: input.name,
955
+ nameEmbedding: await this.embedding.embed(input.name),
956
+ summary: input.summary,
957
+ entityType: input.entityType,
958
+ createdAt: now,
959
+ updatedAt: now
960
+ };
961
+ await this.storage.upsertNode(node);
962
+ return node;
963
+ }
964
+ async addEdge(input) {
965
+ const now = nowISO();
966
+ const edge = {
967
+ id: generateId(),
968
+ projectId: this.resolveProjectId(input.scope),
969
+ sourceId: input.sourceId,
970
+ targetId: input.targetId,
971
+ relation: input.relation,
972
+ fact: input.fact,
973
+ factEmbedding: input.fact ? await this.embedding.embed(input.fact) : void 0,
974
+ createdAt: now,
975
+ validAt: input.validAt
976
+ };
977
+ await this.storage.upsertEdge(edge);
978
+ return edge;
979
+ }
980
+ async getEdge(id) {
981
+ return this.storage.getEdge(id);
982
+ }
983
+ async invalidateEdge(edgeId) {
984
+ const now = nowISO();
985
+ await this.storage.invalidateEdge(edgeId, now, now);
986
+ }
987
+ async updateEdge(edgeId, input) {
988
+ const old = await this.storage.getEdge(edgeId);
989
+ if (!old) return null;
990
+ const now = nowISO();
991
+ await this.storage.invalidateEdge(edgeId, now, now);
992
+ return this.addEdge({
993
+ sourceId: old.sourceId,
994
+ targetId: old.targetId,
995
+ relation: input.relation ?? old.relation,
996
+ fact: input.fact ?? old.fact,
997
+ validAt: now
998
+ });
999
+ }
1000
+ async searchGraph(query, limit = 10, options) {
1001
+ const depth = options?.traverseDepth ?? 1;
1002
+ const projectIds = this.resolveProjectFilter(options?.scope);
1003
+ const queryEmbedding = await this.embedding.embed(query);
1004
+ const nodes = await this.storage.searchNodes(queryEmbedding, Math.ceil(limit / 2), projectIds);
1005
+ const edges = await this.storage.searchEdges(queryEmbedding, { excludeExpired: true }, Math.ceil(limit / 2), projectIds);
1006
+ const results = [];
1007
+ const seenNodeIds = /* @__PURE__ */ new Set();
1008
+ for (const node of nodes) {
1009
+ seenNodeIds.add(node.id);
1010
+ results.push({
1011
+ id: node.id,
1012
+ type: "node",
1013
+ content: `${node.name}${node.summary ? ": " + node.summary : ""}`,
1014
+ score: 1
1015
+ });
1016
+ }
1017
+ if (depth > 0) {
1018
+ for (const node of nodes) {
1019
+ const neighbors = await this.storage.getNeighbors(node.id, depth);
1020
+ for (const neighbor of neighbors) {
1021
+ if (!seenNodeIds.has(neighbor.id)) {
1022
+ seenNodeIds.add(neighbor.id);
1023
+ results.push({
1024
+ id: neighbor.id,
1025
+ type: "node",
1026
+ content: `${neighbor.name}${neighbor.summary ? ": " + neighbor.summary : ""}`,
1027
+ score: 0.5
1028
+ });
1029
+ }
1030
+ }
1031
+ }
1032
+ }
1033
+ for (const edge of edges) {
1034
+ results.push({
1035
+ id: edge.id,
1036
+ type: "edge",
1037
+ content: `${edge.relation}: ${edge.fact ?? ""}`,
1038
+ score: 0.8
1039
+ });
1040
+ }
1041
+ results.sort((a, b) => b.score - a.score);
1042
+ return results.slice(0, limit);
1043
+ }
1044
+ // ── Episodes ──
1045
+ async addEpisode(input, refs) {
1046
+ const episode = {
1047
+ id: generateId(),
1048
+ projectId: this.resolveProjectId(input.scope),
1049
+ content: input.content,
1050
+ contentType: input.contentType,
1051
+ source: input.source,
1052
+ createdAt: nowISO()
1053
+ };
1054
+ await this.storage.addEpisode(episode);
1055
+ if (refs) {
1056
+ for (const ref of refs) {
1057
+ await this.storage.addEpisodeRef({ ...ref, episodeId: episode.id });
1058
+ }
1059
+ }
1060
+ return episode;
1061
+ }
1062
+ async getEpisode(id) {
1063
+ return this.storage.getEpisode(id);
1064
+ }
1065
+ async listEpisodes(limit = 20, scope) {
1066
+ const projectIds = this.resolveProjectFilter(scope);
1067
+ return this.storage.listEpisodes(limit, projectIds);
1068
+ }
1069
+ // ── Sessions ──
1070
+ async sessionStart(metadata) {
1071
+ const session = {
1072
+ id: generateId(),
1073
+ projectId: this.resolveProjectId("project"),
1074
+ status: "active",
1075
+ startedAt: nowISO(),
1076
+ checkpointCount: 0,
1077
+ metadata
1078
+ };
1079
+ await this.storage.addSession(session);
1080
+ return session;
1081
+ }
1082
+ async sessionEnd(id) {
1083
+ await this.storage.updateSession(id, {
1084
+ status: "ended",
1085
+ endedAt: nowISO()
1086
+ });
1087
+ }
1088
+ async sessionCheckpoint(id) {
1089
+ const session = await this.storage.getSession(id);
1090
+ if (!session) throw new Error(`Session not found: ${id}`);
1091
+ await this.storage.updateSession(id, {
1092
+ checkpointCount: session.checkpointCount + 1
1093
+ });
1094
+ }
1095
+ async sessionList(limit = 20, scope) {
1096
+ const projectIds = this.resolveProjectFilter(scope);
1097
+ return this.storage.listSessions(limit, projectIds);
1098
+ }
1099
+ // ── Context ──
1100
+ async getContext(options) {
1101
+ const projectIds = this.resolveProjectFilter(options?.scope);
1102
+ return getContext(this.storage, { ...options, projectIds });
1103
+ }
1104
+ };
1105
+
1106
+ // src/shared/engine.ts
1107
+ var DEFAULT_DB_PATH2 = join(homedir(), ".config", "cohaku", "memory.db");
1108
+ function getDbPath() {
1109
+ return process.env["COHAKU_DB_PATH"] ?? DEFAULT_DB_PATH2;
1110
+ }
1111
+ function detectProjectId() {
1112
+ const envProjectId = process.env["COHAKU_PROJECT_ID"];
1113
+ if (envProjectId) return envProjectId;
1114
+ try {
1115
+ const gitRoot = execSync("git rev-parse --show-toplevel", {
1116
+ encoding: "utf-8",
1117
+ stdio: ["pipe", "pipe", "pipe"]
1118
+ }).trim();
1119
+ return gitRoot || void 0;
1120
+ } catch {
1121
+ return void 0;
1122
+ }
1123
+ }
1124
+ async function createEngine() {
1125
+ const dbPath = getDbPath();
1126
+ const projectId = detectProjectId();
1127
+ const engine = new MemoryEngine({ dbPath, defaultProjectId: projectId });
1128
+ await engine.initialize();
1129
+ return { engine, dbPath, projectId };
1130
+ }
1131
+
1132
+ export {
1133
+ getDbPath,
1134
+ detectProjectId,
1135
+ createEngine
1136
+ };