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 +5 -0
- package/dist/cli/index.js +94 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +94 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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, ...
|
|
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) => !(
|
|
5504
|
-
const autoConcepts = obs.concepts.filter((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
|
-
|
|
5666
|
+
safeAnchor,
|
|
5607
5667
|
void 0,
|
|
5608
|
-
|
|
5609
|
-
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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");
|