pi-hermes-memory 0.6.7 → 0.6.9
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/README.md +18 -3
- package/docs/images/memory-architecture.svg +1 -1
- package/docs/images/session-lifecycle.svg +1 -1
- package/docs/images/source-architecture.svg +1 -1
- package/docs/mermaid/memory-architecture.mmd +22 -4
- package/docs/mermaid/session-lifecycle.mmd +34 -31
- package/docs/mermaid/source-architecture.mmd +17 -1
- package/package.json +1 -1
- package/src/handlers/correction-detector.ts +28 -3
- package/src/handlers/learn-memory.ts +16 -10
- package/src/handlers/preview-context.ts +67 -0
- package/src/handlers/sync-markdown-memories.ts +136 -0
- package/src/index.ts +7 -3
- package/src/store/db.ts +54 -2
- package/src/store/sqlite-memory-store.ts +472 -46
- package/src/tools/memory-tool.ts +155 -4
- package/src/types.ts +3 -1
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { DatabaseManager } from './db.js';
|
|
2
2
|
import type { MemoryCategory } from '../types.js';
|
|
3
3
|
|
|
4
|
+
const MEMORY_SELECT_COLUMNS = `
|
|
5
|
+
id,
|
|
6
|
+
project,
|
|
7
|
+
target,
|
|
8
|
+
category,
|
|
9
|
+
content,
|
|
10
|
+
failure_reason,
|
|
11
|
+
tool_state,
|
|
12
|
+
corrected_to,
|
|
13
|
+
created,
|
|
14
|
+
last_referenced
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const FAILURE_CATEGORY_SET = new Set<MemoryCategory>([
|
|
18
|
+
'failure',
|
|
19
|
+
'correction',
|
|
20
|
+
'insight',
|
|
21
|
+
'preference',
|
|
22
|
+
'convention',
|
|
23
|
+
'tool-quirk',
|
|
24
|
+
]);
|
|
25
|
+
|
|
4
26
|
/**
|
|
5
27
|
* A memory entry stored in SQLite.
|
|
6
28
|
*/
|
|
@@ -17,6 +39,157 @@ export interface SqliteMemoryEntry {
|
|
|
17
39
|
lastReferenced: string;
|
|
18
40
|
}
|
|
19
41
|
|
|
42
|
+
export interface SqliteMemorySyncInput {
|
|
43
|
+
content: string;
|
|
44
|
+
target: 'memory' | 'user' | 'failure';
|
|
45
|
+
project?: string | null;
|
|
46
|
+
category?: MemoryCategory | null;
|
|
47
|
+
failureReason?: string | null;
|
|
48
|
+
toolState?: string | null;
|
|
49
|
+
correctedTo?: string | null;
|
|
50
|
+
created?: string | null;
|
|
51
|
+
lastReferenced?: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SqliteMemorySyncResult {
|
|
55
|
+
action: 'inserted' | 'existing';
|
|
56
|
+
entry: SqliteMemoryEntry;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SqliteMemoryUpdateResult {
|
|
60
|
+
matched: number;
|
|
61
|
+
updated: number;
|
|
62
|
+
entries: SqliteMemoryEntry[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface SqliteMemoryRemoveResult {
|
|
66
|
+
matched: number;
|
|
67
|
+
removed: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ParsedMarkdownMemoryEntry extends SqliteMemorySyncInput {}
|
|
71
|
+
|
|
72
|
+
function today(): string {
|
|
73
|
+
return new Date().toISOString().split('T')[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeNullable(value?: string | null): string | null {
|
|
77
|
+
if (value == null) return null;
|
|
78
|
+
const trimmed = value.trim();
|
|
79
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeCategory(value?: MemoryCategory | null): MemoryCategory | null {
|
|
83
|
+
return value ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mapRow(row: {
|
|
87
|
+
id: number;
|
|
88
|
+
project: string | null;
|
|
89
|
+
target: string;
|
|
90
|
+
category: string | null;
|
|
91
|
+
content: string;
|
|
92
|
+
failure_reason: string | null;
|
|
93
|
+
tool_state: string | null;
|
|
94
|
+
corrected_to: string | null;
|
|
95
|
+
created: string;
|
|
96
|
+
last_referenced: string;
|
|
97
|
+
}): SqliteMemoryEntry {
|
|
98
|
+
return {
|
|
99
|
+
id: row.id,
|
|
100
|
+
project: row.project,
|
|
101
|
+
target: row.target as 'memory' | 'user' | 'failure',
|
|
102
|
+
category: row.category as MemoryCategory | null,
|
|
103
|
+
content: row.content,
|
|
104
|
+
failureReason: row.failure_reason,
|
|
105
|
+
toolState: row.tool_state,
|
|
106
|
+
correctedTo: row.corrected_to,
|
|
107
|
+
created: row.created,
|
|
108
|
+
lastReferenced: row.last_referenced,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildScopeConditions(params: unknown[], target?: string, project?: string | null, category?: MemoryCategory | null): string[] {
|
|
113
|
+
const conditions: string[] = [];
|
|
114
|
+
|
|
115
|
+
if (target) {
|
|
116
|
+
conditions.push('target = ?');
|
|
117
|
+
params.push(target);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (project !== undefined) {
|
|
121
|
+
if (project === null) {
|
|
122
|
+
conditions.push('project IS NULL');
|
|
123
|
+
} else {
|
|
124
|
+
conditions.push('project = ?');
|
|
125
|
+
params.push(project);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (category !== undefined) {
|
|
130
|
+
if (category === null) {
|
|
131
|
+
conditions.push('category IS NULL');
|
|
132
|
+
} else {
|
|
133
|
+
conditions.push('category = ?');
|
|
134
|
+
params.push(category);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return conditions;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getMemoryById(dbManager: DatabaseManager, id: number): SqliteMemoryEntry | null {
|
|
142
|
+
const db = dbManager.getDb();
|
|
143
|
+
const row = db.prepare(`
|
|
144
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
145
|
+
FROM memories
|
|
146
|
+
WHERE id = ?
|
|
147
|
+
`).get(id) as {
|
|
148
|
+
id: number;
|
|
149
|
+
project: string | null;
|
|
150
|
+
target: string;
|
|
151
|
+
category: string | null;
|
|
152
|
+
content: string;
|
|
153
|
+
failure_reason: string | null;
|
|
154
|
+
tool_state: string | null;
|
|
155
|
+
corrected_to: string | null;
|
|
156
|
+
created: string;
|
|
157
|
+
last_referenced: string;
|
|
158
|
+
} | undefined;
|
|
159
|
+
|
|
160
|
+
return row ? mapRow(row) : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function minDate(a: string, b: string): string {
|
|
164
|
+
return a <= b ? a : b;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function maxDate(a: string, b: string): string {
|
|
168
|
+
return a >= b ? a : b;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function escapeLikePattern(text: string): string {
|
|
172
|
+
return text.replace(/[\\%_]/g, '\\$&');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parseMetadataComment(raw: string): { text: string; created: string; lastReferenced: string } {
|
|
176
|
+
const match = raw.match(/^(.*?)\s*<!--\s*created=([^,]+),\s*last=([^>]+)\s*-->\s*$/);
|
|
177
|
+
if (match) {
|
|
178
|
+
return {
|
|
179
|
+
text: match[1].trim(),
|
|
180
|
+
created: match[2].trim(),
|
|
181
|
+
lastReferenced: match[3].trim(),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const fallback = today();
|
|
186
|
+
return {
|
|
187
|
+
text: raw.trim(),
|
|
188
|
+
created: fallback,
|
|
189
|
+
lastReferenced: fallback,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
20
193
|
/**
|
|
21
194
|
* Add a memory entry to the SQLite store.
|
|
22
195
|
*/
|
|
@@ -28,15 +201,16 @@ export function addMemory(
|
|
|
28
201
|
category: MemoryCategory | null = null,
|
|
29
202
|
failureReason: string | null = null,
|
|
30
203
|
toolState: string | null = null,
|
|
31
|
-
correctedTo: string | null = null
|
|
204
|
+
correctedTo: string | null = null,
|
|
205
|
+
created = today(),
|
|
206
|
+
lastReferenced = created
|
|
32
207
|
): SqliteMemoryEntry {
|
|
33
208
|
const db = dbManager.getDb();
|
|
34
|
-
const today = new Date().toISOString().split('T')[0];
|
|
35
209
|
|
|
36
210
|
const result = db.prepare(`
|
|
37
211
|
INSERT INTO memories (project, target, category, content, failure_reason, tool_state, corrected_to, created, last_referenced)
|
|
38
212
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
39
|
-
`).run(project, target, category, content, failureReason, toolState, correctedTo,
|
|
213
|
+
`).run(project, target, category, content, failureReason, toolState, correctedTo, created, lastReferenced);
|
|
40
214
|
|
|
41
215
|
return {
|
|
42
216
|
id: Number(result.lastInsertRowid),
|
|
@@ -47,8 +221,294 @@ export function addMemory(
|
|
|
47
221
|
failureReason,
|
|
48
222
|
toolState,
|
|
49
223
|
correctedTo,
|
|
50
|
-
created
|
|
51
|
-
lastReferenced
|
|
224
|
+
created,
|
|
225
|
+
lastReferenced,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Build the visible failure-memory text stored in Markdown.
|
|
231
|
+
*/
|
|
232
|
+
export function formatFailureMemoryContent(
|
|
233
|
+
content: string,
|
|
234
|
+
options: {
|
|
235
|
+
category: MemoryCategory;
|
|
236
|
+
failureReason?: string | null;
|
|
237
|
+
toolState?: string | null;
|
|
238
|
+
correctedTo?: string | null;
|
|
239
|
+
project?: string | null;
|
|
240
|
+
}
|
|
241
|
+
): string {
|
|
242
|
+
const categoryTag = `[${options.category}]`;
|
|
243
|
+
const parts = [`${categoryTag} ${content.trim()}`.trim()];
|
|
244
|
+
if (options.failureReason) parts.push(`Failed: ${options.failureReason}`);
|
|
245
|
+
if (options.toolState) parts.push(`Tool state: ${options.toolState}`);
|
|
246
|
+
if (options.correctedTo) parts.push(`Corrected to: ${options.correctedTo}`);
|
|
247
|
+
if (options.project) parts.push(`Project: ${options.project}`);
|
|
248
|
+
return parts.join(' — ');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Parse a Markdown memory entry into SQLite sync fields.
|
|
253
|
+
* Best-effort only: if failure metadata cannot be fully reconstructed,
|
|
254
|
+
* content is still imported and available for search.
|
|
255
|
+
*/
|
|
256
|
+
export function parseMarkdownMemoryEntry(
|
|
257
|
+
rawEntry: string,
|
|
258
|
+
target: 'memory' | 'user' | 'failure',
|
|
259
|
+
project: string | null = null,
|
|
260
|
+
): ParsedMarkdownMemoryEntry {
|
|
261
|
+
const { text, created, lastReferenced } = parseMetadataComment(rawEntry);
|
|
262
|
+
const parsedProject = normalizeNullable(project);
|
|
263
|
+
|
|
264
|
+
if (target !== 'failure') {
|
|
265
|
+
return {
|
|
266
|
+
content: text,
|
|
267
|
+
target,
|
|
268
|
+
project: parsedProject,
|
|
269
|
+
created,
|
|
270
|
+
lastReferenced,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let category: MemoryCategory | null = null;
|
|
275
|
+
let failureReason: string | null = null;
|
|
276
|
+
let toolState: string | null = null;
|
|
277
|
+
let correctedTo: string | null = null;
|
|
278
|
+
|
|
279
|
+
const categoryMatch = text.match(/^\[([^\]]+)\]\s+/);
|
|
280
|
+
if (categoryMatch && FAILURE_CATEGORY_SET.has(categoryMatch[1] as MemoryCategory)) {
|
|
281
|
+
category = categoryMatch[1] as MemoryCategory;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const segments = text.split(' — ');
|
|
285
|
+
for (const segment of segments.slice(1)) {
|
|
286
|
+
if (segment.startsWith('Failed: ') && !failureReason) {
|
|
287
|
+
failureReason = segment.slice('Failed: '.length).trim() || null;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (segment.startsWith('Tool state: ') && !toolState) {
|
|
291
|
+
toolState = segment.slice('Tool state: '.length).trim() || null;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (segment.startsWith('Corrected to: ') && !correctedTo) {
|
|
295
|
+
correctedTo = segment.slice('Corrected to: '.length).trim() || null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
content: text,
|
|
301
|
+
target: 'failure',
|
|
302
|
+
project: parsedProject,
|
|
303
|
+
category,
|
|
304
|
+
failureReason,
|
|
305
|
+
toolState,
|
|
306
|
+
correctedTo,
|
|
307
|
+
created,
|
|
308
|
+
lastReferenced,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Idempotently sync a Markdown-backed memory entry into SQLite.
|
|
314
|
+
* Duplicate identity is exact: project + target + category + content.
|
|
315
|
+
*/
|
|
316
|
+
export function syncMemoryEntry(
|
|
317
|
+
dbManager: DatabaseManager,
|
|
318
|
+
input: SqliteMemorySyncInput,
|
|
319
|
+
): SqliteMemorySyncResult {
|
|
320
|
+
const db = dbManager.getDb();
|
|
321
|
+
const content = input.content.trim();
|
|
322
|
+
const project = normalizeNullable(input.project);
|
|
323
|
+
const category = normalizeCategory(input.category);
|
|
324
|
+
const failureReason = normalizeNullable(input.failureReason);
|
|
325
|
+
const toolState = normalizeNullable(input.toolState);
|
|
326
|
+
const correctedTo = normalizeNullable(input.correctedTo);
|
|
327
|
+
const created = input.created?.trim() || today();
|
|
328
|
+
const lastReferenced = input.lastReferenced?.trim() || created;
|
|
329
|
+
|
|
330
|
+
const params: unknown[] = [];
|
|
331
|
+
const conditions = buildScopeConditions(params, input.target, project, category);
|
|
332
|
+
conditions.push('content = ?');
|
|
333
|
+
params.push(content);
|
|
334
|
+
|
|
335
|
+
const existing = db.prepare(`
|
|
336
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
337
|
+
FROM memories
|
|
338
|
+
WHERE ${conditions.join(' AND ')}
|
|
339
|
+
ORDER BY id ASC
|
|
340
|
+
LIMIT 1
|
|
341
|
+
`).get(...params) as {
|
|
342
|
+
id: number;
|
|
343
|
+
project: string | null;
|
|
344
|
+
target: string;
|
|
345
|
+
category: string | null;
|
|
346
|
+
content: string;
|
|
347
|
+
failure_reason: string | null;
|
|
348
|
+
tool_state: string | null;
|
|
349
|
+
corrected_to: string | null;
|
|
350
|
+
created: string;
|
|
351
|
+
last_referenced: string;
|
|
352
|
+
} | undefined;
|
|
353
|
+
|
|
354
|
+
if (!existing) {
|
|
355
|
+
return {
|
|
356
|
+
action: 'inserted',
|
|
357
|
+
entry: addMemory(
|
|
358
|
+
dbManager,
|
|
359
|
+
content,
|
|
360
|
+
input.target,
|
|
361
|
+
project,
|
|
362
|
+
category,
|
|
363
|
+
failureReason,
|
|
364
|
+
toolState,
|
|
365
|
+
correctedTo,
|
|
366
|
+
created,
|
|
367
|
+
lastReferenced,
|
|
368
|
+
),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const updatedCreated = minDate(existing.created, created);
|
|
373
|
+
const updatedLastReferenced = maxDate(existing.last_referenced, lastReferenced);
|
|
374
|
+
const updatedCategory = (existing.category as MemoryCategory | null) ?? category;
|
|
375
|
+
const updatedFailureReason = existing.failure_reason ?? failureReason;
|
|
376
|
+
const updatedToolState = existing.tool_state ?? toolState;
|
|
377
|
+
const updatedCorrectedTo = existing.corrected_to ?? correctedTo;
|
|
378
|
+
|
|
379
|
+
db.prepare(`
|
|
380
|
+
UPDATE memories
|
|
381
|
+
SET category = ?, failure_reason = ?, tool_state = ?, corrected_to = ?, created = ?, last_referenced = ?
|
|
382
|
+
WHERE id = ?
|
|
383
|
+
`).run(
|
|
384
|
+
updatedCategory,
|
|
385
|
+
updatedFailureReason,
|
|
386
|
+
updatedToolState,
|
|
387
|
+
updatedCorrectedTo,
|
|
388
|
+
updatedCreated,
|
|
389
|
+
updatedLastReferenced,
|
|
390
|
+
existing.id,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
action: 'existing',
|
|
395
|
+
entry: getMemoryById(dbManager, existing.id)!,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Best-effort substring replacement for SQLite-backed memory sync.
|
|
401
|
+
* Updates all matches in the scoped slice to recover from prior duplicate rows.
|
|
402
|
+
*/
|
|
403
|
+
export function replaceSyncedMemories(
|
|
404
|
+
dbManager: DatabaseManager,
|
|
405
|
+
oldText: string,
|
|
406
|
+
updates: {
|
|
407
|
+
content: string;
|
|
408
|
+
target: 'memory' | 'user' | 'failure';
|
|
409
|
+
project?: string | null;
|
|
410
|
+
category?: MemoryCategory | null;
|
|
411
|
+
failureReason?: string | null;
|
|
412
|
+
toolState?: string | null;
|
|
413
|
+
correctedTo?: string | null;
|
|
414
|
+
lastReferenced?: string | null;
|
|
415
|
+
},
|
|
416
|
+
): SqliteMemoryUpdateResult {
|
|
417
|
+
const db = dbManager.getDb();
|
|
418
|
+
const params: unknown[] = [];
|
|
419
|
+
const conditions = buildScopeConditions(params, updates.target, updates.project ?? undefined);
|
|
420
|
+
conditions.push(`content LIKE ? ESCAPE '\\'`);
|
|
421
|
+
params.push(`%${escapeLikePattern(oldText)}%`);
|
|
422
|
+
|
|
423
|
+
const rows = db.prepare(`
|
|
424
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
425
|
+
FROM memories
|
|
426
|
+
WHERE ${conditions.join(' AND ')}
|
|
427
|
+
ORDER BY id ASC
|
|
428
|
+
`).all(...params) as Array<{
|
|
429
|
+
id: number;
|
|
430
|
+
project: string | null;
|
|
431
|
+
target: string;
|
|
432
|
+
category: string | null;
|
|
433
|
+
content: string;
|
|
434
|
+
failure_reason: string | null;
|
|
435
|
+
tool_state: string | null;
|
|
436
|
+
corrected_to: string | null;
|
|
437
|
+
created: string;
|
|
438
|
+
last_referenced: string;
|
|
439
|
+
}>;
|
|
440
|
+
|
|
441
|
+
if (rows.length === 0) {
|
|
442
|
+
return { matched: 0, updated: 0, entries: [] };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const nextLastReferenced = updates.lastReferenced?.trim() || today();
|
|
446
|
+
|
|
447
|
+
for (const row of rows) {
|
|
448
|
+
db.prepare(`
|
|
449
|
+
UPDATE memories
|
|
450
|
+
SET content = ?,
|
|
451
|
+
category = ?,
|
|
452
|
+
failure_reason = ?,
|
|
453
|
+
tool_state = ?,
|
|
454
|
+
corrected_to = ?,
|
|
455
|
+
last_referenced = ?
|
|
456
|
+
WHERE id = ?
|
|
457
|
+
`).run(
|
|
458
|
+
updates.content.trim(),
|
|
459
|
+
updates.category === undefined ? row.category : updates.category,
|
|
460
|
+
updates.failureReason === undefined ? row.failure_reason : normalizeNullable(updates.failureReason),
|
|
461
|
+
updates.toolState === undefined ? row.tool_state : normalizeNullable(updates.toolState),
|
|
462
|
+
updates.correctedTo === undefined ? row.corrected_to : normalizeNullable(updates.correctedTo),
|
|
463
|
+
nextLastReferenced,
|
|
464
|
+
row.id,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
matched: rows.length,
|
|
470
|
+
updated: rows.length,
|
|
471
|
+
entries: rows
|
|
472
|
+
.map((row) => getMemoryById(dbManager, row.id))
|
|
473
|
+
.filter((entry): entry is SqliteMemoryEntry => entry !== null),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Best-effort substring removal for SQLite-backed memory sync.
|
|
479
|
+
* Deletes all matches in the scoped slice to recover from prior duplicate rows.
|
|
480
|
+
*/
|
|
481
|
+
export function removeSyncedMemories(
|
|
482
|
+
dbManager: DatabaseManager,
|
|
483
|
+
oldText: string,
|
|
484
|
+
options: {
|
|
485
|
+
target: 'memory' | 'user' | 'failure';
|
|
486
|
+
project?: string | null;
|
|
487
|
+
},
|
|
488
|
+
): SqliteMemoryRemoveResult {
|
|
489
|
+
const db = dbManager.getDb();
|
|
490
|
+
const params: unknown[] = [];
|
|
491
|
+
const conditions = buildScopeConditions(params, options.target, options.project ?? undefined);
|
|
492
|
+
conditions.push(`content LIKE ? ESCAPE '\\'`);
|
|
493
|
+
params.push(`%${escapeLikePattern(oldText)}%`);
|
|
494
|
+
|
|
495
|
+
const matchingIds = db.prepare(`
|
|
496
|
+
SELECT id
|
|
497
|
+
FROM memories
|
|
498
|
+
WHERE ${conditions.join(' AND ')}
|
|
499
|
+
`).all(...params) as Array<{ id: number }>;
|
|
500
|
+
|
|
501
|
+
if (matchingIds.length === 0) {
|
|
502
|
+
return { matched: 0, removed: 0 };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const deleteParams = matchingIds.map((row) => row.id);
|
|
506
|
+
const placeholders = deleteParams.map(() => '?').join(', ');
|
|
507
|
+
const result = db.prepare(`DELETE FROM memories WHERE id IN (${placeholders})`).run(...deleteParams);
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
matched: matchingIds.length,
|
|
511
|
+
removed: result.changes,
|
|
52
512
|
};
|
|
53
513
|
}
|
|
54
514
|
|
|
@@ -105,7 +565,7 @@ export function searchMemories(
|
|
|
105
565
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
106
566
|
|
|
107
567
|
const sql = `
|
|
108
|
-
SELECT
|
|
568
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
109
569
|
FROM memories m
|
|
110
570
|
${whereClause}
|
|
111
571
|
ORDER BY m.last_referenced DESC
|
|
@@ -126,18 +586,7 @@ export function searchMemories(
|
|
|
126
586
|
last_referenced: string;
|
|
127
587
|
}>;
|
|
128
588
|
|
|
129
|
-
return rows.map(
|
|
130
|
-
id: row.id,
|
|
131
|
-
project: row.project,
|
|
132
|
-
target: row.target as 'memory' | 'user' | 'failure',
|
|
133
|
-
category: row.category as MemoryCategory | null,
|
|
134
|
-
content: row.content,
|
|
135
|
-
failureReason: row.failure_reason,
|
|
136
|
-
toolState: row.tool_state,
|
|
137
|
-
correctedTo: row.corrected_to,
|
|
138
|
-
created: row.created,
|
|
139
|
-
lastReferenced: row.last_referenced,
|
|
140
|
-
}));
|
|
589
|
+
return rows.map(mapRow);
|
|
141
590
|
}
|
|
142
591
|
|
|
143
592
|
/**
|
|
@@ -175,7 +624,7 @@ export function getMemories(
|
|
|
175
624
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
176
625
|
|
|
177
626
|
const rows = db.prepare(`
|
|
178
|
-
SELECT
|
|
627
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
179
628
|
FROM memories
|
|
180
629
|
${whereClause}
|
|
181
630
|
ORDER BY last_referenced DESC
|
|
@@ -192,18 +641,7 @@ export function getMemories(
|
|
|
192
641
|
last_referenced: string;
|
|
193
642
|
}>;
|
|
194
643
|
|
|
195
|
-
return rows.map(
|
|
196
|
-
id: row.id,
|
|
197
|
-
project: row.project,
|
|
198
|
-
target: row.target as 'memory' | 'user' | 'failure',
|
|
199
|
-
category: row.category as MemoryCategory | null,
|
|
200
|
-
content: row.content,
|
|
201
|
-
failureReason: row.failure_reason,
|
|
202
|
-
toolState: row.tool_state,
|
|
203
|
-
correctedTo: row.corrected_to,
|
|
204
|
-
created: row.created,
|
|
205
|
-
lastReferenced: row.last_referenced,
|
|
206
|
-
}));
|
|
644
|
+
return rows.map(mapRow);
|
|
207
645
|
}
|
|
208
646
|
|
|
209
647
|
/**
|
|
@@ -241,7 +679,7 @@ export function getRecentFailures(
|
|
|
241
679
|
}
|
|
242
680
|
|
|
243
681
|
const rows = db.prepare(`
|
|
244
|
-
SELECT
|
|
682
|
+
SELECT ${MEMORY_SELECT_COLUMNS}
|
|
245
683
|
FROM memories
|
|
246
684
|
WHERE ${conditions.join(' AND ')}
|
|
247
685
|
ORDER BY created DESC
|
|
@@ -259,18 +697,7 @@ export function getRecentFailures(
|
|
|
259
697
|
last_referenced: string;
|
|
260
698
|
}>;
|
|
261
699
|
|
|
262
|
-
return rows.map(
|
|
263
|
-
id: row.id,
|
|
264
|
-
project: row.project,
|
|
265
|
-
target: row.target as 'memory' | 'user' | 'failure',
|
|
266
|
-
category: row.category as MemoryCategory | null,
|
|
267
|
-
content: row.content,
|
|
268
|
-
failureReason: row.failure_reason,
|
|
269
|
-
toolState: row.tool_state,
|
|
270
|
-
correctedTo: row.corrected_to,
|
|
271
|
-
created: row.created,
|
|
272
|
-
lastReferenced: row.last_referenced,
|
|
273
|
-
}));
|
|
700
|
+
return rows.map(mapRow);
|
|
274
701
|
}
|
|
275
702
|
|
|
276
703
|
/**
|
|
@@ -278,8 +705,7 @@ export function getRecentFailures(
|
|
|
278
705
|
*/
|
|
279
706
|
export function touchMemory(dbManager: DatabaseManager, id: number): void {
|
|
280
707
|
const db = dbManager.getDb();
|
|
281
|
-
|
|
282
|
-
db.prepare('UPDATE memories SET last_referenced = ? WHERE id = ?').run(today, id);
|
|
708
|
+
db.prepare('UPDATE memories SET last_referenced = ? WHERE id = ?').run(today(), id);
|
|
283
709
|
}
|
|
284
710
|
|
|
285
711
|
/**
|