@memories.sh/cli 0.2.0 → 0.2.2

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,146 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getDb
4
+ } from "./chunk-BDPB4ABQ.js";
5
+
6
+ // src/lib/embeddings.ts
7
+ import { pipeline } from "@xenova/transformers";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ import { existsSync, mkdirSync } from "fs";
11
+ var MODEL_NAME = "Xenova/gte-base";
12
+ var EMBEDDING_DIM = 768;
13
+ var embedder = null;
14
+ var modelLoading = null;
15
+ function getModelCacheDir() {
16
+ const cacheDir = join(homedir(), ".cache", "memories", "models");
17
+ if (!existsSync(cacheDir)) {
18
+ mkdirSync(cacheDir, { recursive: true });
19
+ }
20
+ return cacheDir;
21
+ }
22
+ async function getEmbedder() {
23
+ if (embedder) return embedder;
24
+ if (modelLoading) return modelLoading;
25
+ modelLoading = (async () => {
26
+ const cacheDir = getModelCacheDir();
27
+ process.env.TRANSFORMERS_CACHE = cacheDir;
28
+ embedder = await pipeline("feature-extraction", MODEL_NAME, {
29
+ cache_dir: cacheDir,
30
+ quantized: true
31
+ // Use quantized model for faster loading
32
+ });
33
+ return embedder;
34
+ })();
35
+ return modelLoading;
36
+ }
37
+ async function getEmbedding(text) {
38
+ const model = await getEmbedder();
39
+ const output = await model(text, {
40
+ pooling: "mean",
41
+ normalize: true
42
+ });
43
+ const data = output.data;
44
+ return new Float32Array(data);
45
+ }
46
+ function cosineSimilarity(a, b) {
47
+ if (a.length !== b.length) return 0;
48
+ let dotProduct = 0;
49
+ let normA = 0;
50
+ let normB = 0;
51
+ for (let i = 0; i < a.length; i++) {
52
+ dotProduct += a[i] * b[i];
53
+ normA += a[i] * a[i];
54
+ normB += b[i] * b[i];
55
+ }
56
+ if (normA === 0 || normB === 0) return 0;
57
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
58
+ }
59
+ function embeddingToBuffer(embedding) {
60
+ return Buffer.from(embedding.buffer);
61
+ }
62
+ function bufferToEmbedding(buffer) {
63
+ return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
64
+ }
65
+ async function ensureEmbeddingsSchema() {
66
+ const db = await getDb();
67
+ const tableInfo = await db.execute("PRAGMA table_info(memories)");
68
+ const columns = tableInfo.rows;
69
+ const hasEmbedding = columns.some((c) => c.name === "embedding");
70
+ if (!hasEmbedding) {
71
+ await db.execute("ALTER TABLE memories ADD COLUMN embedding BLOB");
72
+ }
73
+ }
74
+ async function storeEmbedding(memoryId, embedding) {
75
+ const db = await getDb();
76
+ await ensureEmbeddingsSchema();
77
+ const buffer = embeddingToBuffer(embedding);
78
+ await db.execute({
79
+ sql: "UPDATE memories SET embedding = ? WHERE id = ?",
80
+ args: [buffer, memoryId]
81
+ });
82
+ }
83
+ async function getStoredEmbedding(memoryId) {
84
+ const db = await getDb();
85
+ const result = await db.execute({
86
+ sql: "SELECT embedding FROM memories WHERE id = ?",
87
+ args: [memoryId]
88
+ });
89
+ if (result.rows.length === 0) return null;
90
+ const row = result.rows[0];
91
+ if (!row.embedding) return null;
92
+ return new Float32Array(row.embedding);
93
+ }
94
+ async function semanticSearch(query, opts) {
95
+ const db = await getDb();
96
+ const limit = opts?.limit ?? 10;
97
+ const threshold = opts?.threshold ?? 0.3;
98
+ const queryEmbedding = await getEmbedding(query);
99
+ let sql = `
100
+ SELECT id, content, embedding, scope, project_id
101
+ FROM memories
102
+ WHERE deleted_at IS NULL AND embedding IS NOT NULL
103
+ `;
104
+ const args = [];
105
+ if (opts?.projectId) {
106
+ sql += " AND (scope = 'global' OR (scope = 'project' AND project_id = ?))";
107
+ args.push(opts.projectId);
108
+ }
109
+ const result = await db.execute({ sql, args });
110
+ const scored = [];
111
+ for (const row of result.rows) {
112
+ const r = row;
113
+ if (!r.embedding) continue;
114
+ const memEmbedding = new Float32Array(r.embedding);
115
+ const score = cosineSimilarity(queryEmbedding, memEmbedding);
116
+ if (score >= threshold) {
117
+ scored.push({ id: r.id, content: r.content, score });
118
+ }
119
+ }
120
+ scored.sort((a, b) => b.score - a.score);
121
+ return scored.slice(0, limit);
122
+ }
123
+ async function isModelAvailable() {
124
+ try {
125
+ await getEmbedder();
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+ function getEmbeddingDimension() {
132
+ return EMBEDDING_DIM;
133
+ }
134
+
135
+ export {
136
+ getEmbedding,
137
+ cosineSimilarity,
138
+ embeddingToBuffer,
139
+ bufferToEmbedding,
140
+ ensureEmbeddingsSchema,
141
+ storeEmbedding,
142
+ getStoredEmbedding,
143
+ semanticSearch,
144
+ isModelAvailable,
145
+ getEmbeddingDimension
146
+ };
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/db.ts
4
+ import { createClient } from "@libsql/client";
5
+ import { mkdir, readFile, writeFile } from "fs/promises";
6
+ import { existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ function resolveConfigDir() {
10
+ return process.env.MEMORIES_DATA_DIR ?? join(homedir(), ".config", "memories");
11
+ }
12
+ function getConfigDir() {
13
+ return resolveConfigDir();
14
+ }
15
+ function getDbPath() {
16
+ return join(resolveConfigDir(), "local.db");
17
+ }
18
+ function getSyncConfigPath() {
19
+ return join(resolveConfigDir(), "sync.json");
20
+ }
21
+ var client;
22
+ async function getDb() {
23
+ if (client) return client;
24
+ const configDir = resolveConfigDir();
25
+ const dbPath = getDbPath();
26
+ await mkdir(configDir, { recursive: true });
27
+ const sync = await readSyncConfig();
28
+ if (sync) {
29
+ client = createClient({
30
+ url: `file:${dbPath}`,
31
+ syncUrl: sync.syncUrl,
32
+ authToken: sync.syncToken
33
+ });
34
+ await runMigrations(client);
35
+ await client.sync();
36
+ } else {
37
+ client = createClient({ url: `file:${dbPath}` });
38
+ await runMigrations(client);
39
+ }
40
+ return client;
41
+ }
42
+ function resetDb() {
43
+ client?.close();
44
+ client = void 0;
45
+ }
46
+ async function syncDb() {
47
+ const db = await getDb();
48
+ await db.sync();
49
+ }
50
+ async function saveSyncConfig(config) {
51
+ const configDir = resolveConfigDir();
52
+ await mkdir(configDir, { recursive: true });
53
+ await writeFile(getSyncConfigPath(), JSON.stringify(config, null, 2), "utf-8");
54
+ }
55
+ async function readSyncConfig() {
56
+ const syncPath = getSyncConfigPath();
57
+ if (!existsSync(syncPath)) return null;
58
+ const raw = await readFile(syncPath, "utf-8");
59
+ return JSON.parse(raw);
60
+ }
61
+ async function runMigrations(db) {
62
+ await db.execute(
63
+ `CREATE TABLE IF NOT EXISTS memories (
64
+ id TEXT PRIMARY KEY,
65
+ content TEXT NOT NULL,
66
+ tags TEXT,
67
+ scope TEXT NOT NULL DEFAULT 'global',
68
+ project_id TEXT,
69
+ type TEXT NOT NULL DEFAULT 'note',
70
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
71
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
72
+ deleted_at TEXT
73
+ )`
74
+ );
75
+ try {
76
+ await db.execute(`ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'`);
77
+ } catch {
78
+ }
79
+ try {
80
+ await db.execute(`ALTER TABLE memories ADD COLUMN project_id TEXT`);
81
+ } catch {
82
+ }
83
+ try {
84
+ await db.execute(`ALTER TABLE memories ADD COLUMN type TEXT NOT NULL DEFAULT 'note'`);
85
+ } catch {
86
+ }
87
+ await db.execute(
88
+ `CREATE TABLE IF NOT EXISTS configs (
89
+ key TEXT PRIMARY KEY,
90
+ value TEXT NOT NULL
91
+ )`
92
+ );
93
+ await db.execute(
94
+ `CREATE TABLE IF NOT EXISTS projects (
95
+ id TEXT PRIMARY KEY,
96
+ name TEXT NOT NULL,
97
+ path TEXT,
98
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
99
+ )`
100
+ );
101
+ await db.execute(
102
+ `CREATE TABLE IF NOT EXISTS sync_state (
103
+ id TEXT PRIMARY KEY,
104
+ last_synced_at TEXT,
105
+ remote_url TEXT
106
+ )`
107
+ );
108
+ await db.execute(
109
+ `CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
110
+ content,
111
+ tags,
112
+ content='memories',
113
+ content_rowid='rowid'
114
+ )`
115
+ );
116
+ await db.execute(`DROP TRIGGER IF EXISTS memories_ai`);
117
+ await db.execute(`DROP TRIGGER IF EXISTS memories_ad`);
118
+ await db.execute(`DROP TRIGGER IF EXISTS memories_au`);
119
+ await db.execute(`
120
+ CREATE TRIGGER memories_ai AFTER INSERT ON memories
121
+ WHEN NEW.deleted_at IS NULL
122
+ BEGIN
123
+ INSERT INTO memories_fts(rowid, content, tags) VALUES (NEW.rowid, NEW.content, NEW.tags);
124
+ END
125
+ `);
126
+ await db.execute(`
127
+ CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN
128
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags) VALUES('delete', OLD.rowid, OLD.content, OLD.tags);
129
+ END
130
+ `);
131
+ await db.execute(`
132
+ CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
133
+ INSERT INTO memories_fts(memories_fts, rowid, content, tags) VALUES('delete', OLD.rowid, OLD.content, OLD.tags);
134
+ INSERT INTO memories_fts(rowid, content, tags)
135
+ SELECT NEW.rowid, NEW.content, NEW.tags WHERE NEW.deleted_at IS NULL;
136
+ END
137
+ `);
138
+ try {
139
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type)`);
140
+ } catch {
141
+ }
142
+ try {
143
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_memories_scope_project ON memories(scope, project_id)`);
144
+ } catch {
145
+ }
146
+ }
147
+
148
+ export {
149
+ getConfigDir,
150
+ getDb,
151
+ resetDb,
152
+ syncDb,
153
+ saveSyncConfig,
154
+ readSyncConfig
155
+ };
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getDb
4
+ } from "./chunk-BDPB4ABQ.js";
5
+
6
+ // src/lib/memory.ts
7
+ import { nanoid } from "nanoid";
8
+
9
+ // src/lib/git.ts
10
+ import { execSync } from "child_process";
11
+ function getGitRemoteUrl(cwd) {
12
+ try {
13
+ const remote = execSync("git remote get-url origin", {
14
+ cwd,
15
+ encoding: "utf-8",
16
+ stdio: ["pipe", "pipe", "pipe"]
17
+ }).trim();
18
+ return remote || null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function getGitRoot(cwd) {
24
+ try {
25
+ const root = execSync("git rev-parse --show-toplevel", {
26
+ cwd,
27
+ encoding: "utf-8",
28
+ stdio: ["pipe", "pipe", "pipe"]
29
+ }).trim();
30
+ return root || null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function normalizeGitUrl(url) {
36
+ let normalized = url.trim();
37
+ if (normalized.endsWith(".git")) {
38
+ normalized = normalized.slice(0, -4);
39
+ }
40
+ const sshMatch = normalized.match(/^git@([^:]+):(.+)$/);
41
+ if (sshMatch) {
42
+ return `${sshMatch[1]}/${sshMatch[2]}`;
43
+ }
44
+ const httpsMatch = normalized.match(/^https?:\/\/([^/]+)\/(.+)$/);
45
+ if (httpsMatch) {
46
+ return `${httpsMatch[1]}/${httpsMatch[2]}`;
47
+ }
48
+ return normalized;
49
+ }
50
+ function getProjectId(cwd) {
51
+ const remoteUrl = getGitRemoteUrl(cwd);
52
+ if (!remoteUrl) return null;
53
+ return normalizeGitUrl(remoteUrl);
54
+ }
55
+
56
+ // src/lib/memory.ts
57
+ async function recordMemoryHistory(memory, changeType) {
58
+ const db = await getDb();
59
+ await db.execute(`
60
+ CREATE TABLE IF NOT EXISTS memory_history (
61
+ id TEXT PRIMARY KEY,
62
+ memory_id TEXT NOT NULL,
63
+ content TEXT NOT NULL,
64
+ tags TEXT,
65
+ type TEXT NOT NULL,
66
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
67
+ change_type TEXT NOT NULL
68
+ )
69
+ `);
70
+ const historyId = `${memory.id}-${Date.now()}`;
71
+ await db.execute({
72
+ sql: `INSERT INTO memory_history (id, memory_id, content, tags, type, change_type) VALUES (?, ?, ?, ?, ?, ?)`,
73
+ args: [historyId, memory.id, memory.content, memory.tags, memory.type, changeType]
74
+ });
75
+ }
76
+ async function addMemory(content, opts) {
77
+ const db = await getDb();
78
+ const id = nanoid(12);
79
+ const tags = opts?.tags?.length ? opts.tags.join(",") : null;
80
+ const type = opts?.type ?? "note";
81
+ let scope = "global";
82
+ let projectId = null;
83
+ if (!opts?.global) {
84
+ projectId = opts?.projectId ?? getProjectId();
85
+ if (projectId) {
86
+ scope = "project";
87
+ }
88
+ }
89
+ await db.execute({
90
+ sql: `INSERT INTO memories (id, content, tags, scope, project_id, type) VALUES (?, ?, ?, ?, ?, ?)`,
91
+ args: [id, content, tags, scope, projectId, type]
92
+ });
93
+ generateEmbeddingAsync(id, content);
94
+ const result = await db.execute({
95
+ sql: `SELECT * FROM memories WHERE id = ?`,
96
+ args: [id]
97
+ });
98
+ return result.rows[0];
99
+ }
100
+ async function generateEmbeddingAsync(memoryId, content) {
101
+ try {
102
+ const { getEmbedding, storeEmbedding, ensureEmbeddingsSchema } = await import("./embeddings-GH4HB6X7.js");
103
+ await ensureEmbeddingsSchema();
104
+ const embedding = await getEmbedding(content);
105
+ await storeEmbedding(memoryId, embedding);
106
+ } catch {
107
+ }
108
+ }
109
+ async function getMemoryById(id) {
110
+ const db = await getDb();
111
+ const result = await db.execute({
112
+ sql: `SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL`,
113
+ args: [id]
114
+ });
115
+ return result.rows.length > 0 ? result.rows[0] : null;
116
+ }
117
+ async function searchMemories(query, opts) {
118
+ const db = await getDb();
119
+ const limit = opts?.limit ?? 20;
120
+ const includeGlobal = opts?.includeGlobal ?? true;
121
+ const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
122
+ const scopeConditions = [];
123
+ const args = [];
124
+ if (includeGlobal) {
125
+ scopeConditions.push("m.scope = 'global'");
126
+ }
127
+ if (projectId) {
128
+ scopeConditions.push("(m.scope = 'project' AND m.project_id = ?)");
129
+ args.push(projectId);
130
+ }
131
+ if (scopeConditions.length === 0) {
132
+ return [];
133
+ }
134
+ let typeFilter = "";
135
+ if (opts?.types?.length) {
136
+ const placeholders = opts.types.map(() => "?").join(", ");
137
+ typeFilter = `AND m.type IN (${placeholders})`;
138
+ args.push(...opts.types);
139
+ }
140
+ const ftsQuery = query.split(/\s+/).filter(Boolean).map((term) => `"${term}"*`).join(" OR ");
141
+ args.push(limit);
142
+ try {
143
+ const result = await db.execute({
144
+ sql: `
145
+ SELECT m.*, bm25(memories_fts) as rank
146
+ FROM memories m
147
+ JOIN memories_fts fts ON m.rowid = fts.rowid
148
+ WHERE memories_fts MATCH ?
149
+ AND m.deleted_at IS NULL
150
+ AND (${scopeConditions.join(" OR ")})
151
+ ${typeFilter}
152
+ ORDER BY rank ASC, m.created_at DESC
153
+ LIMIT ?
154
+ `,
155
+ args: [ftsQuery, ...args]
156
+ });
157
+ return result.rows;
158
+ } catch (error) {
159
+ console.error("FTS search failed, falling back to LIKE:", error);
160
+ return searchMemoriesLike(query, opts);
161
+ }
162
+ }
163
+ async function searchMemoriesLike(query, opts) {
164
+ const db = await getDb();
165
+ const limit = opts?.limit ?? 20;
166
+ const includeGlobal = opts?.includeGlobal ?? true;
167
+ const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
168
+ const conditions = ["deleted_at IS NULL", "content LIKE ?"];
169
+ const args = [`%${query}%`];
170
+ const scopeConditions = [];
171
+ if (includeGlobal) {
172
+ scopeConditions.push("scope = 'global'");
173
+ }
174
+ if (projectId) {
175
+ scopeConditions.push("(scope = 'project' AND project_id = ?)");
176
+ args.push(projectId);
177
+ }
178
+ if (scopeConditions.length === 0) {
179
+ return [];
180
+ }
181
+ conditions.push(`(${scopeConditions.join(" OR ")})`);
182
+ if (opts?.types?.length) {
183
+ const placeholders = opts.types.map(() => "?").join(", ");
184
+ conditions.push(`type IN (${placeholders})`);
185
+ args.push(...opts.types);
186
+ }
187
+ args.push(limit);
188
+ const result = await db.execute({
189
+ sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC LIMIT ?`,
190
+ args
191
+ });
192
+ return result.rows;
193
+ }
194
+ async function listMemories(opts) {
195
+ const db = await getDb();
196
+ const limit = opts?.limit ?? 50;
197
+ const includeGlobal = opts?.includeGlobal ?? true;
198
+ const projectId = opts?.globalOnly ? void 0 : opts?.projectId ?? getProjectId();
199
+ const conditions = ["deleted_at IS NULL"];
200
+ const args = [];
201
+ const scopeConditions = [];
202
+ if (includeGlobal) {
203
+ scopeConditions.push("scope = 'global'");
204
+ }
205
+ if (projectId) {
206
+ scopeConditions.push("(scope = 'project' AND project_id = ?)");
207
+ args.push(projectId);
208
+ }
209
+ if (scopeConditions.length === 0) {
210
+ return [];
211
+ }
212
+ conditions.push(`(${scopeConditions.join(" OR ")})`);
213
+ if (opts?.tags?.length) {
214
+ const tagClauses = opts.tags.map(() => `tags LIKE ?`).join(" OR ");
215
+ conditions.push(`(${tagClauses})`);
216
+ args.push(...opts.tags.map((t) => `%${t}%`));
217
+ }
218
+ if (opts?.types?.length) {
219
+ const placeholders = opts.types.map(() => "?").join(", ");
220
+ conditions.push(`type IN (${placeholders})`);
221
+ args.push(...opts.types);
222
+ }
223
+ args.push(limit);
224
+ const result = await db.execute({
225
+ sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY type ASC, scope ASC, created_at DESC LIMIT ?`,
226
+ args
227
+ });
228
+ return result.rows;
229
+ }
230
+ async function getRules(opts) {
231
+ const db = await getDb();
232
+ const projectId = opts?.projectId ?? getProjectId();
233
+ const conditions = ["deleted_at IS NULL", "type = 'rule'"];
234
+ const args = [];
235
+ const scopeConditions = ["scope = 'global'"];
236
+ if (projectId) {
237
+ scopeConditions.push("(scope = 'project' AND project_id = ?)");
238
+ args.push(projectId);
239
+ }
240
+ conditions.push(`(${scopeConditions.join(" OR ")})`);
241
+ const result = await db.execute({
242
+ sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY scope ASC, created_at ASC`,
243
+ args
244
+ });
245
+ return result.rows;
246
+ }
247
+ async function getContext(query, opts) {
248
+ const projectId = opts?.projectId ?? getProjectId();
249
+ const limit = opts?.limit ?? 10;
250
+ const rules = await getRules({ projectId: projectId ?? void 0 });
251
+ let memories = [];
252
+ if (query) {
253
+ memories = await searchMemories(query, {
254
+ projectId: projectId ?? void 0,
255
+ limit,
256
+ types: ["decision", "fact", "note"]
257
+ // Exclude rules, they're already included
258
+ });
259
+ }
260
+ return { rules, memories };
261
+ }
262
+ async function updateMemory(id, updates, options) {
263
+ const db = await getDb();
264
+ const existing = await db.execute({
265
+ sql: `SELECT * FROM memories WHERE id = ? AND deleted_at IS NULL`,
266
+ args: [id]
267
+ });
268
+ if (existing.rows.length === 0) return null;
269
+ if (!options?.skipHistory) {
270
+ const old = existing.rows[0];
271
+ await recordMemoryHistory(old, "updated");
272
+ }
273
+ const setClauses = ["updated_at = datetime('now')"];
274
+ const args = [];
275
+ if (updates.content !== void 0) {
276
+ setClauses.push("content = ?");
277
+ args.push(updates.content);
278
+ }
279
+ if (updates.tags !== void 0) {
280
+ setClauses.push("tags = ?");
281
+ args.push(updates.tags.length ? updates.tags.join(",") : null);
282
+ }
283
+ if (updates.type !== void 0) {
284
+ setClauses.push("type = ?");
285
+ args.push(updates.type);
286
+ }
287
+ args.push(id);
288
+ await db.execute({
289
+ sql: `UPDATE memories SET ${setClauses.join(", ")} WHERE id = ?`,
290
+ args
291
+ });
292
+ const result = await db.execute({
293
+ sql: `SELECT * FROM memories WHERE id = ?`,
294
+ args: [id]
295
+ });
296
+ return result.rows[0];
297
+ }
298
+ async function forgetMemory(id) {
299
+ const db = await getDb();
300
+ const existing = await db.execute({
301
+ sql: `SELECT id FROM memories WHERE id = ? AND deleted_at IS NULL`,
302
+ args: [id]
303
+ });
304
+ if (existing.rows.length === 0) return false;
305
+ await db.execute({
306
+ sql: `UPDATE memories SET deleted_at = datetime('now') WHERE id = ?`,
307
+ args: [id]
308
+ });
309
+ return true;
310
+ }
311
+ async function findMemoriesToForget(filter) {
312
+ const db = await getDb();
313
+ const conditions = ["deleted_at IS NULL"];
314
+ const args = [];
315
+ if (filter.types?.length) {
316
+ const placeholders = filter.types.map(() => "?").join(", ");
317
+ conditions.push(`type IN (${placeholders})`);
318
+ args.push(...filter.types);
319
+ }
320
+ if (filter.tags?.length) {
321
+ const tagClauses = filter.tags.map(() => `tags LIKE ?`).join(" OR ");
322
+ conditions.push(`(${tagClauses})`);
323
+ args.push(...filter.tags.map((t) => `%${t}%`));
324
+ }
325
+ if (filter.olderThanDays !== void 0) {
326
+ conditions.push(`created_at < datetime('now', ?)`);
327
+ args.push(`-${filter.olderThanDays} days`);
328
+ }
329
+ if (filter.pattern) {
330
+ const likePattern = filter.pattern.replace(/%/g, "\\%").replace(/_/g, "\\_").replace(/\*/g, "%").replace(/\?/g, "_");
331
+ conditions.push(`content LIKE ? ESCAPE '\\'`);
332
+ args.push(`%${likePattern}%`);
333
+ }
334
+ if (filter.projectId) {
335
+ conditions.push("(scope = 'project' AND project_id = ?)");
336
+ args.push(filter.projectId);
337
+ }
338
+ const result = await db.execute({
339
+ sql: `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`,
340
+ args
341
+ });
342
+ return result.rows;
343
+ }
344
+ async function bulkForgetByIds(ids) {
345
+ if (ids.length === 0) return 0;
346
+ const db = await getDb();
347
+ const batchSize = 500;
348
+ for (let i = 0; i < ids.length; i += batchSize) {
349
+ const batch = ids.slice(i, i + batchSize);
350
+ const placeholders = batch.map(() => "?").join(", ");
351
+ await db.execute({
352
+ sql: `UPDATE memories SET deleted_at = datetime('now') WHERE id IN (${placeholders})`,
353
+ args: batch
354
+ });
355
+ }
356
+ return ids.length;
357
+ }
358
+
359
+ export {
360
+ getGitRoot,
361
+ getProjectId,
362
+ recordMemoryHistory,
363
+ addMemory,
364
+ getMemoryById,
365
+ searchMemories,
366
+ listMemories,
367
+ getRules,
368
+ getContext,
369
+ updateMemory,
370
+ forgetMemory,
371
+ findMemoriesToForget,
372
+ bulkForgetByIds
373
+ };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ bufferToEmbedding,
4
+ cosineSimilarity,
5
+ embeddingToBuffer,
6
+ ensureEmbeddingsSchema,
7
+ getEmbedding,
8
+ getEmbeddingDimension,
9
+ getStoredEmbedding,
10
+ isModelAvailable,
11
+ semanticSearch,
12
+ storeEmbedding
13
+ } from "./chunk-4GNYCHD4.js";
14
+ import "./chunk-BDPB4ABQ.js";
15
+ export {
16
+ bufferToEmbedding,
17
+ cosineSimilarity,
18
+ embeddingToBuffer,
19
+ ensureEmbeddingsSchema,
20
+ getEmbedding,
21
+ getEmbeddingDimension,
22
+ getStoredEmbedding,
23
+ isModelAvailable,
24
+ semanticSearch,
25
+ storeEmbedding
26
+ };