@lotargo/memory_plugin 1.3.0 → 1.3.1
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { MEMORY_DIR,
|
|
3
|
+
import { MEMORY_DIR, ensureDirSync } from "../memory.js";
|
|
4
4
|
|
|
5
5
|
const CONFIG_FILE = path.join(MEMORY_DIR, "config.json");
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ export function getConfig() {
|
|
|
23
23
|
return cachedConfig;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
ensureDirSync();
|
|
27
27
|
|
|
28
28
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
29
29
|
try {
|
|
@@ -42,7 +42,7 @@ export function getConfig() {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export function saveConfig(newConfig) {
|
|
45
|
-
|
|
45
|
+
ensureDirSync();
|
|
46
46
|
cachedConfig = Object.freeze({ ...DEFAULT_CONFIG, ...newConfig });
|
|
47
47
|
try {
|
|
48
48
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cachedConfig, null, 2), "utf-8");
|
package/mcp-server/index.js
CHANGED
|
@@ -453,12 +453,13 @@ server.registerTool(
|
|
|
453
453
|
description:
|
|
454
454
|
"Ingest a document into the RAG knowledge base. " +
|
|
455
455
|
"Accepts local file paths, web URLs, or raw Markdown/text content. " +
|
|
456
|
+
"For type='file' the file is read from disk and indexed with a code-block wrapper. " +
|
|
456
457
|
"For type='url' the page is fetched and its content is indexed (not just the URL). " +
|
|
457
458
|
"Processes document through 3-tier hierarchy chunking (Big/Medium/Small), " +
|
|
458
459
|
"computes dense vectors, and extracts GraphRAG code symbols.",
|
|
459
460
|
inputSchema: z.object({
|
|
460
|
-
content: z.string().describe("Raw text content, file path, or web URL"),
|
|
461
|
-
type: z.enum(["text", "file", "url"]).nullish().transform((v) => v || "text").describe("Input content type: 'text', 'file', or 'url' (
|
|
461
|
+
content: z.string().describe("Raw text content, file path, or web URL. For type='file' this can be the file path (reads from disk) or the file content directly"),
|
|
462
|
+
type: z.enum(["text", "file", "url"]).nullish().transform((v) => v || "text").describe("Input content type: 'text' (raw content), 'file' (reads from disk, wraps in code block), or 'url' (fetches page content)"),
|
|
462
463
|
title: optStr().describe("Document title"),
|
|
463
464
|
path: optStr().describe("Original document file path"),
|
|
464
465
|
generateEmbeddings: defBool(true).describe("Compute dense vector embeddings"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { getDatabase, BLOBS_DIR } from "../db/database.js";
|
|
3
4
|
import { saveBlob, deleteBlob } from "../storage/blob_store.js";
|
|
4
5
|
import { normalizeContent, fetchUrlContent } from "./normalizer.js";
|
|
@@ -28,6 +29,13 @@ export async function ingestDocument({
|
|
|
28
29
|
effectiveType = "text";
|
|
29
30
|
effectiveTitle = title || fetched.title;
|
|
30
31
|
effectivePath = path || fetched.finalUrl || content;
|
|
32
|
+
} else if (type === "file") {
|
|
33
|
+
const filePath = effectivePath || content;
|
|
34
|
+
const needsRead = !content || content === filePath;
|
|
35
|
+
if (needsRead && filePath) {
|
|
36
|
+
content = await readFile(filePath, "utf-8");
|
|
37
|
+
effectivePath = filePath;
|
|
38
|
+
}
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
const { markdown, title: docTitle, metadata } = normalizeContent({ content, type: effectiveType, path: effectivePath, title: effectiveTitle });
|
package/mcp-server/memory.js
CHANGED
|
@@ -1,192 +1,203 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join, basename, resolve } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
|
|
6
|
-
function resolveMemoryDir() {
|
|
7
|
-
if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
|
|
8
|
-
if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
|
|
9
|
-
|
|
10
|
-
const legacyDir = join(homedir(), ".config", "opencode", "memory");
|
|
11
|
-
if (existsSync(legacyDir)) {
|
|
12
|
-
return legacyDir;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (process.platform === "win32") {
|
|
16
|
-
const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
|
|
17
|
-
return join(appData, "opencode", "memory");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
-
return join(configHome, "opencode", "memory");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const MEMORY_DIR = resolveMemoryDir();
|
|
25
|
-
export const GLOBAL_KEY = "global";
|
|
26
|
-
|
|
27
|
-
export async function ensureDir() {
|
|
28
|
-
if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
|
|
29
|
-
const storageDir = join(MEMORY_DIR, "storage");
|
|
30
|
-
const blobsDir = join(storageDir, "blobs");
|
|
31
|
-
const modelsDir = join(storageDir, "models");
|
|
32
|
-
const exportsDir = join(MEMORY_DIR, "exports");
|
|
33
|
-
if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
|
|
34
|
-
if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
|
|
35
|
-
if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
import { readFile, writeFile, mkdir, unlink, readdir } from "fs/promises";
|
|
2
|
+
import { existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { join, basename, resolve } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
function resolveMemoryDir() {
|
|
7
|
+
if (process.env.MEMORY_DIR) return process.env.MEMORY_DIR;
|
|
8
|
+
if (process.env.OPENCODE_CONFIG_DIR) return join(process.env.OPENCODE_CONFIG_DIR, "memory");
|
|
9
|
+
|
|
10
|
+
const legacyDir = join(homedir(), ".config", "opencode", "memory");
|
|
11
|
+
if (existsSync(legacyDir)) {
|
|
12
|
+
return legacyDir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (process.platform === "win32") {
|
|
16
|
+
const appData = process.env.LOCALAPPDATA || process.env.APPDATA || join(homedir(), "AppData", "Local");
|
|
17
|
+
return join(appData, "opencode", "memory");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
+
return join(configHome, "opencode", "memory");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const MEMORY_DIR = resolveMemoryDir();
|
|
25
|
+
export const GLOBAL_KEY = "global";
|
|
26
|
+
|
|
27
|
+
export async function ensureDir() {
|
|
28
|
+
if (!existsSync(MEMORY_DIR)) await mkdir(MEMORY_DIR, { recursive: true });
|
|
29
|
+
const storageDir = join(MEMORY_DIR, "storage");
|
|
30
|
+
const blobsDir = join(storageDir, "blobs");
|
|
31
|
+
const modelsDir = join(storageDir, "models");
|
|
32
|
+
const exportsDir = join(MEMORY_DIR, "exports");
|
|
33
|
+
if (!existsSync(blobsDir)) await mkdir(blobsDir, { recursive: true });
|
|
34
|
+
if (!existsSync(modelsDir)) await mkdir(modelsDir, { recursive: true });
|
|
35
|
+
if (!existsSync(exportsDir)) await mkdir(exportsDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ensureDirSync() {
|
|
39
|
+
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
|
|
40
|
+
const storageDir = join(MEMORY_DIR, "storage");
|
|
41
|
+
const blobsDir = join(storageDir, "blobs");
|
|
42
|
+
const modelsDir = join(storageDir, "models");
|
|
43
|
+
const exportsDir = join(MEMORY_DIR, "exports");
|
|
44
|
+
if (!existsSync(blobsDir)) mkdirSync(blobsDir, { recursive: true });
|
|
45
|
+
if (!existsSync(modelsDir)) mkdirSync(modelsDir, { recursive: true });
|
|
46
|
+
if (!existsSync(exportsDir)) mkdirSync(exportsDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Canonical absolute path key: forward slashes, lowercase drive letter on win32.
|
|
50
|
+
export function canonicalPath(dir) {
|
|
51
|
+
let p = resolve(dir || process.cwd());
|
|
52
|
+
if (process.platform === "win32") {
|
|
53
|
+
p = p.replace(/\\/g, "/").replace(/^([a-zA-Z]):/, (_, d) => `${d.toLowerCase()}:`);
|
|
54
|
+
}
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Project store key = full directory path. This removes basename collisions and
|
|
59
|
+
// binds each store to the real project location.
|
|
60
|
+
export function projectKey(worktree, directory) {
|
|
61
|
+
return canonicalPath(worktree || directory);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Display label for a project (basename of the resolved directory).
|
|
65
|
+
export function projectName(worktree, directory) {
|
|
66
|
+
const dir = worktree || directory || process.cwd();
|
|
67
|
+
return dir ? basename(resolve(dir)) : "default";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function scopeKey(scope, worktree, directory) {
|
|
71
|
+
return scope === "global" ? GLOBAL_KEY : projectKey(worktree, directory);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function slugify(key) {
|
|
75
|
+
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function memoryPath(key) {
|
|
79
|
+
return join(MEMORY_DIR, `${slugify(key)}.md`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function memoryFileName(key) {
|
|
83
|
+
return basename(memoryPath(key));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function storeFilePath(key) {
|
|
87
|
+
return memoryPath(key);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseMeta(content) {
|
|
91
|
+
const m = content.match(/<!-- path: (.+?) -->/);
|
|
92
|
+
return { path: m ? m[1].trim() : null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isSimpleKey(key) {
|
|
96
|
+
return /^[a-zA-Z0-9_-]+$/.test(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Lazy migration: when reading a project path store that doesn't exist yet but a
|
|
100
|
+
// legacy <basename>.md store (without path binding) does, claim it under the path.
|
|
101
|
+
async function maybeMigrateLegacy(key) {
|
|
102
|
+
if (key === GLOBAL_KEY || isSimpleKey(key)) return null;
|
|
103
|
+
const legacyBasename = basename(key);
|
|
104
|
+
if (!legacyBasename) return null;
|
|
105
|
+
const legacyFp = join(MEMORY_DIR, `${legacyBasename}.md`);
|
|
106
|
+
if (slugify(key) === legacyBasename || !existsSync(legacyFp)) return null;
|
|
107
|
+
const content = await readFile(legacyFp, "utf-8");
|
|
108
|
+
if (parseMeta(content).path) return null; // already bound to another project
|
|
109
|
+
// Collision guard: a different path with the same basename is already bound,
|
|
110
|
+
// so this legacy store is ambiguous and must not be silently claimed.
|
|
111
|
+
const files = await readdir(MEMORY_DIR).catch(() => []);
|
|
112
|
+
for (const f of files) {
|
|
113
|
+
if (!f.endsWith(".md") || f === `${legacyBasename}.md` || f === `${GLOBAL_KEY}.md`) continue;
|
|
114
|
+
try {
|
|
115
|
+
const other = parseMeta(await readFile(join(MEMORY_DIR, f), "utf-8")).path;
|
|
116
|
+
if (other && basename(other) === legacyBasename) return null;
|
|
117
|
+
} catch (e) {}
|
|
118
|
+
}
|
|
119
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
120
|
+
await writeMemory(key, facts);
|
|
121
|
+
try {
|
|
122
|
+
await unlink(legacyFp);
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
return facts;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function readMemory(key) {
|
|
128
|
+
const fp = memoryPath(key);
|
|
129
|
+
if (existsSync(fp)) {
|
|
130
|
+
const content = await readFile(fp, "utf-8");
|
|
131
|
+
return content.split("\n").filter((l) => l.startsWith("- ["));
|
|
132
|
+
}
|
|
133
|
+
const migrated = await maybeMigrateLegacy(key);
|
|
134
|
+
return migrated || [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function readMemoryRaw(key) {
|
|
138
|
+
return (await readMemory(key)).map((e) => e.slice(2));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function writeMemory(key, entries) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
if (key === GLOBAL_KEY) {
|
|
144
|
+
lines.push("# Global Memory", "");
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(`# Memory: ${basename(key) || key}`, "");
|
|
147
|
+
if (!isSimpleKey(key)) {
|
|
148
|
+
lines.push(`<!-- path: ${key} -->`, "");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const content = lines.join("\n") + "\n" + (entries.length ? entries.join("\n") + "\n" : "");
|
|
152
|
+
await writeFile(memoryPath(key), content);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function listProjectStores() {
|
|
156
|
+
const stores = [];
|
|
157
|
+
const files = await readdir(MEMORY_DIR).catch(() => []);
|
|
158
|
+
for (const f of files) {
|
|
159
|
+
if (!f.endsWith(".md") || f === `${GLOBAL_KEY}.md`) continue;
|
|
160
|
+
const fp = join(MEMORY_DIR, f);
|
|
161
|
+
let content = "";
|
|
162
|
+
try {
|
|
163
|
+
content = await readFile(fp, "utf-8");
|
|
164
|
+
} catch (e) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
168
|
+
const meta = parseMeta(content);
|
|
169
|
+
const key = meta.path || f.slice(0, -3);
|
|
170
|
+
stores.push({
|
|
171
|
+
key,
|
|
172
|
+
path: meta.path,
|
|
173
|
+
basename: basename(meta.path || key) || key,
|
|
174
|
+
file: f,
|
|
175
|
+
count: facts.length,
|
|
176
|
+
legacy: !meta.path,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
stores.sort((a, b) => a.basename.localeCompare(b.basename));
|
|
180
|
+
return stores;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Bind an unbound legacy store (e.g. "comfy-meta-viewer") to a directory path.
|
|
184
|
+
export async function migrateLegacyStore(legacyKey, targetDir) {
|
|
185
|
+
const legacyFp = join(MEMORY_DIR, `${legacyKey.replace(/[^a-zA-Z0-9_-]/g, "_")}.md`);
|
|
186
|
+
if (!existsSync(legacyFp)) return { ok: false, reason: "not_found", key: legacyKey };
|
|
187
|
+
const content = await readFile(legacyFp, "utf-8");
|
|
188
|
+
if (parseMeta(content).path) return { ok: false, reason: "already_bound", key: legacyKey };
|
|
189
|
+
const targetKey = projectKey(targetDir, null);
|
|
190
|
+
const facts = content.split("\n").filter((l) => l.startsWith("- ["));
|
|
191
|
+
await writeMemory(targetKey, facts);
|
|
192
|
+
try {
|
|
193
|
+
await unlink(legacyFp);
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
return { ok: true, key: targetKey, file: memoryPath(targetKey), facts: facts.length };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function today() {
|
|
199
|
+
const d = new Date();
|
|
200
|
+
const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
201
|
+
const time = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
202
|
+
return `${date} ${time}`;
|
|
203
|
+
}
|
|
@@ -388,10 +388,10 @@ export async function getReranker(modelName = "Xenova/bge-reranker-base", progre
|
|
|
388
388
|
return rerankerInstance;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
const cacheDir = ensureValidModelDirectory();
|
|
392
392
|
|
|
393
393
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
394
|
-
env.cacheDir =
|
|
394
|
+
env.cacheDir = cacheDir;
|
|
395
395
|
env.allowLocalModels = true;
|
|
396
396
|
env.allowRemoteModels = true;
|
|
397
397
|
env.remoteHost = "https://huggingface.co";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotargo/memory_plugin",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Persistent memory agent for coding AI tools — remembers user preferences and project context across sessions. Works with Antigravity, OpenCode, Claude Code, and Codex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "opencode-plugin/index.js",
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: using-memory
|
|
3
|
-
description: Comprehensive guide for using the Memory
|
|
3
|
+
description: Comprehensive guide for using the Memory, Hybrid RAG Knowledge Engine & MCP Helper tools (remember, recall, forget, update_fact, memory_info, link_knowledge, ingest_document, query_knowledge_base, manage_knowledge_base, list-mcp-tools, mcp-reminder). Trigger proactively whenever user preferences, project conventions, technology stack choices, or architecture decisions are introduced, or when querying ingested documentation, indexing files/repos, managing persistent knowledge, or looking up available MCP tool integrations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Using Memory
|
|
6
|
+
# Using Memory, Hybrid RAG Knowledge Engine & MCP Helper Tools
|
|
7
7
|
|
|
8
|
-
You have access to a persistent dual-layer memory engine supercharged with an **Agent-Driven Knowledge Graph
|
|
8
|
+
You have access to a persistent dual-layer memory engine supercharged with an **Agent-Driven Knowledge Graph** and general MCP integration helpers:
|
|
9
9
|
1. **Layer 1: Notebook Store (Key-Value Facts)**: Stores high-signal personal preferences, project conventions, and durable rules in clean Markdown.
|
|
10
10
|
2. **Layer 2: RAG Knowledge Base**: Indexes documentation, repositories, and technical guides for hybrid semantic retrieval.
|
|
11
11
|
3. **Layer 3: Agent-Driven Knowledge Graph**: Connects Notebook facts (Layer 1) to specific Knowledge Base documents, sections, and **exact line ranges** (Layer 2).
|
|
12
|
+
4. **Integration Layer (General MCP Helpers)**: Quickly discovers connected MCP servers and identifies appropriate tools for specific tasks.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
@@ -28,7 +29,9 @@ You have access to a persistent dual-layer memory engine supercharged with an **
|
|
|
28
29
|
| User asks to index a documentation URL, file, or repository | `ingest_document` | `content` or `source_path`, `title`, `metadata` |
|
|
29
30
|
| User asks a complex question about indexed docs or code | `query_knowledge_base` | `query`, `limit`, `generateEmbeddings` |
|
|
30
31
|
| Read full raw content of an ambiguous/abstract document | `manage_knowledge_base` | `action: "read_document"`, `docId` |
|
|
31
|
-
| User asks to view database stats, list indexed docs, or export snapshots | `manage_knowledge_base` | `action` ("stats", "list", "read_document", "delete", "export_snapshot") |
|
|
32
|
+
| User asks to view database stats, list indexed docs, or export snapshots | `manage_knowledge_base` | `action` ("stats", "list", "read_document", "delete", "export_snapshot", "import_snapshot") |
|
|
33
|
+
| Discover available MCP servers and their specific purposes | `list-mcp-tools` | — |
|
|
34
|
+
| Ask which MCP tool / server is suitable for a specific task | `mcp-reminder` | `task` (string, e.g., "db migration") |
|
|
32
35
|
|
|
33
36
|
---
|
|
34
37
|
|
|
@@ -113,7 +116,7 @@ Use this tool when adding technical documentation, API specs, architectural docu
|
|
|
113
116
|
|
|
114
117
|
### Hybrid Retrieval (`query_knowledge_base`)
|
|
115
118
|
Use this tool BEFORE answering deep architectural or technical questions when indexed documents exist.
|
|
116
|
-
- Performs **Hybrid RRF Fusion** combining SQLite FTS5 BM25 keyword matching with dense ONNX vector semantic search.
|
|
119
|
+
- Performs **Hybrid RRF/RSF Fusion** combining SQLite FTS5 BM25 keyword matching with dense ONNX vector semantic search.
|
|
117
120
|
- Returns candidate sections with breadcrumb paths and defined code symbols (classes, functions, types).
|
|
118
121
|
|
|
119
122
|
#### Query Formulation Rules (CRITICAL for retrieval quality)
|
|
@@ -154,13 +157,28 @@ In such cases, use the **Full Raw Document Reading** mechanism:
|
|
|
154
157
|
- Use `action: "list"` to see all ingested documents.
|
|
155
158
|
- Use `action: "read_document"` with `docId` to read the complete raw text content of any document.
|
|
156
159
|
- Use `action: "delete"` with `docId` to remove an outdated document and purge its CAS blob.
|
|
160
|
+
- Use `action: "export_snapshot"` with `snapshotPath` to export a JSON backup of the RAG base.
|
|
161
|
+
- Use `action: "import_snapshot"` with `snapshotPath` to import and merge a JSON backup into the current database.
|
|
157
162
|
|
|
158
163
|
---
|
|
159
164
|
|
|
160
|
-
## 4.
|
|
165
|
+
## 4. General MCP Helpers (`list-mcp-tools`, `mcp-reminder`)
|
|
166
|
+
|
|
167
|
+
### Discovering Connected MCP Servers (`list-mcp-tools`)
|
|
168
|
+
When working in multi-server environments (e.g., OpenCode, Claude Code), you might have several auxiliary servers installed (for database, UI design, browser automation, etc.).
|
|
169
|
+
- Use `list-mcp-tools` to immediately view all registered servers and their descriptions. This avoids guessing what other capabilities are available in the current workspace.
|
|
170
|
+
|
|
171
|
+
### Contextual Tool Reminders (`mcp-reminder`)
|
|
172
|
+
- If you are unsure which tool/server is best suited for the task at hand (e.g., how to do browser testing, or run a database migration), run `mcp-reminder(task: "your current task definition")`.
|
|
173
|
+
- It analyzes your task and suggests appropriate servers (like `playwright` for testing, `supabase` for DB, or `stitch` for UI design).
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 5. Core Directives for AI Agents
|
|
161
178
|
|
|
162
179
|
1. **Read Memories First (MANDATORY)**: At the very start of any session or conversation, your VERY FIRST STEP MUST BE to execute `recall` to load all saved facts, user context, and project guidelines BEFORE performing any other task or code analysis.
|
|
163
180
|
2. **Be Proactive**: When the user mentions a durable preference, personal fact, or constraint, save it immediately using `remember`. Do not wait for explicit user commands.
|
|
164
|
-
3. **Check Knowledge Base First**: If a
|
|
181
|
+
3. **Check Knowledge Base First**: If a query is related to specialized documentation, APIs, or project architectures, call `query_knowledge_base` using concept-dense search phrases.
|
|
165
182
|
4. **Inspect Ambiguous Docs Directly**: If querying produces low relevance scores on abstractly-named documents, call `manage_knowledge_base(action: "read_document")` to inspect the full text directly.
|
|
166
183
|
5. **Keep Memory Clean**: If a preference changes, call `update_fact` to edit it in place, or `remember` with `supersedes` to keep a version trail. Use `keep: true` for facts that must survive an accidental `forget`, and give ephemeral facts a `ttl` so stale ones surface as `[EXPIRED]`.
|
|
184
|
+
6. **Leverage MCP Servers**: Proactively list available tools using `list-mcp-tools` and query `mcp-reminder` if unsure of which platform tool can help you automate tasks.
|