@lotargo/memory_plugin 1.4.621 → 1.5.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.
Files changed (39) hide show
  1. package/README.md +352 -366
  2. package/mcp-server/admin/auth.js +31 -4
  3. package/mcp-server/admin/snapshot.js +19 -7
  4. package/mcp-server/cli/direct_commands.js +313 -0
  5. package/mcp-server/cli/handlers/cloud_actions.js +138 -0
  6. package/mcp-server/cli/handlers/diagnostics_actions.js +107 -0
  7. package/mcp-server/cli/handlers/engine_actions.js +214 -0
  8. package/mcp-server/cli/handlers/prompt_actions.js +24 -0
  9. package/mcp-server/cli/handlers/storage_actions.js +749 -0
  10. package/mcp-server/cli/quick_stats.js +39 -0
  11. package/mcp-server/cli/ui.js +565 -0
  12. package/mcp-server/cli.js +324 -2085
  13. package/mcp-server/config/auth_store.js +56 -9
  14. package/mcp-server/config/config_manager.js +1 -0
  15. package/mcp-server/db/database.js +14 -1
  16. package/mcp-server/db/migrations.js +28 -0
  17. package/mcp-server/fact_format.js +244 -177
  18. package/mcp-server/graph/graph_extractor.js +20 -5
  19. package/mcp-server/identity.js +152 -0
  20. package/mcp-server/index.js +42 -679
  21. package/mcp-server/ingest/normalizer.js +40 -4
  22. package/mcp-server/ingest/pipeline.js +6 -13
  23. package/mcp-server/memory.js +50 -63
  24. package/mcp-server/prompt_manager.js +1 -1
  25. package/mcp-server/retrieval/retriever.js +59 -42
  26. package/mcp-server/tools/helpers.js +39 -0
  27. package/mcp-server/tools/identity_tools.js +277 -0
  28. package/mcp-server/tools/index.js +9 -0
  29. package/mcp-server/tools/memory_tools.js +506 -0
  30. package/mcp-server/tools/rag_tools.js +235 -0
  31. package/opencode-plugin/index.js +460 -48
  32. package/package.json +7 -3
  33. package/skills/using-memory/SKILL.md +31 -14
  34. package/mcp-server/benchmarks/fetch_real_corpus.js +0 -351
  35. package/mcp-server/benchmarks/gpu_profile_benchmark.js +0 -170
  36. package/mcp-server/benchmarks/quality_evaluator.js +0 -600
  37. package/mcp-server/benchmarks/run_benchmarks.js +0 -347
  38. package/mcp-server/benchmarks/stress_ingestion.js +0 -195
  39. package/mcp-server/benchmarks/test_dual_layer.js +0 -140
@@ -0,0 +1,277 @@
1
+ import * as z from "zod/v4";
2
+ import { basename } from "node:path";
3
+ import { scopeKey, canonicalPath, readMemory, writeMemory, storeFilePath } from "../memory.js";
4
+ import { factBody } from "../fact_format.js";
5
+ import { optStr, optNum, defStr, defBool, requireProjectKey } from "./helpers.js";
6
+
7
+ export function registerIdentityTools(server) {
8
+ server.registerTool(
9
+ "link_knowledge",
10
+ {
11
+ description:
12
+ "Explicitly link a Notebook memory fact to a Knowledge Base document, section, or line range. " +
13
+ "Creates Agent-driven Graph Edges connecting memory to RAG documents.",
14
+ inputSchema: z.object({
15
+ action: z.enum(["link", "list_links", "get_doc_links"]).nullish().transform((v) => v || "link").describe("Action type"),
16
+ factText: optStr().describe("Memory fact text or keyword"),
17
+ docId: optStr().describe("Document ID, title, or file path"),
18
+ scope: defStr("project").describe("'project' (default) or 'global'"),
19
+ startLine: optNum().describe("Starting line number in target document"),
20
+ endLine: optNum().describe("Ending line number in target document"),
21
+ relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'EXPLAINS')"),
22
+ }),
23
+ },
24
+ async ({ action, factText, docId, scope, startLine, endLine, relationType }) => {
25
+ const { linkFactToDocument, getLinksForDoc, listAllLinks } = await import("../graph/knowledge_linker.js");
26
+ const key = await scopeKey(scope, null, null);
27
+
28
+ if (action === "link" || action === "list_links") {
29
+ requireProjectKey(key);
30
+ }
31
+
32
+ if (action === "link") {
33
+ if (!factText || !docId) {
34
+ throw new Error("factText and docId are required parameters for link action");
35
+ }
36
+ const res = linkFactToDocument({
37
+ factKey: key,
38
+ factText,
39
+ docId,
40
+ startLine,
41
+ endLine,
42
+ relationType,
43
+ });
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
46
+ };
47
+ }
48
+
49
+ if (action === "get_doc_links") {
50
+ if (!docId) throw new Error("docId parameter is required for get_doc_links action");
51
+ const links = getLinksForDoc(docId);
52
+ return {
53
+ content: [{ type: "text", text: JSON.stringify(links, null, 2) }],
54
+ };
55
+ }
56
+
57
+ if (action === "list_links") {
58
+ const links = listAllLinks(key);
59
+ return {
60
+ content: [{ type: "text", text: JSON.stringify(links, null, 2) }],
61
+ };
62
+ }
63
+
64
+ throw new Error(`Unknown action: ${action}`);
65
+ }
66
+ );
67
+
68
+ server.registerTool(
69
+ "link_project_memory",
70
+ {
71
+ description: "Link the current directory to a Git-based project identity, register aliases, and optionally migrate legacy/path stores.",
72
+ inputSchema: z.object({
73
+ directory: optStr().describe("Directory path to link (default: current directory)"),
74
+ remote: optStr().describe("Optional explicit remote URL to use as primary identity key"),
75
+ }),
76
+ },
77
+ async ({ directory, remote }) => {
78
+ const { getDatabase } = await import("../db/database.js");
79
+ const { resolveProjectIdentity, upsertIdentity, registerAlias, normalizeRemoteUrl } = await import("../identity.js");
80
+ const db = await getDatabase();
81
+
82
+ const dir = directory || process.cwd();
83
+ const identity = await resolveProjectIdentity(dir);
84
+ if (!identity && !remote) {
85
+ throw new Error("No Git repository detected and no remote URL specified.");
86
+ }
87
+
88
+ let key = identity ? identity.key : `git:${normalizeRemoteUrl(remote)}`;
89
+ let name = identity ? identity.name : basename(dir) || "unbound";
90
+ let primaryRemote = remote ? normalizeRemoteUrl(remote) : (identity ? identity.primaryRemote : null);
91
+
92
+ await upsertIdentity(db, { key, name, primaryRemote });
93
+
94
+ const aliases = [];
95
+ if (primaryRemote) {
96
+ aliases.push({ alias: `remote:${primaryRemote}`, kind: "remote" });
97
+ }
98
+ aliases.push({ alias: `path:${canonicalPath(dir)}`, kind: "path" });
99
+ aliases.push({ alias: `basename:${name}`, kind: "basename" });
100
+
101
+ for (const a of aliases) {
102
+ await registerAlias(db, { alias: a.alias, identityKey: key, kind: a.kind });
103
+ }
104
+
105
+ let migrated = false;
106
+ const legacyPathKey = canonicalPath(dir);
107
+ const legacyEntries = await readMemory(legacyPathKey);
108
+ if (legacyEntries && legacyEntries.length > 0) {
109
+ const gitEntries = await readMemory(key);
110
+ const seen = new Set(gitEntries.map((e) => factBody(e).toLowerCase().trim()));
111
+ let mergedCount = 0;
112
+ for (const entry of legacyEntries) {
113
+ const body = factBody(entry).toLowerCase().trim();
114
+ if (!seen.has(body)) {
115
+ seen.add(body);
116
+ gitEntries.push(entry);
117
+ mergedCount++;
118
+ }
119
+ }
120
+ if (mergedCount > 0) {
121
+ await writeMemory(key, gitEntries);
122
+ migrated = true;
123
+ }
124
+ try {
125
+ const legacyFp = storeFilePath(legacyPathKey);
126
+ const { existsSync } = await import("node:fs");
127
+ if (existsSync(legacyFp)) {
128
+ const { unlink } = await import("node:fs/promises");
129
+ await unlink(legacyFp);
130
+ }
131
+ } catch (e) {}
132
+ }
133
+
134
+ return {
135
+ content: [
136
+ {
137
+ type: "text",
138
+ text: JSON.stringify(
139
+ {
140
+ status: "success",
141
+ key,
142
+ name,
143
+ primaryRemote,
144
+ aliases: aliases.map((a) => a.alias),
145
+ migrated,
146
+ },
147
+ null,
148
+ 2
149
+ ),
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ );
155
+
156
+ server.registerTool(
157
+ "unlink_project_memory",
158
+ {
159
+ description: "Remove the path alias link for the specified project directory.",
160
+ inputSchema: z.object({
161
+ directory: optStr().describe("Directory path to unlink (default: current directory)"),
162
+ purge: defBool(false).describe("If true, completely purge the project identity from the SQLite store"),
163
+ }),
164
+ },
165
+ async ({ directory, purge }) => {
166
+ const { getDatabase } = await import("../db/database.js");
167
+ const { unregisterAlias, removeIdentity, resolveProjectIdentity } = await import("../identity.js");
168
+ const db = await getDatabase();
169
+
170
+ const dir = directory || process.cwd();
171
+ const alias = `path:${canonicalPath(dir)}`;
172
+ await unregisterAlias(db, alias);
173
+
174
+ let key = null;
175
+ if (purge) {
176
+ const identity = await resolveProjectIdentity(dir);
177
+ if (identity) {
178
+ key = identity.key;
179
+ await removeIdentity(db, key);
180
+ }
181
+ }
182
+
183
+ return {
184
+ content: [
185
+ {
186
+ type: "text",
187
+ text: JSON.stringify(
188
+ {
189
+ status: "success",
190
+ alias,
191
+ purgedIdentityKey: key,
192
+ },
193
+ null,
194
+ 2
195
+ ),
196
+ },
197
+ ],
198
+ };
199
+ }
200
+ );
201
+
202
+ server.registerTool(
203
+ "relink_project_memory",
204
+ {
205
+ description: "Move or merge project memories from the current identity to a new target identity.",
206
+ inputSchema: z.object({
207
+ directory: optStr().describe("Directory path to relink (default: current directory)"),
208
+ remote: z.string().describe("New target remote URL / identity key to move memories to"),
209
+ }),
210
+ },
211
+ async ({ directory, remote }) => {
212
+ const { getDatabase } = await import("../db/database.js");
213
+ const { resolveProjectIdentity, upsertIdentity, removeIdentity, normalizeRemoteUrl } = await import("../identity.js");
214
+ const db = await getDatabase();
215
+
216
+ const dir = directory || process.cwd();
217
+ const sourceIdentity = await resolveProjectIdentity(dir);
218
+ if (!sourceIdentity) {
219
+ throw new Error("Source project identity not detected.");
220
+ }
221
+
222
+ const targetKey = `git:${normalizeRemoteUrl(remote)}`;
223
+ const sourceKey = sourceIdentity.key;
224
+
225
+ if (sourceKey === targetKey) {
226
+ return { content: [{ type: "text", text: "Source and target identities are already identical." }] };
227
+ }
228
+
229
+ const sourceFacts = await readMemory(sourceKey);
230
+ const targetFacts = await readMemory(targetKey);
231
+ const seen = new Set(targetFacts.map((e) => factBody(e).toLowerCase().trim()));
232
+
233
+ let mergedCount = 0;
234
+ for (const f of sourceFacts) {
235
+ const body = factBody(f).toLowerCase().trim();
236
+ if (!seen.has(body)) {
237
+ seen.add(body);
238
+ targetFacts.push(f);
239
+ mergedCount++;
240
+ }
241
+ }
242
+
243
+ await writeMemory(targetKey, targetFacts);
244
+
245
+ await db.prepare("UPDATE project_aliases SET identity_key = ? WHERE identity_key = ?;").run(targetKey, sourceKey);
246
+ await upsertIdentity(db, { key: targetKey, name: sourceIdentity.name, primaryRemote: normalizeRemoteUrl(remote) });
247
+ await removeIdentity(db, sourceKey);
248
+
249
+ try {
250
+ const sourceFp = storeFilePath(sourceKey);
251
+ const { existsSync } = await import("node:fs");
252
+ if (existsSync(sourceFp)) {
253
+ const { unlink } = await import("node:fs/promises");
254
+ await unlink(sourceFp);
255
+ }
256
+ } catch (e) {}
257
+
258
+ return {
259
+ content: [
260
+ {
261
+ type: "text",
262
+ text: JSON.stringify(
263
+ {
264
+ status: "success",
265
+ sourceKey,
266
+ targetKey,
267
+ mergedFacts: mergedCount,
268
+ },
269
+ null,
270
+ 2
271
+ ),
272
+ },
273
+ ],
274
+ };
275
+ }
276
+ );
277
+ }
@@ -0,0 +1,9 @@
1
+ import { registerMemoryTools } from "./memory_tools.js";
2
+ import { registerIdentityTools } from "./identity_tools.js";
3
+ import { registerRagTools } from "./rag_tools.js";
4
+
5
+ export function registerAllTools(server) {
6
+ registerMemoryTools(server);
7
+ registerIdentityTools(server);
8
+ registerRagTools(server);
9
+ }