@lotargo/memory_plugin 1.6.1 → 1.6.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/CHANGELOG.md +42 -1
- package/README.md +59 -20
- package/mcp-server/cli/direct_commands.js +10 -1
- package/mcp-server/cli.js +2 -1
- package/mcp-server/codex_config.js +171 -0
- package/mcp-server/codex_diagnostics.js +263 -0
- package/mcp-server/config/config_manager.js +1 -0
- package/mcp-server/db/migrations.js +15 -0
- package/mcp-server/index.js +4 -2
- package/mcp-server/ingest/chunker.js +179 -12
- package/mcp-server/ingest/pipeline.js +3 -3
- package/mcp-server/prompt_manager.js +221 -218
- package/mcp-server/retrieval/retriever.js +142 -12
- package/mcp-server/setup.js +37 -21
- package/mcp-server/tools/rag_tools.js +344 -283
- package/package.json +8 -4
- package/skills/using-memory/SKILL.md +11 -1
|
@@ -138,6 +138,21 @@ const MIGRATIONS = [
|
|
|
138
138
|
`);
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
version: 5,
|
|
143
|
+
name: "005_retrieval_policy",
|
|
144
|
+
up: async (db) => {
|
|
145
|
+
try {
|
|
146
|
+
await db.exec(`ALTER TABLE micro_chunks ADD COLUMN retrieval_policy TEXT DEFAULT 'micro_chunk';`);
|
|
147
|
+
} catch (e) {}
|
|
148
|
+
try {
|
|
149
|
+
await db.exec(`ALTER TABLE micro_chunks ADD COLUMN policy_source_id TEXT;`);
|
|
150
|
+
} catch (e) {}
|
|
151
|
+
await db.exec(`
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_micro_chunks_retrieval_policy ON micro_chunks(retrieval_policy);
|
|
153
|
+
`);
|
|
154
|
+
},
|
|
155
|
+
},
|
|
141
156
|
];
|
|
142
157
|
|
|
143
158
|
export async function runMigrations(db) {
|
package/mcp-server/index.js
CHANGED
|
@@ -36,7 +36,8 @@ const CLI_COMMANDS = new Set([
|
|
|
36
36
|
"identity",
|
|
37
37
|
"migrate_titles",
|
|
38
38
|
"enable-prompt",
|
|
39
|
-
"disable-prompt",
|
|
39
|
+
"disable-prompt",
|
|
40
|
+
"doctor",
|
|
40
41
|
]);
|
|
41
42
|
|
|
42
43
|
function printUsage() {
|
|
@@ -51,7 +52,8 @@ Usage:
|
|
|
51
52
|
memory_plugin auth-status
|
|
52
53
|
memory_plugin link|unlink|relink|identity [--dir <path>] [--remote <url>]
|
|
53
54
|
memory_plugin migrate_titles [--key <key>]
|
|
54
|
-
memory_plugin enable-prompt | disable-prompt
|
|
55
|
+
memory_plugin enable-prompt | disable-prompt
|
|
56
|
+
memory_plugin doctor --codex
|
|
55
57
|
|
|
56
58
|
Options:
|
|
57
59
|
-h, --help Show this help text
|
|
@@ -5,6 +5,159 @@ export function estimateTokens(text) {
|
|
|
5
5
|
return Math.ceil(text.length / 4);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const CODE_SIGNATURE_REGEX = /^\s*(?:export\s+|async\s+)?(?:function|class|def|pub\s+fn|fn|struct|interface|enum)\s+/;
|
|
9
|
+
|
|
10
|
+
function _classifyLine(line) {
|
|
11
|
+
const t = line.trimStart();
|
|
12
|
+
if (t.startsWith("/**")) return "jsdoc_start";
|
|
13
|
+
if (t.startsWith("*/")) return "jsdoc_end";
|
|
14
|
+
if (t.startsWith("*")) return "jsdoc_mid";
|
|
15
|
+
if (t.startsWith("//")) return "line_comment";
|
|
16
|
+
if (t.startsWith("#")) return "hash_comment";
|
|
17
|
+
if (t.startsWith("'''") || t.startsWith('"""')) return "py_docstring";
|
|
18
|
+
return "code";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function extractCodeSignatures(codeContent) {
|
|
22
|
+
const lines = codeContent.split("\n");
|
|
23
|
+
const signatures = [];
|
|
24
|
+
|
|
25
|
+
const fenceMatch = lines[0] && lines[0].match(/^(\s*)(```|~~~)/);
|
|
26
|
+
const bodyStart = fenceMatch ? 1 : 0
|
|
27
|
+
const bodyEnd = fenceMatch && (lines[lines.length - 1].startsWith("```") || lines[lines.length - 1].startsWith("~~~")) ? lines.length - 1 : lines.length;
|
|
28
|
+
const bodyLines = lines.slice(bodyStart, bodyEnd);
|
|
29
|
+
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < bodyLines.length) {
|
|
32
|
+
const line = bodyLines[i];
|
|
33
|
+
const type = _classifyLine(line);
|
|
34
|
+
|
|
35
|
+
if (type === "jsdoc_start") {
|
|
36
|
+
const jsdocBlock = [line];
|
|
37
|
+
let j = i + 1;
|
|
38
|
+
while (j < bodyLines.length) {
|
|
39
|
+
jsdocBlock.push(bodyLines[j]);
|
|
40
|
+
if (_classifyLine(bodyLines[j]) === "jsdoc_end") {
|
|
41
|
+
j++;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
j++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (j < bodyLines.length && CODE_SIGNATURE_REGEX.test(bodyLines[j])) {
|
|
48
|
+
const sigLines = [bodyLines[j]];
|
|
49
|
+
const pyDocResult = _tryPyDocstring(bodyLines, j + 1);
|
|
50
|
+
if (pyDocResult.docLines.length > 0) {
|
|
51
|
+
sigLines.push(...pyDocResult.docLines);
|
|
52
|
+
}
|
|
53
|
+
const endIdx = pyDocResult.docLines.length > 0 ? pyDocResult.endIdx : j;
|
|
54
|
+
|
|
55
|
+
signatures.push({
|
|
56
|
+
signature: [...jsdocBlock, ...sigLines].join("\n").trim(),
|
|
57
|
+
line_number: j + bodyStart + 1,
|
|
58
|
+
});
|
|
59
|
+
i = endIdx + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
i = j;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (type === "line_comment" || type === "hash_comment") {
|
|
68
|
+
const commentBlock = [line];
|
|
69
|
+
let j = i + 1;
|
|
70
|
+
while (j < bodyLines.length && _classifyLine(bodyLines[j]) === type) {
|
|
71
|
+
commentBlock.push(bodyLines[j]);
|
|
72
|
+
j++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (j < bodyLines.length && CODE_SIGNATURE_REGEX.test(bodyLines[j])) {
|
|
76
|
+
const sigLines = [bodyLines[j]];
|
|
77
|
+
const pyDocResult = _tryPyDocstring(bodyLines, j + 1);
|
|
78
|
+
if (pyDocResult.docLines.length > 0) {
|
|
79
|
+
sigLines.push(...pyDocResult.docLines);
|
|
80
|
+
}
|
|
81
|
+
const endIdx = pyDocResult.docLines.length > 0 ? pyDocResult.endIdx : j;
|
|
82
|
+
|
|
83
|
+
signatures.push({
|
|
84
|
+
signature: [...commentBlock, ...sigLines].join("\n").trim(),
|
|
85
|
+
line_number: j + bodyStart + 1,
|
|
86
|
+
});
|
|
87
|
+
i = endIdx + 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
i = j;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (CODE_SIGNATURE_REGEX.test(line)) {
|
|
96
|
+
const sigLines = [line];
|
|
97
|
+
const pyDocResult = _tryPyDocstring(bodyLines, i + 1);
|
|
98
|
+
if (pyDocResult.docLines.length > 0) {
|
|
99
|
+
sigLines.push(...pyDocResult.docLines);
|
|
100
|
+
}
|
|
101
|
+
const endIdx = pyDocResult.docLines.length > 0 ? pyDocResult.endIdx : i;
|
|
102
|
+
|
|
103
|
+
signatures.push({
|
|
104
|
+
signature: sigLines.join("\n").trim(),
|
|
105
|
+
line_number: i + bodyStart + 1,
|
|
106
|
+
});
|
|
107
|
+
i = endIdx + 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return signatures;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _tryPyDocstring(bodyLines, startIdx) {
|
|
118
|
+
let k = startIdx;
|
|
119
|
+
while (k < bodyLines.length && bodyLines[k].trim() === "") k++;
|
|
120
|
+
if (k >= bodyLines.length) return { docLines: [], endIdx: startIdx - 1 };
|
|
121
|
+
|
|
122
|
+
const line = bodyLines[k];
|
|
123
|
+
const tripleDouble = /^\s*"""/.test(line);
|
|
124
|
+
const tripleSingle = /^\s*'''/.test(line);
|
|
125
|
+
const marker = tripleDouble ? '"""' : tripleSingle ? "'''" : null;
|
|
126
|
+
if (!marker) return { docLines: [], endIdx: startIdx - 1 };
|
|
127
|
+
|
|
128
|
+
const docLines = [line];
|
|
129
|
+
if (line.includes(marker.repeat(2)) && line.indexOf(marker) !== line.lastIndexOf(marker)) {
|
|
130
|
+
return { docLines, endIdx: k };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (let m = k + 1; m < bodyLines.length; m++) {
|
|
134
|
+
docLines.push(bodyLines[m]);
|
|
135
|
+
if (bodyLines[m].includes(marker)) {
|
|
136
|
+
return { docLines, endIdx: m };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { docLines, endIdx: k };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function generateTableSummary(tableContent, breadcrumbs = "") {
|
|
143
|
+
const lines = tableContent.split("\n").filter((l) => l.trim().length > 0);
|
|
144
|
+
if (lines.length === 0) return null;
|
|
145
|
+
|
|
146
|
+
const headerLine = lines[0];
|
|
147
|
+
const columns = headerLine
|
|
148
|
+
.split("|")
|
|
149
|
+
.map((c) => c.trim())
|
|
150
|
+
.filter((c) => c.length > 0);
|
|
151
|
+
|
|
152
|
+
const separatorLine = lines[1] || "";
|
|
153
|
+
const hasSeparator = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(separatorLine);
|
|
154
|
+
const dataLines = hasSeparator ? lines.slice(2) : lines.slice(1);
|
|
155
|
+
const rowCount = dataLines.length;
|
|
156
|
+
|
|
157
|
+
const contextPart = breadcrumbs ? ` Context: ${breadcrumbs}.` : "";
|
|
158
|
+
return `Table with columns [${columns.join(", ")}] containing ${rowCount} row${rowCount !== 1 ? "s" : ""}.${contextPart}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
8
161
|
// 1. BIG LEVEL: Heading & Section Hierarchy Parser
|
|
9
162
|
export function parseSections(markdown, docTitle = "Document") {
|
|
10
163
|
const lines = markdown.split("\n");
|
|
@@ -72,7 +225,7 @@ export function extractMediumBlocks(section, sectionId, docId) {
|
|
|
72
225
|
let blockIndex = 0;
|
|
73
226
|
|
|
74
227
|
let currentLines = [];
|
|
75
|
-
let currentBlockType = "paragraph";
|
|
228
|
+
let currentBlockType = "paragraph";
|
|
76
229
|
|
|
77
230
|
function pushCurrentBlock() {
|
|
78
231
|
const blockContent = currentLines.join("\n").trim();
|
|
@@ -97,7 +250,6 @@ export function extractMediumBlocks(section, sectionId, docId) {
|
|
|
97
250
|
for (let i = 0; i < lines.length; i++) {
|
|
98
251
|
const line = lines[i];
|
|
99
252
|
|
|
100
|
-
// Check code fence
|
|
101
253
|
const fenceMatch = line.match(/^(\s*)(```|~~~)/);
|
|
102
254
|
if (fenceMatch) {
|
|
103
255
|
if (!inFencedCode) {
|
|
@@ -121,7 +273,6 @@ export function extractMediumBlocks(section, sectionId, docId) {
|
|
|
121
273
|
continue;
|
|
122
274
|
}
|
|
123
275
|
|
|
124
|
-
// Check table line
|
|
125
276
|
const isTableLine = /^\s*\|.*\|\s*$/.test(line);
|
|
126
277
|
if (isTableLine) {
|
|
127
278
|
if (currentBlockType !== "table" && currentLines.length > 0) {
|
|
@@ -134,7 +285,6 @@ export function extractMediumBlocks(section, sectionId, docId) {
|
|
|
134
285
|
pushCurrentBlock();
|
|
135
286
|
}
|
|
136
287
|
|
|
137
|
-
// Check list item line
|
|
138
288
|
const isListLine = /^\s*([*+-]|\d+\.)\s+/.test(line);
|
|
139
289
|
if (isListLine) {
|
|
140
290
|
if (currentBlockType !== "list" && currentBlockType !== "paragraph" && currentLines.length > 0) {
|
|
@@ -145,7 +295,6 @@ export function extractMediumBlocks(section, sectionId, docId) {
|
|
|
145
295
|
continue;
|
|
146
296
|
}
|
|
147
297
|
|
|
148
|
-
// Check empty line
|
|
149
298
|
if (line.trim().length === 0) {
|
|
150
299
|
if (currentLines.length > 0) {
|
|
151
300
|
pushCurrentBlock();
|
|
@@ -169,6 +318,7 @@ export function createSmallChunks(mediumBlock, sectionId, docId) {
|
|
|
169
318
|
|
|
170
319
|
function makeChunk(chunkText, extraMeta = {}) {
|
|
171
320
|
if (!chunkText || chunkText.trim().length === 0) return;
|
|
321
|
+
const { retrieval_policy, policy_source_id, ...rest } = extraMeta;
|
|
172
322
|
smallChunks.push({
|
|
173
323
|
id: `${mediumBlock.id}_s${smallIdx++}`,
|
|
174
324
|
medium_id: mediumBlock.id,
|
|
@@ -177,14 +327,24 @@ export function createSmallChunks(mediumBlock, sectionId, docId) {
|
|
|
177
327
|
content: chunkText.trim(),
|
|
178
328
|
breadcrumbs: mediumBlock.breadcrumbs,
|
|
179
329
|
token_count: estimateTokens(chunkText),
|
|
180
|
-
|
|
330
|
+
retrieval_policy: retrieval_policy || "micro_chunk",
|
|
331
|
+
policy_source_id: policy_source_id || null,
|
|
332
|
+
...rest,
|
|
181
333
|
});
|
|
182
334
|
}
|
|
183
335
|
|
|
184
336
|
// RULE FOR TABLES
|
|
185
337
|
if (mediumBlock.block_type === "table") {
|
|
338
|
+
const summary = generateTableSummary(content, mediumBlock.breadcrumbs);
|
|
339
|
+
if (summary) {
|
|
340
|
+
makeChunk(summary, {
|
|
341
|
+
retrieval_policy: "table_summary",
|
|
342
|
+
policy_source_id: mediumBlock.id,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
186
346
|
if (tokenCount <= 350) {
|
|
187
|
-
makeChunk(content);
|
|
347
|
+
makeChunk(content, { retrieval_policy: "micro_chunk" });
|
|
188
348
|
return smallChunks;
|
|
189
349
|
}
|
|
190
350
|
|
|
@@ -205,15 +365,23 @@ export function createSmallChunks(mediumBlock, sectionId, docId) {
|
|
|
205
365
|
for (let i = 0; i < dataLines.length; i += chunkSize) {
|
|
206
366
|
const rowBatch = dataLines.slice(i, i + chunkSize);
|
|
207
367
|
const tableChunkText = `${headerStr}\n${rowBatch.join("\n")}`;
|
|
208
|
-
makeChunk(tableChunkText);
|
|
368
|
+
makeChunk(tableChunkText, { retrieval_policy: "micro_chunk" });
|
|
209
369
|
}
|
|
210
370
|
return smallChunks;
|
|
211
371
|
}
|
|
212
372
|
|
|
213
373
|
// RULE FOR CODE BLOCKS
|
|
214
374
|
if (mediumBlock.block_type === "code") {
|
|
375
|
+
const signatures = extractCodeSignatures(content);
|
|
376
|
+
for (const sig of signatures) {
|
|
377
|
+
makeChunk(sig.signature, {
|
|
378
|
+
retrieval_policy: "code_signature",
|
|
379
|
+
policy_source_id: mediumBlock.id,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
215
383
|
if (tokenCount <= 350) {
|
|
216
|
-
makeChunk(content);
|
|
384
|
+
makeChunk(content, { retrieval_policy: "micro_chunk" });
|
|
217
385
|
return smallChunks;
|
|
218
386
|
}
|
|
219
387
|
|
|
@@ -243,7 +411,7 @@ export function createSmallChunks(mediumBlock, sectionId, docId) {
|
|
|
243
411
|
|
|
244
412
|
for (const block of astBlocks) {
|
|
245
413
|
const fullChunk = fenceHeader ? `${fenceHeader}\n${block}\n${fenceFooter}` : block;
|
|
246
|
-
makeChunk(fullChunk);
|
|
414
|
+
makeChunk(fullChunk, { retrieval_policy: "micro_chunk" });
|
|
247
415
|
}
|
|
248
416
|
return smallChunks;
|
|
249
417
|
}
|
|
@@ -265,8 +433,7 @@ export function createSmallChunks(mediumBlock, sectionId, docId) {
|
|
|
265
433
|
|
|
266
434
|
if (currentTokens + sTokens > TARGET_WINDOW_TOKENS && currentWindow.length > 0) {
|
|
267
435
|
makeChunk(currentWindow.join(" "));
|
|
268
|
-
|
|
269
|
-
// Safe Overlap: Keep the last sentence of the previous window if available
|
|
436
|
+
|
|
270
437
|
const lastSentence = currentWindow[currentWindow.length - 1];
|
|
271
438
|
currentWindow = [lastSentence, sentence];
|
|
272
439
|
currentTokens = estimateTokens(lastSentence) + sTokens;
|
|
@@ -129,8 +129,8 @@ export async function ingestDocument({
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const insertMicroStmt = db.prepare(`
|
|
132
|
-
INSERT INTO micro_chunks (id, section_id, doc_id, content, vector, token_count, medium_id)
|
|
133
|
-
VALUES (?, ?, ?, ?, ?, ?, ?);
|
|
132
|
+
INSERT INTO micro_chunks (id, section_id, doc_id, content, vector, token_count, medium_id, retrieval_policy, policy_source_id)
|
|
133
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
134
134
|
`);
|
|
135
135
|
const insertFtsStmt = db.prepare(`
|
|
136
136
|
INSERT INTO micro_chunks_fts (id, content, breadcrumbs)
|
|
@@ -138,7 +138,7 @@ export async function ingestDocument({
|
|
|
138
138
|
`);
|
|
139
139
|
|
|
140
140
|
for (const micro of hierarchy.microChunks) {
|
|
141
|
-
await insertMicroStmt.run(micro.id, micro.section_id, micro.doc_id, micro.content, micro.vector, micro.token_count, micro.medium_id || null);
|
|
141
|
+
await insertMicroStmt.run(micro.id, micro.section_id, micro.doc_id, micro.content, micro.vector, micro.token_count, micro.medium_id || null, micro.retrieval_policy || "micro_chunk", micro.policy_source_id || null);
|
|
142
142
|
await insertFtsStmt.run(micro.id, micro.content, micro.breadcrumbs);
|
|
143
143
|
}
|
|
144
144
|
|