@haisto/opencode-mem 2.16.0-beta.1 → 2.16.0-beta.3

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 CHANGED
@@ -99,6 +99,9 @@ Configure at `~/.config/opencode/opencode-mem.jsonc`:
99
99
  "excludeCurrentSession": true,
100
100
  "maxAgeDays": undefined,
101
101
  "injectOn": "first",
102
+ "injectMaxPreferences": 5,
103
+ "injectMaxPatterns": 5,
104
+ "injectMaxWorkflows": 3,
102
105
  },
103
106
  }
104
107
  ```
package/dist/config.d.ts CHANGED
@@ -55,6 +55,9 @@ export declare let CONFIG: {
55
55
  excludeCurrentSession: boolean | undefined;
56
56
  maxAgeDays: number | undefined;
57
57
  injectOn: "first" | "always";
58
+ injectMaxPreferences: number | undefined;
59
+ injectMaxPatterns: number | undefined;
60
+ injectMaxWorkflows: number | undefined;
58
61
  };
59
62
  };
60
63
  export declare function initConfig(directory: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA2jBA,eAAO,IAAI,MAAM;;;;;;;;;;;;;;;;;oBA3DT,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;mBASX,eAAe,GACf,SAAS,GACT,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAuCV,OAAO,GACP,QAAQ;;CAMgC,CAAC;AAEnD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CASlD;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAulBA,eAAO,IAAI,MAAM;;;;;;;;;;;;;;;;;oBAjET,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;mBASX,eAAe,GACf,SAAS,GACT,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAuCV,OAAO,GACP,QAAQ;;;;;CAYgC,CAAC;AAEnD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CASlD;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
package/dist/config.js CHANGED
@@ -59,6 +59,9 @@ const DEFAULTS = {
59
59
  excludeCurrentSession: true,
60
60
  maxAgeDays: undefined,
61
61
  injectOn: "first",
62
+ injectMaxPreferences: 5,
63
+ injectMaxPatterns: 5,
64
+ injectMaxWorkflows: 3,
62
65
  },
63
66
  };
64
67
  function expandPath(path) {
@@ -319,6 +322,22 @@ const CONFIG_TEMPLATE = `{
319
322
  // Maximum number of memories to return in search results
320
323
  "maxMemories": 10,
321
324
 
325
+ // ============================================
326
+ // Chat Message Injection
327
+ // ============================================
328
+
329
+ // Inject relevant memories into AI chat context
330
+ "chatMessage": {
331
+ "enabled": true,
332
+ "maxMemories": 3,
333
+ "excludeCurrentSession": true,
334
+ "maxAgeDays": undefined,
335
+ "injectOn": "first",
336
+ "injectMaxPreferences": 5,
337
+ "injectMaxPatterns": 5,
338
+ "injectMaxWorkflows": 3
339
+ },
340
+
322
341
  // ============================================
323
342
  // Advanced Settings
324
343
  // ============================================
@@ -437,6 +456,9 @@ function buildConfig(fileConfig) {
437
456
  excludeCurrentSession: fileConfig.chatMessage?.excludeCurrentSession ?? DEFAULTS.chatMessage.excludeCurrentSession,
438
457
  maxAgeDays: fileConfig.chatMessage?.maxAgeDays,
439
458
  injectOn: (fileConfig.chatMessage?.injectOn ?? DEFAULTS.chatMessage.injectOn),
459
+ injectMaxPreferences: fileConfig.chatMessage?.injectMaxPreferences ?? DEFAULTS.chatMessage.injectMaxPreferences,
460
+ injectMaxPatterns: fileConfig.chatMessage?.injectMaxPatterns ?? DEFAULTS.chatMessage.injectMaxPatterns,
461
+ injectMaxWorkflows: fileConfig.chatMessage?.injectMaxWorkflows ?? DEFAULTS.chatMessage.injectMaxWorkflows,
440
462
  },
441
463
  };
442
464
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAmB/D,eAAO,MAAM,iBAAiB,EAAE,MA4hB/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAmB/D,eAAO,MAAM,iBAAiB,EAAE,MAqiB/B,CAAC"}
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { performUserProfileLearning } from "./services/user-memory-learning.js";
8
8
  import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
9
9
  import { startWebServer, WebServer } from "./services/web-server.js";
10
10
  import { isConfigured, CONFIG, initConfig } from "./config.js";
11
- import { log } from "./services/logger.js";
11
+ import { log, logTrace } from "./services/logger.js";
12
12
  import { getLanguageName } from "./services/language-detector.js";
13
13
  export const OpenCodeMemPlugin = async (ctx) => {
14
14
  const { directory } = ctx;
@@ -165,8 +165,14 @@ export const OpenCodeMemPlugin = async (ctx) => {
165
165
  const cutoffDate = Date.now() - CONFIG.chatMessage.maxAgeDays * 86400000;
166
166
  memories = memories.filter((m) => new Date(m.createdAt).getTime() > cutoffDate);
167
167
  }
168
- if (memories.length === 0)
168
+ if (memories.length === 0) {
169
+ logTrace("inject: no memories found, skipping injection");
169
170
  return;
171
+ }
172
+ logTrace("inject: memories loaded", {
173
+ count: memories.length,
174
+ summaries: memories.map((m) => m.summary?.slice(0, 120)),
175
+ });
170
176
  const projectMemories = {
171
177
  results: memories.map((m) => ({
172
178
  similarity: 1.0,
@@ -176,8 +182,9 @@ export const OpenCodeMemPlugin = async (ctx) => {
176
182
  timing: 0,
177
183
  };
178
184
  const userId = tags.user.userEmail || null;
179
- const memoryContext = formatContextForPrompt(userId, projectMemories);
185
+ const memoryContext = formatContextForPrompt(userId, projectMemories, userMessage);
180
186
  if (memoryContext) {
187
+ logTrace("inject: memoryContext", { context: memoryContext });
181
188
  const contextPart = {
182
189
  id: `prt-memory-context-${Date.now()}`,
183
190
  sessionID: input.sessionID,
@@ -327,7 +334,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
327
334
  const existingProfile = userProfileManager.getActiveProfile(userId);
328
335
  if (existingProfile) {
329
336
  const existingData = JSON.parse(existingProfile.profileData);
330
- const mergedData = userProfileManager.mergeProfileData(existingData, {
337
+ const mergedData = await userProfileManager.smartMergeProfileData(existingData, {
331
338
  preferences: [newPreference],
332
339
  });
333
340
  userProfileManager.updateProfile(existingProfile.id, mergedData, 0, `Explicit preference added: ${sanitizedContent.slice(0, 80)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
@@ -933,7 +933,6 @@ export async function handleRunTagMigrationBatch(batchSize = 5) {
933
933
  const { buildMemoryProviderConfig } = await import("./ai/provider-config.js");
934
934
  const providerConfig = buildMemoryProviderConfig(CONFIG, {
935
935
  maxIterations: 1,
936
- iterationTimeout: 30000,
937
936
  });
938
937
  const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
939
938
  const projectShards = shardManager.getAllShards("project", "");
@@ -6,6 +6,6 @@ interface MemoryResultMinimal {
6
6
  interface MemoriesResponseMinimal {
7
7
  results?: MemoryResultMinimal[];
8
8
  }
9
- export declare function formatContextForPrompt(userId: string | null, projectMemories: MemoriesResponseMinimal): string;
9
+ export declare function formatContextForPrompt(userId: string | null, projectMemories: MemoriesResponseMinimal, userMessage?: string): string;
10
10
  export {};
11
11
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,eAAe,EAAE,uBAAuB,GACvC,MAAM,CAkCR"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,eAAe,EAAE,uBAAuB,EACxC,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAkCR"}
@@ -1,10 +1,10 @@
1
1
  import { CONFIG } from "../config.js";
2
2
  import { getUserProfileContext } from "./user-profile/profile-context.js";
3
3
  import { logDebug } from "./logger.js";
4
- export function formatContextForPrompt(userId, projectMemories) {
4
+ export function formatContextForPrompt(userId, projectMemories, userMessage) {
5
5
  const parts = ["[MEMORY]"];
6
6
  if (CONFIG.injectProfile && userId) {
7
- const profileContext = getUserProfileContext(userId);
7
+ const profileContext = getUserProfileContext(userId, userMessage);
8
8
  if (profileContext) {
9
9
  parts.push("\n" + profileContext);
10
10
  }
@@ -72,7 +72,7 @@ function writeLog(level, message, data) {
72
72
  const timestamp = localTimestamp();
73
73
  const levelTag = level.toUpperCase().padEnd(5);
74
74
  const line = data
75
- ? `[${timestamp}] [${levelTag}] ${message}: ${JSON.stringify(data)}\n`
75
+ ? `[${timestamp}] [${levelTag}] ${message}: ${JSON.stringify(data, null, 2)}\n`
76
76
  : `[${timestamp}] [${levelTag}] ${message}\n`;
77
77
  appendFileSync(logFile, line);
78
78
  }
@@ -88,6 +88,8 @@ ${prompts.map((p, i) => `${i + 1}. ${p.content}`).join("\n\n")}
88
88
 
89
89
  ## Analysis Guidelines
90
90
 
91
+ This profile is **cross-project** — it should capture the user's personal style, not project-specific details.
92
+
91
93
  Identify and ${existingProfile ? "update" : "create"}:
92
94
 
93
95
  1. **Preferences** (max ${CONFIG.userProfileMaxPreferences})
@@ -119,6 +121,8 @@ async function analyzeUserProfile(context, existingProfile) {
119
121
 
120
122
  Your task is to analyze user prompts and ${existingProfile ? "update" : "create"} a comprehensive user profile.
121
123
 
124
+ The profile is **cross-project** — capture the user's personal style and preferences, not project-specific details.
125
+
122
126
  CRITICAL: Detect the language used by the user in their prompts. You MUST output all descriptions, categories, and text in the SAME language as the user's prompts.
123
127
 
124
128
  Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
@@ -169,6 +173,8 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
169
173
 
170
174
  Your task is to analyze user prompts and ${existingProfile ? "update" : "create"} a comprehensive user profile.
171
175
 
176
+ The profile is **cross-project** — capture the user's personal style and preferences, not project-specific details.
177
+
172
178
  CRITICAL: Detect the language used by the user in their prompts. You MUST output all descriptions, categories, and text in the SAME language as the user's prompts.
173
179
 
174
180
  Use the update_user_profile tool to save the ${existingProfile ? "updated" : "new"} profile.`;
@@ -229,7 +235,7 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
229
235
  const rawData = result.data;
230
236
  if (existingProfile) {
231
237
  const existingData = JSON.parse(existingProfile.profileData);
232
- return userProfileManager.mergeProfileData(existingData, rawData);
238
+ return await userProfileManager.smartMergeProfileData(existingData, rawData);
233
239
  }
234
240
  return rawData;
235
241
  }
@@ -1,2 +1,2 @@
1
- export declare function getUserProfileContext(userId: string): string | null;
1
+ export declare function getUserProfileContext(userId: string, userMessage?: string): string | null;
2
2
  //# sourceMappingURL=profile-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profile-context.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-context.ts"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6CnE"}
1
+ {"version":3,"file":"profile-context.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-context.ts"],"names":[],"mappings":"AA8DA,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoDzF"}
@@ -1,37 +1,91 @@
1
1
  import { userProfileManager } from "./user-profile-manager.js";
2
- export function getUserProfileContext(userId) {
2
+ import { CONFIG } from "../../config.js";
3
+ import { extractTerms } from "./profile-utils.js";
4
+ /**
5
+ * Compute relevance score between a query and a target text.
6
+ * Uses term overlap across all languages via Intl.Segmenter + CJK bigrams.
7
+ */
8
+ function computeRelevance(query, target) {
9
+ const q = query.toLowerCase();
10
+ const t = target.toLowerCase();
11
+ const terms = extractTerms(q);
12
+ if (terms.length === 0)
13
+ return 0;
14
+ let matches = 0;
15
+ for (const term of terms) {
16
+ if (t.includes(term))
17
+ matches++;
18
+ }
19
+ return matches / terms.length;
20
+ }
21
+ /**
22
+ * Filter items by relevance to query.
23
+ * When query is empty/undefined, returns top items (must be pre-sorted).
24
+ */
25
+ function filterRelevant(items, query, maxItems, getText) {
26
+ if (!query || !query.trim()) {
27
+ return items.slice(0, maxItems);
28
+ }
29
+ const q = query.trim();
30
+ const scored = items.map((item, index) => ({
31
+ item,
32
+ index,
33
+ score: computeRelevance(q, getText(item)),
34
+ }));
35
+ const matched = scored
36
+ .filter(({ score }) => score > 0)
37
+ .sort((a, b) => {
38
+ if (b.score !== a.score)
39
+ return b.score - a.score;
40
+ return a.index - b.index; // preserve original order
41
+ })
42
+ .slice(0, maxItems)
43
+ .map(({ item }) => item);
44
+ // Fallback: no items matched the query, return default top maxItems
45
+ if (matched.length === 0) {
46
+ return items.slice(0, maxItems);
47
+ }
48
+ return matched;
49
+ }
50
+ export function getUserProfileContext(userId, userMessage) {
3
51
  const profile = userProfileManager.getActiveProfile(userId);
4
52
  if (!profile) {
5
53
  return null;
6
54
  }
7
55
  const profileData = JSON.parse(profile.profileData);
8
56
  const parts = [];
57
+ const prefMax = CONFIG.chatMessage.injectMaxPreferences ?? 5;
58
+ const patternMax = CONFIG.chatMessage.injectMaxPatterns ?? 5;
59
+ const workflowMax = CONFIG.chatMessage.injectMaxWorkflows ?? 3;
9
60
  if (profileData.preferences.length > 0) {
10
- parts.push("User Preferences:");
11
- profileData.preferences
12
- .sort((a, b) => b.confidence - a.confidence)
13
- .slice(0, 5)
14
- .forEach((pref) => {
15
- parts.push(`- [${pref.category}] ${pref.description}`);
16
- });
61
+ const items = [...profileData.preferences].sort((a, b) => b.confidence - a.confidence);
62
+ const relevant = filterRelevant(items, userMessage, prefMax, (p) => `${p.category} ${p.description}`);
63
+ if (relevant.length > 0) {
64
+ parts.push("User Preferences:");
65
+ relevant.forEach((pref) => {
66
+ parts.push(`- [${pref.category}] ${pref.description}`);
67
+ });
68
+ }
17
69
  }
18
70
  if (profileData.patterns.length > 0) {
19
- parts.push("\nUser Patterns:");
20
- profileData.patterns
21
- .sort((a, b) => b.frequency - a.frequency)
22
- .slice(0, 5)
23
- .forEach((pattern) => {
24
- parts.push(`- [${pattern.category}] ${pattern.description}`);
25
- });
71
+ const items = [...profileData.patterns].sort((a, b) => b.frequency - a.frequency);
72
+ const relevant = filterRelevant(items, userMessage, patternMax, (p) => `${p.category} ${p.description}`);
73
+ if (relevant.length > 0) {
74
+ parts.push("\nUser Patterns:");
75
+ relevant.forEach((pattern) => {
76
+ parts.push(`- [${pattern.category}] ${pattern.description}`);
77
+ });
78
+ }
26
79
  }
27
80
  if (profileData.workflows.length > 0) {
28
- parts.push("\nUser Workflows:");
29
- profileData.workflows
30
- .sort((a, b) => b.frequency - a.frequency)
31
- .slice(0, 3)
32
- .forEach((workflow) => {
33
- parts.push(`- ${workflow.description}`);
34
- });
81
+ const items = [...profileData.workflows].sort((a, b) => b.frequency - a.frequency);
82
+ const relevant = filterRelevant(items, userMessage, workflowMax, (w) => w.description);
83
+ if (relevant.length > 0) {
84
+ parts.push("\nUser Workflows:");
85
+ relevant.forEach((workflow) => {
86
+ parts.push(`- ${workflow.description}`);
87
+ });
88
+ }
35
89
  }
36
90
  if (parts.length === 0) {
37
91
  return null;
@@ -1,3 +1,18 @@
1
+ /**
2
+ * Extract indexable terms from text, supporting all languages.
3
+ *
4
+ * Strategy:
5
+ * 1. Intl.Segmenter (word granularity) across the whole text — captures
6
+ * words/identifiers in any script (Latin, Arabic, Cyrillic, Devanagari, etc.)
7
+ * 2. CJK bigram pass — adds adjacent pairs of CJK characters because word
8
+ * segmenters often under-split Chinese/Japanese/Korean text.
9
+ * 3. All terms are lowercased, deduplicated, and must be >= 2 characters.
10
+ */
11
+ export declare function extractTerms(text: string): string[];
12
+ /**
13
+ * Jaccard similarity between two texts based on extracted terms (0-1).
14
+ */
15
+ export declare function computeTermOverlap(text1: string, text2: string): number;
1
16
  export declare const safeArray: <T>(arr: any) => T[];
2
17
  export declare const safeObject: <T extends object>(obj: any, fallback: T) => T;
3
18
  //# sourceMappingURL=profile-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profile-utils.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,KAAK,GAAG,KAAG,CAAC,EA0BxC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,GAAG,EAAE,UAAU,CAAC,KAAG,CAWpE,CAAC"}
1
+ {"version":3,"file":"profile-utils.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA8BnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYvE;AAED,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,KAAK,GAAG,KAAG,CAAC,EA0BxC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,GAAG,EAAE,UAAU,CAAC,KAAG,CAWpE,CAAC"}
@@ -1,3 +1,57 @@
1
+ /**
2
+ * Extract indexable terms from text, supporting all languages.
3
+ *
4
+ * Strategy:
5
+ * 1. Intl.Segmenter (word granularity) across the whole text — captures
6
+ * words/identifiers in any script (Latin, Arabic, Cyrillic, Devanagari, etc.)
7
+ * 2. CJK bigram pass — adds adjacent pairs of CJK characters because word
8
+ * segmenters often under-split Chinese/Japanese/Korean text.
9
+ * 3. All terms are lowercased, deduplicated, and must be >= 2 characters.
10
+ */
11
+ export function extractTerms(text) {
12
+ const terms = new Set();
13
+ // Step 1: Intl.Segmenter — works for all languages
14
+ try {
15
+ const segmenter = new Intl.Segmenter("zh", { granularity: "word" });
16
+ for (const seg of segmenter.segment(text)) {
17
+ const word = seg.segment.toLowerCase();
18
+ if (seg.isWordLike && word.length >= 2) {
19
+ terms.add(word);
20
+ }
21
+ }
22
+ }
23
+ catch {
24
+ // Intl.Segmenter not available (very old runtime), fallback to simple regex
25
+ const words = text.toLowerCase().match(/[a-z][a-z0-9_]{2,}/g);
26
+ if (words)
27
+ words.forEach((w) => terms.add(w));
28
+ }
29
+ // Step 2: CJK bigram — catches sub-word units in CJK text
30
+ const cjkChars = [...text].filter((ch) => /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(ch));
31
+ if (cjkChars.length > 1) {
32
+ for (let i = 0; i < cjkChars.length - 1; i++) {
33
+ const bigram = (cjkChars[i] + cjkChars[i + 1]).toLowerCase();
34
+ terms.add(bigram);
35
+ }
36
+ }
37
+ return Array.from(terms);
38
+ }
39
+ /**
40
+ * Jaccard similarity between two texts based on extracted terms (0-1).
41
+ */
42
+ export function computeTermOverlap(text1, text2) {
43
+ const t1 = new Set(extractTerms(text1));
44
+ const t2 = new Set(extractTerms(text2));
45
+ if (t1.size === 0 || t2.size === 0)
46
+ return 0;
47
+ let intersection = 0;
48
+ for (const t of t1) {
49
+ if (t2.has(t))
50
+ intersection++;
51
+ }
52
+ const union = new Set([...t1, ...t2]);
53
+ return intersection / union.size;
54
+ }
1
55
  export const safeArray = (arr) => {
2
56
  if (!arr)
3
57
  return [];
@@ -1,4 +1,11 @@
1
1
  import type { UserProfile, UserProfileChangelog, UserProfileData } from "./types.js";
2
+ export type MatchCallback = (items: {
3
+ category: string;
4
+ description: string;
5
+ }[], newItem: {
6
+ category: string;
7
+ description: string;
8
+ }, itemType: "preference" | "pattern" | "workflow") => Promise<number>;
2
9
  export declare class UserProfileManager {
3
10
  private db;
4
11
  private readonly dbPath;
@@ -24,6 +31,22 @@ export declare class UserProfileManager {
24
31
  private rowToProfile;
25
32
  private rowToChangelog;
26
33
  mergeProfileData(existing: UserProfileData, updates: Partial<UserProfileData>): UserProfileData;
34
+ /**
35
+ * Find the best semiically similar match in existing items using term overlap.
36
+ * Returns the index of the best match, or -1 if no match exceeds the threshold.
37
+ */
38
+ private findSemanticMatch;
39
+ /**
40
+ * Smart merge: tries LLM matching (via optional callback), falls back to term overlap.
41
+ *
42
+ * For matched items: merges in-place (updates description, confidence,
43
+ * evidence, lastUpdated) — the incoming description becomes the latest.
44
+ * For unmatched items: delegates to mergeProfileData for normal addition.
45
+ *
46
+ * This ensures that manual writes (e.g. "50年经验" → "20年") update
47
+ * the description content instead of being overwritten by the redirect.
48
+ */
49
+ smartMergeProfileData(existing: UserProfileData, updates: Partial<UserProfileData>, llmMatch?: MatchCallback): Promise<UserProfileData>;
27
50
  private ensureArray;
28
51
  }
29
52
  export declare const userProfileManager: UserProfileManager;
@@ -1 +1 @@
1
- {"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AASrF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IAoCT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAmCP;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB7D,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB1D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB3D,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA+F7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;IAsG/F,OAAO,CAAC,WAAW;CAWpB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
1
+ {"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AASrF,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,EAClD,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAClD,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,KAC5C,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IAoCT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAmCP;;;OAGG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB7D,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB1D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAgB3D,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA+F7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;IAsG/F;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA6BzB;;;;;;;;;OASG;IACG,qBAAqB,CACzB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,eAAe,CAAC;IAgH3B,OAAO,CAAC,WAAW;CAWpB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -2,7 +2,7 @@ import { getDatabase } from "../sqlite/sqlite-bootstrap.js";
2
2
  import { join } from "node:path";
3
3
  import { connectionManager } from "../sqlite/connection-manager.js";
4
4
  import { CONFIG } from "../../config.js";
5
- import { safeArray, safeObject } from "./profile-utils.js";
5
+ import { safeArray, safeObject, computeTermOverlap } from "./profile-utils.js";
6
6
  import { log, logTrace } from "../logger.js";
7
7
  const Database = getDatabase();
8
8
  const USER_PROFILES_DB_NAME = "user-profiles.db";
@@ -395,6 +395,137 @@ export class UserProfileManager {
395
395
  }
396
396
  return merged;
397
397
  }
398
+ /**
399
+ * Find the best semiically similar match in existing items using term overlap.
400
+ * Returns the index of the best match, or -1 if no match exceeds the threshold.
401
+ */
402
+ findSemanticMatch(items, newItem, getText, threshold = 0.3) {
403
+ const newText = getText(newItem);
404
+ if (!newText || newText.length < 5)
405
+ return -1;
406
+ let bestIndex = -1;
407
+ let bestScore = threshold;
408
+ for (let i = 0; i < items.length; i++) {
409
+ const existingText = getText(items[i]);
410
+ if (!existingText)
411
+ continue;
412
+ // Exact match → immediate return
413
+ if (existingText === newText)
414
+ return i;
415
+ const score = computeTermOverlap(newText, existingText);
416
+ if (score > bestScore) {
417
+ bestScore = score;
418
+ bestIndex = i;
419
+ }
420
+ }
421
+ return bestIndex;
422
+ }
423
+ /**
424
+ * Smart merge: tries LLM matching (via optional callback), falls back to term overlap.
425
+ *
426
+ * For matched items: merges in-place (updates description, confidence,
427
+ * evidence, lastUpdated) — the incoming description becomes the latest.
428
+ * For unmatched items: delegates to mergeProfileData for normal addition.
429
+ *
430
+ * This ensures that manual writes (e.g. "50年经验" → "20年") update
431
+ * the description content instead of being overwritten by the redirect.
432
+ */
433
+ async smartMergeProfileData(existing, updates, llmMatch) {
434
+ // Clone existing — we'll mutate this and fall through matched items
435
+ const result = {
436
+ preferences: [...this.ensureArray(existing?.preferences)],
437
+ patterns: [...this.ensureArray(existing?.patterns)],
438
+ workflows: [...this.ensureArray(existing?.workflows)],
439
+ };
440
+ const unmatched = {
441
+ preferences: [],
442
+ patterns: [],
443
+ workflows: [],
444
+ };
445
+ // --- preferences ---
446
+ if (updates.preferences) {
447
+ for (const pref of updates.preferences) {
448
+ let idx = this.findSemanticMatch(result.preferences, pref, (p) => `${p.category} ${p.description}`, 0.4);
449
+ if (idx < 0 && llmMatch) {
450
+ idx = await llmMatch(result.preferences, pref, "preference");
451
+ }
452
+ if (idx >= 0 && result.preferences[idx]) {
453
+ const target = result.preferences[idx];
454
+ result.preferences[idx] = {
455
+ category: target.category,
456
+ description: pref.description, // ← latest content wins
457
+ confidence: Math.max(pref.confidence ?? 0, target.confidence ?? 0),
458
+ evidence: [
459
+ ...new Set([
460
+ ...this.ensureArray(target.evidence),
461
+ ...this.ensureArray(pref.evidence),
462
+ ]),
463
+ ].slice(0, 5),
464
+ lastUpdated: Date.now(),
465
+ };
466
+ }
467
+ else {
468
+ unmatched.preferences.push(pref);
469
+ }
470
+ }
471
+ }
472
+ // --- patterns ---
473
+ if (updates.patterns) {
474
+ for (const pattern of updates.patterns) {
475
+ let idx = this.findSemanticMatch(result.patterns, pattern, (p) => `${p.category} ${p.description}`, 0.4);
476
+ if (idx < 0 && llmMatch) {
477
+ idx = await llmMatch(result.patterns, pattern, "pattern");
478
+ }
479
+ if (idx >= 0 && result.patterns[idx]) {
480
+ const target = result.patterns[idx];
481
+ const newFreq = Math.max((pattern.frequency || 0), (target.frequency || 1) + 1);
482
+ result.patterns[idx] = {
483
+ category: target.category,
484
+ description: pattern.description, // latest content wins
485
+ frequency: newFreq,
486
+ lastSeen: Date.now(),
487
+ };
488
+ }
489
+ else {
490
+ unmatched.patterns.push(pattern);
491
+ }
492
+ }
493
+ }
494
+ // --- workflows ---
495
+ if (updates.workflows) {
496
+ for (const wf of updates.workflows) {
497
+ let idx = this.findSemanticMatch(result.workflows, wf, (w) => w.description, 0.4);
498
+ if (idx < 0 && llmMatch) {
499
+ idx = await llmMatch(result.workflows.map((w) => ({ category: "", description: w.description })), { category: "", description: wf.description }, "workflow");
500
+ }
501
+ if (idx >= 0 && result.workflows[idx]) {
502
+ const target = result.workflows[idx];
503
+ const newFreq = Math.max((wf.frequency || 0), (target.frequency || 1) + 1);
504
+ result.workflows[idx] = {
505
+ ...target,
506
+ description: wf.description, // latest content wins
507
+ frequency: newFreq,
508
+ lastSeen: Date.now(),
509
+ };
510
+ }
511
+ else {
512
+ unmatched.workflows.push(wf);
513
+ }
514
+ }
515
+ }
516
+ // Send unmatched items through mergeProfileData for normal addition
517
+ const hasUnmatched = unmatched.preferences.length > 0 ||
518
+ unmatched.patterns.length > 0 ||
519
+ unmatched.workflows.length > 0;
520
+ if (hasUnmatched) {
521
+ return this.mergeProfileData(result, {
522
+ preferences: unmatched.preferences.length > 0 ? unmatched.preferences : undefined,
523
+ patterns: unmatched.patterns.length > 0 ? unmatched.patterns : undefined,
524
+ workflows: unmatched.workflows.length > 0 ? unmatched.workflows : undefined,
525
+ });
526
+ }
527
+ return result;
528
+ }
398
529
  ensureArray(val) {
399
530
  if (typeof val === "string") {
400
531
  try {
@@ -1842,7 +1842,7 @@ textarea:focus-visible {
1842
1842
  .pref-bar-btn:hover {
1843
1843
  background: var(--om-accent-red);
1844
1844
  color: var(--om-text-inverse);
1845
- }
1845
+ }
1846
1846
 
1847
1847
  .pref-delete-bar .btn-danger {
1848
1848
  background: var(--om-accent-red);
@@ -1909,4 +1909,4 @@ textarea:focus-visible {
1909
1909
  border-width: 2px;
1910
1910
  background: var(--om-blue-tint);
1911
1911
  box-shadow: 0 0 0 1px var(--om-blue-border-tint);
1912
- }
1912
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haisto/opencode-mem",
3
- "version": "2.16.0-beta.1",
3
+ "version": "2.16.0-beta.3",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",