@lotargo/memory_plugin 1.6.3 → 1.6.5

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 * as z from "zod/v4";
2
2
  import { basename } from "node:path";
3
- import { scopeKey, canonicalPath, readMemory, writeMemory, storeFilePath } from "../memory.js";
3
+ import { GLOBAL_KEY, scopeKey, canonicalPath, readMemory, writeMemory, storeFilePath } from "../memory.js";
4
4
  import { factBody } from "../fact_format.js";
5
5
  import { optStr, optNum, defStr, defBool, requireProjectKey } from "./helpers.js";
6
6
 
@@ -16,14 +16,16 @@ export function registerIdentityTools(server) {
16
16
  factText: optStr().describe("Memory fact text or keyword"),
17
17
  docId: optStr().describe("Document ID, title, or file path"),
18
18
  scope: defStr("project").describe("'project' (default) or 'global'"),
19
+ directory: optStr().describe("Optional workspace/project directory path"),
20
+ project: optStr().describe("Alias for directory"),
19
21
  startLine: optNum().describe("Starting line number in target document"),
20
22
  endLine: optNum().describe("Ending line number in target document"),
21
23
  relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'EXPLAINS')"),
22
24
  }),
23
25
  },
24
- async ({ action, factText, docId, scope, startLine, endLine, relationType }) => {
26
+ async ({ action, factText, docId, scope, directory, project, startLine, endLine, relationType }) => {
25
27
  const { linkFactToDocument, getLinksForDoc, listAllLinks } = await import("../graph/knowledge_linker.js");
26
- const key = await scopeKey(scope, null, null);
28
+ const key = await scopeKey(scope, null, directory || project || null);
27
29
 
28
30
  if (action === "link" || action === "list_links") {
29
31
  requireProjectKey(key);
@@ -33,9 +35,18 @@ export function registerIdentityTools(server) {
33
35
  if (!factText || !docId) {
34
36
  throw new Error("factText and docId are required parameters for link action");
35
37
  }
38
+ const facts = await readMemory(key);
39
+ const needle = factText.toLowerCase().trim();
40
+ const matches = facts.filter((entry) => {
41
+ const body = factBody(entry).toLowerCase();
42
+ return body === needle || body.includes(needle) || entry.toLowerCase().includes(needle);
43
+ });
44
+ if (matches.length === 0) throw new Error(`Notebook fact not found for link: ${factText}`);
45
+ if (matches.length > 1) throw new Error(`Notebook fact match is ambiguous; use a more specific factText: ${factText}`);
46
+ const resolvedFactText = factBody(matches[0]);
36
47
  const res = await linkFactToDocument({
37
48
  factKey: key,
38
- factText,
49
+ factText: resolvedFactText,
39
50
  docId,
40
51
  startLine,
41
52
  endLine,
@@ -48,7 +59,8 @@ export function registerIdentityTools(server) {
48
59
 
49
60
  if (action === "get_doc_links") {
50
61
  if (!docId) throw new Error("docId parameter is required for get_doc_links action");
51
- const links = await getLinksForDoc(docId);
62
+ const allowedScopes = key === GLOBAL_KEY ? [GLOBAL_KEY] : [GLOBAL_KEY, key];
63
+ const links = await getLinksForDoc(docId, allowedScopes);
52
64
  return {
53
65
  content: [{ type: "text", text: JSON.stringify(links, null, 2) }],
54
66
  };
@@ -130,6 +142,9 @@ export function registerIdentityTools(server) {
130
142
  }
131
143
  } catch (e) {}
132
144
  }
145
+ const { moveKnowledgeScope } = await import("../graph/knowledge_linker.js");
146
+ const migratedKnowledge = await moveKnowledgeScope(db, legacyPathKey, key);
147
+ if (migratedKnowledge.movedLinks > 0 || migratedKnowledge.movedDocuments > 0) migrated = true;
133
148
 
134
149
  return {
135
150
  content: [
@@ -242,8 +257,10 @@ export function registerIdentityTools(server) {
242
257
 
243
258
  await writeMemory(targetKey, targetFacts);
244
259
 
245
- await db.prepare("UPDATE project_aliases SET identity_key = ? WHERE identity_key = ?;").run(targetKey, sourceKey);
246
260
  await upsertIdentity(db, { key: targetKey, name: sourceIdentity.name, primaryRemote: normalizeRemoteUrl(remote) });
261
+ await db.prepare("UPDATE project_aliases SET identity_key = ? WHERE identity_key = ?;").run(targetKey, sourceKey);
262
+ const { moveKnowledgeScope } = await import("../graph/knowledge_linker.js");
263
+ const movedKnowledge = await moveKnowledgeScope(db, sourceKey, targetKey);
247
264
  await removeIdentity(db, sourceKey);
248
265
 
249
266
  try {
@@ -265,6 +282,8 @@ export function registerIdentityTools(server) {
265
282
  sourceKey,
266
283
  targetKey,
267
284
  mergedFacts: mergedCount,
285
+ movedKnowledgeLinks: movedKnowledge.movedLinks,
286
+ movedRagDocuments: movedKnowledge.movedDocuments,
268
287
  },
269
288
  null,
270
289
  2
@@ -1,123 +1,138 @@
1
- import * as z from "zod/v4";
2
- import { optStr, optNum, defStr, defBool } from "./helpers.js";
3
- import {
4
- rememberFact,
5
- recallFacts,
6
- getFactById,
7
- forgetFacts,
8
- updateFactText,
9
- memoryInfo,
10
- } from "./core/memory_core.js";
11
-
12
- export function registerMemoryTools(server) {
13
- server.registerTool(
14
- "remember",
15
- {
16
- description:
17
- "Save an important, durable fact to memory. Only use for high-signal information " +
18
- "(name, goals, constraints, tech preferences, project conventions). " +
19
- "docId/startLine/endLine/relationType are OPTIONAL and only used to link the fact to a " +
20
- "Knowledge Base document or line range; omit them when no linking is needed. " +
21
- "ttl is OPTIONAL (e.g. '90d', '2w', '24h') expired facts are shown with [EXPIRED] but not auto-deleted. " +
22
- "keep=true protects the fact from forget deletion unless force=true. " +
23
- "tags is OPTIONAL comma-separated text for filtering. " +
24
- "supersedes is OPTIONAL: a number (from recall), id, or text of a fact this one replaces; " +
25
- "the target is then marked [SUPERSEDED]. " +
26
- "Translate the fact into English and keep it concise. " +
27
- "scope: 'project' (default) or 'global'",
28
- inputSchema: z.object({
29
- fact: z.string().describe("The fact to remember, written in English"),
30
- title: optStr().describe("Optional title for the fact. If not specified, one is auto-generated."),
31
- scope: defStr("project").describe("'project' (default) or 'global'"),
32
- docId: optStr().describe("Optional document ID, title, or path to link this fact to"),
33
- startLine: optNum().describe("Optional starting line number in target document"),
34
- endLine: optNum().describe("Optional ending line number in target document"),
35
- relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'REFERENCES')"),
36
- ttl: optStr().describe("Optional time-to-live, e.g. '90d', '2w', '24h', '12m'"),
37
- keep: defBool(false).describe("Protect the fact from forget deletion unless force=true"),
38
- tags: optStr().describe("Optional comma-separated tags, e.g. 'pref,arch'"),
39
- supersedes: optStr().describe("Optional number, id, or text of the fact this one replaces"),
40
- }),
41
- },
42
- async (args) => ({ content: [{ type: "text", text: await rememberFact(args) }] })
43
- );
44
-
45
- server.registerTool(
46
- "recall",
47
- {
48
- description:
49
- "Show saved facts with any Agent-linked Knowledge Base documents/lines. " +
50
- "scope: 'project', 'global', 'all' (default), or 'list_projects'. " +
51
- "Use project: '<directory path>' with scope 'project'/'all' to read facts of a specific project from any working directory. " +
52
- "query filters by keyword (all space-separated terms must match). " +
53
- "tags filters by comma-separated tags. since/until filter by date (YYYY-MM-DD, inclusive). " +
54
- "Expired facts are shown with [EXPIRED], protected ones with [KEEP]. The response includes the store file paths.",
55
- inputSchema: z.object({
56
- scope: defStr("all").describe("'project', 'global', 'all', or 'list_projects'"),
57
- project: optStr().describe("Directory path of the project to read facts from (e.g. 'F:/projects/plugins/memory')"),
58
- query: optStr().describe("Optional keyword filter; all space-separated terms must match"),
59
- tags: optStr().describe("Optional comma-separated tag filter (any match)"),
60
- since: optStr().describe("Optional start date filter, YYYY-MM-DD (inclusive)"),
61
- until: optStr().describe("Optional end date filter, YYYY-MM-DD (inclusive)"),
62
- mode: z.enum(["headers", "full"]).nullish().transform((v) => v || "full").describe("Result mode: 'full' (with body, default) or 'headers' (title and badges only)"),
63
- offset: optNum().describe("Pagination offset (optional)"),
64
- limit: optNum().describe("Pagination limit (optional)"),
65
- }),
66
- },
67
- async (args) => ({ content: [{ type: "text", text: await recallFacts(args) }] })
68
- );
69
-
70
- server.registerTool(
71
- "get_fact",
72
- {
73
- description: "Get the full text and metadata of a single fact by its metadata id.",
74
- inputSchema: z.object({
75
- id: z.string().describe("The unique metadata id of the fact (e.g. '8f3a2c')"),
76
- scope: defStr("all").describe("'project', 'global', or 'all' (default)"),
77
- }),
78
- },
79
- async (args) => ({ content: [{ type: "text", text: await getFactById(args) }] })
80
- );
81
-
82
- server.registerTool(
83
- "forget",
84
- {
85
- description:
86
- "Delete a fact by number (from recall), by range (e.g. '3-30', inclusive), or by text search. " +
87
- "Protected facts (remember with keep=true) are skipped unless force=true.",
88
- inputSchema: z.object({
89
- query: z.string().describe("Number, range like '3-30', or text to search for"),
90
- scope: defStr("project").describe("'project' (default) or 'global'"),
91
- force: defBool(false).describe("Also delete protected (keep) facts"),
92
- }),
93
- },
94
- async (args) => ({ content: [{ type: "text", text: await forgetFacts(args) }] })
95
- );
96
-
97
- server.registerTool(
98
- "update_fact",
99
- {
100
- description:
101
- "Update the text of an existing fact by number (from recall), id, or text match, " +
102
- "preserving its original date and metadata. Linked Knowledge Base documents are re-pointed to the new text.",
103
- inputSchema: z.object({
104
- id: z.string().describe("Number (from recall), metadata id, or text of the fact to update"),
105
- newText: z.string().describe("New fact text"),
106
- title: optStr().describe("Optional new title for the fact"),
107
- scope: defStr("project").describe("'project' (default) or 'global'"),
108
- }),
109
- },
110
- async (args) => ({ content: [{ type: "text", text: await updateFactText(args) }] })
111
- );
112
-
113
- server.registerTool(
114
- "memory_info",
115
- {
116
- description:
117
- "Show memory storage paths (store file locations, MEMORY_DIR, SQLite DB), fact counts, " +
118
- "Knowledge Base stats, and the installed package version.",
119
- inputSchema: z.object({}),
120
- },
121
- async () => ({ content: [{ type: "text", text: await memoryInfo() }] })
122
- );
123
- }
1
+ import * as z from "zod/v4";
2
+ import { optStr, optNum, defStr, defBool } from "./helpers.js";
3
+ import {
4
+ rememberFact,
5
+ recallFacts,
6
+ getFactById,
7
+ forgetFacts,
8
+ updateFactText,
9
+ memoryInfo,
10
+ } from "./core/memory_core.js";
11
+
12
+ export function registerMemoryTools(server) {
13
+ server.registerTool(
14
+ "remember",
15
+ {
16
+ description:
17
+ "Save an important, durable fact to memory. Only use for high-signal information " +
18
+ "(name, goals, constraints, tech preferences, project conventions). " +
19
+ "directory: optional workspace/project directory path to target (ensures saving into the project store even from external cwd). " +
20
+ "docId/startLine/endLine/relationType are OPTIONAL and only used to link the fact to a " +
21
+ "Knowledge Base document or line range; omit them when no linking is needed. " +
22
+ "ttl is OPTIONAL (e.g. '90d', '2w', '24h') expired facts are shown with [EXPIRED] but not auto-deleted. " +
23
+ "keep=true protects the fact from forget deletion unless force=true. " +
24
+ "tags is OPTIONAL comma-separated text for filtering. " +
25
+ "supersedes is OPTIONAL: a number (from recall), id, or text of a fact this one replaces; " +
26
+ "the target is then marked [SUPERSEDED]. " +
27
+ "Translate the fact into English and keep it concise. " +
28
+ "scope: 'project' (default) or 'global'",
29
+ inputSchema: z.object({
30
+ fact: z.string().describe("The fact to remember, written in English"),
31
+ title: optStr().describe("Optional title for the fact. If not specified, one is auto-generated."),
32
+ scope: defStr("project").describe("'project' (default) or 'global'"),
33
+ directory: optStr().describe("Optional workspace/project directory path to target when scope='project' (e.g. 'F:/projects/my-app')"),
34
+ project: optStr().describe("Alias for directory"),
35
+ docId: optStr().describe("Optional document ID, title, or path to link this fact to"),
36
+ startLine: optNum().describe("Optional starting line number in target document"),
37
+ endLine: optNum().describe("Optional ending line number in target document"),
38
+ relationType: defStr("LINKS_TO").describe("Relation type (e.g. 'RULES_FOR', 'IMPLEMENTS', 'REFERENCES')"),
39
+ ttl: optStr().describe("Optional time-to-live, e.g. '90d', '2w', '24h', '12m'"),
40
+ keep: defBool(false).describe("Protect the fact from forget deletion unless force=true"),
41
+ tags: optStr().describe("Optional comma-separated tags, e.g. 'pref,arch'"),
42
+ supersedes: optStr().describe("Optional number, id, or text of the fact this one replaces"),
43
+ }),
44
+ },
45
+ async (args) => ({ content: [{ type: "text", text: await rememberFact(args) }] })
46
+ );
47
+
48
+ server.registerTool(
49
+ "recall",
50
+ {
51
+ description:
52
+ "Show saved facts with any Agent-linked Knowledge Base documents/lines. " +
53
+ "scope: 'project', 'global', 'all' (default), or 'list_projects'. " +
54
+ "Use directory: '<directory path>' with scope 'project'/'all' to read facts of a specific project from any working directory. " +
55
+ "query filters by keyword (all space-separated terms must match). " +
56
+ "tags filters by comma-separated tags. since/until filter by date (YYYY-MM-DD, inclusive). " +
57
+ "Superseded facts are excluded by default; pass includeSuperseded=true to inspect history. " +
58
+ "Expired facts are shown with [EXPIRED], protected ones with [KEEP]. The response includes the store file paths.",
59
+ inputSchema: z.object({
60
+ scope: defStr("all").describe("'project', 'global', 'all', or 'list_projects'"),
61
+ directory: optStr().describe("Directory path of the project to read facts from (e.g. 'F:/projects/plugins/memory')"),
62
+ project: optStr().describe("Alias for directory"),
63
+ query: optStr().describe("Optional keyword filter; all space-separated terms must match"),
64
+ tags: optStr().describe("Optional comma-separated tag filter (any match)"),
65
+ since: optStr().describe("Optional start date filter, YYYY-MM-DD (inclusive)"),
66
+ until: optStr().describe("Optional end date filter, YYYY-MM-DD (inclusive)"),
67
+ mode: z.enum(["headers", "full"]).nullish().transform((v) => v || "full").describe("Result mode: 'full' (with body, default) or 'headers' (title and badges only)"),
68
+ offset: optNum().describe("Pagination offset (optional)"),
69
+ limit: optNum().describe("Pagination limit (optional)"),
70
+ includeSuperseded: defBool(false).describe("Include superseded historical facts (excluded by default)"),
71
+ }),
72
+ },
73
+ async (args) => ({ content: [{ type: "text", text: await recallFacts(args) }] })
74
+ );
75
+
76
+ server.registerTool(
77
+ "get_fact",
78
+ {
79
+ description: "Get the full text and metadata of a single fact by its metadata id.",
80
+ inputSchema: z.object({
81
+ id: z.string().describe("The unique metadata id of the fact (e.g. '8f3a2c')"),
82
+ scope: defStr("all").describe("'project', 'global', or 'all' (default)"),
83
+ directory: optStr().describe("Optional workspace/project directory path"),
84
+ project: optStr().describe("Alias for directory"),
85
+ }),
86
+ },
87
+ async (args) => ({ content: [{ type: "text", text: await getFactById(args) }] })
88
+ );
89
+
90
+ server.registerTool(
91
+ "forget",
92
+ {
93
+ description:
94
+ "Delete a fact by number (from recall), by range (e.g. '3-30', inclusive), or by text search. " +
95
+ "Protected facts (remember with keep=true) are skipped unless force=true.",
96
+ inputSchema: z.object({
97
+ query: z.string().describe("Number, range like '3-30', or text to search for"),
98
+ scope: defStr("project").describe("'project' (default) or 'global'"),
99
+ directory: optStr().describe("Optional workspace/project directory path"),
100
+ project: optStr().describe("Alias for directory"),
101
+ force: defBool(false).describe("Also delete protected (keep) facts"),
102
+ }),
103
+ },
104
+ async (args) => ({ content: [{ type: "text", text: await forgetFacts(args) }] })
105
+ );
106
+
107
+ server.registerTool(
108
+ "update_fact",
109
+ {
110
+ description:
111
+ "Update the text of an existing fact by number (from recall), id, or text match, " +
112
+ "preserving its original date and metadata. Linked Knowledge Base documents are re-pointed to the new text.",
113
+ inputSchema: z.object({
114
+ id: z.string().describe("Number (from recall), metadata id, or text of the fact to update"),
115
+ newText: z.string().describe("New fact text"),
116
+ title: optStr().describe("Optional new title for the fact"),
117
+ scope: defStr("project").describe("'project' (default) or 'global'"),
118
+ directory: optStr().describe("Optional workspace/project directory path"),
119
+ project: optStr().describe("Alias for directory"),
120
+ }),
121
+ },
122
+ async (args) => ({ content: [{ type: "text", text: await updateFactText(args) }] })
123
+ );
124
+
125
+ server.registerTool(
126
+ "memory_info",
127
+ {
128
+ description:
129
+ "Show memory storage paths (store file locations, MEMORY_DIR, SQLite DB), fact counts, " +
130
+ "Knowledge Base stats, and the installed package version.",
131
+ inputSchema: z.object({
132
+ directory: optStr().describe("Optional workspace/project directory path to inspect (default: current directory)"),
133
+ project: optStr().describe("Alias for directory"),
134
+ }),
135
+ },
136
+ async (args) => ({ content: [{ type: "text", text: await memoryInfo(args) }] })
137
+ );
138
+ }