fastrmemory 0.3.0 → 0.4.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.
Files changed (67) hide show
  1. package/.claude-plugin/marketplace.json +14 -14
  2. package/.claude-plugin/plugin.json +18 -18
  3. package/.cursor/mcp.json +3 -1
  4. package/.cursor/skills/fastr-compress/SKILL.md +33 -33
  5. package/.cursor/skills/fastr-curate/SKILL.md +62 -62
  6. package/.cursor/skills/fastr-dag/SKILL.md +47 -47
  7. package/.cursor/skills/fastr-doctor/SKILL.md +39 -39
  8. package/.cursor/skills/fastr-memory/SKILL.md +51 -51
  9. package/.cursor/skills/fastr-plan-estimate/SKILL.md +45 -45
  10. package/.cursor/skills/fastr-session-commit/SKILL.md +49 -49
  11. package/.cursor/skills/fastr-setup/SKILL.md +45 -45
  12. package/.cursor/skills/fastr-trap-log/SKILL.md +39 -39
  13. package/.cursor/skills/fastr-vision/SKILL.md +35 -35
  14. package/.cursor/skills/skills.json +65 -65
  15. package/README.md +642 -729
  16. package/bin/fastr-guard.mjs +61 -0
  17. package/dist/bin/fastr.js +68 -10
  18. package/dist/bin/fastr.js.map +1 -1
  19. package/dist/src/bench/benchmark.js +13 -13
  20. package/dist/src/bench/harness.js +17 -17
  21. package/dist/src/bench/public-harness.js +5 -5
  22. package/dist/src/branch/manager.js +31 -31
  23. package/dist/src/ci/guardian.js +9 -9
  24. package/dist/src/db/doctor.js +25 -25
  25. package/dist/src/db/hygiene.js +23 -23
  26. package/dist/src/db/repository.js +283 -283
  27. package/dist/src/db/schema.js +268 -268
  28. package/dist/src/db/spool.js +4 -4
  29. package/dist/src/export/obsidian.js +15 -15
  30. package/dist/src/import/local.js +4 -4
  31. package/dist/src/installer/mcp-init.d.ts +65 -0
  32. package/dist/src/installer/mcp-init.d.ts.map +1 -0
  33. package/dist/src/installer/mcp-init.js +293 -0
  34. package/dist/src/installer/mcp-init.js.map +1 -0
  35. package/dist/src/installer/setup.d.ts +2 -0
  36. package/dist/src/installer/setup.d.ts.map +1 -1
  37. package/dist/src/installer/setup.js +24 -18
  38. package/dist/src/installer/setup.js.map +1 -1
  39. package/dist/src/mcp/server.js +25 -25
  40. package/dist/src/mirror/markdown-pages.js +22 -22
  41. package/dist/src/search/backfill.js +6 -6
  42. package/dist/src/search/hybrid-rrf.js +60 -60
  43. package/dist/src/ui/server.js +1259 -1259
  44. package/dist/src/vision/assets.js +6 -6
  45. package/dist-ui/assets/ActionsPage-B3quzFNF.js +11 -11
  46. package/dist-ui/assets/GraphPage-DetiATmC.js +1 -1
  47. package/dist-ui/assets/ObservatoryPage-QIGMwENf.js +16 -16
  48. package/dist-ui/assets/TreemapPage-C7uLZd37.js +2 -2
  49. package/dist-ui/assets/index-BJnPINTu.css +1 -1
  50. package/dist-ui/assets/index-DWAH--Fu.js +329 -329
  51. package/dist-ui/assets/maximize-2-BebkGbZA.js +6 -6
  52. package/dist-ui/build-manifest.json +95 -95
  53. package/dist-ui/index.html +26 -26
  54. package/llms-install.md +77 -69
  55. package/package.json +108 -107
  56. package/server.json +28 -28
  57. package/skills/fastr-compress/SKILL.md +33 -33
  58. package/skills/fastr-curate/SKILL.md +62 -62
  59. package/skills/fastr-dag/SKILL.md +47 -47
  60. package/skills/fastr-doctor/SKILL.md +39 -39
  61. package/skills/fastr-memory/SKILL.md +51 -51
  62. package/skills/fastr-plan-estimate/SKILL.md +45 -45
  63. package/skills/fastr-session-commit/SKILL.md +49 -49
  64. package/skills/fastr-setup/SKILL.md +45 -45
  65. package/skills/fastr-trap-log/SKILL.md +39 -39
  66. package/skills/fastr-vision/SKILL.md +35 -35
  67. package/skills/skills.json +65 -65
@@ -72,10 +72,10 @@ export class FastrRepository {
72
72
  agent_id: event.agent_id || 'default'
73
73
  };
74
74
  try {
75
- this.db.prepare(`
76
- INSERT OR IGNORE INTO raw_events (
77
- id, project_id, session_id, agent_id, event_type, tool_name, file_path, raw_payload, content_hash
78
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
75
+ this.db.prepare(`
76
+ INSERT OR IGNORE INTO raw_events (
77
+ id, project_id, session_id, agent_id, event_type, tool_name, file_path, raw_payload, content_hash
78
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
79
79
  `).run(fullEvent.id, fullEvent.project_id, fullEvent.session_id, fullEvent.agent_id, fullEvent.event_type, fullEvent.tool_name || null, fullEvent.file_path || null, fullEvent.raw_payload, fullEvent.content_hash);
80
80
  }
81
81
  catch (err) {
@@ -105,8 +105,8 @@ export class FastrRepository {
105
105
  if (!Array.isArray(eventIds) || eventIds.length === 0)
106
106
  return [];
107
107
  const placeholders = eventIds.map(() => '?').join(',');
108
- const rows = this.db.prepare(`
109
- SELECT * FROM raw_events WHERE id IN (${placeholders}) ORDER BY created_at ASC
108
+ const rows = this.db.prepare(`
109
+ SELECT * FROM raw_events WHERE id IN (${placeholders}) ORDER BY created_at ASC
110
110
  `).all(...eventIds);
111
111
  return rows;
112
112
  }
@@ -132,10 +132,10 @@ export class FastrRepository {
132
132
  content: params.content,
133
133
  createdAt: nowIso
134
134
  });
135
- this.db.prepare(`
136
- INSERT INTO ${targetTable} (
137
- id, project_id, agent_id, module, kind, title, content, status, pinned, source_event_ids, created_at, provenance_hash
138
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
135
+ this.db.prepare(`
136
+ INSERT INTO ${targetTable} (
137
+ id, project_id, agent_id, module, kind, title, content, status, pinned, source_event_ids, created_at, provenance_hash
138
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
139
139
  `).run(id, targetProject, agentId, module, kind, params.title, params.content, status, pinned, sourceIds, nowIso, provenanceHash);
140
140
  // Explicit file links
141
141
  if (params.files && params.files.length > 0) {
@@ -148,17 +148,17 @@ export class FastrRepository {
148
148
  for (const ent of extracted) {
149
149
  const entId = `ent_${crypto.createHash('sha256').update(`${params.projectId}:${ent.name.toLowerCase()}`).digest('hex').slice(0, 12)}`;
150
150
  // Upsert entity
151
- this.db.prepare(`
152
- INSERT INTO entities (id, project_id, name, entity_type)
153
- VALUES (?, ?, ?, ?)
154
- ON CONFLICT(id) DO NOTHING
151
+ this.db.prepare(`
152
+ INSERT INTO entities (id, project_id, name, entity_type)
153
+ VALUES (?, ?, ?, ?)
154
+ ON CONFLICT(id) DO NOTHING
155
155
  `).run(entId, params.projectId, ent.name, ent.type);
156
156
  // Link memory to entity in knowledge graph
157
157
  const edgeId = `edge_${crypto.randomBytes(6).toString('hex')}`;
158
- this.db.prepare(`
159
- INSERT INTO graph_edges (id, project_id, source_id, target_id, edge_type, weight)
160
- VALUES (?, ?, ?, ?, 'entity_link', 1.0)
161
- ON CONFLICT(id) DO NOTHING
158
+ this.db.prepare(`
159
+ INSERT INTO graph_edges (id, project_id, source_id, target_id, edge_type, weight)
160
+ VALUES (?, ?, ?, ?, 'entity_link', 1.0)
161
+ ON CONFLICT(id) DO NOTHING
162
162
  `).run(edgeId, params.projectId, id, entId);
163
163
  if (ent.type === 'file' && (!params.files || params.files.length === 0)) {
164
164
  autoFiles.push(ent.name);
@@ -173,17 +173,17 @@ export class FastrRepository {
173
173
  }
174
174
  // 2. Proactive Contradiction & Conflict Detection
175
175
  try {
176
- const candidates = this.db.prepare(`
177
- SELECT id, title, content FROM memories
178
- WHERE project_id = ? AND valid_to IS NULL AND id != ?
179
- ORDER BY created_at DESC LIMIT 50
176
+ const candidates = this.db.prepare(`
177
+ SELECT id, title, content FROM memories
178
+ WHERE project_id = ? AND valid_to IS NULL AND id != ?
179
+ ORDER BY created_at DESC LIMIT 50
180
180
  `).all(params.projectId, id);
181
181
  for (const c of candidates) {
182
182
  const check = detectConflict(params.title, params.content, c.title, c.content);
183
183
  if (check.hasConflict) {
184
- this.db.prepare(`
185
- INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
186
- VALUES (?, ?, ?, ?, ?)
184
+ this.db.prepare(`
185
+ INSERT INTO memory_conflicts (project_id, memory_a_id, memory_b_id, reason, score)
186
+ VALUES (?, ?, ?, ?, ?)
187
187
  `).run(params.projectId, id, c.id, check.reason, check.score);
188
188
  }
189
189
  }
@@ -213,12 +213,12 @@ export class FastrRepository {
213
213
  return row || null;
214
214
  }
215
215
  listActiveMemories(projectId, module) {
216
- let query = `
217
- SELECT * FROM memories
218
- WHERE (project_id = ? OR project_id = 'global')
219
- AND valid_to IS NULL
220
- AND status != 'superseded'
221
- AND status != 'dormant'
216
+ let query = `
217
+ SELECT * FROM memories
218
+ WHERE (project_id = ? OR project_id = 'global')
219
+ AND valid_to IS NULL
220
+ AND status != 'superseded'
221
+ AND status != 'dormant'
222
222
  `;
223
223
  const params = [projectId];
224
224
  if (module) {
@@ -226,12 +226,12 @@ export class FastrRepository {
226
226
  params.push(module.trim().toLowerCase());
227
227
  }
228
228
  if (this.hasGlobalDb()) {
229
- let gQuery = `
230
- UNION ALL
231
- SELECT * FROM global_db.memories
232
- WHERE valid_to IS NULL
233
- AND status != 'superseded'
234
- AND status != 'dormant'
229
+ let gQuery = `
230
+ UNION ALL
231
+ SELECT * FROM global_db.memories
232
+ WHERE valid_to IS NULL
233
+ AND status != 'superseded'
234
+ AND status != 'dormant'
235
235
  `;
236
236
  if (module) {
237
237
  gQuery += ' AND module = ?';
@@ -243,22 +243,22 @@ export class FastrRepository {
243
243
  return this.db.prepare(query).all(...params);
244
244
  }
245
245
  listTraps(projectId) {
246
- let query = `
247
- SELECT * FROM memories
248
- WHERE (project_id = ? OR project_id = 'global')
249
- AND kind = 'error_trap'
250
- AND valid_to IS NULL
251
- AND status != 'superseded'
252
- AND status != 'dormant'
246
+ let query = `
247
+ SELECT * FROM memories
248
+ WHERE (project_id = ? OR project_id = 'global')
249
+ AND kind = 'error_trap'
250
+ AND valid_to IS NULL
251
+ AND status != 'superseded'
252
+ AND status != 'dormant'
253
253
  `;
254
254
  if (this.hasGlobalDb()) {
255
- query += `
256
- UNION ALL
257
- SELECT * FROM global_db.memories
258
- WHERE kind = 'error_trap'
259
- AND valid_to IS NULL
260
- AND status != 'superseded'
261
- AND status != 'dormant'
255
+ query += `
256
+ UNION ALL
257
+ SELECT * FROM global_db.memories
258
+ WHERE kind = 'error_trap'
259
+ AND valid_to IS NULL
260
+ AND status != 'superseded'
261
+ AND status != 'dormant'
262
262
  `;
263
263
  }
264
264
  query += ' ORDER BY reward_score DESC, created_at DESC';
@@ -277,13 +277,13 @@ export class FastrRepository {
277
277
  }
278
278
  const inGlobal = table === 'global_db.memories';
279
279
  // 1. Mark old memory as superseded (only if it is still the live head of its chain)
280
- const res = this.db.prepare(`
281
- UPDATE ${table}
282
- SET valid_to = ?,
283
- status = 'superseded',
284
- superseded_by_id = ?,
285
- superseded_reason = ?
286
- WHERE id = ? AND valid_to IS NULL
280
+ const res = this.db.prepare(`
281
+ UPDATE ${table}
282
+ SET valid_to = ?,
283
+ status = 'superseded',
284
+ superseded_by_id = ?,
285
+ superseded_reason = ?
286
+ WHERE id = ? AND valid_to IS NULL
287
287
  `).run(nowIso, newId, reason, oldMemoryId);
288
288
  if (Number(res.changes) === 0) {
289
289
  throw new Error(`Memory ${oldMemoryId} is no longer active (already superseded by ${oldMem.superseded_by_id ?? 'another update'}); supersede the current version instead.`);
@@ -298,10 +298,10 @@ export class FastrRepository {
298
298
  content: newContent,
299
299
  createdAt: nowIso
300
300
  });
301
- this.db.prepare(`
302
- INSERT INTO ${table} (
303
- id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score, source_event_ids, created_at, provenance_hash
304
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 'verified', ?, ?, ?, ?, ?)
301
+ this.db.prepare(`
302
+ INSERT INTO ${table} (
303
+ id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score, source_event_ids, created_at, provenance_hash
304
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'verified', ?, ?, ?, ?, ?)
305
305
  `).run(newId, oldMem.project_id, oldMem.agent_id, oldMem.module, oldMem.kind, newTitle, newContent, oldMem.pinned, oldMem.reward_score, oldMem.source_event_ids, nowIso, provHash);
306
306
  // 3. Link files
307
307
  const targetFiles = files && files.length > 0 ? files : this.getFilesForMemory(oldMemoryId);
@@ -341,11 +341,11 @@ export class FastrRepository {
341
341
  else {
342
342
  newScore = Math.max(0.1, Number((rewardBefore - 0.5).toFixed(2)));
343
343
  // Count consecutive bad outcomes from the append-only feedback log.
344
- const recent = this.db.prepare(`
345
- SELECT outcome FROM memory_feedback
346
- WHERE memory_id = ?
347
- ORDER BY created_at DESC, rowid DESC
348
- LIMIT 2
344
+ const recent = this.db.prepare(`
345
+ SELECT outcome FROM memory_feedback
346
+ WHERE memory_id = ?
347
+ ORDER BY created_at DESC, rowid DESC
348
+ LIMIT 2
349
349
  `).all(memoryId);
350
350
  badStreak = 1;
351
351
  for (const row of recent) {
@@ -358,20 +358,20 @@ export class FastrRepository {
358
358
  if (outcome === 'bad' && badStreak >= 3 && statusBefore !== 'superseded') {
359
359
  newStatus = 'dormant';
360
360
  }
361
- const res = this.db.prepare(`
362
- UPDATE ${table}
363
- SET reward_score = ?, status = ?
364
- WHERE id = ?
361
+ const res = this.db.prepare(`
362
+ UPDATE ${table}
363
+ SET reward_score = ?, status = ?
364
+ WHERE id = ?
365
365
  `).run(newScore, newStatus, memoryId);
366
366
  if (Number(res.changes) === 0) {
367
367
  throw new Error(`Memory not found: ${memoryId}`);
368
368
  }
369
369
  const tier = confidenceTier({ status: newStatus, reward_score: newScore });
370
- this.db.prepare(`
371
- INSERT INTO memory_feedback (
372
- id, project_id, memory_id, outcome, note, reward_before, reward_after,
373
- status_before, status_after, confidence_tier, bad_streak, created_at
374
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
370
+ this.db.prepare(`
371
+ INSERT INTO memory_feedback (
372
+ id, project_id, memory_id, outcome, note, reward_before, reward_after,
373
+ status_before, status_after, confidence_tier, bad_streak, created_at
374
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
375
375
  `).run(`fb_${crypto.randomBytes(4).toString('hex')}`, mem.project_id, memoryId, outcome, note ? scrubSecrets(note) : null, rewardBefore, newScore, statusBefore, newStatus, tier, badStreak, new Date().toISOString());
376
376
  this.db.exec('COMMIT');
377
377
  return {
@@ -391,10 +391,10 @@ export class FastrRepository {
391
391
  const table = this.locateMemoryTable(memoryId);
392
392
  if (!table)
393
393
  return { success: false, pinned, error: `Memory not found: ${memoryId}` };
394
- const res = this.db.prepare(`
395
- UPDATE ${table}
396
- SET pinned = ?
397
- WHERE id = ?
394
+ const res = this.db.prepare(`
395
+ UPDATE ${table}
396
+ SET pinned = ?
397
+ WHERE id = ?
398
398
  `).run(pinned ? 1 : 0, memoryId);
399
399
  if (Number(res.changes) === 0)
400
400
  return { success: false, pinned, error: `Memory not found: ${memoryId}` };
@@ -405,20 +405,20 @@ export class FastrRepository {
405
405
  if (!table)
406
406
  return { success: false, error: `Memory not found: ${memoryId}` };
407
407
  const nowIso = new Date().toISOString();
408
- const res = this.db.prepare(`
409
- UPDATE ${table}
410
- SET status = 'superseded', valid_to = ?, superseded_reason = ?
411
- WHERE id = ? AND valid_to IS NULL
408
+ const res = this.db.prepare(`
409
+ UPDATE ${table}
410
+ SET status = 'superseded', valid_to = ?, superseded_reason = ?
411
+ WHERE id = ? AND valid_to IS NULL
412
412
  `).run(nowIso, reason, memoryId);
413
413
  if (Number(res.changes) === 0)
414
414
  return { success: false, error: `Memory ${memoryId} is already inactive` };
415
415
  return { success: true };
416
416
  }
417
417
  verifyStaging(projectId) {
418
- const res = this.db.prepare(`
419
- UPDATE memories
420
- SET status = 'verified'
421
- WHERE project_id = ? AND status = 'staging'
418
+ const res = this.db.prepare(`
419
+ UPDATE memories
420
+ SET status = 'verified'
421
+ WHERE project_id = ? AND status = 'staging'
422
422
  `).run(projectId);
423
423
  return Number(res.changes);
424
424
  }
@@ -427,27 +427,27 @@ export class FastrRepository {
427
427
  return;
428
428
  const nowIso = new Date().toISOString();
429
429
  const placeholders = memoryIds.map(() => '?').join(',');
430
- this.db.prepare(`
431
- UPDATE memories
432
- SET retrieval_count = retrieval_count + 1,
433
- last_recalled_at = ?
434
- WHERE id IN (${placeholders})
430
+ this.db.prepare(`
431
+ UPDATE memories
432
+ SET retrieval_count = retrieval_count + 1,
433
+ last_recalled_at = ?
434
+ WHERE id IN (${placeholders})
435
435
  `).run(nowIso, ...memoryIds);
436
436
  }
437
437
  updateEmbedding(memoryId, embedding) {
438
438
  const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
439
- this.db.prepare(`
440
- UPDATE memories
441
- SET embedding = ?, embedded = 1
442
- WHERE id = ?
439
+ this.db.prepare(`
440
+ UPDATE memories
441
+ SET embedding = ?, embedded = 1
442
+ WHERE id = ?
443
443
  `).run(buffer, memoryId);
444
444
  }
445
445
  // --- FILE LINKS ---
446
446
  linkFiles(memoryId, files, symbols, inGlobal = false) {
447
447
  const table = inGlobal && this.hasGlobalDb() ? 'global_db.file_links' : 'file_links';
448
- const stmt = this.db.prepare(`
449
- INSERT OR IGNORE INTO ${table} (memory_id, file_path, symbol_name)
450
- VALUES (?, ?, ?)
448
+ const stmt = this.db.prepare(`
449
+ INSERT OR IGNORE INTO ${table} (memory_id, file_path, symbol_name)
450
+ VALUES (?, ?, ?)
451
451
  `);
452
452
  const symbolList = symbols && symbols.length > 0 ? symbols : [''];
453
453
  for (const f of files) {
@@ -460,39 +460,39 @@ export class FastrRepository {
460
460
  }
461
461
  }
462
462
  getFilesForMemory(memoryId) {
463
- let rows = this.db.prepare(`
464
- SELECT DISTINCT file_path FROM file_links WHERE memory_id = ?
463
+ let rows = this.db.prepare(`
464
+ SELECT DISTINCT file_path FROM file_links WHERE memory_id = ?
465
465
  `).all(memoryId);
466
466
  if (rows.length === 0 && this.hasGlobalDb()) {
467
- rows = this.db.prepare(`
468
- SELECT DISTINCT file_path FROM global_db.file_links WHERE memory_id = ?
467
+ rows = this.db.prepare(`
468
+ SELECT DISTINCT file_path FROM global_db.file_links WHERE memory_id = ?
469
469
  `).all(memoryId);
470
470
  }
471
471
  return rows.map((r) => r.file_path);
472
472
  }
473
473
  getMemoriesForFile(projectId, filePath) {
474
474
  const targetPath = filePath.replace(/\\/g, '/').trim();
475
- let innerQuery = `
476
- SELECT m.*
477
- FROM memories m
478
- JOIN file_links fl ON fl.memory_id = m.id
479
- WHERE (m.project_id = ? OR m.project_id = 'global')
480
- AND fl.file_path = ?
481
- AND m.valid_to IS NULL
482
- AND m.status != 'superseded'
483
- AND m.status != 'dormant'
475
+ let innerQuery = `
476
+ SELECT m.*
477
+ FROM memories m
478
+ JOIN file_links fl ON fl.memory_id = m.id
479
+ WHERE (m.project_id = ? OR m.project_id = 'global')
480
+ AND fl.file_path = ?
481
+ AND m.valid_to IS NULL
482
+ AND m.status != 'superseded'
483
+ AND m.status != 'dormant'
484
484
  `;
485
485
  const params = [projectId, targetPath];
486
486
  if (this.hasGlobalDb()) {
487
- innerQuery += `
488
- UNION ALL
489
- SELECT gm.*
490
- FROM global_db.memories gm
491
- JOIN global_db.file_links gfl ON gfl.memory_id = gm.id
492
- WHERE gfl.file_path = ?
493
- AND gm.valid_to IS NULL
494
- AND gm.status != 'superseded'
495
- AND gm.status != 'dormant'
487
+ innerQuery += `
488
+ UNION ALL
489
+ SELECT gm.*
490
+ FROM global_db.memories gm
491
+ JOIN global_db.file_links gfl ON gfl.memory_id = gm.id
492
+ WHERE gfl.file_path = ?
493
+ AND gm.valid_to IS NULL
494
+ AND gm.status != 'superseded'
495
+ AND gm.status != 'dormant'
496
496
  `;
497
497
  params.push(targetPath);
498
498
  }
@@ -502,24 +502,24 @@ export class FastrRepository {
502
502
  }
503
503
  listGlobalMemories() {
504
504
  if (this.hasGlobalDb()) {
505
- return this.db.prepare(`
506
- SELECT * FROM global_db.memories
507
- WHERE valid_to IS NULL AND status != 'superseded'
508
- ORDER BY reward_score DESC, created_at DESC
505
+ return this.db.prepare(`
506
+ SELECT * FROM global_db.memories
507
+ WHERE valid_to IS NULL AND status != 'superseded'
508
+ ORDER BY reward_score DESC, created_at DESC
509
509
  `).all();
510
510
  }
511
- return this.db.prepare(`
512
- SELECT * FROM memories
513
- WHERE project_id = 'global' AND valid_to IS NULL AND status != 'superseded'
514
- ORDER BY reward_score DESC, created_at DESC
511
+ return this.db.prepare(`
512
+ SELECT * FROM memories
513
+ WHERE project_id = 'global' AND valid_to IS NULL AND status != 'superseded'
514
+ ORDER BY reward_score DESC, created_at DESC
515
515
  `).all();
516
516
  }
517
517
  getTimeline(projectId, options = {}) {
518
518
  const limit = options.limit || 50;
519
- let query = `
520
- SELECT id, kind, title, module, status, reward_score, created_at, valid_to, superseded_by_id, superseded_reason
521
- FROM memories
522
- WHERE project_id = ?
519
+ let query = `
520
+ SELECT id, kind, title, module, status, reward_score, created_at, valid_to, superseded_by_id, superseded_reason
521
+ FROM memories
522
+ WHERE project_id = ?
523
523
  `;
524
524
  const params = [projectId];
525
525
  if (options.module) {
@@ -587,11 +587,11 @@ export class FastrRepository {
587
587
  }
588
588
  // Complete graph
589
589
  const memories = this.listActiveMemories(projectId);
590
- const fileLinks = this.db.prepare(`
591
- SELECT fl.memory_id, fl.file_path, m.title, m.kind, m.module, m.reward_score
592
- FROM file_links fl
593
- JOIN memories m ON m.id = fl.memory_id
594
- WHERE m.project_id = ? AND m.valid_to IS NULL
590
+ const fileLinks = this.db.prepare(`
591
+ SELECT fl.memory_id, fl.file_path, m.title, m.kind, m.module, m.reward_score
592
+ FROM file_links fl
593
+ JOIN memories m ON m.id = fl.memory_id
594
+ WHERE m.project_id = ? AND m.valid_to IS NULL
595
595
  `).all(projectId);
596
596
  const nodesMap = new Map();
597
597
  const edges = [];
@@ -610,8 +610,8 @@ export class FastrRepository {
610
610
  nodesMap.set(ent.id, { id: ent.id, label: ent.name, type: 'entity', kind: ent.entity_type });
611
611
  }
612
612
  }
613
- const graphEdges = this.db.prepare(`
614
- SELECT source_id, target_id, edge_type FROM graph_edges WHERE project_id = ?
613
+ const graphEdges = this.db.prepare(`
614
+ SELECT source_id, target_id, edge_type FROM graph_edges WHERE project_id = ?
615
615
  `).all(projectId);
616
616
  for (const ge of graphEdges) {
617
617
  edges.push({ source: ge.source_id, target: ge.target_id, relation: ge.edge_type });
@@ -626,9 +626,9 @@ export class FastrRepository {
626
626
  const activeFilesJson = typeof handoff.active_files === 'string'
627
627
  ? handoff.active_files
628
628
  : JSON.stringify(handoff.active_files || []);
629
- const res = this.db.prepare(`
630
- INSERT INTO handoffs (project_id, session_id, task_title, summary, next_step, active_files)
631
- VALUES (?, ?, ?, ?, ?, ?)
629
+ const res = this.db.prepare(`
630
+ INSERT INTO handoffs (project_id, session_id, task_title, summary, next_step, active_files)
631
+ VALUES (?, ?, ?, ?, ?, ?)
632
632
  `).run(handoff.project_id, handoff.session_id, handoff.task_title, handoff.summary, handoff.next_step, activeFilesJson);
633
633
  return {
634
634
  ...handoff,
@@ -637,26 +637,26 @@ export class FastrRepository {
637
637
  };
638
638
  }
639
639
  getLatestHandoff(projectId) {
640
- const row = this.db.prepare(`
641
- SELECT * FROM handoffs WHERE project_id = ? ORDER BY id DESC LIMIT 1
640
+ const row = this.db.prepare(`
641
+ SELECT * FROM handoffs WHERE project_id = ? ORDER BY id DESC LIMIT 1
642
642
  `).get(projectId);
643
643
  return row || null;
644
644
  }
645
645
  // --- STATUS ---
646
646
  getStatus(projectId) {
647
- const counts = this.db.prepare(`
648
- SELECT
649
- COUNT(CASE WHEN valid_to IS NULL AND status = 'verified' THEN 1 END) as activeCount,
650
- COUNT(CASE WHEN valid_to IS NULL AND status = 'staging' THEN 1 END) as stagingCount,
651
- COUNT(CASE WHEN status = 'superseded' THEN 1 END) as supersededCount,
652
- COUNT(CASE WHEN status = 'dormant' THEN 1 END) as dormantCount,
653
- COUNT(CASE WHEN embedded = 1 THEN 1 END) as embeddedCount,
654
- COUNT(*) as totalMemories
655
- FROM memories
656
- WHERE project_id = ?
647
+ const counts = this.db.prepare(`
648
+ SELECT
649
+ COUNT(CASE WHEN valid_to IS NULL AND status = 'verified' THEN 1 END) as activeCount,
650
+ COUNT(CASE WHEN valid_to IS NULL AND status = 'staging' THEN 1 END) as stagingCount,
651
+ COUNT(CASE WHEN status = 'superseded' THEN 1 END) as supersededCount,
652
+ COUNT(CASE WHEN status = 'dormant' THEN 1 END) as dormantCount,
653
+ COUNT(CASE WHEN embedded = 1 THEN 1 END) as embeddedCount,
654
+ COUNT(*) as totalMemories
655
+ FROM memories
656
+ WHERE project_id = ?
657
657
  `).get(projectId);
658
- const rawEvents = this.db.prepare(`
659
- SELECT COUNT(*) as count FROM raw_events WHERE project_id = ?
658
+ const rawEvents = this.db.prepare(`
659
+ SELECT COUNT(*) as count FROM raw_events WHERE project_id = ?
660
660
  `).get(projectId);
661
661
  const total = counts.totalMemories || 0;
662
662
  const embedded = counts.embeddedCount || 0;
@@ -678,17 +678,17 @@ export class FastrRepository {
678
678
  const id = `snap_${crypto.randomBytes(4).toString('hex')}`;
679
679
  // Only this project's local rows: rollback rewrites main.memories, so global/federated rows
680
680
  // must never be captured (they would be copied into the local DB on restore).
681
- const memories = this.db.prepare(`
682
- SELECT * FROM main.memories
683
- WHERE project_id = ? AND valid_to IS NULL
684
- ORDER BY pinned DESC, reward_score DESC, created_at DESC
681
+ const memories = this.db.prepare(`
682
+ SELECT * FROM main.memories
683
+ WHERE project_id = ? AND valid_to IS NULL
684
+ ORDER BY pinned DESC, reward_score DESC, created_at DESC
685
685
  `).all(projectId);
686
686
  for (const m of memories)
687
687
  m.embedding = null;
688
- const fileLinks = this.db.prepare(`
689
- SELECT fl.* FROM main.file_links fl
690
- JOIN main.memories m ON m.id = fl.memory_id
691
- WHERE m.project_id = ? AND m.valid_to IS NULL
688
+ const fileLinks = this.db.prepare(`
689
+ SELECT fl.* FROM main.file_links fl
690
+ JOIN main.memories m ON m.id = fl.memory_id
691
+ WHERE m.project_id = ? AND m.valid_to IS NULL
692
692
  `).all(projectId);
693
693
  const state = {
694
694
  memories,
@@ -697,24 +697,24 @@ export class FastrRepository {
697
697
  };
698
698
  const stateJson = JSON.stringify(state);
699
699
  const createdAt = new Date().toISOString();
700
- this.db.prepare(`
701
- INSERT INTO memory_snapshots (id, project_id, tag, description, memory_count, state_json, created_at)
702
- VALUES (?, ?, ?, ?, ?, ?, ?)
700
+ this.db.prepare(`
701
+ INSERT INTO memory_snapshots (id, project_id, tag, description, memory_count, state_json, created_at)
702
+ VALUES (?, ?, ?, ?, ?, ?, ?)
703
703
  `).run(id, projectId, tag, description || null, memories.length, stateJson, createdAt);
704
704
  return { id, tag, memoryCount: memories.length, createdAt };
705
705
  }
706
706
  listSnapshots(projectId) {
707
- const rows = this.db.prepare(`
708
- SELECT id, tag, description, memory_count as memoryCount, created_at as createdAt
709
- FROM memory_snapshots
710
- WHERE project_id = ?
711
- ORDER BY created_at DESC
707
+ const rows = this.db.prepare(`
708
+ SELECT id, tag, description, memory_count as memoryCount, created_at as createdAt
709
+ FROM memory_snapshots
710
+ WHERE project_id = ?
711
+ ORDER BY created_at DESC
712
712
  `).all(projectId);
713
713
  return rows;
714
714
  }
715
715
  getSnapshot(snapshotId) {
716
- const row = this.db.prepare(`
717
- SELECT * FROM memory_snapshots WHERE id = ?
716
+ const row = this.db.prepare(`
717
+ SELECT * FROM memory_snapshots WHERE id = ?
718
718
  `).get(snapshotId);
719
719
  return row || null;
720
720
  }
@@ -732,34 +732,34 @@ export class FastrRepository {
732
732
  try {
733
733
  // 1. Mark all current active memories valid_to = now (superseded by rollback)
734
734
  const nowIso = new Date().toISOString();
735
- this.db.prepare(`
736
- UPDATE memories
737
- SET valid_to = ?, status = 'superseded', superseded_reason = ?
738
- WHERE project_id = ? AND valid_to IS NULL
735
+ this.db.prepare(`
736
+ UPDATE memories
737
+ SET valid_to = ?, status = 'superseded', superseded_reason = ?
738
+ WHERE project_id = ? AND valid_to IS NULL
739
739
  `).run(nowIso, `Rollback to snapshot ${snap.tag} (${snap.id})`, projectId);
740
740
  // 2. Re-insert or restore the snapshot memories
741
741
  for (const m of targetMemories) {
742
- this.db.prepare(`
743
- INSERT INTO memories (
744
- id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score,
745
- source_event_ids, valid_from, valid_to, created_at
746
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
747
- ON CONFLICT(id) DO UPDATE SET
748
- valid_to = NULL,
749
- status = excluded.status,
750
- pinned = excluded.pinned,
751
- reward_score = excluded.reward_score,
752
- superseded_by_id = NULL,
753
- superseded_reason = NULL
742
+ this.db.prepare(`
743
+ INSERT INTO memories (
744
+ id, project_id, agent_id, module, kind, title, content, status, pinned, reward_score,
745
+ source_event_ids, valid_from, valid_to, created_at
746
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
747
+ ON CONFLICT(id) DO UPDATE SET
748
+ valid_to = NULL,
749
+ status = excluded.status,
750
+ pinned = excluded.pinned,
751
+ reward_score = excluded.reward_score,
752
+ superseded_by_id = NULL,
753
+ superseded_reason = NULL
754
754
  `).run(m.id, m.project_id, m.agent_id, m.module, m.kind, m.title, m.content, m.status, m.pinned, m.reward_score, m.source_event_ids, nowIso, m.created_at);
755
755
  }
756
756
  // 3. Restore file links
757
757
  for (const fl of targetLinks) {
758
758
  if (!restoredIds.has(fl.memory_id))
759
759
  continue;
760
- this.db.prepare(`
761
- INSERT OR IGNORE INTO file_links (memory_id, file_path, symbol_name)
762
- VALUES (?, ?, ?)
760
+ this.db.prepare(`
761
+ INSERT OR IGNORE INTO file_links (memory_id, file_path, symbol_name)
762
+ VALUES (?, ?, ?)
763
763
  `).run(fl.memory_id, fl.file_path, fl.symbol_name || '');
764
764
  }
765
765
  this.db.exec('COMMIT');
@@ -772,10 +772,10 @@ export class FastrRepository {
772
772
  }
773
773
  // --- CONFLICTS & NER ---
774
774
  listConflicts(projectId, status = 'open') {
775
- const rows = this.db.prepare(`
776
- SELECT * FROM memory_conflicts
777
- WHERE project_id = ? AND status = ?
778
- ORDER BY id DESC
775
+ const rows = this.db.prepare(`
776
+ SELECT * FROM memory_conflicts
777
+ WHERE project_id = ? AND status = ?
778
+ ORDER BY id DESC
779
779
  `).all(projectId, status);
780
780
  return rows;
781
781
  }
@@ -797,26 +797,26 @@ export class FastrRepository {
797
797
  this.db.exec('BEGIN TRANSACTION');
798
798
  try {
799
799
  // 1. Mark conflict as resolved
800
- this.db.prepare(`
801
- UPDATE memory_conflicts
802
- SET status = 'resolved', resolved_winner_id = ?, resolved_at = ?
803
- WHERE id = ?
800
+ this.db.prepare(`
801
+ UPDATE memory_conflicts
802
+ SET status = 'resolved', resolved_winner_id = ?, resolved_at = ?
803
+ WHERE id = ?
804
804
  `).run(keepMemoryId, nowIso, conflictId);
805
805
  // 2. Handle loser memory
806
806
  if (forgetLoser) {
807
807
  // Soft-delete / supersede loser
808
- this.db.prepare(`
809
- UPDATE ${loserTable}
810
- SET valid_to = ?, status = 'superseded', superseded_by_id = ?, superseded_reason = ?
811
- WHERE id = ? AND valid_to IS NULL
808
+ this.db.prepare(`
809
+ UPDATE ${loserTable}
810
+ SET valid_to = ?, status = 'superseded', superseded_by_id = ?, superseded_reason = ?
811
+ WHERE id = ? AND valid_to IS NULL
812
812
  `).run(nowIso, keepMemoryId, resolutionReason, loserId);
813
813
  }
814
814
  else {
815
815
  // Weaken loser reward score by -0.4 (same 0.1 floor as updateFeedback)
816
- this.db.prepare(`
817
- UPDATE ${loserTable}
818
- SET reward_score = MAX(0.1, reward_score - 0.4)
819
- WHERE id = ?
816
+ this.db.prepare(`
817
+ UPDATE ${loserTable}
818
+ SET reward_score = MAX(0.1, reward_score - 0.4)
819
+ WHERE id = ?
820
820
  `).run(loserId);
821
821
  }
822
822
  this.db.exec('COMMIT');
@@ -828,26 +828,26 @@ export class FastrRepository {
828
828
  }
829
829
  }
830
830
  getEntities(projectId) {
831
- const rows = this.db.prepare(`
832
- SELECT * FROM entities WHERE project_id = ? ORDER BY name ASC
831
+ const rows = this.db.prepare(`
832
+ SELECT * FROM entities WHERE project_id = ? ORDER BY name ASC
833
833
  `).all(projectId);
834
834
  return rows;
835
835
  }
836
836
  // --- PREDICTIONS & PLANNING FALLACY ---
837
837
  createPrediction(params) {
838
- const res = this.db.prepare(`
839
- INSERT INTO predictions (project_id, class_tag, claim_text, estimate_value, estimate_unit)
840
- VALUES (?, ?, ?, ?, ?)
838
+ const res = this.db.prepare(`
839
+ INSERT INTO predictions (project_id, class_tag, claim_text, estimate_value, estimate_unit)
840
+ VALUES (?, ?, ?, ?, ?)
841
841
  `).run(params.projectId, params.classTag.trim().toLowerCase(), params.claimText, params.estimateValue ?? null, params.estimateUnit ?? null);
842
842
  const row = this.db.prepare('SELECT * FROM predictions WHERE id = ?').get(res.lastInsertRowid);
843
843
  return row;
844
844
  }
845
845
  closePrediction(id, actualValue) {
846
846
  const nowIso = new Date().toISOString();
847
- this.db.prepare(`
848
- UPDATE predictions
849
- SET actual_value = ?, closure_state = 'closed', closed_at = ?
850
- WHERE id = ?
847
+ this.db.prepare(`
848
+ UPDATE predictions
849
+ SET actual_value = ?, closure_state = 'closed', closed_at = ?
850
+ WHERE id = ?
851
851
  `).run(actualValue, nowIso, id);
852
852
  const row = this.db.prepare('SELECT * FROM predictions WHERE id = ?').get(id);
853
853
  if (!row)
@@ -870,10 +870,10 @@ export class FastrRepository {
870
870
  }
871
871
  getPredictionBaserate(projectId, classTag) {
872
872
  const normalizedTag = classTag.trim().toLowerCase();
873
- const rows = this.db.prepare(`
874
- SELECT estimate_value, actual_value
875
- FROM predictions
876
- WHERE project_id = ? AND class_tag = ? AND closure_state = 'closed'
873
+ const rows = this.db.prepare(`
874
+ SELECT estimate_value, actual_value
875
+ FROM predictions
876
+ WHERE project_id = ? AND class_tag = ? AND closure_state = 'closed'
877
877
  `).all(projectId, normalizedTag);
878
878
  return calculateBaserate(normalizedTag, rows);
879
879
  }
@@ -885,23 +885,23 @@ export class FastrRepository {
885
885
  const assignedTo = params.assignedTo ?? null;
886
886
  this.db.exec('BEGIN TRANSACTION');
887
887
  try {
888
- this.db.prepare(`
889
- INSERT INTO actions (id, project_id, title, description, status, priority, assigned_to)
890
- VALUES (?, ?, ?, ?, 'pending', ?, ?)
888
+ this.db.prepare(`
889
+ INSERT INTO actions (id, project_id, title, description, status, priority, assigned_to)
890
+ VALUES (?, ?, ?, ?, 'pending', ?, ?)
891
891
  `).run(actionId, params.projectId, params.title, description, priority, assignedTo);
892
892
  if (params.requires && params.requires.length > 0) {
893
- const stmt = this.db.prepare(`
894
- INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
895
- VALUES (?, ?, 'requires')
893
+ const stmt = this.db.prepare(`
894
+ INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
895
+ VALUES (?, ?, 'requires')
896
896
  `);
897
897
  for (const req of params.requires) {
898
898
  stmt.run(actionId, req);
899
899
  }
900
900
  }
901
901
  if (params.conflictsWith && params.conflictsWith.length > 0) {
902
- const stmt = this.db.prepare(`
903
- INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
904
- VALUES (?, ?, 'conflicts_with')
902
+ const stmt = this.db.prepare(`
903
+ INSERT OR IGNORE INTO action_edges (source_action_id, target_action_id, edge_type)
904
+ VALUES (?, ?, 'conflicts_with')
905
905
  `);
906
906
  for (const conf of params.conflictsWith) {
907
907
  stmt.run(actionId, conf);
@@ -921,16 +921,16 @@ export class FastrRepository {
921
921
  const action = this.db.prepare('SELECT * FROM actions WHERE id = ?').get(id);
922
922
  if (!action)
923
923
  return null;
924
- const requiresRows = this.db.prepare(`
925
- SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'requires'
924
+ const requiresRows = this.db.prepare(`
925
+ SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'requires'
926
926
  `).all(id);
927
- const unlocksRows = this.db.prepare(`
928
- SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'requires'
927
+ const unlocksRows = this.db.prepare(`
928
+ SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'requires'
929
929
  `).all(id);
930
- const conflictsRows = this.db.prepare(`
931
- SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'conflicts_with'
932
- UNION
933
- SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'conflicts_with'
930
+ const conflictsRows = this.db.prepare(`
931
+ SELECT target_action_id FROM action_edges WHERE source_action_id = ? AND edge_type = 'conflicts_with'
932
+ UNION
933
+ SELECT source_action_id FROM action_edges WHERE target_action_id = ? AND edge_type = 'conflicts_with'
934
934
  `).all(id, id);
935
935
  return {
936
936
  ...action,
@@ -1009,32 +1009,32 @@ export class FastrRepository {
1009
1009
  return this.db.prepare(sql).all(...params);
1010
1010
  }
1011
1011
  getFrontier(projectId) {
1012
- const query = `
1013
- SELECT a.*
1014
- FROM actions a
1015
- WHERE a.project_id = ?
1016
- AND a.status IN ('pending', 'active')
1017
- AND NOT EXISTS (
1018
- SELECT 1
1019
- FROM action_edges e
1020
- JOIN actions prereq ON prereq.id = e.target_action_id
1021
- WHERE e.source_action_id = a.id
1022
- AND e.edge_type = 'requires'
1023
- AND prereq.status != 'done'
1024
- )
1025
- AND NOT EXISTS (
1026
- SELECT 1
1027
- FROM action_edges e
1028
- JOIN actions conf ON (
1029
- (e.source_action_id = a.id AND conf.id = e.target_action_id)
1030
- OR
1031
- (e.target_action_id = a.id AND conf.id = e.source_action_id)
1032
- )
1033
- WHERE e.edge_type = 'conflicts_with'
1034
- AND conf.status = 'active'
1035
- AND conf.id != a.id
1036
- )
1037
- ORDER BY a.priority DESC, a.created_at ASC
1012
+ const query = `
1013
+ SELECT a.*
1014
+ FROM actions a
1015
+ WHERE a.project_id = ?
1016
+ AND a.status IN ('pending', 'active')
1017
+ AND NOT EXISTS (
1018
+ SELECT 1
1019
+ FROM action_edges e
1020
+ JOIN actions prereq ON prereq.id = e.target_action_id
1021
+ WHERE e.source_action_id = a.id
1022
+ AND e.edge_type = 'requires'
1023
+ AND prereq.status != 'done'
1024
+ )
1025
+ AND NOT EXISTS (
1026
+ SELECT 1
1027
+ FROM action_edges e
1028
+ JOIN actions conf ON (
1029
+ (e.source_action_id = a.id AND conf.id = e.target_action_id)
1030
+ OR
1031
+ (e.target_action_id = a.id AND conf.id = e.source_action_id)
1032
+ )
1033
+ WHERE e.edge_type = 'conflicts_with'
1034
+ AND conf.status = 'active'
1035
+ AND conf.id != a.id
1036
+ )
1037
+ ORDER BY a.priority DESC, a.created_at ASC
1038
1038
  `;
1039
1039
  return this.db.prepare(query).all(projectId);
1040
1040
  }
@@ -1048,9 +1048,9 @@ export class FastrRepository {
1048
1048
  // Hold the write lock across read + claim so two agents can never claim the same action.
1049
1049
  this.db.exec('BEGIN IMMEDIATE');
1050
1050
  try {
1051
- const claim = this.db.prepare(`
1052
- UPDATE actions SET status = 'active', assigned_to = ?
1053
- WHERE id = ? AND status = 'pending'
1051
+ const claim = this.db.prepare(`
1052
+ UPDATE actions SET status = 'active', assigned_to = ?
1053
+ WHERE id = ? AND status = 'pending'
1054
1054
  `);
1055
1055
  for (const candidate of this.getFrontier(projectId)) {
1056
1056
  if (candidate.status === 'active') {
@@ -1094,32 +1094,32 @@ export class FastrRepository {
1094
1094
  throw new Error(`Content length (${content.length}) exceeds slot size limit (${sizeLimit} chars). Summarize before storing.`);
1095
1095
  }
1096
1096
  const nowIso = new Date().toISOString();
1097
- this.db.prepare(`
1098
- INSERT INTO memory_slots (project_id, slot_key, content, size_limit, updated_at)
1099
- VALUES (?, ?, ?, ?, ?)
1100
- ON CONFLICT(project_id, slot_key) DO UPDATE SET
1101
- content = excluded.content,
1102
- size_limit = excluded.size_limit,
1103
- updated_at = excluded.updated_at
1097
+ this.db.prepare(`
1098
+ INSERT INTO memory_slots (project_id, slot_key, content, size_limit, updated_at)
1099
+ VALUES (?, ?, ?, ?, ?)
1100
+ ON CONFLICT(project_id, slot_key) DO UPDATE SET
1101
+ content = excluded.content,
1102
+ size_limit = excluded.size_limit,
1103
+ updated_at = excluded.updated_at
1104
1104
  `).run(projectId, normalizedKey, content, sizeLimit, nowIso);
1105
1105
  return this.getSlot(projectId, normalizedKey);
1106
1106
  }
1107
1107
  getSlot(projectId, slotKey) {
1108
1108
  const normalizedKey = slotKey.trim().toLowerCase();
1109
- const row = this.db.prepare(`
1110
- SELECT * FROM memory_slots WHERE project_id = ? AND slot_key = ?
1109
+ const row = this.db.prepare(`
1110
+ SELECT * FROM memory_slots WHERE project_id = ? AND slot_key = ?
1111
1111
  `).get(projectId, normalizedKey);
1112
1112
  return row ?? null;
1113
1113
  }
1114
1114
  listSlots(projectId) {
1115
- return this.db.prepare(`
1116
- SELECT * FROM memory_slots WHERE project_id = ? ORDER BY slot_key ASC
1115
+ return this.db.prepare(`
1116
+ SELECT * FROM memory_slots WHERE project_id = ? ORDER BY slot_key ASC
1117
1117
  `).all(projectId);
1118
1118
  }
1119
1119
  deleteSlot(projectId, slotKey) {
1120
1120
  const normalizedKey = slotKey.trim().toLowerCase();
1121
- const res = this.db.prepare(`
1122
- DELETE FROM memory_slots WHERE project_id = ? AND slot_key = ?
1121
+ const res = this.db.prepare(`
1122
+ DELETE FROM memory_slots WHERE project_id = ? AND slot_key = ?
1123
1123
  `).run(projectId, normalizedKey);
1124
1124
  return res.changes > 0;
1125
1125
  }