toon-memory 1.8.0 → 2.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.
package/mcp/server.js CHANGED
@@ -16506,8 +16506,8 @@ var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
16506
16506
  }
16507
16507
  return count;
16508
16508
  }
16509
- function getFullPath(resolver, id = "", normalize) {
16510
- if (normalize !== false) id = normalizeId(id);
16509
+ function getFullPath(resolver, id = "", normalize2) {
16510
+ if (normalize2 !== false) id = normalizeId(id);
16511
16511
  return _getFullPath(resolver, resolver.parse(id));
16512
16512
  }
16513
16513
  exports.getFullPath = getFullPath;
@@ -17602,7 +17602,7 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17602
17602
  var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17603
17603
  const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
17604
17604
  const { SCHEMES, getSchemeHandler } = require_schemes();
17605
- function normalize(uri, options) {
17605
+ function normalize2(uri, options) {
17606
17606
  if (typeof uri === "string") uri = serialize(parse3(uri, options), options);
17607
17607
  else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
17608
17608
  return uri;
@@ -17780,7 +17780,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17780
17780
  }
17781
17781
  const fastUri = {
17782
17782
  SCHEMES,
17783
- normalize,
17783
+ normalize: normalize2,
17784
17784
  resolve,
17785
17785
  resolveComponent,
17786
17786
  equal,
@@ -27796,6 +27796,27 @@ function parseRelativeDate(since) {
27796
27796
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27797
27797
  return today;
27798
27798
  }
27799
+ var normalize = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
27800
+ function findRelatedEntries(text, excludeKey = "", limit = 3) {
27801
+ const data = readMemory();
27802
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
27803
+ const queryTokens = normalize(text).split(" ").filter(Boolean);
27804
+ const scored = lines.map((line) => {
27805
+ const trimmed = line.trim();
27806
+ const parts = trimmed.split("|");
27807
+ if (parts.length < 7) return null;
27808
+ const [id, cat, key, content, file2, tags, date5] = parts;
27809
+ if (key === excludeKey) return null;
27810
+ const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
27811
+ let score = 0;
27812
+ for (const token of queryTokens) {
27813
+ if (searchStr.includes(token)) score++;
27814
+ }
27815
+ if (score === 0) return null;
27816
+ return { id, cat, key, content, file: file2, tags, date: date5, score };
27817
+ }).filter(Boolean).sort((a, b) => b.score - a.score).slice(0, limit);
27818
+ return scored;
27819
+ }
27799
27820
  function archiveOldEntries() {
27800
27821
  const data = readMemory();
27801
27822
  const lines = data.split("\n");
@@ -27852,8 +27873,58 @@ archived:
27852
27873
  return { archived: toArchive.length, kept: toKeep.length };
27853
27874
  }
27854
27875
  var server = new McpServer(
27855
- { name: "toon-memory", version: "1.1.0" },
27856
- { capabilities: { tools: { listChanged: true } } }
27876
+ { name: "toon-memory", version: "2.0.0" },
27877
+ { capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }
27878
+ );
27879
+ server.registerResource(
27880
+ "memory-entries",
27881
+ "toon://memory/entries",
27882
+ { title: "Memory Entries", mimeType: "text/plain" },
27883
+ async (uri) => {
27884
+ const data = readMemory();
27885
+ return { contents: [{ uri: uri.href, text: data }] };
27886
+ }
27887
+ );
27888
+ server.registerResource(
27889
+ "memory-stats",
27890
+ "toon://memory/stats",
27891
+ { title: "Memory Stats", mimeType: "text/plain" },
27892
+ async (uri) => {
27893
+ const data = readMemory();
27894
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
27895
+ const entries = lines.map((l) => {
27896
+ const parts = l.trim().split("|");
27897
+ return { category: parts[1] || "unknown", ttl: parts[7] || "" };
27898
+ });
27899
+ const byCategory = {};
27900
+ for (const e of entries) {
27901
+ byCategory[e.category] = (byCategory[e.category] || 0) + 1;
27902
+ }
27903
+ const withTtl = entries.filter((e) => e.ttl).length;
27904
+ const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length;
27905
+ const text = [
27906
+ `Entradas totales: ${entries.length}`,
27907
+ `TTL: ${withTtl} con expiraci\xF3n, ${expired} expiradas`,
27908
+ "Por categor\xEDa:",
27909
+ ...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`)
27910
+ ].join("\n");
27911
+ return { contents: [{ uri: uri.href, text }] };
27912
+ }
27913
+ );
27914
+ server.registerResource(
27915
+ "memory-summaries",
27916
+ "toon://memory/summaries",
27917
+ { title: "Memory Summaries", mimeType: "text/plain" },
27918
+ async (uri) => {
27919
+ const data = readMemory();
27920
+ const lines = data.split("\n");
27921
+ const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"));
27922
+ if (summaryIdx === -1) {
27923
+ return { contents: [{ uri: uri.href, text: "No hay res\xFAmenes guardados" }] };
27924
+ }
27925
+ const summaryText = lines.slice(summaryIdx + 1).filter((l) => l.includes(":")).join("\n");
27926
+ return { contents: [{ uri: uri.href, text: summaryText || "No hay res\xFAmenes guardados" }] };
27927
+ }
27857
27928
  );
27858
27929
  server.registerTool(
27859
27930
  "memory_remember",
@@ -27922,9 +27993,18 @@ server.registerTool(
27922
27993
  \u23F0 TTL: ${resolvedTtl}` : "";
27923
27994
  const inferredMsg = tagsInferred ? `
27924
27995
  \u{1F3F7}\uFE0F Tags inferidos: ${resolvedTags}` : "";
27996
+ const related = findRelatedEntries(`${key} ${content} ${resolvedTags}`, key, 3);
27997
+ let relatedMsg = "";
27998
+ if (related.length > 0) {
27999
+ const items = related.map((r) => ` [${r.cat}] ${r.key} \u2014 ${r.content.slice(0, 80)}`).join("\n");
28000
+ relatedMsg = `
28001
+
28002
+ \u{1F517} Entradas relacionadas:
28003
+ ${items}`;
28004
+ }
27925
28005
  return {
27926
28006
  content: [{ type: "text", text: `\u{1F9E0} ${action}: ${category}/${key} (${entryId})
27927
- ${content}${ttlMsg}${inferredMsg}${archiveMsg}` }]
28007
+ ${content}${ttlMsg}${inferredMsg}${archiveMsg}${relatedMsg}` }]
27928
28008
  };
27929
28009
  }
27930
28010
  );
@@ -27943,8 +28023,8 @@ server.registerTool(
27943
28023
  async ({ query, category, from_date, to_date }) => {
27944
28024
  const data = readMemory();
27945
28025
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"));
27946
- const normalize = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
27947
- const queryTokens = normalize(query).split(" ").filter(Boolean);
28026
+ const normalize2 = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
28027
+ const queryTokens = normalize2(query).split(" ").filter(Boolean);
27948
28028
  const results = lines.map((line) => {
27949
28029
  const trimmed = line.trim();
27950
28030
  const parts = trimmed.split("|");
@@ -27954,7 +28034,7 @@ server.registerTool(
27954
28034
  if (from_date && date5 < from_date) return null;
27955
28035
  if (to_date && date5 > to_date) return null;
27956
28036
  if (ttl && isExpired(ttl)) return null;
27957
- const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
28037
+ const searchStr = normalize2(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
27958
28038
  if (!queryTokens.every((token) => searchStr.includes(token))) return null;
27959
28039
  return { id, cat, key, content, file: file2, tags, date: date5 };
27960
28040
  }).filter(Boolean);
@@ -28088,6 +28168,29 @@ server.registerTool(
28088
28168
  return { content: [{ type: "text", text: sections.join("\n") }] };
28089
28169
  }
28090
28170
  );
28171
+ server.registerTool(
28172
+ "memory_suggest",
28173
+ {
28174
+ title: "Suggest Related Memories",
28175
+ description: "Sugiere entradas de memoria relacionadas con un contexto dado. \xDAtil para obtener contexto antes de una tarea.",
28176
+ inputSchema: {
28177
+ context: external_exports.string().describe("Texto o contexto para buscar sugerencias"),
28178
+ limit: external_exports.number().optional().default(5).describe("M\xE1ximo de sugerencias")
28179
+ }
28180
+ },
28181
+ async ({ context, limit }) => {
28182
+ const related = findRelatedEntries(context, "", limit);
28183
+ if (related.length === 0) {
28184
+ return { content: [{ type: "text", text: `No se encontraron entradas relacionadas con "${context}"` }] };
28185
+ }
28186
+ const formatted = related.map((r) => `[${r.cat}] ${r.key} (${r.id})
28187
+ ${r.content}
28188
+ File: ${r.file} | Tags: ${r.tags} | Date: ${r.date}`).join("\n\n");
28189
+ return { content: [{ type: "text", text: `\u{1F50D} Sugerencias para "${context}":
28190
+
28191
+ ${formatted}` }] };
28192
+ }
28193
+ );
28091
28194
  server.registerTool(
28092
28195
  "memory_summary",
28093
28196
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-memory",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "Persistent memory system for AI coding agents using TOON format (40% fewer tokens than JSON)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/mcp/server.ts CHANGED
@@ -298,6 +298,39 @@ function parseRelativeDate(since: string): string {
298
298
  return today
299
299
  }
300
300
 
301
+ const normalize = (s: string) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim()
302
+
303
+ /**
304
+ * Find entries related to the given text by fuzzy matching.
305
+ * Returns top N results ranked by match quality.
306
+ */
307
+ function findRelatedEntries(text: string, excludeKey: string = "", limit: number = 3): Array<{ id: string; cat: string; key: string; content: string; file: string; tags: string; date: string; score: number }> {
308
+ const data = readMemory()
309
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"))
310
+ const queryTokens = normalize(text).split(" ").filter(Boolean)
311
+
312
+ const scored = lines
313
+ .map((line) => {
314
+ const trimmed = line.trim()
315
+ const parts = trimmed.split("|")
316
+ if (parts.length < 7) return null
317
+ const [id, cat, key, content, file, tags, date] = parts
318
+ if (key === excludeKey) return null
319
+ const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file} ${tags}`)
320
+ let score = 0
321
+ for (const token of queryTokens) {
322
+ if (searchStr.includes(token)) score++
323
+ }
324
+ if (score === 0) return null
325
+ return { id, cat, key, content, file, tags, date, score }
326
+ })
327
+ .filter(Boolean)
328
+ .sort((a, b) => b!.score - a!.score)
329
+ .slice(0, limit)
330
+
331
+ return scored as Array<{ id: string; cat: string; key: string; content: string; file: string; tags: string; date: string; score: number }>
332
+ }
333
+
301
334
  /**
302
335
  * Archive entries older than ARCHIVE_DAYS to archive.toon.
303
336
  *
@@ -381,8 +414,71 @@ function archiveOldEntries(): { archived: number; kept: number } {
381
414
  }
382
415
 
383
416
  const server = new McpServer(
384
- { name: "toon-memory", version: "1.1.0" },
385
- { capabilities: { tools: { listChanged: true } } }
417
+ { name: "toon-memory", version: "2.0.0" },
418
+ { capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }
419
+ )
420
+
421
+ /**
422
+ * Register memory entries as an MCP resource.
423
+ * Agents can read this as context in the system prompt.
424
+ */
425
+ server.registerResource(
426
+ "memory-entries",
427
+ "toon://memory/entries",
428
+ { title: "Memory Entries", mimeType: "text/plain" },
429
+ async (uri) => {
430
+ const data = readMemory()
431
+ return { contents: [{ uri: uri.href, text: data }] }
432
+ }
433
+ )
434
+
435
+ /**
436
+ * Register memory stats as an MCP resource.
437
+ */
438
+ server.registerResource(
439
+ "memory-stats",
440
+ "toon://memory/stats",
441
+ { title: "Memory Stats", mimeType: "text/plain" },
442
+ async (uri) => {
443
+ const data = readMemory()
444
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"))
445
+ const entries = lines.map((l) => {
446
+ const parts = l.trim().split("|")
447
+ return { category: parts[1] || "unknown", ttl: parts[7] || "" }
448
+ })
449
+ const byCategory: Record<string, number> = {}
450
+ for (const e of entries) {
451
+ byCategory[e.category] = (byCategory[e.category] || 0) + 1
452
+ }
453
+ const withTtl = entries.filter((e) => e.ttl).length
454
+ const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length
455
+ const text = [
456
+ `Entradas totales: ${entries.length}`,
457
+ `TTL: ${withTtl} con expiración, ${expired} expiradas`,
458
+ "Por categoría:",
459
+ ...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`),
460
+ ].join("\n")
461
+ return { contents: [{ uri: uri.href, text }] }
462
+ }
463
+ )
464
+
465
+ /**
466
+ * Register memory summaries as an MCP resource.
467
+ */
468
+ server.registerResource(
469
+ "memory-summaries",
470
+ "toon://memory/summaries",
471
+ { title: "Memory Summaries", mimeType: "text/plain" },
472
+ async (uri) => {
473
+ const data = readMemory()
474
+ const lines = data.split("\n")
475
+ const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"))
476
+ if (summaryIdx === -1) {
477
+ return { contents: [{ uri: uri.href, text: "No hay resúmenes guardados" }] }
478
+ }
479
+ const summaryText = lines.slice(summaryIdx + 1).filter((l) => l.includes(":")).join("\n")
480
+ return { contents: [{ uri: uri.href, text: summaryText || "No hay resúmenes guardados" }] }
481
+ }
386
482
  )
387
483
 
388
484
  /**
@@ -474,8 +570,16 @@ server.registerTool(
474
570
 
475
571
  const ttlMsg = resolvedTtl ? `\n⏰ TTL: ${resolvedTtl}` : ""
476
572
  const inferredMsg = tagsInferred ? `\n🏷️ Tags inferidos: ${resolvedTags}` : ""
573
+
574
+ const related = findRelatedEntries(`${key} ${content} ${resolvedTags}`, key, 3)
575
+ let relatedMsg = ""
576
+ if (related.length > 0) {
577
+ const items = related.map((r) => ` [${r.cat}] ${r.key} — ${r.content.slice(0, 80)}`).join("\n")
578
+ relatedMsg = `\n\n🔗 Entradas relacionadas:\n${items}`
579
+ }
580
+
477
581
  return {
478
- content: [{ type: "text" as const, text: `🧠 ${action}: ${category}/${key} (${entryId})\n${content}${ttlMsg}${inferredMsg}${archiveMsg}` }],
582
+ content: [{ type: "text" as const, text: `🧠 ${action}: ${category}/${key} (${entryId})\n${content}${ttlMsg}${inferredMsg}${archiveMsg}${relatedMsg}` }],
479
583
  }
480
584
  }
481
585
  )
@@ -704,6 +808,35 @@ server.registerTool(
704
808
  }
705
809
  )
706
810
 
811
+ /**
812
+ * Register the memory_suggest tool.
813
+ * Suggest related entries for a given text context.
814
+ */
815
+ server.registerTool(
816
+ "memory_suggest",
817
+ {
818
+ title: "Suggest Related Memories",
819
+ description: "Sugiere entradas de memoria relacionadas con un contexto dado. Útil para obtener contexto antes de una tarea.",
820
+ inputSchema: {
821
+ context: z.string().describe("Texto o contexto para buscar sugerencias"),
822
+ limit: z.number().optional().default(5).describe("Máximo de sugerencias"),
823
+ },
824
+ },
825
+ async ({ context, limit }) => {
826
+ const related = findRelatedEntries(context, "", limit)
827
+
828
+ if (related.length === 0) {
829
+ return { content: [{ type: "text" as const, text: `No se encontraron entradas relacionadas con "${context}"` }] }
830
+ }
831
+
832
+ const formatted = related
833
+ .map((r) => `[${r.cat}] ${r.key} (${r.id})\n ${r.content}\n File: ${r.file} | Tags: ${r.tags} | Date: ${r.date}`)
834
+ .join("\n\n")
835
+
836
+ return { content: [{ type: "text" as const, text: `🔍 Sugerencias para "${context}":\n\n${formatted}` }] }
837
+ }
838
+ )
839
+
707
840
  /**
708
841
  * Register the memory_summary tool.
709
842
  * Get or set file summaries to save tokens when reading large files.