toon-memory 1.6.2 → 1.6.4

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
@@ -27793,7 +27793,7 @@ server.registerTool(
27793
27793
  },
27794
27794
  async ({ category, key, content, file: file2, tags }) => {
27795
27795
  const data = readMemory();
27796
- const id = generateId();
27796
+ const newId = generateId();
27797
27797
  const date5 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27798
27798
  const lines = data.split("\n");
27799
27799
  let headerIdx = lines.findIndex((l) => l.startsWith("entries[") || /^\[\d+\|]/.test(l));
@@ -27801,14 +27801,34 @@ server.registerTool(
27801
27801
  lines.push(`[0|]`);
27802
27802
  headerIdx = lines.length - 1;
27803
27803
  }
27804
- const match = lines[headerIdx].match(/\[(\d+)\|/);
27805
- const count = match ? parseInt(match[1]) : 0;
27806
- const newEntry = `${id}|${category}|${key}|${content}|${file2 || ""}|${tags || ""}|${date5}`;
27807
- lines.splice(headerIdx + 1, 0, ` ${newEntry}`);
27808
- lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`);
27804
+ let existingIdx = -1;
27805
+ let existingId = newId;
27806
+ for (let i = headerIdx + 1; i < lines.length; i++) {
27807
+ const line = lines[i];
27808
+ if (!line.startsWith(" ") || !line.includes("|")) continue;
27809
+ if (line.startsWith(" summaries:")) break;
27810
+ const parts = line.trim().split("|");
27811
+ if (parts[2] === key) {
27812
+ existingIdx = i;
27813
+ existingId = parts[0];
27814
+ break;
27815
+ }
27816
+ }
27817
+ const entryId = existingIdx !== -1 ? existingId : newId;
27818
+ const newEntry = `${entryId}|${category}|${key}|${content}|${file2 || ""}|${tags || ""}|${date5}`;
27819
+ let action = "Guardado";
27820
+ if (existingIdx !== -1) {
27821
+ lines[existingIdx] = ` ${newEntry}`;
27822
+ action = "Actualizado";
27823
+ } else {
27824
+ const match = lines[headerIdx].match(/\[(\d+)\|/);
27825
+ const count = match ? parseInt(match[1]) : 0;
27826
+ lines.splice(headerIdx + 1, 0, ` ${newEntry}`);
27827
+ lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`);
27828
+ }
27809
27829
  writeMemory(lines.join("\n"));
27810
27830
  return {
27811
- content: [{ type: "text", text: `\u{1F9E0} Guardado: ${category}/${key} (${id})
27831
+ content: [{ type: "text", text: `\u{1F9E0} ${action}: ${category}/${key} (${entryId})
27812
27832
  ${content}` }]
27813
27833
  };
27814
27834
  }
@@ -27828,7 +27848,8 @@ server.registerTool(
27828
27848
  async ({ query, category, from_date, to_date }) => {
27829
27849
  const data = readMemory();
27830
27850
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"));
27831
- const queryLower = query.toLowerCase();
27851
+ const normalize = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
27852
+ const queryTokens = normalize(query).split(" ").filter(Boolean);
27832
27853
  const results = lines.map((line) => {
27833
27854
  const trimmed = line.trim();
27834
27855
  const parts = trimmed.split("|");
@@ -27837,8 +27858,8 @@ server.registerTool(
27837
27858
  if (category && cat !== category) return null;
27838
27859
  if (from_date && date5 < from_date) return null;
27839
27860
  if (to_date && date5 > to_date) return null;
27840
- const searchStr = `${id} ${cat} ${key} ${content} ${file2} ${tags}`.toLowerCase();
27841
- if (!searchStr.includes(queryLower)) return null;
27861
+ const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
27862
+ if (!queryTokens.every((token) => searchStr.includes(token))) return null;
27842
27863
  return { id, cat, key, content, file: file2, tags, date: date5 };
27843
27864
  }).filter(Boolean);
27844
27865
  if (results.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-memory",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
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
@@ -306,7 +306,7 @@ server.registerTool(
306
306
  },
307
307
  async ({ category, key, content, file, tags }) => {
308
308
  const data = readMemory()
309
- const id = generateId()
309
+ const newId = generateId()
310
310
  const date = new Date().toISOString().split("T")[0]
311
311
  const lines = data.split("\n")
312
312
 
@@ -316,16 +316,40 @@ server.registerTool(
316
316
  headerIdx = lines.length - 1
317
317
  }
318
318
 
319
- const match = lines[headerIdx].match(/\[(\d+)\|/)
320
- const count = match ? parseInt(match[1]) : 0
321
- const newEntry = `${id}|${category}|${key}|${content}|${file || ""}|${tags || ""}|${date}`
319
+ // Find existing entry with same key (upsert)
320
+ let existingIdx = -1
321
+ let existingId = newId
322
+ for (let i = headerIdx + 1; i < lines.length; i++) {
323
+ const line = lines[i]
324
+ if (!line.startsWith(" ") || !line.includes("|")) continue
325
+ if (line.startsWith(" summaries:")) break
326
+ const parts = line.trim().split("|")
327
+ if (parts[2] === key) {
328
+ existingIdx = i
329
+ existingId = parts[0] // Preserve original ID
330
+ break
331
+ }
332
+ }
322
333
 
323
- lines.splice(headerIdx + 1, 0, ` ${newEntry}`)
324
- lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`)
334
+ const entryId = existingIdx !== -1 ? existingId : newId
335
+ const newEntry = `${entryId}|${category}|${key}|${content}|${file || ""}|${tags || ""}|${date}`
336
+ let action = "Guardado"
337
+
338
+ if (existingIdx !== -1) {
339
+ // Update existing entry
340
+ lines[existingIdx] = ` ${newEntry}`
341
+ action = "Actualizado"
342
+ } else {
343
+ // Create new entry
344
+ const match = lines[headerIdx].match(/\[(\d+)\|/)
345
+ const count = match ? parseInt(match[1]) : 0
346
+ lines.splice(headerIdx + 1, 0, ` ${newEntry}`)
347
+ lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`)
348
+ }
325
349
 
326
350
  writeMemory(lines.join("\n"))
327
351
  return {
328
- content: [{ type: "text" as const, text: `🧠 Guardado: ${category}/${key} (${id})\n${content}` }],
352
+ content: [{ type: "text" as const, text: `🧠 ${action}: ${category}/${key} (${entryId})\n${content}` }],
329
353
  }
330
354
  }
331
355
  )
@@ -356,7 +380,10 @@ server.registerTool(
356
380
  async ({ query, category, from_date, to_date }) => {
357
381
  const data = readMemory()
358
382
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"))
359
- const queryLower = query.toLowerCase()
383
+
384
+ // Normalize for fuzzy matching: hyphens/underscores → spaces, collapse whitespace
385
+ const normalize = (s: string) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim()
386
+ const queryTokens = normalize(query).split(" ").filter(Boolean)
360
387
 
361
388
  const results = lines
362
389
  .map((line) => {
@@ -367,8 +394,9 @@ server.registerTool(
367
394
  if (category && cat !== category) return null
368
395
  if (from_date && date < from_date) return null
369
396
  if (to_date && date > to_date) return null
370
- const searchStr = `${id} ${cat} ${key} ${content} ${file} ${tags}`.toLowerCase()
371
- if (!searchStr.includes(queryLower)) return null
397
+ const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file} ${tags}`)
398
+ // All query tokens must match (AND logic)
399
+ if (!queryTokens.every((token) => searchStr.includes(token))) return null
372
400
  return { id, cat, key, content, file, tags, date }
373
401
  })
374
402
  .filter(Boolean)