memorix 0.9.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.9.1] — 2026-02-25
6
+
7
+ ### Fixed
8
+ - **Defensive parameter coercion** — All 24 MCP tools now gracefully handle string-encoded arrays and numbers (e.g., `"[16]"` → `[16]`, `"20"` → `20`). Fixes compatibility with Claude Code CLI's known serialization bug ([#5504](https://github.com/anthropics/claude-code/issues/5504), [#26027](https://github.com/anthropics/claude-code/issues/26027)) and non-Anthropic models (GLM, etc.) that may produce incorrectly typed tool call arguments. Codex, Windsurf, and Cursor were already unaffected.
9
+
5
10
  ## [0.9.0] — 2026-02-24
6
11
 
7
12
  ### Added
package/dist/cli/index.js CHANGED
@@ -5337,6 +5337,58 @@ __export(server_exports2, {
5337
5337
  import { watch } from "fs";
5338
5338
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5339
5339
  import { z } from "zod";
5340
+ function coerceNumberArray(val) {
5341
+ if (Array.isArray(val)) return val.map(Number);
5342
+ if (typeof val === "string") {
5343
+ try {
5344
+ const parsed = JSON.parse(val);
5345
+ if (Array.isArray(parsed)) return parsed.map(Number);
5346
+ } catch {
5347
+ }
5348
+ }
5349
+ return [];
5350
+ }
5351
+ function coerceNumber(val, fallback) {
5352
+ if (typeof val === "number") return val;
5353
+ if (typeof val === "string") {
5354
+ const n = Number(val);
5355
+ if (!Number.isNaN(n)) return n;
5356
+ }
5357
+ return fallback;
5358
+ }
5359
+ function coerceStringArray(val) {
5360
+ if (Array.isArray(val)) return val.map(String);
5361
+ if (typeof val === "string") {
5362
+ try {
5363
+ const parsed = JSON.parse(val);
5364
+ if (Array.isArray(parsed)) return parsed.map(String);
5365
+ } catch {
5366
+ }
5367
+ }
5368
+ return [];
5369
+ }
5370
+ function coerceObjectArray(val) {
5371
+ if (Array.isArray(val)) {
5372
+ return val.map((item) => {
5373
+ if (typeof item === "string") {
5374
+ try {
5375
+ return JSON.parse(item);
5376
+ } catch {
5377
+ return item;
5378
+ }
5379
+ }
5380
+ return item;
5381
+ });
5382
+ }
5383
+ if (typeof val === "string") {
5384
+ try {
5385
+ const parsed = JSON.parse(val);
5386
+ if (Array.isArray(parsed)) return parsed;
5387
+ } catch {
5388
+ }
5389
+ }
5390
+ return [];
5391
+ }
5340
5392
  async function createMemorixServer(cwd, existingServer) {
5341
5393
  const project = detectProject(cwd);
5342
5394
  if (project.id === "__invalid__") {
@@ -5472,6 +5524,9 @@ async function createMemorixServer(cwd, existingServer) {
5472
5524
  }
5473
5525
  },
5474
5526
  async ({ entityName, type, title, narrative, facts, filesModified, concepts, topicKey }) => {
5527
+ const safeFacts = facts ? coerceStringArray(facts) : void 0;
5528
+ const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
5529
+ const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
5475
5530
  await graphManager.createEntities([
5476
5531
  { name: entityName, entityType: "auto", observations: [] }
5477
5532
  ]);
@@ -5487,9 +5542,9 @@ async function createMemorixServer(cwd, existingServer) {
5487
5542
  type,
5488
5543
  title,
5489
5544
  narrative,
5490
- facts,
5491
- filesModified,
5492
- concepts,
5545
+ facts: safeFacts,
5546
+ filesModified: safeFiles,
5547
+ concepts: safeConcepts,
5493
5548
  projectId: project.id,
5494
5549
  topicKey,
5495
5550
  sessionId
@@ -5497,11 +5552,11 @@ async function createMemorixServer(cwd, existingServer) {
5497
5552
  await graphManager.addObservations([
5498
5553
  { entityName, contents: [`[#${obs.id}] ${title}`] }
5499
5554
  ]);
5500
- const extracted = extractEntities([title, narrative, ...facts ?? []].join(" "));
5555
+ const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
5501
5556
  const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
5502
5557
  const enrichmentParts = [];
5503
- const autoFiles = obs.filesModified.filter((f) => !(filesModified ?? []).includes(f));
5504
- const autoConcepts = obs.concepts.filter((c) => !(concepts ?? []).includes(c));
5558
+ const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
5559
+ const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
5505
5560
  if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
5506
5561
  if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
5507
5562
  if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
@@ -5565,11 +5620,13 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
5565
5620
  }
5566
5621
  },
5567
5622
  async ({ query, limit, type, maxTokens, scope, since, until }) => {
5623
+ const safeLimit = limit != null ? coerceNumber(limit, 20) : void 0;
5624
+ const safeMaxTokens = maxTokens != null ? coerceNumber(maxTokens, 0) : void 0;
5568
5625
  const result = await compactSearch({
5569
5626
  query,
5570
- limit,
5627
+ limit: safeLimit,
5571
5628
  type,
5572
- maxTokens,
5629
+ maxTokens: safeMaxTokens,
5573
5630
  since,
5574
5631
  until,
5575
5632
  // Default to current project scope; 'global' removes the project filter
@@ -5602,11 +5659,14 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
5602
5659
  }
5603
5660
  },
5604
5661
  async ({ anchorId, depthBefore, depthAfter }) => {
5662
+ const safeAnchor = coerceNumber(anchorId, 0);
5663
+ const safeBefore = depthBefore != null ? coerceNumber(depthBefore, 3) : void 0;
5664
+ const safeAfter = depthAfter != null ? coerceNumber(depthAfter, 3) : void 0;
5605
5665
  const result = await compactTimeline(
5606
- anchorId,
5666
+ safeAnchor,
5607
5667
  void 0,
5608
- depthBefore,
5609
- depthAfter
5668
+ safeBefore,
5669
+ safeAfter
5610
5670
  );
5611
5671
  return {
5612
5672
  content: [
@@ -5628,12 +5688,13 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
5628
5688
  }
5629
5689
  },
5630
5690
  async ({ ids }) => {
5631
- const result = await compactDetail(ids);
5691
+ const safeIds = coerceNumberArray(ids);
5692
+ const result = await compactDetail(safeIds);
5632
5693
  return {
5633
5694
  content: [
5634
5695
  {
5635
5696
  type: "text",
5636
- text: result.documents.length > 0 ? result.formatted : `No observations found for IDs: ${ids.join(", ")}`
5697
+ text: result.documents.length > 0 ? result.formatted : `No observations found for IDs: ${safeIds.join(", ")}`
5637
5698
  }
5638
5699
  ]
5639
5700
  };
@@ -5735,7 +5796,8 @@ Archived memories can be restored manually if needed.` }]
5735
5796
  }
5736
5797
  },
5737
5798
  async ({ entities }) => {
5738
- const result = await graphManager.createEntities(entities);
5799
+ const safeEntities = coerceObjectArray(entities);
5800
+ const result = await graphManager.createEntities(safeEntities);
5739
5801
  return {
5740
5802
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
5741
5803
  };
@@ -5755,7 +5817,8 @@ Archived memories can be restored manually if needed.` }]
5755
5817
  }
5756
5818
  },
5757
5819
  async ({ relations }) => {
5758
- const result = await graphManager.createRelations(relations);
5820
+ const safeRelations = coerceObjectArray(relations);
5821
+ const result = await graphManager.createRelations(safeRelations);
5759
5822
  return {
5760
5823
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
5761
5824
  };
@@ -5774,7 +5837,8 @@ Archived memories can be restored manually if needed.` }]
5774
5837
  }
5775
5838
  },
5776
5839
  async ({ observations: observations2 }) => {
5777
- const result = await graphManager.addObservations(observations2);
5840
+ const safeObs = coerceObjectArray(observations2);
5841
+ const result = await graphManager.addObservations(safeObs);
5778
5842
  return {
5779
5843
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
5780
5844
  };
@@ -5790,7 +5854,8 @@ Archived memories can be restored manually if needed.` }]
5790
5854
  }
5791
5855
  },
5792
5856
  async ({ entityNames }) => {
5793
- await graphManager.deleteEntities(entityNames);
5857
+ const safeNames = coerceStringArray(entityNames);
5858
+ await graphManager.deleteEntities(safeNames);
5794
5859
  return {
5795
5860
  content: [{ type: "text", text: "Entities deleted successfully" }]
5796
5861
  };
@@ -5809,7 +5874,8 @@ Archived memories can be restored manually if needed.` }]
5809
5874
  }
5810
5875
  },
5811
5876
  async ({ deletions }) => {
5812
- await graphManager.deleteObservations(deletions);
5877
+ const safeDeletions = coerceObjectArray(deletions);
5878
+ await graphManager.deleteObservations(safeDeletions);
5813
5879
  return {
5814
5880
  content: [{ type: "text", text: "Observations deleted successfully" }]
5815
5881
  };
@@ -5829,7 +5895,8 @@ Archived memories can be restored manually if needed.` }]
5829
5895
  }
5830
5896
  },
5831
5897
  async ({ relations }) => {
5832
- await graphManager.deleteRelations(relations);
5898
+ const safeRelations = coerceObjectArray(relations);
5899
+ await graphManager.deleteRelations(safeRelations);
5833
5900
  return {
5834
5901
  content: [{ type: "text", text: "Relations deleted successfully" }]
5835
5902
  };
@@ -5875,7 +5942,8 @@ Archived memories can be restored manually if needed.` }]
5875
5942
  }
5876
5943
  },
5877
5944
  async ({ names }) => {
5878
- const graph = await graphManager.openNodes(names);
5945
+ const safeNames = coerceStringArray(names);
5946
+ const graph = await graphManager.openNodes(safeNames);
5879
5947
  return {
5880
5948
  content: [{ type: "text", text: JSON.stringify(graph, null, 2) }]
5881
5949
  };
@@ -6154,9 +6222,10 @@ ${skill.content}` }]
6154
6222
  }
6155
6223
  },
6156
6224
  async ({ action, threshold }) => {
6225
+ const safeThreshold = threshold != null ? coerceNumber(threshold, 0.45) : void 0;
6157
6226
  const { findConsolidationCandidates: findConsolidationCandidates2, executeConsolidation: executeConsolidation2 } = await Promise.resolve().then(() => (init_consolidation(), consolidation_exports));
6158
6227
  if (action === "preview") {
6159
- const clusters = await findConsolidationCandidates2(projectDir2, project.id, { threshold });
6228
+ const clusters = await findConsolidationCandidates2(projectDir2, project.id, { threshold: safeThreshold });
6160
6229
  if (clusters.length === 0) {
6161
6230
  return { content: [{ type: "text", text: "\u2705 No consolidation candidates found. Your memories are already clean!" }] };
6162
6231
  }
@@ -6172,7 +6241,7 @@ ${skill.content}` }]
6172
6241
  lines2.push(`> Run with \`action: "execute"\` to merge. This will remove **${totalMergeable}** duplicate observations.`);
6173
6242
  return { content: [{ type: "text", text: lines2.join("\n") }] };
6174
6243
  }
6175
- const result = await executeConsolidation2(projectDir2, project.id, { threshold });
6244
+ const result = await executeConsolidation2(projectDir2, project.id, { threshold: safeThreshold });
6176
6245
  if (result.clustersFound === 0) {
6177
6246
  return { content: [{ type: "text", text: "\u2705 No consolidation needed. Memories are already clean!" }] };
6178
6247
  }
@@ -6257,8 +6326,9 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
6257
6326
  }
6258
6327
  },
6259
6328
  async ({ limit }) => {
6329
+ const safeLimit = limit != null ? coerceNumber(limit, 3) : 3;
6260
6330
  const { getSessionContext: getSessionContext2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session(), session_exports));
6261
- const context = await getSessionContext2(projectDir2, project.id, limit ?? 3);
6331
+ const context = await getSessionContext2(projectDir2, project.id, safeLimit);
6262
6332
  const sessions = await listSessions2(projectDir2, project.id);
6263
6333
  const activeSessions = sessions.filter((s) => s.status === "active");
6264
6334
  const completedSessions = sessions.filter((s) => s.status === "completed");
@@ -6355,7 +6425,7 @@ ${json}
6355
6425
  }
6356
6426
  },
6357
6427
  async ({ port: dashboardPort }) => {
6358
- const portNum = dashboardPort || 3210;
6428
+ const portNum = dashboardPort != null ? coerceNumber(dashboardPort, 3210) : 3210;
6359
6429
  const url = `http://localhost:${portNum}`;
6360
6430
  if (dashboardRunning) {
6361
6431
  const { createConnection } = await import("net");