memtrace 0.3.23 → 0.3.24

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/README.md CHANGED
@@ -238,7 +238,7 @@ Uses **Structural Significance Budgeting** to surface the minimum set of changes
238
238
  For manual setup:
239
239
 
240
240
  ```bash
241
- claude plugin marketplace add syncable-dev/memtrace
241
+ claude plugin marketplace add https://github.com/syncable-dev/memtrace-public.git
242
242
  claude plugin install memtrace-skills@memtrace --scope user
243
243
  claude mcp add memtrace -- memtrace mcp -e MEMTRACE_ARCADEDB_BOLT_URL=bolt://localhost:7687
244
244
  ```
@@ -23,6 +23,15 @@ export declare function registerMcpInSettingsAt(settingsPath: string, memtraceBi
23
23
  * Never overwrites a malformed file.
24
24
  */
25
25
  export declare function enablePluginInSettingsAt(settingsPath: string): SettingsMutationResult;
26
+ export interface SettingsCleanupResult {
27
+ changed: boolean;
28
+ backupPath?: string;
29
+ }
30
+ /**
31
+ * Remove every Claude settings entry Memtrace may have written across
32
+ * installer versions and Claude marketplace schema changes.
33
+ */
34
+ export declare function removeClaudeSettingsEntriesAt(settingsPath: string): SettingsCleanupResult;
26
35
  /**
27
36
  * Full Claude Code plugin installation.
28
37
  *
@@ -5,7 +5,23 @@ import { execCommand, commandExists } from '../utils.js';
5
5
  import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
6
6
  const PLUGIN_NAME = 'memtrace-skills';
7
7
  const MARKETPLACE_NAME = 'memtrace';
8
- const MARKETPLACE_REPO = 'syncable-dev/memtrace';
8
+ const MARKETPLACE_REPO = 'syncable-dev/memtrace-public';
9
+ const MARKETPLACE_GIT_URL = `https://github.com/${MARKETPLACE_REPO}.git`;
10
+ const PLUGIN_KEY = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
11
+ const MARKETPLACE_SETTING_KEYS = [
12
+ MARKETPLACE_NAME,
13
+ 'syncable-dev-memtrace',
14
+ 'syncable-dev-memtrace-public',
15
+ ];
16
+ const MARKETPLACE_SETTING_CONTAINERS = [
17
+ 'extraKnownMarketplaces',
18
+ 'marketplaces',
19
+ 'pluginMarketplaces',
20
+ 'knownMarketplaces',
21
+ ];
22
+ function isRecord(value) {
23
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
24
+ }
9
25
  /**
10
26
  * Try to register the memtrace MCP server via `claude mcp add-json`.
11
27
  * Returns true on success, false on timeout/error/missing CLI.
@@ -87,7 +103,7 @@ async function tryClaudeCliInstall() {
87
103
  if (!hasClaude)
88
104
  return false;
89
105
  try {
90
- await execCommand(`claude plugin marketplace add ${MARKETPLACE_REPO}`);
106
+ await execCommand(`claude plugin marketplace add --scope user ${MARKETPLACE_GIT_URL}`);
91
107
  }
92
108
  catch {
93
109
  // Marketplace may already exist
@@ -101,8 +117,7 @@ async function tryClaudeCliInstall() {
101
117
  const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
102
118
  if (fs.existsSync(settingsPath)) {
103
119
  const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
104
- const key = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
105
- if (settings.enabledPlugins?.[key] === true) {
120
+ if (settings.enabledPlugins?.[PLUGIN_KEY] === true) {
106
121
  return true;
107
122
  }
108
123
  }
@@ -168,10 +183,10 @@ export function enablePluginInSettingsAt(settingsPath) {
168
183
  }
169
184
  const settings = (value ?? {});
170
185
  settings.enabledPlugins = settings.enabledPlugins ?? {};
171
- settings.enabledPlugins[`${PLUGIN_NAME}@${MARKETPLACE_NAME}`] = true;
186
+ settings.enabledPlugins[PLUGIN_KEY] = true;
172
187
  settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
173
188
  settings.extraKnownMarketplaces[MARKETPLACE_NAME] = {
174
- source: { source: 'github', repo: MARKETPLACE_REPO },
189
+ source: { source: 'git', url: MARKETPLACE_GIT_URL },
175
190
  };
176
191
  writeJsonAtomic(settingsPath, settings);
177
192
  return { registered: true };
@@ -183,6 +198,79 @@ function enablePluginInSettings() {
183
198
  const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
184
199
  enablePluginInSettingsAt(settingsFile);
185
200
  }
201
+ /**
202
+ * Remove every Claude settings entry Memtrace may have written across
203
+ * installer versions and Claude marketplace schema changes.
204
+ */
205
+ export function removeClaudeSettingsEntriesAt(settingsPath) {
206
+ const { value, corrupted, backupPath } = safeReadJson(settingsPath);
207
+ if (corrupted) {
208
+ console.warn(`memtrace: ${settingsPath} is malformed; backed up to ${backupPath}. Skipped Claude settings cleanup.`);
209
+ return { changed: false, backupPath };
210
+ }
211
+ if (!value)
212
+ return { changed: false };
213
+ const settings = value;
214
+ let changed = false;
215
+ const enabledPlugins = settings.enabledPlugins;
216
+ if (isRecord(enabledPlugins)) {
217
+ for (const key of Object.keys(enabledPlugins)) {
218
+ if (key === PLUGIN_KEY || (key.startsWith(`${PLUGIN_NAME}@`) && key.includes('memtrace'))) {
219
+ delete enabledPlugins[key];
220
+ changed = true;
221
+ }
222
+ }
223
+ if (Object.keys(enabledPlugins).length === 0)
224
+ delete settings.enabledPlugins;
225
+ }
226
+ const mcpServers = settings.mcpServers;
227
+ if (isRecord(mcpServers) && Object.hasOwn(mcpServers, 'memtrace')) {
228
+ delete mcpServers.memtrace;
229
+ changed = true;
230
+ if (Object.keys(mcpServers).length === 0)
231
+ delete settings.mcpServers;
232
+ }
233
+ for (const containerName of MARKETPLACE_SETTING_CONTAINERS) {
234
+ const container = settings[containerName];
235
+ if (!isRecord(container))
236
+ continue;
237
+ for (const key of MARKETPLACE_SETTING_KEYS) {
238
+ if (Object.hasOwn(container, key)) {
239
+ delete container[key];
240
+ changed = true;
241
+ }
242
+ }
243
+ if (Object.keys(container).length === 0)
244
+ delete settings[containerName];
245
+ }
246
+ if (changed)
247
+ writeJsonAtomic(settingsPath, settings);
248
+ return { changed };
249
+ }
250
+ function removeClaudeMarketplaceCacheDirs() {
251
+ const marketplacesRoot = path.join(os.homedir(), '.claude', 'plugins', 'marketplaces');
252
+ for (const entry of MARKETPLACE_SETTING_KEYS) {
253
+ const dir = path.join(marketplacesRoot, entry);
254
+ if (fs.existsSync(dir)) {
255
+ fs.rmSync(dir, { recursive: true, force: true });
256
+ }
257
+ }
258
+ }
259
+ function removeClaudeInstalledPluginMetadata() {
260
+ const installedPluginsFile = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
261
+ const { value, corrupted } = safeReadJson(installedPluginsFile);
262
+ if (corrupted || !value || !isRecord(value.plugins))
263
+ return;
264
+ let changed = false;
265
+ for (const key of Object.keys(value.plugins)) {
266
+ if (key === PLUGIN_KEY || (key.startsWith(`${PLUGIN_NAME}@`) && key.includes('memtrace'))) {
267
+ delete value.plugins[key];
268
+ changed = true;
269
+ }
270
+ }
271
+ if (changed)
272
+ writeJsonAtomic(installedPluginsFile, value);
273
+ }
186
274
  /**
187
275
  * Register the memtrace MCP server in Claude Code's settings.
188
276
  * This adds the MCP server config so Claude Code can connect to memtrace tools.
@@ -204,9 +292,13 @@ async function registerMcpServer(memtraceBinaryPath) {
204
292
  * 3. Register MCP server for memtrace tools
205
293
  */
206
294
  export async function installClaudePlugin(skills, memtraceBinaryPath) {
207
- // Step 1: Try CLI install
295
+ // Step 1: make sure Claude sees the public HTTPS marketplace source before
296
+ // the CLI attempts to clone/update it.
297
+ enablePluginInSettings();
298
+ removeClaudeMarketplaceCacheDirs();
299
+ // Step 2: Try CLI install
208
300
  await tryClaudeCliInstall();
209
- // Step 2: Find or create cache dir
301
+ // Step 3: Find or create cache dir
210
302
  let cacheDir = findCliInstalledCacheDir();
211
303
  if (cacheDir) {
212
304
  const orphanedFile = path.join(cacheDir, '.orphaned_at');
@@ -216,7 +308,7 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
216
308
  else {
217
309
  cacheDir = getClaudePluginCacheDir();
218
310
  }
219
- // Step 3: Clean up old versions
311
+ // Step 4: Clean up old versions
220
312
  const pluginRoot = getPluginCacheRoot();
221
313
  if (fs.existsSync(pluginRoot)) {
222
314
  const activeDirName = path.basename(cacheDir);
@@ -226,7 +318,7 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
226
318
  }
227
319
  }
228
320
  }
229
- // Step 4: Write skills
321
+ // Step 5: Write skills
230
322
  const skillsDir = path.join(cacheDir, 'skills');
231
323
  if (fs.existsSync(skillsDir)) {
232
324
  fs.rmSync(skillsDir, { recursive: true });
@@ -241,9 +333,9 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
241
333
  }
242
334
  writePluginManifest(cacheDir);
243
335
  enablePluginInSettings();
244
- // Step 5: Register MCP server
336
+ // Step 6: Register MCP server
245
337
  await registerMcpServer(memtraceBinaryPath);
246
- // Step 6: Write user-level skills for SDK-based integrations
338
+ // Step 7: Write user-level skills for SDK-based integrations
247
339
  writeUserLevelSkills(skills);
248
340
  return { cacheDir, skillCount: skills.length };
249
341
  }
@@ -281,18 +373,12 @@ export async function uninstallClaudePlugin() {
281
373
  const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
282
374
  if (fs.existsSync(settingsFile)) {
283
375
  try {
284
- const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
285
- const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
286
- if (settings.enabledPlugins && typeof settings.enabledPlugins === 'object') {
287
- delete settings.enabledPlugins[pluginKey];
288
- }
289
- if (settings.mcpServers && typeof settings.mcpServers === 'object') {
290
- delete settings.mcpServers['memtrace'];
291
- }
292
- fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
376
+ removeClaudeSettingsEntriesAt(settingsFile);
293
377
  }
294
378
  catch { /* */ }
295
379
  }
380
+ removeClaudeMarketplaceCacheDirs();
381
+ removeClaudeInstalledPluginMetadata();
296
382
  // Clean up user-level skills
297
383
  const userSkillsDir = path.join(os.homedir(), '.claude', 'skills');
298
384
  if (fs.existsSync(userSkillsDir)) {
@@ -16,6 +16,10 @@ Memtrace is the memory layer of the codebase. It has the full knowledge graph: e
16
16
 
17
17
  **97% better accuracy. 83% fewer wasted tokens. No exceptions.**
18
18
 
19
+ ## Value Tracking
20
+
21
+ Do not print usage receipts in normal answers. Memtrace records tool usage, graph facts, file references, and estimated context avoided internally. Users can inspect that in the local UI's Value panel.
22
+
19
23
  ## Parameter Types — Read This Before Calling Any Tool
20
24
 
21
25
  All memtrace MCP tools are **strictly typed**. Pass JSON numbers (not strings) for integer parameters.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memtrace",
3
- "version": "0.3.23",
3
+ "version": "0.3.24",
4
4
  "description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
5
5
  "keywords": [
6
6
  "mcp",
@@ -36,9 +36,9 @@
36
36
  "fs-extra": "^11.0.0"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@memtrace/darwin-arm64": "0.3.23",
40
- "@memtrace/linux-x64": "0.3.23",
41
- "@memtrace/win32-x64": "0.3.23"
39
+ "@memtrace/darwin-arm64": "0.3.24",
40
+ "@memtrace/linux-x64": "0.3.24",
41
+ "@memtrace/win32-x64": "0.3.24"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18"
@@ -18,6 +18,10 @@ Memtrace is the memory layer of the codebase. It has the full knowledge graph: e
18
18
 
19
19
  **97% better accuracy. 83% fewer wasted tokens. No exceptions for what's in the graph.**
20
20
 
21
+ ## Value Tracking
22
+
23
+ Do not print usage receipts in normal answers. Memtrace records tool usage, graph facts, file references, and estimated context avoided internally. Users can inspect that in the local UI's Value panel.
24
+
21
25
  ## What Memtrace actually indexes
22
26
 
23
27
  Memtrace's hybrid search = **BM25 over symbol metadata** (name, signature, file_path, kind) **+ semantic vector search over embedded code bodies** (first ~1500 chars of every Function / Method / Class / Struct / Interface body), fused via Reciprocal Rank Fusion.
package/uninstall.js CHANGED
@@ -11,6 +11,18 @@ const { execSync, spawnSync } = require("child_process");
11
11
  const PLUGIN_NAME = "memtrace-skills";
12
12
  const MARKETPLACE_NAME = "memtrace";
13
13
  const MCP_SERVER_NAME = "memtrace";
14
+ const PLUGIN_KEY = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
15
+ const MARKETPLACE_SETTING_KEYS = [
16
+ MARKETPLACE_NAME,
17
+ "syncable-dev-memtrace",
18
+ "syncable-dev-memtrace-public",
19
+ ];
20
+ const MARKETPLACE_SETTING_CONTAINERS = [
21
+ "extraKnownMarketplaces",
22
+ "marketplaces",
23
+ "pluginMarketplaces",
24
+ "knownMarketplaces",
25
+ ];
14
26
 
15
27
  const SKILL_NAMES = [
16
28
  // Commands
@@ -71,13 +83,17 @@ function writeSettings(settings) {
71
83
  fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
72
84
  }
73
85
 
86
+ function isRecord(value) {
87
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88
+ }
89
+
74
90
  // ── Remove MCP server ───────────────────────────────────────────────────────
75
91
 
76
92
  function removeMcpServer() {
77
93
  const settings = readSettings();
78
94
  let changed = false;
79
95
 
80
- if (settings.mcpServers && settings.mcpServers[MCP_SERVER_NAME]) {
96
+ if (isRecord(settings.mcpServers) && settings.mcpServers[MCP_SERVER_NAME]) {
81
97
  delete settings.mcpServers[MCP_SERVER_NAME];
82
98
  if (Object.keys(settings.mcpServers).length === 0) {
83
99
  delete settings.mcpServers;
@@ -94,29 +110,82 @@ function removeMcpServer() {
94
110
  function removePlugin() {
95
111
  const settings = readSettings();
96
112
  let changed = false;
97
- const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
98
113
 
99
- if (settings.enabledPlugins && settings.enabledPlugins[pluginKey] !== undefined) {
100
- delete settings.enabledPlugins[pluginKey];
114
+ if (isRecord(settings.enabledPlugins)) {
115
+ for (const key of Object.keys(settings.enabledPlugins)) {
116
+ if (key === PLUGIN_KEY || (key.startsWith(`${PLUGIN_NAME}@`) && key.includes("memtrace"))) {
117
+ delete settings.enabledPlugins[key];
118
+ changed = true;
119
+ }
120
+ }
101
121
  if (Object.keys(settings.enabledPlugins).length === 0) {
102
122
  delete settings.enabledPlugins;
103
123
  }
104
- changed = true;
105
- console.log(`memtrace: removed plugin ${pluginKey}`);
124
+ if (changed) console.log(`memtrace: removed plugin ${PLUGIN_KEY}`);
106
125
  }
107
126
 
108
- if (settings.extraKnownMarketplaces && settings.extraKnownMarketplaces[MARKETPLACE_NAME]) {
109
- delete settings.extraKnownMarketplaces[MARKETPLACE_NAME];
110
- if (Object.keys(settings.extraKnownMarketplaces).length === 0) {
111
- delete settings.extraKnownMarketplaces;
127
+ let removedMarketplace = false;
128
+ for (const containerName of MARKETPLACE_SETTING_CONTAINERS) {
129
+ const container = settings[containerName];
130
+ if (!isRecord(container)) continue;
131
+ for (const key of MARKETPLACE_SETTING_KEYS) {
132
+ if (Object.prototype.hasOwnProperty.call(container, key)) {
133
+ delete container[key];
134
+ changed = true;
135
+ removedMarketplace = true;
136
+ }
112
137
  }
113
- changed = true;
138
+ if (Object.keys(container).length === 0) {
139
+ delete settings[containerName];
140
+ }
141
+ }
142
+
143
+ if (removedMarketplace) {
114
144
  console.log(`memtrace: removed marketplace ${MARKETPLACE_NAME}`);
115
145
  }
116
146
 
117
147
  if (changed) writeSettings(settings);
118
148
  }
119
149
 
150
+ function removeMarketplaceCaches() {
151
+ const marketplacesRoot = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
152
+ for (const entry of MARKETPLACE_SETTING_KEYS) {
153
+ const dir = path.join(marketplacesRoot, entry);
154
+ if (fs.existsSync(dir)) {
155
+ try {
156
+ fs.rmSync(dir, { recursive: true, force: true });
157
+ console.log(`memtrace: removed marketplace cache ${entry}`);
158
+ } catch (e) {
159
+ console.warn(`memtrace: failed to remove marketplace cache ${entry}: ${e.message}`);
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ function removeInstalledPluginMetadata() {
166
+ const installedPluginsFile = path.join(os.homedir(), ".claude", "plugins", "installed_plugins.json");
167
+ if (!fs.existsSync(installedPluginsFile)) return;
168
+ let data;
169
+ try {
170
+ data = JSON.parse(fs.readFileSync(installedPluginsFile, "utf-8"));
171
+ } catch {
172
+ return;
173
+ }
174
+ if (!isRecord(data.plugins)) return;
175
+
176
+ let changed = false;
177
+ for (const key of Object.keys(data.plugins)) {
178
+ if (key === PLUGIN_KEY || (key.startsWith(`${PLUGIN_NAME}@`) && key.includes("memtrace"))) {
179
+ delete data.plugins[key];
180
+ changed = true;
181
+ }
182
+ }
183
+ if (changed) {
184
+ fs.writeFileSync(installedPluginsFile, JSON.stringify(data, null, 2) + "\n");
185
+ console.log("memtrace: removed plugin metadata");
186
+ }
187
+ }
188
+
120
189
  // ── Try Claude CLI uninstall ────────────────────────────────────────────────
121
190
 
122
191
  function tryClaudeCliUninstall() {
@@ -140,11 +209,15 @@ function tryClaudeCliUninstall() {
140
209
 
141
210
  // ── Main uninstall logic ───────────────────────────────────────────────────
142
211
 
143
- function legacyCleanup() {
212
+ function legacyCleanup(options = {}) {
144
213
  try { removeSkills(); } catch (e) { console.warn(`memtrace: skill removal failed: ${e.message}`); }
145
- try { tryClaudeCliUninstall(); } catch { /* silent */ }
214
+ if (!options.skipClaudeCli) {
215
+ try { tryClaudeCliUninstall(); } catch { /* silent */ }
216
+ }
146
217
  try { removePlugin(); } catch (e) { console.warn(`memtrace: plugin removal failed: ${e.message}`); }
147
218
  try { removeMcpServer(); } catch (e) { console.warn(`memtrace: MCP server removal failed: ${e.message}`); }
219
+ try { removeMarketplaceCaches(); } catch (e) { console.warn(`memtrace: marketplace cache cleanup failed: ${e.message}`); }
220
+ try { removeInstalledPluginMetadata(); } catch (e) { console.warn(`memtrace: installed plugin metadata cleanup failed: ${e.message}`); }
148
221
  }
149
222
 
150
223
  function run() {
@@ -159,6 +232,9 @@ function run() {
159
232
  if (result.status !== 0) {
160
233
  console.warn(`memtrace: installer uninstall exited ${result.status}; falling back to legacy cleanup`);
161
234
  legacyCleanup();
235
+ } else {
236
+ // Older bundled uninstallers did not remove every marketplace shape.
237
+ legacyCleanup({ skipClaudeCli: true });
162
238
  }
163
239
  } else {
164
240
  // No bundled installer — use legacy JS-only cleanup (Claude-only)