oh-memos-mcp 1.0.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 (41) hide show
  1. package/.env.example +27 -0
  2. package/README.md +71 -0
  3. package/dist/api-client.d.ts +22 -0
  4. package/dist/api-client.js +127 -0
  5. package/dist/config.d.ts +30 -0
  6. package/dist/config.js +163 -0
  7. package/dist/cube-manager.d.ts +16 -0
  8. package/dist/cube-manager.js +357 -0
  9. package/dist/formatters.d.ts +16 -0
  10. package/dist/formatters.js +174 -0
  11. package/dist/handlers/admin.d.ts +13 -0
  12. package/dist/handlers/admin.js +331 -0
  13. package/dist/handlers/calendar.d.ts +8 -0
  14. package/dist/handlers/calendar.js +281 -0
  15. package/dist/handlers/graph.d.ts +11 -0
  16. package/dist/handlers/graph.js +502 -0
  17. package/dist/handlers/index.d.ts +7 -0
  18. package/dist/handlers/index.js +71 -0
  19. package/dist/handlers/memory.d.ts +12 -0
  20. package/dist/handlers/memory.js +270 -0
  21. package/dist/handlers/search.d.ts +11 -0
  22. package/dist/handlers/search.js +334 -0
  23. package/dist/handlers/utils.d.ts +22 -0
  24. package/dist/handlers/utils.js +75 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.js +18 -0
  27. package/dist/keyword-enhancer.d.ts +18 -0
  28. package/dist/keyword-enhancer.js +335 -0
  29. package/dist/memory-analysis.d.ts +7 -0
  30. package/dist/memory-analysis.js +45 -0
  31. package/dist/models.d.ts +21 -0
  32. package/dist/models.js +101 -0
  33. package/dist/query-processing.d.ts +20 -0
  34. package/dist/query-processing.js +265 -0
  35. package/dist/server.d.ts +5 -0
  36. package/dist/server.js +114 -0
  37. package/dist/tools-registry.d.ts +303 -0
  38. package/dist/tools-registry.js +369 -0
  39. package/dist/types.d.ts +118 -0
  40. package/dist/types.js +5 -0
  41. package/package.json +53 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Handler Utilities
3
+ *
4
+ * Shared error formatting and cube ID resolution.
5
+ */
6
+ import { detectCubeFromPath } from "../keyword-enhancer.js";
7
+ import { getDefaultCubeId } from "../cube-manager.js";
8
+ // ============================================================================
9
+ // Error Codes
10
+ // ============================================================================
11
+ export const ERR_API_UNREACHABLE = "API_UNREACHABLE";
12
+ export const ERR_API_ERROR = "API_ERROR";
13
+ export const ERR_CUBE_NOT_FOUND = "CUBE_NOT_FOUND";
14
+ export const ERR_CUBE_REGISTRATION = "CUBE_REGISTRATION_FAILED";
15
+ export const ERR_PARAM_MISSING = "PARAM_MISSING";
16
+ export const ERR_PARAM_INVALID = "PARAM_INVALID";
17
+ export const ERR_NEO4J_CONFIG = "NEO4J_CONFIG_MISSING";
18
+ export const ERR_OPERATION_FAILED = "OPERATION_FAILED";
19
+ export const ERR_DELETE_DISABLED = "DELETE_DISABLED";
20
+ export const ERR_USER_ERROR = "USER_ERROR";
21
+ // ============================================================================
22
+ // Response Helpers
23
+ // ============================================================================
24
+ export function errorResponse(message, errorCode, suggestions) {
25
+ const parts = [];
26
+ if (errorCode) {
27
+ parts.push(`❌ [${errorCode}] ${message}`);
28
+ }
29
+ else {
30
+ parts.push(`❌ ${message}`);
31
+ }
32
+ if (suggestions && suggestions.length > 0) {
33
+ parts.push("");
34
+ parts.push("💡 Suggestions:");
35
+ for (const s of suggestions) {
36
+ parts.push(`- ${s}`);
37
+ }
38
+ }
39
+ return [{ type: "text", text: parts.join("\n") }];
40
+ }
41
+ export function successResponse(message) {
42
+ return [{ type: "text", text: message }];
43
+ }
44
+ export function cubeRegistrationError(cubeId, detail) {
45
+ return errorResponse(`Cube '${cubeId}' registration failed: ${detail ?? "unknown error"}`, ERR_CUBE_REGISTRATION, [
46
+ `Check if MemOS API is running: \`curl http://localhost:18000/health\``,
47
+ `Verify cube exists: \`memos_list_cubes(include_status=True)\``,
48
+ `Try manual registration: \`memos_register_cube(cube_id="...")\``,
49
+ ]);
50
+ }
51
+ export function apiErrorResponse(operation, statusOrMsg) {
52
+ return errorResponse(`${operation} failed: ${statusOrMsg}`, ERR_API_ERROR, [
53
+ "Check API health: `curl http://localhost:18000/health/detail`",
54
+ "Check API logs for details",
55
+ ]);
56
+ }
57
+ // ============================================================================
58
+ // Cube ID Resolution
59
+ // ============================================================================
60
+ export function getCubeIdFromArgs(arguments_) {
61
+ const argCubeId = arguments_.cube_id;
62
+ const projectPath = arguments_.project_path;
63
+ if (projectPath) {
64
+ try {
65
+ const derived = detectCubeFromPath(projectPath);
66
+ if (derived)
67
+ return derived;
68
+ }
69
+ catch {
70
+ // ignore
71
+ }
72
+ }
73
+ return argCubeId ?? getDefaultCubeId();
74
+ }
75
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * oh-memos-mcp — MCP Server for MemOS Intelligent Memory Management
4
+ *
5
+ * Usage:
6
+ * npx oh-memos-mcp
7
+ * node dist/index.js
8
+ *
9
+ * Configuration via environment variables or CLI args.
10
+ * See README.md for full documentation.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * oh-memos-mcp — MCP Server for MemOS Intelligent Memory Management
4
+ *
5
+ * Usage:
6
+ * npx oh-memos-mcp
7
+ * node dist/index.js
8
+ *
9
+ * Configuration via environment variables or CLI args.
10
+ * See README.md for full documentation.
11
+ */
12
+ import { runServer } from "./server.js";
13
+ import { logger } from "./config.js";
14
+ runServer().catch((err) => {
15
+ logger.error(`Fatal error: ${err}`);
16
+ process.exit(1);
17
+ });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Keyword Enhancer Module
3
+ *
4
+ * - Extended stopwords (programming/technical + Chinese)
5
+ * - Fuzzy matching with Levenshtein distance
6
+ * - Smart cube auto-detection from project path
7
+ * - Structured field weighting
8
+ */
9
+ export declare const ENGLISH_STOPWORDS: Set<string>;
10
+ export declare const CHINESE_STOPWORDS: Set<string>;
11
+ export declare const ALL_STOPWORDS: Set<string>;
12
+ export declare function levenshteinDistance(s1: string, s2: string): number;
13
+ export declare function fuzzyMatchScore(query: string, text: string, threshold?: number): number;
14
+ export declare function findFuzzyMatches(keyword: string, text: string, threshold?: number, maxWordLenDiff?: number): Array<[string, number]>;
15
+ export declare function detectCubeFromPath(projectPath?: string): string;
16
+ export declare function extractKeywordsEnhanced(query: string, stopwords?: Set<string>, minLength?: number): string[];
17
+ export declare function keywordMatchScoreEnhanced(text: string, keywords: string[], metadata?: Record<string, unknown>, enableFuzzy?: boolean, fuzzyThreshold?: number): number;
18
+ //# sourceMappingURL=keyword-enhancer.d.ts.map
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Keyword Enhancer Module
3
+ *
4
+ * - Extended stopwords (programming/technical + Chinese)
5
+ * - Fuzzy matching with Levenshtein distance
6
+ * - Smart cube auto-detection from project path
7
+ * - Structured field weighting
8
+ */
9
+ // =============================================================================
10
+ // Extended Stopwords
11
+ // =============================================================================
12
+ export const ENGLISH_STOPWORDS = new Set([
13
+ // General English
14
+ "the", "and", "or", "a", "an", "to", "of", "for", "with", "in", "on",
15
+ "at", "is", "are", "was", "were", "be", "been", "being", "this", "that",
16
+ "it", "as", "from", "by", "about", "into", "over", "after", "before",
17
+ "not", "but", "if", "then", "else", "when", "where", "which", "who",
18
+ "whom", "whose", "why", "how", "all", "each", "every", "both", "few",
19
+ "more", "most", "other", "some", "such", "no", "nor", "only", "own",
20
+ "same", "so", "than", "too", "very", "just", "can", "will", "should",
21
+ "would", "could", "may", "might", "must", "shall", "do", "does", "did",
22
+ "doing", "done", "have", "has", "had", "having", "get", "got", "gets",
23
+ "getting", "make", "makes", "made", "making", "go", "goes", "went",
24
+ "going", "come", "comes", "came", "coming", "take", "takes", "took",
25
+ "taking", "see", "sees", "saw", "seeing", "know", "knows", "knew",
26
+ "knowing", "think", "thinks", "thought", "thinking", "want", "wants",
27
+ "wanted", "wanting", "use", "uses", "used", "using", "find", "finds",
28
+ "found", "finding", "give", "gives", "gave", "giving", "tell", "tells",
29
+ "told", "telling", "say", "says", "said", "saying", "try", "tries",
30
+ "tried", "trying", "need", "needs", "needed", "needing", "let", "lets",
31
+ "put", "puts", "keep", "keeps", "kept", "keeping", "begin", "begins",
32
+ "began", "beginning", "show", "shows", "showed", "showing",
33
+ // Programming common words (usually not meaningful for search)
34
+ "todo", "fixme", "hack", "note", "xxx", "temp", "tmp", "foo", "bar",
35
+ "baz", "qux", "test", "example", "sample", "demo", "main", "index",
36
+ "app", "application", "module", "package", "import", "export", "require",
37
+ "define", "declare", "const", "var", "function", "class", "method",
38
+ "return", "void", "null", "undefined", "true", "false", "new", "delete",
39
+ "typeof", "instanceof", "catch", "finally", "throw", "async",
40
+ "await", "yield", "public", "private", "protected", "static", "final",
41
+ "abstract", "interface", "extends", "implements", "super", "self", "cls",
42
+ "args", "kwargs", "param", "params", "arg", "argument", "arguments",
43
+ "value", "values", "result", "results", "data", "info", "item", "items",
44
+ "list", "array", "object", "dict", "map", "tuple", "string",
45
+ "str", "int", "float", "bool", "boolean", "number", "num", "char",
46
+ "byte", "bytes", "type", "types", "name", "names", "key", "keys",
47
+ "val", "vals", "obj", "objs", "elem", "elems", "element", "elements",
48
+ "node", "nodes", "attr", "attrs", "attribute", "attributes", "prop",
49
+ "props", "property", "properties", "opt", "opts", "option", "options",
50
+ "cfg", "conf", "config", "configuration", "setting", "settings",
51
+ "env", "environment", "ctx", "context", "req", "request", "res",
52
+ "response", "err", "error", "errors", "msg", "message", "messages",
53
+ "log", "logs", "debug", "warn", "warning", "warnings", "input", "output",
54
+ "out", "src", "source", "dest", "destination", "target", "path", "paths",
55
+ "file", "files", "dir", "directory", "directories", "folder", "folders",
56
+ "url", "urls", "uri", "uris", "id", "ids", "uid", "uuid", "guid",
57
+ "ref", "refs", "reference", "references", "ptr", "pointer", "pointers",
58
+ "handle", "handles", "callback", "callbacks", "handler", "handlers",
59
+ "listener", "listeners", "event", "events", "action", "actions",
60
+ "state", "states", "status", "flag", "flags", "mode", "modes",
61
+ "level", "levels", "size", "sizes", "length", "len", "count", "counts",
62
+ "total", "sum", "avg", "average", "min", "max", "first", "last",
63
+ "prev", "previous", "next", "current", "cur", "old", "start", "end",
64
+ "init", "initialize", "initialized", "setup", "teardown", "cleanup",
65
+ "reset", "clear", "load", "save", "post", "update", "remove", "insert",
66
+ "append", "push", "pop", "shift", "unshift", "sub", "mul", "div",
67
+ "mod", "xor", "bit", "bits", "word", "words", "line", "lines",
68
+ "row", "rows", "col", "column", "columns", "cell", "cells", "grid",
69
+ "grids", "table", "tables", "record", "records", "field", "fields",
70
+ "schema", "schemas", "model", "models", "view", "views", "controller",
71
+ "controllers", "service", "services", "repository", "repositories",
72
+ "factory", "factories", "builder", "builders", "adapter", "adapters",
73
+ "wrapper", "wrappers", "helper", "helpers", "util", "utils", "utility",
74
+ "utilities", "tool", "tools", "lib", "library", "libraries", "framework",
75
+ "frameworks", "plugin", "plugins", "extension", "extensions",
76
+ "component", "components", "widget", "widgets", "block", "blocks",
77
+ "section", "sections", "part", "parts", "piece", "pieces", "chunk",
78
+ "chunks", "segment", "segments", "unit", "units", "modules", "system",
79
+ "systems", "layer", "layers", "process", "processes", "thread", "threads",
80
+ "task", "tasks", "job", "jobs", "work", "works", "worker", "workers",
81
+ "queue", "queues", "stack", "stacks", "pool", "pools", "cache", "caches",
82
+ "buffer", "buffers", "stream", "streams", "pipe", "pipes", "channel",
83
+ "channels", "socket", "sockets", "port", "ports", "host", "hosts",
84
+ "server", "servers", "client", "clients", "user", "users", "admin",
85
+ "admins", "root", "guest", "account", "accounts", "profile", "profiles",
86
+ "session", "sessions", "token", "tokens", "auth", "authentication",
87
+ "authorization", "permission", "permissions", "role", "roles", "access",
88
+ "grant", "grants", "deny", "denies", "filter", "filters",
89
+ "validate", "validates", "validation", "check", "checks", "verify",
90
+ "verifies", "verification", "confirm", "confirms", "confirmation",
91
+ "approve", "approves", "approval", "reject", "rejects", "rejection",
92
+ "accept", "accepts", "acceptance", "decline", "declines", "cancel",
93
+ "cancels", "cancellation", "abort", "aborts", "retry", "retries",
94
+ "timeout", "timeouts", "expire", "expires", "expiration",
95
+ "refresh", "refreshes", "renew", "renews", "renewal",
96
+ ]);
97
+ export const CHINESE_STOPWORDS = new Set([
98
+ "的", "了", "和", "与", "在", "是", "有", "我", "你", "他", "她", "它",
99
+ "我们", "你们", "他们", "她们", "它们", "这", "那", "这个", "那个",
100
+ "这些", "那些", "这里", "那里", "这儿", "那儿", "什么", "怎么", "怎样",
101
+ "如何", "为什么", "哪", "哪个", "哪些", "哪里", "哪儿", "谁", "多少",
102
+ "几", "多", "少", "大", "小", "好", "坏", "对", "错", "行", "不行",
103
+ "可以", "不可以", "能", "不能", "会", "不会", "要", "不要", "想",
104
+ "不想", "应该", "不应该", "必须", "不必", "可能", "不可能", "一定",
105
+ "也许", "或许", "大概", "肯定", "当然", "确实", "真的", "假的",
106
+ "及", "或", "或者", "还是", "以及", "并", "并且", "而",
107
+ "而且", "但", "但是", "不过", "然而", "可是", "虽然", "尽管", "即使",
108
+ "如果", "假如", "要是", "只要", "除非", "无论", "不管", "不论",
109
+ "因为", "由于", "所以", "因此", "因而", "于是", "那么", "这样",
110
+ "既然", "只有", "才", "就", "便", "却", "倒", "反而", "否则",
111
+ "不然", "要不", "要不然", "要么", "以便", "以免", "以致", "为了",
112
+ "为着", "对于", "关于", "至于", "根据", "按照", "依照", "通过",
113
+ "经过", "随着", "沿着", "顺着", "朝着", "向着", "趁着", "除了",
114
+ "除开", "除去", "除却", "除外",
115
+ "自己", "自身", "本人", "本身", "彼此", "相互", "互相", "大家",
116
+ "各位", "诸位", "各自", "某", "某个", "某些", "其", "其中", "其他",
117
+ "其它", "其余", "另", "另外", "另一", "别", "别的", "任何", "一切",
118
+ "所有", "全部", "整个", "每", "每个", "各", "各个", "同", "同一",
119
+ "同样", "一样", "一般", "一种", "某种", "这种", "那种", "各种",
120
+ "现在", "目前", "当前", "眼前", "此时", "此刻", "刚才", "方才",
121
+ "刚刚", "正在", "正", "将", "将要", "快要", "就要", "已经", "已",
122
+ "曾", "曾经", "过去", "以前", "之前", "以后", "之后", "后来", "然后",
123
+ "接着", "继而", "随后", "最后", "最终", "终于", "始终", "一直",
124
+ "常常", "经常", "往往", "总是", "老是", "有时", "有时候", "偶尔",
125
+ "突然", "忽然", "马上", "立刻", "立即", "赶紧", "赶快", "连忙",
126
+ "从来", "一向", "向来", "素来", "历来", "原来", "本来", "起初",
127
+ "很", "非常", "十分", "极", "极其", "极为", "特别", "尤其", "格外",
128
+ "更", "更加", "越", "越发", "越来越", "最", "顶", "太", "过于",
129
+ "稍", "稍微", "略", "略微", "比较", "相当", "相对", "颇", "挺",
130
+ "不", "没", "没有", "未", "尚未", "并不", "并非", "并没", "并没有",
131
+ "也", "还", "又", "再", "仍", "仍然", "依然", "依旧", "照样",
132
+ "都", "全", "全都", "皆", "均", "统统", "通通", "一概", "一律",
133
+ "只", "只是", "仅", "仅仅", "光", "单", "单独",
134
+ "啊", "呀", "哇", "哦", "噢", "哎", "唉", "嗨", "喂", "嘿",
135
+ "嗯", "哼", "吧", "呢", "吗", "嘛", "啦", "喽", "呗",
136
+ "罢了", "而已", "罢", "地", "得", "着", "过", "们",
137
+ "做", "用", "把", "被", "给", "让", "叫", "使",
138
+ "令", "请", "来", "去", "到", "进", "出", "上", "下", "起", "开",
139
+ "关", "拿", "放", "看", "听", "说", "写", "读",
140
+ ]);
141
+ export const ALL_STOPWORDS = new Set([
142
+ ...ENGLISH_STOPWORDS,
143
+ ...CHINESE_STOPWORDS,
144
+ ]);
145
+ // =============================================================================
146
+ // Fuzzy Matching
147
+ // =============================================================================
148
+ export function levenshteinDistance(s1, s2) {
149
+ if (s1.length < s2.length) {
150
+ [s1, s2] = [s2, s1];
151
+ }
152
+ if (s2.length === 0)
153
+ return s1.length;
154
+ let previousRow = Array.from({ length: s2.length + 1 }, (_, i) => i);
155
+ for (let i = 0; i < s1.length; i++) {
156
+ const currentRow = [i + 1];
157
+ for (let j = 0; j < s2.length; j++) {
158
+ const insertions = previousRow[j + 1] + 1;
159
+ const deletions = currentRow[j] + 1;
160
+ const substitutions = previousRow[j] + (s1[i] !== s2[j] ? 1 : 0);
161
+ currentRow.push(Math.min(insertions, deletions, substitutions));
162
+ }
163
+ previousRow = currentRow;
164
+ }
165
+ return previousRow[previousRow.length - 1];
166
+ }
167
+ export function fuzzyMatchScore(query, text, threshold = 0.7) {
168
+ if (!query || !text)
169
+ return 0.0;
170
+ const queryLower = query.toLowerCase();
171
+ const textLower = text.toLowerCase();
172
+ if (queryLower === textLower)
173
+ return 1.0;
174
+ if (textLower.includes(queryLower))
175
+ return 0.9;
176
+ const maxLen = Math.max(queryLower.length, textLower.length);
177
+ if (maxLen === 0)
178
+ return 0.0;
179
+ const distance = levenshteinDistance(queryLower, textLower);
180
+ const similarity = 1 - distance / maxLen;
181
+ return similarity >= threshold ? similarity : 0.0;
182
+ }
183
+ export function findFuzzyMatches(keyword, text, threshold = 0.7, maxWordLenDiff = 3) {
184
+ if (!keyword || !text)
185
+ return [];
186
+ const matches = [];
187
+ const keywordLower = keyword.toLowerCase();
188
+ const keywordLen = keywordLower.length;
189
+ const words = text.match(/[A-Za-z0-9_]+|[\u4e00-\u9fff]+/g) ?? [];
190
+ for (const word of words) {
191
+ const wordLower = word.toLowerCase();
192
+ const wordLen = wordLower.length;
193
+ if (Math.abs(wordLen - keywordLen) > maxWordLenDiff)
194
+ continue;
195
+ const score = fuzzyMatchScore(keywordLower, wordLower, threshold);
196
+ if (score > 0) {
197
+ matches.push([word, score]);
198
+ }
199
+ }
200
+ return matches.sort((a, b) => b[1] - a[1]);
201
+ }
202
+ // =============================================================================
203
+ // Smart Cube Detection
204
+ // =============================================================================
205
+ export function detectCubeFromPath(projectPath) {
206
+ const targetPath = projectPath ?? process.cwd();
207
+ // Normalize: handle Windows backslashes
208
+ const normalized = targetPath.replace(/\\/g, "/");
209
+ // Extract folder name (last segment)
210
+ const segments = normalized.replace(/\/$/, "").split("/");
211
+ let folderName = segments[segments.length - 1] ?? "";
212
+ // Windows drive handling: "G:" → empty, take next
213
+ if (/^[A-Za-z]:$/.test(folderName)) {
214
+ folderName = segments[segments.length - 2] ?? "default";
215
+ }
216
+ if (!folderName)
217
+ return "default_cube";
218
+ // Normalize: lowercase, replace -, ., space with _
219
+ let cubeId = folderName.toLowerCase();
220
+ cubeId = cubeId.replace(/[-.\s]+/g, "_");
221
+ cubeId = cubeId.replace(/[^a-z0-9_]/g, "");
222
+ // Ensure doesn't start with digit
223
+ if (cubeId && /^\d/.test(cubeId)) {
224
+ cubeId = "_" + cubeId;
225
+ }
226
+ if (!cubeId.endsWith("_cube")) {
227
+ cubeId = cubeId + "_cube";
228
+ }
229
+ return cubeId || "default_cube";
230
+ }
231
+ // =============================================================================
232
+ // Enhanced Keyword Extraction and Scoring
233
+ // =============================================================================
234
+ export function extractKeywordsEnhanced(query, stopwords = ALL_STOPWORDS, minLength = 2) {
235
+ if (!query)
236
+ return [];
237
+ const rawTokens = query.match(/[A-Za-z0-9_]+|[\u4e00-\u9fff]+/g) ?? [];
238
+ const keywords = [];
239
+ const seen = new Set();
240
+ for (const token of rawTokens) {
241
+ if (!token)
242
+ continue;
243
+ // Chinese token
244
+ if (/[\u4e00-\u9fff]/.test(token)) {
245
+ if (token.length < minLength)
246
+ continue;
247
+ if (stopwords.has(token))
248
+ continue;
249
+ if (!seen.has(token)) {
250
+ keywords.push(token);
251
+ seen.add(token);
252
+ }
253
+ continue;
254
+ }
255
+ // English token
256
+ const lowered = token.toLowerCase();
257
+ if (lowered.length < minLength)
258
+ continue;
259
+ if (stopwords.has(lowered))
260
+ continue;
261
+ if (!seen.has(lowered)) {
262
+ keywords.push(lowered);
263
+ seen.add(lowered);
264
+ }
265
+ }
266
+ return keywords;
267
+ }
268
+ export function keywordMatchScoreEnhanced(text, keywords, metadata, enableFuzzy = true, fuzzyThreshold = 0.75) {
269
+ if (!text || keywords.length === 0)
270
+ return 0.0;
271
+ const textLower = text.toLowerCase();
272
+ let score = 0.0;
273
+ let matchedCount = 0;
274
+ const keyField = metadata ? String(metadata.key ?? "").toLowerCase() : "";
275
+ const rawTags = metadata?.tags;
276
+ const tags = Array.isArray(rawTags)
277
+ ? rawTags.map((t) => String(t).toLowerCase())
278
+ : [];
279
+ for (const kw of keywords) {
280
+ const kwLower = kw.toLowerCase();
281
+ const isChinese = /[\u4e00-\u9fff]/.test(kw);
282
+ let kwMatched = false;
283
+ // 1. Key field match (highest weight)
284
+ if (keyField && (keyField.includes(kwLower) || (isChinese && keyField.includes(kw)))) {
285
+ score += 5.0;
286
+ kwMatched = true;
287
+ }
288
+ // 2. Tags match
289
+ for (const tag of tags) {
290
+ if (tag.includes(kwLower) || (isChinese && tag.includes(kw))) {
291
+ score += 3.0;
292
+ kwMatched = true;
293
+ break;
294
+ }
295
+ }
296
+ // 3. Text match
297
+ if (isChinese) {
298
+ if (text.includes(kw)) {
299
+ score += 2.0;
300
+ kwMatched = true;
301
+ }
302
+ }
303
+ else {
304
+ const wordBoundaryRegex = new RegExp(`\\b${escapeRegex(kwLower)}\\b`);
305
+ if (wordBoundaryRegex.test(textLower)) {
306
+ score += 2.5;
307
+ kwMatched = true;
308
+ }
309
+ else if (textLower.includes(kwLower)) {
310
+ score += 1.5;
311
+ kwMatched = true;
312
+ }
313
+ }
314
+ // 4. Fuzzy match
315
+ if (enableFuzzy && !kwMatched && !isChinese) {
316
+ const fuzzyMatches = findFuzzyMatches(kwLower, textLower, fuzzyThreshold);
317
+ if (fuzzyMatches.length > 0) {
318
+ score += fuzzyMatches[0][1] * 1.0;
319
+ kwMatched = true;
320
+ }
321
+ }
322
+ if (kwMatched)
323
+ matchedCount++;
324
+ }
325
+ // Coverage bonus
326
+ if (matchedCount > 0) {
327
+ const coverage = matchedCount / keywords.length;
328
+ score += coverage * 1.5;
329
+ }
330
+ return score;
331
+ }
332
+ function escapeRegex(str) {
333
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
334
+ }
335
+ //# sourceMappingURL=keyword-enhancer.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Memory Analysis Module
3
+ *
4
+ * Generates search suggestions based on context.
5
+ */
6
+ export declare function suggestSearchQueries(context: string): string[];
7
+ //# sourceMappingURL=memory-analysis.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Memory Analysis Module
3
+ *
4
+ * Generates search suggestions based on context.
5
+ */
6
+ import { extractKeywords } from "./query-processing.js";
7
+ const MEMORY_TYPE_KEYWORDS = {
8
+ ERROR_PATTERN: ["error", "bug", "exception", "fail", "crash", "traceback", "错误", "异常", "失败"],
9
+ DECISION: ["decision", "decided", "choose", "选择", "决定", "方案", "架构"],
10
+ BUGFIX: ["fix", "fixed", "patch", "repair", "修复", "解决"],
11
+ GOTCHA: ["gotcha", "trap", "caveat", "workaround", "注意", "陷阱", "坑"],
12
+ CODE_PATTERN: ["pattern", "template", "snippet", "example", "模板", "示例"],
13
+ CONFIG: ["config", "configuration", "setting", "env", "配置", "环境"],
14
+ MILESTONE: ["milestone", "completed", "released", "shipped", "完成", "里程碑"],
15
+ FEATURE: ["feature", "added", "implemented", "new", "新功能", "实现"],
16
+ };
17
+ export function suggestSearchQueries(context) {
18
+ if (!context)
19
+ return [];
20
+ const suggestions = [];
21
+ const contextLower = context.toLowerCase();
22
+ // Check for memory type keywords
23
+ for (const [memType, keywords] of Object.entries(MEMORY_TYPE_KEYWORDS)) {
24
+ if (keywords.some((kw) => contextLower.includes(kw))) {
25
+ const extracted = extractKeywords(context);
26
+ if (extracted.length > 0) {
27
+ suggestions.push(`${memType} ${extracted.slice(0, 2).join(" ")}`);
28
+ }
29
+ else {
30
+ suggestions.push(memType);
31
+ }
32
+ }
33
+ }
34
+ // Add generic keyword-based suggestions
35
+ const keywords = extractKeywords(context).slice(0, 3);
36
+ if (keywords.length > 0 && suggestions.length === 0) {
37
+ suggestions.push(keywords.join(" "));
38
+ if (keywords.length > 1) {
39
+ suggestions.push(keywords[0]);
40
+ }
41
+ }
42
+ // Remove duplicates and limit to 5
43
+ return [...new Set(suggestions)].slice(0, 5);
44
+ }
45
+ //# sourceMappingURL=memory-analysis.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Layered Memory Models + Compaction Logic
3
+ */
4
+ import type { MemoryNode, MemoryMinimal, MemoryBrief, MemoryFull } from "./types.js";
5
+ export declare const COMPACTION_THRESHOLD = 15;
6
+ export declare const PREVIEW_COUNT = 5;
7
+ export declare function shouldCompact(count: number): boolean;
8
+ export declare function getTypeIcon(memoryType: string): string;
9
+ export declare function toMinimal(memory: MemoryNode | Record<string, unknown>): MemoryMinimal;
10
+ export declare function toBrief(memory: MemoryNode | Record<string, unknown>): MemoryBrief;
11
+ export declare function toFull(memory: MemoryNode | Record<string, unknown>, cubeId?: string, userId?: string): MemoryFull;
12
+ export interface CompactedSearchResultData {
13
+ preview: MemoryMinimal[];
14
+ totalCount: number;
15
+ omittedCount: number;
16
+ message: string;
17
+ query: string;
18
+ cubeId: string;
19
+ }
20
+ export declare function compactedResultToText(result: CompactedSearchResultData): string;
21
+ //# sourceMappingURL=models.d.ts.map
package/dist/models.js ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Layered Memory Models + Compaction Logic
3
+ */
4
+ import { extractMcpType } from "./query-processing.js";
5
+ // ============================================================================
6
+ // Compaction Configuration
7
+ // ============================================================================
8
+ export const COMPACTION_THRESHOLD = 15;
9
+ export const PREVIEW_COUNT = 5;
10
+ export function shouldCompact(count) {
11
+ return count > COMPACTION_THRESHOLD;
12
+ }
13
+ // ============================================================================
14
+ // Type Icon
15
+ // ============================================================================
16
+ export function getTypeIcon(memoryType) {
17
+ const icons = {
18
+ ERROR_PATTERN: "🔴",
19
+ BUGFIX: "🐛",
20
+ DECISION: "📋",
21
+ GOTCHA: "⚠️",
22
+ CODE_PATTERN: "📝",
23
+ CONFIG: "⚙️",
24
+ FEATURE: "✨",
25
+ MILESTONE: "🎯",
26
+ PROGRESS: "📊",
27
+ INFERRED: "🔗",
28
+ };
29
+ return icons[memoryType] ?? "📌";
30
+ }
31
+ // ============================================================================
32
+ // Conversion Utilities
33
+ // ============================================================================
34
+ export function toMinimal(memory) {
35
+ const memoryType = extractMcpType(memory);
36
+ const content = memory.memory ?? memory.content ?? "";
37
+ const cleanContent = content.replace(/^\[[A-Z_]+\]\s*/, "");
38
+ const lines = cleanContent.split("\n").filter((l) => l.trim());
39
+ const firstLine = lines[0] ?? cleanContent;
40
+ const summary = firstLine.length > 100 ? firstLine.slice(0, 100) + "..." : firstLine;
41
+ const meta = memory.metadata ?? {};
42
+ const created = memory.updated_at ??
43
+ memory.created_at ??
44
+ meta.updated_at ??
45
+ meta.created_at;
46
+ return {
47
+ id: memory.id ?? "",
48
+ memoryType,
49
+ summary,
50
+ createdAt: created,
51
+ };
52
+ }
53
+ export function toBrief(memory) {
54
+ const minimal = toMinimal(memory);
55
+ const meta = memory.metadata ?? {};
56
+ const rawTags = memory.tags ?? meta.tags ?? [];
57
+ return {
58
+ ...minimal,
59
+ key: memory.key ?? meta.key,
60
+ tags: Array.isArray(rawTags) ? rawTags.map(String) : [],
61
+ relevance: meta.relativity ?? 1.0,
62
+ };
63
+ }
64
+ export function toFull(memory, cubeId = "", userId = "") {
65
+ const brief = toBrief(memory);
66
+ const meta = memory.metadata ?? {};
67
+ const content = memory.memory ?? memory.content ?? "";
68
+ const cleanContent = content.replace(/^\[[A-Z_]+\]\s*/, "");
69
+ return {
70
+ ...brief,
71
+ content: cleanContent,
72
+ background: memory.background ?? meta.background,
73
+ cubeId,
74
+ userId,
75
+ relations: memory.relations ?? [],
76
+ };
77
+ }
78
+ export function compactedResultToText(result) {
79
+ const lines = [
80
+ "## 🔍 Search Results (Compacted)",
81
+ "",
82
+ `**Query**: \`${result.query}\``,
83
+ `**Total**: ${result.totalCount} memories found`,
84
+ `**Showing**: Top ${result.preview.length} (omitted ${result.omittedCount})`,
85
+ "",
86
+ "### Preview",
87
+ "",
88
+ ];
89
+ for (let i = 0; i < result.preview.length; i++) {
90
+ const mem = result.preview[i];
91
+ const icon = getTypeIcon(mem.memoryType);
92
+ lines.push(`${i + 1}. ${icon} **[${mem.memoryType}]** ${mem.summary}`);
93
+ lines.push(` ID: \`${mem.id}\``);
94
+ lines.push("");
95
+ }
96
+ lines.push("---");
97
+ lines.push("");
98
+ lines.push('💡 **Tip**: Use `memos_get(memory_id="<id>")` to get full details of a specific memory.');
99
+ return lines.join("\n");
100
+ }
101
+ //# sourceMappingURL=models.js.map