opencode-mem 2.5.0 → 2.6.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.
@@ -3,6 +3,7 @@ import { shardManager } from "./sqlite/shard-manager.js";
3
3
  import { vectorSearch } from "./sqlite/vector-search.js";
4
4
  import { connectionManager } from "./sqlite/connection-manager.js";
5
5
  import { log } from "./logger.js";
6
+ import { CONFIG } from "../config.js";
6
7
  import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
7
8
  function safeToISOString(timestamp) {
8
9
  try {
@@ -55,9 +56,8 @@ export async function handleListTags() {
55
56
  try {
56
57
  await embeddingService.warmup();
57
58
  const projectShards = shardManager.getAllShards("project", "");
58
- const allShards = [...projectShards];
59
59
  const tagsMap = new Map();
60
- for (const shard of allShards) {
60
+ for (const shard of projectShards) {
61
61
  const db = connectionManager.getConnection(shard.dbPath);
62
62
  const tags = vectorSearch.getDistinctTags(db);
63
63
  for (const t of tags) {
@@ -80,10 +80,7 @@ export async function handleListTags() {
80
80
  projectTags.push(tagInfo);
81
81
  }
82
82
  }
83
- return {
84
- success: true,
85
- data: { project: projectTags },
86
- };
83
+ return { success: true, data: { project: projectTags } };
87
84
  }
88
85
  catch (error) {
89
86
  log("handleListTags: error", { error: String(error) });
@@ -118,6 +115,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
118
115
  id: r.id,
119
116
  content: r.content,
120
117
  memoryType: r.type,
118
+ tags: r.tags ? r.tags.split(",").map((t) => t.trim()) : [],
121
119
  createdAt: Number(r.created_at),
122
120
  updatedAt: r.updated_at ? Number(r.updated_at) : undefined,
123
121
  metadata,
@@ -191,6 +189,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
191
189
  id: item.id,
192
190
  content: item.content,
193
191
  memoryType: item.memoryType,
192
+ tags: item.tags,
194
193
  createdAt: safeToISOString(item.createdAt),
195
194
  updatedAt: item.updatedAt ? safeToISOString(item.updatedAt) : undefined,
196
195
  metadata: item.metadata,
@@ -216,16 +215,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, includePr
216
215
  };
217
216
  }
218
217
  });
219
- return {
220
- success: true,
221
- data: {
222
- items,
223
- total,
224
- page,
225
- pageSize,
226
- totalPages,
227
- },
228
- };
218
+ return { success: true, data: { items, total, page, pageSize, totalPages } };
229
219
  }
230
220
  catch (error) {
231
221
  log("handleListMemories: error", { error: String(error) });
@@ -238,7 +228,13 @@ export async function handleAddMemory(data) {
238
228
  return { success: false, error: "content and containerTag are required" };
239
229
  }
240
230
  await embeddingService.warmup();
241
- const vector = await embeddingService.embedWithTimeout(data.content);
231
+ const tags = data.tags || [];
232
+ const embeddingInput = tags.length > 0 ? `${data.content}\nTags: ${tags.join(", ")}` : data.content;
233
+ const vector = await embeddingService.embedWithTimeout(embeddingInput);
234
+ let tagsVector = undefined;
235
+ if (tags.length > 0) {
236
+ tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
237
+ }
242
238
  const { scope, hash } = extractScopeFromTag(data.containerTag);
243
239
  const shard = shardManager.getWriteShard(scope, hash);
244
240
  const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
@@ -247,7 +243,9 @@ export async function handleAddMemory(data) {
247
243
  id,
248
244
  content: data.content,
249
245
  vector,
246
+ tagsVector,
250
247
  containerTag: data.containerTag,
248
+ tags: tags.length > 0 ? tags.join(",") : undefined,
251
249
  type: data.type,
252
250
  createdAt: now,
253
251
  updatedAt: now,
@@ -257,9 +255,7 @@ export async function handleAddMemory(data) {
257
255
  projectPath: data.projectPath,
258
256
  projectName: data.projectName,
259
257
  gitRepoUrl: data.gitRepoUrl,
260
- metadata: JSON.stringify({
261
- source: "api",
262
- }),
258
+ metadata: JSON.stringify({ source: "api" }),
263
259
  };
264
260
  const db = connectionManager.getConnection(shard.dbPath);
265
261
  vectorSearch.insertVector(db, record);
@@ -273,27 +269,25 @@ export async function handleAddMemory(data) {
273
269
  }
274
270
  export async function handleDeleteMemory(id, cascade = false) {
275
271
  try {
276
- if (!id) {
272
+ if (!id)
277
273
  return { success: false, error: "id is required" };
278
- }
279
274
  const projectShards = shardManager.getAllShards("project", "");
280
- const allShards = [...projectShards];
281
- let deletedPrompt = false;
282
- for (const shard of allShards) {
275
+ for (const shard of projectShards) {
283
276
  const db = connectionManager.getConnection(shard.dbPath);
284
277
  const memory = vectorSearch.getMemoryById(db, id);
285
278
  if (memory) {
286
279
  if (cascade) {
287
280
  const metadata = safeJSONParse(memory.metadata);
288
281
  const linkedPromptId = metadata?.promptId;
289
- if (linkedPromptId) {
282
+ if (linkedPromptId)
290
283
  userPromptManager.deletePrompt(linkedPromptId);
291
- deletedPrompt = true;
292
- }
293
284
  }
294
285
  vectorSearch.deleteVector(db, id);
295
286
  shardManager.decrementVectorCount(shard.id);
296
- return { success: true, data: { deletedPrompt } };
287
+ return {
288
+ success: true,
289
+ data: { deletedPrompt: cascade && !!safeJSONParse(memory.metadata)?.promptId },
290
+ };
297
291
  }
298
292
  }
299
293
  return { success: false, error: "Memory not found" };
@@ -305,15 +299,13 @@ export async function handleDeleteMemory(id, cascade = false) {
305
299
  }
306
300
  export async function handleBulkDelete(ids, cascade = false) {
307
301
  try {
308
- if (!ids || ids.length === 0) {
302
+ if (!ids || ids.length === 0)
309
303
  return { success: false, error: "ids array is required" };
310
- }
311
304
  let deleted = 0;
312
305
  for (const id of ids) {
313
306
  const result = await handleDeleteMemory(id, cascade);
314
- if (result.success) {
307
+ if (result.success)
315
308
  deleted++;
316
- }
317
309
  }
318
310
  return { success: true, data: { deleted } };
319
311
  }
@@ -324,15 +316,12 @@ export async function handleBulkDelete(ids, cascade = false) {
324
316
  }
325
317
  export async function handleUpdateMemory(id, data) {
326
318
  try {
327
- if (!id) {
319
+ if (!id)
328
320
  return { success: false, error: "id is required" };
329
- }
330
321
  await embeddingService.warmup();
331
322
  const projectShards = shardManager.getAllShards("project", "");
332
- const allShards = [...projectShards];
333
- let foundShard = null;
334
- let existingMemory = null;
335
- for (const shard of allShards) {
323
+ let foundShard = null, existingMemory = null;
324
+ for (const shard of projectShards) {
336
325
  const db = connectionManager.getConnection(shard.dbPath);
337
326
  const memory = vectorSearch.getMemoryById(db, id);
338
327
  if (memory) {
@@ -341,19 +330,25 @@ export async function handleUpdateMemory(id, data) {
341
330
  break;
342
331
  }
343
332
  }
344
- if (!foundShard || !existingMemory) {
333
+ if (!foundShard || !existingMemory)
345
334
  return { success: false, error: "Memory not found" };
346
- }
347
335
  const db = connectionManager.getConnection(foundShard.dbPath);
348
336
  vectorSearch.deleteVector(db, id);
349
337
  shardManager.decrementVectorCount(foundShard.id);
350
338
  const newContent = data.content || existingMemory.content;
339
+ const tags = data.tags || (existingMemory.tags ? existingMemory.tags.split(",") : []);
351
340
  const vector = await embeddingService.embedWithTimeout(newContent);
341
+ let tagsVector = undefined;
342
+ if (tags.length > 0) {
343
+ tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
344
+ }
352
345
  const updatedRecord = {
353
346
  id,
354
347
  content: newContent,
355
348
  vector,
349
+ tagsVector,
356
350
  containerTag: existingMemory.container_tag,
351
+ tags: tags.length > 0 ? tags.join(",") : undefined,
357
352
  type: data.type || existingMemory.type,
358
353
  createdAt: existingMemory.created_at,
359
354
  updatedAt: Date.now(),
@@ -376,9 +371,8 @@ export async function handleUpdateMemory(id, data) {
376
371
  }
377
372
  export async function handleSearch(query, tag, page = 1, pageSize = 20) {
378
373
  try {
379
- if (!query) {
374
+ if (!query)
380
375
  return { success: false, error: "query is required" };
381
- }
382
376
  await embeddingService.warmup();
383
377
  const queryVector = await embeddingService.embedWithTimeout(query);
384
378
  let memoryResults = [];
@@ -400,15 +394,13 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
400
394
  }
401
395
  else {
402
396
  const projectShards = shardManager.getAllShards("project", "");
403
- const allShards = [...projectShards];
404
397
  const uniqueTags = new Set();
405
- for (const shard of allShards) {
398
+ for (const shard of projectShards) {
406
399
  const db = connectionManager.getConnection(shard.dbPath);
407
400
  const tags = vectorSearch.getDistinctTags(db);
408
401
  for (const t of tags) {
409
- if (t.container_tag) {
402
+ if (t.container_tag)
410
403
  uniqueTags.add(t.container_tag);
411
- }
412
404
  }
413
405
  }
414
406
  for (const containerTag of uniqueTags) {
@@ -441,6 +433,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
441
433
  id: r.id,
442
434
  content: r.memory,
443
435
  memoryType: r.metadata?.type,
436
+ tags: r.tags,
444
437
  createdAt: safeToISOString(r.metadata?.createdAt),
445
438
  updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
446
439
  similarity: r.similarity,
@@ -463,13 +456,11 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
463
456
  const missingMemoryIds = new Set();
464
457
  for (const item of paginatedResults) {
465
458
  if (item.type === "memory" && item.linkedPromptId) {
466
- const hasPrompt = paginatedResults.some((p) => p.id === item.linkedPromptId);
467
- if (!hasPrompt)
459
+ if (!paginatedResults.some((p) => p.id === item.linkedPromptId))
468
460
  missingPromptIds.add(item.linkedPromptId);
469
461
  }
470
462
  else if (item.type === "prompt" && item.linkedMemoryId) {
471
- const hasMemory = paginatedResults.some((m) => m.id === item.linkedMemoryId);
472
- if (!hasMemory)
463
+ if (!paginatedResults.some((m) => m.id === item.linkedMemoryId))
473
464
  missingMemoryIds.add(item.linkedMemoryId);
474
465
  }
475
466
  }
@@ -501,6 +492,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
501
492
  id: m.id,
502
493
  content: m.content,
503
494
  memoryType: m.type,
495
+ tags: m.tags ? m.tags.split(",").map((t) => t.trim()) : [],
504
496
  createdAt: safeToISOString(m.created_at),
505
497
  updatedAt: m.updated_at ? safeToISOString(m.updated_at) : undefined,
506
498
  similarity: 0,
@@ -519,16 +511,7 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
519
511
  }
520
512
  }
521
513
  }
522
- return {
523
- success: true,
524
- data: {
525
- items: paginatedResults,
526
- total,
527
- page,
528
- pageSize,
529
- totalPages,
530
- },
531
- };
514
+ return { success: true, data: { items: paginatedResults, total, page, pageSize, totalPages } };
532
515
  }
533
516
  catch (error) {
534
517
  log("handleSearch: error", { error: String(error) });
@@ -539,23 +522,18 @@ export async function handleStats() {
539
522
  try {
540
523
  await embeddingService.warmup();
541
524
  const projectShards = shardManager.getAllShards("project", "");
542
- const allShards = [...projectShards];
543
- let userCount = 0;
544
- let projectCount = 0;
525
+ let userCount = 0, projectCount = 0;
545
526
  const typeCount = {};
546
- for (const shard of allShards) {
527
+ for (const shard of projectShards) {
547
528
  const db = connectionManager.getConnection(shard.dbPath);
548
529
  const memories = vectorSearch.getAllMemories(db);
549
530
  for (const r of memories) {
550
- if (r.container_tag?.includes("_user_")) {
531
+ if (r.container_tag?.includes("_user_"))
551
532
  userCount++;
552
- }
553
- else if (r.container_tag?.includes("_project_")) {
533
+ else if (r.container_tag?.includes("_project_"))
554
534
  projectCount++;
555
- }
556
- if (r.type) {
535
+ if (r.type)
557
536
  typeCount[r.type] = (typeCount[r.type] || 0) + 1;
558
- }
559
537
  }
560
538
  }
561
539
  return {
@@ -574,12 +552,10 @@ export async function handleStats() {
574
552
  }
575
553
  export async function handlePinMemory(id) {
576
554
  try {
577
- if (!id) {
555
+ if (!id)
578
556
  return { success: false, error: "id is required" };
579
- }
580
557
  const projectShards = shardManager.getAllShards("project", "");
581
- const allShards = [...projectShards];
582
- for (const shard of allShards) {
558
+ for (const shard of projectShards) {
583
559
  const db = connectionManager.getConnection(shard.dbPath);
584
560
  const memory = vectorSearch.getMemoryById(db, id);
585
561
  if (memory) {
@@ -596,12 +572,10 @@ export async function handlePinMemory(id) {
596
572
  }
597
573
  export async function handleUnpinMemory(id) {
598
574
  try {
599
- if (!id) {
575
+ if (!id)
600
576
  return { success: false, error: "id is required" };
601
- }
602
577
  const projectShards = shardManager.getAllShards("project", "");
603
- const allShards = [...projectShards];
604
- for (const shard of allShards) {
578
+ for (const shard of projectShards) {
605
579
  const db = connectionManager.getConnection(shard.dbPath);
606
580
  const memory = vectorSearch.getMemoryById(db, id);
607
581
  if (memory) {
@@ -662,19 +636,16 @@ export async function handleRunMigration(strategy) {
662
636
  }
663
637
  export async function handleDeletePrompt(id, cascade = false) {
664
638
  try {
665
- if (!id) {
639
+ if (!id)
666
640
  return { success: false, error: "id is required" };
667
- }
668
641
  const prompt = userPromptManager.getPromptById(id);
669
- if (!prompt) {
642
+ if (!prompt)
670
643
  return { success: false, error: "Prompt not found" };
671
- }
672
644
  let deletedMemory = false;
673
645
  if (cascade && prompt.linkedMemoryId) {
674
646
  const result = await handleDeleteMemory(prompt.linkedMemoryId, false);
675
- if (result.success) {
647
+ if (result.success)
676
648
  deletedMemory = true;
677
- }
678
649
  }
679
650
  userPromptManager.deletePrompt(id);
680
651
  return { success: true, data: { deletedMemory } };
@@ -686,15 +657,13 @@ export async function handleDeletePrompt(id, cascade = false) {
686
657
  }
687
658
  export async function handleBulkDeletePrompts(ids, cascade = false) {
688
659
  try {
689
- if (!ids || ids.length === 0) {
660
+ if (!ids || ids.length === 0)
690
661
  return { success: false, error: "ids array is required" };
691
- }
692
662
  let deleted = 0;
693
663
  for (const id of ids) {
694
664
  const result = await handleDeletePrompt(id, cascade);
695
- if (result.success) {
665
+ if (result.success)
696
666
  deleted++;
697
- }
698
667
  }
699
668
  return { success: true, data: { deleted } };
700
669
  }
@@ -713,7 +682,7 @@ export async function handleGetUserProfile(userId) {
713
682
  targetUserId = tags.user.userEmail || "unknown";
714
683
  }
715
684
  const profile = userProfileManager.getActiveProfile(targetUserId);
716
- if (!profile) {
685
+ if (!profile)
717
686
  return {
718
687
  success: true,
719
688
  data: {
@@ -722,7 +691,6 @@ export async function handleGetUserProfile(userId) {
722
691
  message: "No profile found. Keep chatting to build your profile.",
723
692
  },
724
693
  };
725
- }
726
694
  const profileData = JSON.parse(profile.profileData);
727
695
  return {
728
696
  success: true,
@@ -748,9 +716,8 @@ export async function handleGetUserProfile(userId) {
748
716
  }
749
717
  export async function handleGetProfileChangelog(profileId, limit = 5) {
750
718
  try {
751
- if (!profileId) {
719
+ if (!profileId)
752
720
  return { success: false, error: "profileId is required" };
753
- }
754
721
  const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
755
722
  const changelogs = userProfileManager.getProfileChangelogs(profileId, limit);
756
723
  const formattedChangelogs = changelogs.map((c) => ({
@@ -770,15 +737,13 @@ export async function handleGetProfileChangelog(profileId, limit = 5) {
770
737
  }
771
738
  export async function handleGetProfileSnapshot(changelogId) {
772
739
  try {
773
- if (!changelogId) {
740
+ if (!changelogId)
774
741
  return { success: false, error: "changelogId is required" };
775
- }
776
742
  const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
777
743
  const changelogs = userProfileManager.getProfileChangelogs("", 1000);
778
744
  const changelog = changelogs.find((c) => c.id === changelogId);
779
- if (!changelog) {
745
+ if (!changelog)
780
746
  return { success: false, error: "Changelog not found" };
781
- }
782
747
  const profileData = JSON.parse(changelog.profileDataSnapshot);
783
748
  return {
784
749
  success: true,
@@ -818,3 +783,86 @@ export async function handleRefreshProfile(userId) {
818
783
  return { success: false, error: String(error) };
819
784
  }
820
785
  }
786
+ export async function handleDetectTagMigration() {
787
+ try {
788
+ const projectShards = shardManager.getAllShards("project", "");
789
+ let untaggedCount = 0;
790
+ for (const shard of projectShards) {
791
+ const db = connectionManager.getConnection(shard.dbPath);
792
+ const rows = db
793
+ .prepare("SELECT COUNT(*) as count FROM memories WHERE tags IS NULL OR tags = ''")
794
+ .get();
795
+ untaggedCount += rows.count;
796
+ }
797
+ return { success: true, data: { needsMigration: untaggedCount > 0, count: untaggedCount } };
798
+ }
799
+ catch (error) {
800
+ return { success: false, error: String(error) };
801
+ }
802
+ }
803
+ export async function handleRunTagMigration() {
804
+ try {
805
+ const startTime = Date.now();
806
+ const { AIProviderFactory } = await import("./ai/ai-provider-factory.js");
807
+ const providerConfig = {
808
+ model: CONFIG.memoryModel,
809
+ apiUrl: CONFIG.memoryApiUrl,
810
+ apiKey: CONFIG.memoryApiKey,
811
+ maxIterations: 1,
812
+ iterationTimeout: 30000,
813
+ };
814
+ const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
815
+ const projectShards = shardManager.getAllShards("project", "");
816
+ let processed = 0;
817
+ for (const shard of projectShards) {
818
+ const db = connectionManager.getConnection(shard.dbPath);
819
+ const memories = db.prepare("SELECT * FROM memories").all();
820
+ for (const m of memories) {
821
+ try {
822
+ let currentTags = m.tags
823
+ ? m.tags
824
+ .split(",")
825
+ .map((t) => t.trim())
826
+ .filter((t) => t)
827
+ : [];
828
+ if (currentTags.length === 0) {
829
+ const prompt = `Generate 2-4 short technical tags for this memory content:\n\n${m.content}\n\nReturn ONLY a comma-separated list of tags.`;
830
+ const result = await provider.executeToolCall("You are a technical tagger.", prompt, {
831
+ type: "function",
832
+ function: {
833
+ name: "save_tags",
834
+ description: "Save generated tags",
835
+ parameters: {
836
+ type: "object",
837
+ properties: { tags: { type: "array", items: { type: "string" } } },
838
+ required: ["tags"],
839
+ },
840
+ },
841
+ }, `migration_${m.id}`);
842
+ if (result.success && result.data?.tags) {
843
+ currentTags = result.data.tags;
844
+ db.prepare("UPDATE memories SET tags = ? WHERE id = ?").run(currentTags.join(","), m.id);
845
+ }
846
+ }
847
+ const vector = await embeddingService.embedWithTimeout(m.content);
848
+ const vectorBuffer = new Uint8Array(vector.buffer);
849
+ db.prepare("UPDATE memories SET vector = ?, updated_at = ? WHERE id = ?").run(vectorBuffer, Date.now(), m.id);
850
+ db.prepare("INSERT OR REPLACE INTO vec_memories (memory_id, embedding) VALUES (?, ?)").run(m.id, vectorBuffer);
851
+ if (currentTags.length > 0) {
852
+ const tagsVector = await embeddingService.embedWithTimeout(currentTags.join(", "));
853
+ const tagsVectorBuffer = new Uint8Array(tagsVector.buffer);
854
+ db.prepare("INSERT OR REPLACE INTO vec_tags (memory_id, embedding) VALUES (?, ?)").run(m.id, tagsVectorBuffer);
855
+ }
856
+ processed++;
857
+ }
858
+ catch (e) {
859
+ log("Migration error for memory", { id: m.id, error: String(e) });
860
+ }
861
+ }
862
+ }
863
+ return { success: true, data: { success: true, processed, duration: Date.now() - startTime } };
864
+ }
865
+ catch (error) {
866
+ return { success: false, error: String(error) };
867
+ }
868
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAoGf"}
1
+ {"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAqGf"}
@@ -46,6 +46,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
46
46
  const result = await memoryClient.addMemory(summaryResult.summary, tags.project.tag, {
47
47
  source: "auto-capture",
48
48
  type: summaryResult.type,
49
+ tags: summaryResult.tags,
49
50
  sessionID,
50
51
  promptId: prompt.id,
51
52
  captureTimestamp: Date.now(),
@@ -209,7 +210,8 @@ RULES:
209
210
  2. SKIP non-technical by returning type="skip"
210
211
  3. NO meta-commentary or behavior analysis
211
212
  4. Include specific file names, functions, technical details
212
- 5. You MUST write the summary in ${langName}.
213
+ 5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
214
+ 6. You MUST write the summary in ${langName}.
213
215
 
214
216
  FORMAT:
215
217
  ## Request
@@ -222,7 +224,7 @@ SKIP if: greetings, casual chat, no code/decisions made
222
224
  CAPTURE if: code changed, bug fixed, feature added, decision made`;
223
225
  const aiPrompt = `${context}
224
226
 
225
- Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
227
+ Analyze this conversation. If it contains technical work (code, bugs, features, decisions), create a concise summary and relevant tags. If it's non-technical (greetings, casual chat, incomplete requests), return type="skip" with empty summary.`;
226
228
  const toolSchema = {
227
229
  type: "function",
228
230
  function: {
@@ -239,8 +241,13 @@ Analyze this conversation. If it contains technical work (code, bugs, features,
239
241
  type: "string",
240
242
  description: "Type of memory: 'skip' for non-technical conversations, or technical type (feature, bug-fix, refactor, analysis, configuration, discussion, other)",
241
243
  },
244
+ tags: {
245
+ type: "array",
246
+ items: { type: "string" },
247
+ description: "List of 2-4 technical tags related to the memory",
248
+ },
242
249
  },
243
- required: ["summary", "type"],
250
+ required: ["summary", "type", "tags"],
244
251
  },
245
252
  },
246
253
  };
@@ -251,5 +258,6 @@ Analyze this conversation. If it contains technical work (code, bugs, features,
251
258
  return {
252
259
  summary: result.data.summary,
253
260
  type: result.data.type,
261
+ tags: result.data.tags || [],
254
262
  };
255
263
  }
@@ -28,6 +28,7 @@ export declare class LocalMemoryClient {
28
28
  addMemory(content: string, containerTag: string, metadata?: {
29
29
  type?: MemoryType;
30
30
  source?: "manual" | "auto-capture" | "import" | "api";
31
+ tags?: string[];
31
32
  tool?: string;
32
33
  sessionID?: string;
33
34
  reasoning?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA4CpD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;IA6BlD,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IAqDG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA4CpD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;IA6BlD,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IA+DG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDpD;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
@@ -99,17 +99,24 @@ export class LocalMemoryClient {
99
99
  async addMemory(content, containerTag, metadata) {
100
100
  try {
101
101
  await this.initialize();
102
+ const tags = metadata?.tags || [];
102
103
  const vector = await embeddingService.embedWithTimeout(content);
104
+ let tagsVector = undefined;
105
+ if (tags.length > 0) {
106
+ tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
107
+ }
103
108
  const { scope, hash } = extractScopeFromContainerTag(containerTag);
104
109
  const shard = shardManager.getWriteShard(scope, hash);
105
110
  const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
106
111
  const now = Date.now();
107
- const { displayName, userName, userEmail, projectPath, projectName, gitRepoUrl, type, ...dynamicMetadata } = metadata || {};
112
+ const { displayName, userName, userEmail, projectPath, projectName, gitRepoUrl, type, tags: _tags, ...dynamicMetadata } = metadata || {};
108
113
  const record = {
109
114
  id,
110
115
  content,
111
116
  vector,
117
+ tagsVector,
112
118
  containerTag,
119
+ tags: tags.length > 0 ? tags.join(",") : undefined,
113
120
  type,
114
121
  createdAt: now,
115
122
  updatedAt: now,
@@ -4,6 +4,7 @@ export declare class ConnectionManager {
4
4
  private sqliteConfigured;
5
5
  private configureSqlite;
6
6
  private initDatabase;
7
+ private migrateSchema;
7
8
  getConnection(dbPath: string): Database;
8
9
  closeConnection(dbPath: string): void;
9
10
  closeAll(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe;IA+EvB,OAAO,CAAC,YAAY;IAqBpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAmBvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;CAWjB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
1
+ {"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe;IA+EvB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,aAAa;IAqBrB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAmBvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;CAWjB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -97,6 +97,26 @@ export class ConnectionManager {
97
97
  `1. Install: brew install sqlite\n` +
98
98
  `2. Configure customSqlitePath in ~/.config/opencode/opencode-mem.jsonc`);
99
99
  }
100
+ this.migrateSchema(db);
101
+ }
102
+ migrateSchema(db) {
103
+ try {
104
+ const columns = db.prepare("PRAGMA table_info(memories)").all();
105
+ const hasTags = columns.some((c) => c.name === "tags");
106
+ if (!hasTags && columns.length > 0) {
107
+ db.run("ALTER TABLE memories ADD COLUMN tags TEXT");
108
+ log("Migrated schema: added tags column to memories table");
109
+ }
110
+ db.run(`
111
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_tags USING vec0(
112
+ memory_id TEXT PRIMARY KEY,
113
+ embedding BLOB float32
114
+ )
115
+ `);
116
+ }
117
+ catch (error) {
118
+ log("Schema migration error", { error: String(error) });
119
+ }
100
120
  }
101
121
  getConnection(dbPath) {
102
122
  if (this.connections.has(dbPath)) {
@@ -5,6 +5,7 @@ export declare class ShardManager {
5
5
  constructor();
6
6
  private initMetadataDb;
7
7
  private getShardPath;
8
+ private resolveStoredPath;
8
9
  getActiveShard(scope: "user" | "project", scopeHash: string): ShardInfo | null;
9
10
  getAllShards(scope: "user" | "project", scopeHash: string): ShardInfo[];
10
11
  createShard(scope: "user" | "project", scopeHash: string, shardIndex: number): ShardInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,YAAY;IAKpB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAsB9E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAgCvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IA4BxF,OAAO,CAAC,WAAW;IAmDnB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;IAetE,OAAO,CAAC,iBAAiB;IAQzB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAiBhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAsBnC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
1
+ {"version":3,"file":"shard-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/shard-manager.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAW;IAC7B,OAAO,CAAC,YAAY,CAAS;;IAQ7B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,iBAAiB;IAKzB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAsB9E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAgCvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IA6BxF,OAAO,CAAC,WAAW;IA2DnB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;IAetE,OAAO,CAAC,iBAAiB;IAQzB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAkBhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAuBnC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}