memtrace 0.3.31 → 0.3.34
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 +52 -15
- package/bin/memtrace.js +19 -7
- package/install.js +13 -2
- package/installer/dist/commands/doctor.js +97 -1
- package/installer/dist/index.js +2 -2
- package/installer/dist/transformers/claude.js +14 -0
- package/installer/dist/transformers/codex.js +7 -0
- package/installer/dist/transformers/cursor.js +7 -0
- package/installer/dist/transformers/hermes.d.ts +5 -0
- package/installer/dist/transformers/hermes.js +136 -0
- package/installer/dist/transformers/index.d.ts +6 -1
- package/installer/dist/transformers/index.js +16 -2
- package/installer/dist/transformers/kiro.d.ts +2 -0
- package/installer/dist/transformers/kiro.js +69 -0
- package/installer/dist/transformers/opencode.d.ts +2 -0
- package/installer/dist/transformers/opencode.js +46 -0
- package/installer/dist/transformers/shared.d.ts +20 -0
- package/installer/dist/transformers/shared.js +124 -0
- package/installer/dist/transformers/types.d.ts +3 -2
- package/installer/dist/transformers/vscode.d.ts +3 -0
- package/installer/dist/transformers/vscode.js +53 -0
- package/installer/dist/transformers/windsurf.d.ts +3 -0
- package/installer/dist/transformers/windsurf.js +43 -0
- package/installer/package.json +1 -0
- package/installer/skills/commands/memtrace-search.md +17 -0
- package/installer/skills/workflows/memtrace-first.md +25 -14
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -24,11 +24,11 @@ Memtrace gives coding agents something they've never had: **structural memory**.
|
|
|
24
24
|
Index once. Every agent query after that resolves through graph traversal — callers, callees, implementations, imports, blast radius, temporal evolution — in milliseconds, with zero token waste.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npm install -g memtrace # binary +
|
|
27
|
+
npm install -g memtrace # binary + 17 skills + MCP server — one command
|
|
28
28
|
memtrace start # launches the graph database and auto-indexes the current project
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
That's it. Run `memtrace start` from your project root — it spins up the graph database and kicks off indexing automatically. Claude and
|
|
31
|
+
That's it. Run `memtrace start` from your project root — it spins up the graph database and kicks off indexing automatically. Claude, Cursor, Codex, Windsurf, VS Code/Copilot, Hermes, OpenCode, and Kiro pick up the skills/guidance and MCP tools automatically.
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -175,9 +175,9 @@ Memtrace exposes a full structural toolkit via the [Model Context Protocol](http
|
|
|
175
175
|
</tr>
|
|
176
176
|
</table>
|
|
177
177
|
|
|
178
|
-
##
|
|
178
|
+
## 17 Agent Skills
|
|
179
179
|
|
|
180
|
-
Memtrace ships skills that teach
|
|
180
|
+
Memtrace ships skills that teach agents *how* to use the graph. They fire automatically based on what you ask — no prompt engineering required.
|
|
181
181
|
|
|
182
182
|
| | Skill | You say... |
|
|
183
183
|
|:--|:------|:-----------|
|
|
@@ -189,8 +189,9 @@ Memtrace ships skills that teach Claude *how* to use the graph. They fire automa
|
|
|
189
189
|
| **Architecture** | `memtrace-graph` | _"show me the architecture"_, _"find bottlenecks"_ |
|
|
190
190
|
| **APIs** | `memtrace-api-topology` | _"list API endpoints"_, _"service dependencies"_ |
|
|
191
191
|
| **Index** | `memtrace-index` | _"index this project"_, _"parse this codebase"_ |
|
|
192
|
+
| **Co-change** | `memtrace-cochange` | _"what else changes with this"_, _"hidden coupling"_ |
|
|
192
193
|
|
|
193
|
-
Plus **
|
|
194
|
+
Plus **8 workflow skills** that chain multiple tools with decision logic:
|
|
194
195
|
|
|
195
196
|
| Skill | You say... |
|
|
196
197
|
|:------|:-----------|
|
|
@@ -198,6 +199,10 @@ Plus **4 workflow skills** that chain multiple tools with decision logic:
|
|
|
198
199
|
| `memtrace-change-impact-analysis` | _"what will break if I refactor this"_ |
|
|
199
200
|
| `memtrace-incident-investigation` | _"something broke"_, _"root cause analysis"_ |
|
|
200
201
|
| `memtrace-refactoring-guide` | _"help me refactor"_, _"clean up tech debt"_ |
|
|
202
|
+
| `memtrace-first` | _"use Memtrace before file search"_ |
|
|
203
|
+
| `memtrace-continuous-memory` | _"watch this repo"_, _"keep the graph fresh"_ |
|
|
204
|
+
| `memtrace-episode-replay` | _"why does this code look this way"_ |
|
|
205
|
+
| `memtrace-session-continuity` | _"continue"_, _"catch me up"_ |
|
|
201
206
|
|
|
202
207
|
## Temporal Engine
|
|
203
208
|
|
|
@@ -216,24 +221,27 @@ Uses **Structural Significance Budgeting** to surface the minimum set of changes
|
|
|
216
221
|
|
|
217
222
|
## Compatibility
|
|
218
223
|
|
|
219
|
-
| Editor / Agent | MCP Tools (25+) | Skills
|
|
224
|
+
| Editor / Agent | MCP Tools (25+) | Skills / Guidance | Install |
|
|
220
225
|
|:---------------|:---------------:|:-----------:|:--------|
|
|
221
226
|
| **Claude Code** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
222
227
|
| **Claude Desktop** | ✅ | ✅ | Automatic — shared with Claude Code |
|
|
223
228
|
| **Cursor** (v2.4+) | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
224
|
-
| **
|
|
225
|
-
| **
|
|
229
|
+
| **Codex CLI** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
230
|
+
| **Windsurf** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
231
|
+
| **VS Code (Copilot)** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
232
|
+
| **Hermes** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
233
|
+
| **OpenCode** | ✅ | ✅ | `npm install -g memtrace` — fully automatic |
|
|
234
|
+
| **Kiro** | ✅ | Steering | `npm install -g memtrace` — fully automatic |
|
|
226
235
|
| **Cline / Roo Code** | ✅ | — | Add MCP server manually |
|
|
227
|
-
| **Codex CLI** | ✅ | Coming soon | Add MCP server manually |
|
|
228
236
|
| **Any MCP client** | ✅ | — | Add MCP server manually |
|
|
229
237
|
|
|
230
|
-
> **MCP tools** work with any editor or agent that supports the [Model Context Protocol](https://modelcontextprotocol.io). **Skills** are workflow prompts that teach the agent *how* to chain tools
|
|
238
|
+
> **MCP tools** work with any editor or agent that supports the [Model Context Protocol](https://modelcontextprotocol.io). **Skills** are workflow prompts that teach the agent *how* to chain tools. Kiro does not use `SKILL.md`, so Memtrace writes equivalent auto steering files instead.
|
|
231
239
|
|
|
232
240
|
## Setup
|
|
233
241
|
|
|
234
242
|
### Claude Code + Claude Desktop
|
|
235
243
|
|
|
236
|
-
`npm install -g memtrace` handles everything automatically — binary,
|
|
244
|
+
`npm install -g memtrace` handles everything automatically — binary, 17 skills, MCP server, plugin, and marketplace all register in one command for both Claude Code and Claude Desktop.
|
|
237
245
|
|
|
238
246
|
For manual setup:
|
|
239
247
|
|
|
@@ -249,17 +257,42 @@ Cursor **v2.4+** supports Agent Skills natively, and `npm install -g memtrace` h
|
|
|
249
257
|
|
|
250
258
|
What the installer writes:
|
|
251
259
|
- **MCP server** → `~/.cursor/mcp.json` (global — works in every project you open)
|
|
252
|
-
- **
|
|
260
|
+
- **17 skills/workflows** → `~/.cursor/skills/memtrace-*/SKILL.md`
|
|
253
261
|
|
|
254
262
|
For a **project-local** install (so the skills travel with your repo and teammates get them on clone), run inside the project:
|
|
255
263
|
|
|
256
264
|
```bash
|
|
257
|
-
memtrace install --only cursor --local
|
|
265
|
+
npx memtrace-skills install --only cursor --local
|
|
258
266
|
```
|
|
259
267
|
|
|
260
|
-
###
|
|
268
|
+
### Codex, Windsurf, VS Code, Hermes, OpenCode, and Kiro
|
|
261
269
|
|
|
262
|
-
|
|
270
|
+
The installer also writes skills/guidance and MCP configuration for the newer agent surfaces:
|
|
271
|
+
|
|
272
|
+
| Agent | Global skills / guidance | Global MCP config | Project-local support |
|
|
273
|
+
|:------|:-------------------------|:------------------|:----------------------|
|
|
274
|
+
| **Codex** | `~/.agents/skills/` | `~/.codex/config.toml` | `.agents/skills/`, `.codex/config.toml` |
|
|
275
|
+
| **Windsurf** | `~/.codeium/windsurf/skills/` | `~/.codeium/windsurf/mcp_config.json` | `.windsurf/skills/`; MCP remains user-level |
|
|
276
|
+
| **VS Code / Copilot** | `~/.copilot/skills/` | VS Code user `mcp.json` | `.github/skills/`, `.vscode/mcp.json` |
|
|
277
|
+
| **Hermes** | `~/.hermes/skills/` | `~/.hermes/config.yaml` | user-level only |
|
|
278
|
+
| **OpenCode** | `~/.config/opencode/skills/` | `~/.config/opencode/opencode.json` | `.opencode/skills/`, `opencode.json` |
|
|
279
|
+
| **Kiro** | `~/.kiro/steering/` | `~/.kiro/settings/mcp.json` | `.kiro/steering/`, `.kiro/settings/mcp.json` |
|
|
280
|
+
|
|
281
|
+
Install only selected integrations:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npx memtrace-skills install --only codex,windsurf,vscode,hermes,opencode,kiro
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Install project-local config where supported:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npx memtrace-skills install --only codex,vscode,opencode,kiro --local
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Other MCP Clients
|
|
294
|
+
|
|
295
|
+
For Cline, Roo Code, or any client that only needs MCP tools, add this server manually:
|
|
263
296
|
|
|
264
297
|
```json
|
|
265
298
|
{
|
|
@@ -280,6 +313,10 @@ After `npm install -g memtrace`, add the MCP server to your editor's config:
|
|
|
280
313
|
|:-------|:------------|
|
|
281
314
|
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` |
|
|
282
315
|
| **VS Code (Copilot)** | `.vscode/mcp.json` in your project root |
|
|
316
|
+
| **Codex** | `~/.codex/config.toml` or `.codex/config.toml` |
|
|
317
|
+
| **Hermes** | `~/.hermes/config.yaml` |
|
|
318
|
+
| **OpenCode** | `~/.config/opencode/opencode.json` or project `opencode.json` |
|
|
319
|
+
| **Kiro** | `~/.kiro/settings/mcp.json` or `.kiro/settings/mcp.json` |
|
|
283
320
|
| **Cline** | Cline MCP settings in the extension panel |
|
|
284
321
|
|
|
285
322
|
</details>
|
package/bin/memtrace.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require("path");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const { spawnSync, spawn } = require("child_process");
|
|
8
8
|
const { getBinaryPath } = require("../install.js");
|
|
9
|
+
const { platformBinary, spawnOptionsForPlatform } = require("../lib/spawn-helper");
|
|
9
10
|
|
|
10
11
|
// ── Handle `memtrace uninstall` before delegating to the Rust binary ────────
|
|
11
12
|
// npm v7+ does NOT fire preuninstall hooks for global packages (npm/cli#3042).
|
|
@@ -27,14 +28,20 @@ const args = process.argv.slice(2);
|
|
|
27
28
|
// which now resolves to the freshly-installed shim, not this old one.
|
|
28
29
|
|
|
29
30
|
if (args[0] === "install" || args[0] === "update" || args[0] === "upgrade") {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// `npm.cmd` on win32 needs `shell: true` since the CVE-2024-27980
|
|
32
|
+
// mitigation in Node 18.20+ / 20.12+ / 21.7+. The helpers in
|
|
33
|
+
// `lib/spawn-helper.js` are unit + property tested.
|
|
34
|
+
const npmCmd = platformBinary("npm", process.platform);
|
|
35
|
+
const memtraceCmd = platformBinary("memtrace", process.platform);
|
|
32
36
|
|
|
33
37
|
process.stdout.write("memtrace: fetching latest from npm registry…\n");
|
|
34
38
|
const installResult = spawnSync(
|
|
35
39
|
npmCmd,
|
|
36
40
|
["install", "-g", "memtrace@latest"],
|
|
37
|
-
|
|
41
|
+
spawnOptionsForPlatform(process.platform, {
|
|
42
|
+
stdio: "inherit",
|
|
43
|
+
env: process.env,
|
|
44
|
+
})
|
|
38
45
|
);
|
|
39
46
|
|
|
40
47
|
if (installResult.error) {
|
|
@@ -60,10 +67,15 @@ if (args[0] === "install" || args[0] === "update" || args[0] === "upgrade") {
|
|
|
60
67
|
process.stdout.write(
|
|
61
68
|
`memtrace: upgrade complete — running 'memtrace ${rest.join(" ")}'\n`
|
|
62
69
|
);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
// memtrace.cmd on Windows needs the same shell:true handling.
|
|
71
|
+
const runResult = spawnSync(
|
|
72
|
+
memtraceCmd,
|
|
73
|
+
rest,
|
|
74
|
+
spawnOptionsForPlatform(process.platform, {
|
|
75
|
+
stdio: "inherit",
|
|
76
|
+
env: process.env,
|
|
77
|
+
})
|
|
78
|
+
);
|
|
67
79
|
if (runResult.error) {
|
|
68
80
|
console.error(`memtrace: failed to chain command — ${runResult.error.message}`);
|
|
69
81
|
process.exit(1);
|
package/install.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require("os");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const { spawnSync } = require("child_process");
|
|
8
|
+
const { platformBinary, spawnOptionsForPlatform } = require("./lib/spawn-helper");
|
|
8
9
|
|
|
9
10
|
// ── Platform binary resolution (preserved from legacy) ───────────────────────
|
|
10
11
|
|
|
@@ -68,10 +69,20 @@ function selfHealPlatformPackage() {
|
|
|
68
69
|
`memtrace: optional platform dep ${pkg} was not installed; ` +
|
|
69
70
|
`running 'npm install ${versioned}' to fetch it…`
|
|
70
71
|
);
|
|
72
|
+
// On Windows, Node.js 18.20+ / 20.12+ / 21.7+ refuse to spawn `.cmd`
|
|
73
|
+
// and `.bat` files without `shell: true` as part of the
|
|
74
|
+
// CVE-2024-27980 mitigation. The platform-aware helpers in
|
|
75
|
+
// `lib/spawn-helper.js` are unit + property tested so the shell
|
|
76
|
+
// flag is set IFF process.platform === "win32" — see
|
|
77
|
+
// `test/spawn-helper.test.js` for the full regression coverage.
|
|
71
78
|
const result = spawnSync(
|
|
72
|
-
|
|
79
|
+
platformBinary("npm", process.platform),
|
|
73
80
|
["install", "--no-save", versioned],
|
|
74
|
-
|
|
81
|
+
spawnOptionsForPlatform(process.platform, {
|
|
82
|
+
cwd: __dirname,
|
|
83
|
+
stdio: "inherit",
|
|
84
|
+
env: process.env,
|
|
85
|
+
})
|
|
75
86
|
);
|
|
76
87
|
if (result.status !== 0) {
|
|
77
88
|
console.warn(
|
|
@@ -3,6 +3,7 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { commandExists, execCommand } from '../utils.js';
|
|
5
5
|
import { safeReadJson } from '../fs-safe.js';
|
|
6
|
+
import { ALL_TRANSFORMERS } from '../transformers/index.js';
|
|
6
7
|
async function checkBinary() {
|
|
7
8
|
if (!(await commandExists('memtrace')))
|
|
8
9
|
return { binaryOk: false };
|
|
@@ -28,18 +29,58 @@ function countMemtraceSkills(dir) {
|
|
|
28
29
|
.filter(e => fs.existsSync(path.join(dir, e, 'SKILL.md')))
|
|
29
30
|
.length;
|
|
30
31
|
}
|
|
32
|
+
function countMemtraceMarkdown(dir) {
|
|
33
|
+
if (!fs.existsSync(dir))
|
|
34
|
+
return 0;
|
|
35
|
+
return fs.readdirSync(dir)
|
|
36
|
+
.filter(e => e.startsWith('memtrace-') && e.endsWith('.md'))
|
|
37
|
+
.length;
|
|
38
|
+
}
|
|
31
39
|
function mcpHasMemtrace(file) {
|
|
32
40
|
const { value } = safeReadJson(file);
|
|
33
41
|
if (!value)
|
|
34
42
|
return false;
|
|
35
43
|
return Boolean(value.mcpServers && value.mcpServers['memtrace']);
|
|
36
44
|
}
|
|
45
|
+
function vscodeMcpHasMemtrace(file) {
|
|
46
|
+
const { value } = safeReadJson(file);
|
|
47
|
+
if (!value)
|
|
48
|
+
return false;
|
|
49
|
+
return Boolean(value.servers && value.servers['memtrace']);
|
|
50
|
+
}
|
|
51
|
+
function opencodeMcpHasMemtrace(file) {
|
|
52
|
+
const { value } = safeReadJson(file);
|
|
53
|
+
if (!value)
|
|
54
|
+
return false;
|
|
55
|
+
return Boolean(value.mcp && value.mcp['memtrace']);
|
|
56
|
+
}
|
|
37
57
|
function codexMcpHasMemtrace(file) {
|
|
38
58
|
if (!fs.existsSync(file))
|
|
39
59
|
return false;
|
|
40
60
|
const raw = fs.readFileSync(file, 'utf-8');
|
|
41
61
|
return /^\s*\[mcp_servers\.(?:"memtrace"|memtrace)\]\s*$/m.test(raw);
|
|
42
62
|
}
|
|
63
|
+
function hermesMcpHasMemtrace(file) {
|
|
64
|
+
if (!fs.existsSync(file))
|
|
65
|
+
return false;
|
|
66
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
67
|
+
return /^mcp_servers:\s*$(?:[\s\S]*?)^\s{2}memtrace:\s*$/m.test(raw);
|
|
68
|
+
}
|
|
69
|
+
function opencodeConfigDir() {
|
|
70
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
71
|
+
return path.join(xdg, 'opencode');
|
|
72
|
+
}
|
|
73
|
+
function vscodeUserMcpPath() {
|
|
74
|
+
if (process.platform === 'win32') {
|
|
75
|
+
const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
|
|
76
|
+
return path.join(appData, 'Code', 'User', 'mcp.json');
|
|
77
|
+
}
|
|
78
|
+
if (process.platform === 'darwin') {
|
|
79
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
80
|
+
}
|
|
81
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
82
|
+
return path.join(xdg, 'Code', 'User', 'mcp.json');
|
|
83
|
+
}
|
|
43
84
|
function checkAgent(agent) {
|
|
44
85
|
if (agent === 'claude') {
|
|
45
86
|
const skillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
@@ -63,6 +104,61 @@ function checkAgent(agent) {
|
|
|
63
104
|
mcpConfigPath,
|
|
64
105
|
};
|
|
65
106
|
}
|
|
107
|
+
if (agent === 'windsurf') {
|
|
108
|
+
const skillsDir = path.join(os.homedir(), '.codeium', 'windsurf', 'skills');
|
|
109
|
+
const mcpConfigPath = path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
110
|
+
return {
|
|
111
|
+
agent,
|
|
112
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
113
|
+
skillsDir,
|
|
114
|
+
mcpRegistered: mcpHasMemtrace(mcpConfigPath),
|
|
115
|
+
mcpConfigPath,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (agent === 'vscode') {
|
|
119
|
+
const skillsDir = path.join(os.homedir(), '.copilot', 'skills');
|
|
120
|
+
const mcpConfigPath = vscodeUserMcpPath();
|
|
121
|
+
return {
|
|
122
|
+
agent,
|
|
123
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
124
|
+
skillsDir,
|
|
125
|
+
mcpRegistered: vscodeMcpHasMemtrace(mcpConfigPath),
|
|
126
|
+
mcpConfigPath,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (agent === 'hermes') {
|
|
130
|
+
const skillsDir = path.join(os.homedir(), '.hermes', 'skills');
|
|
131
|
+
const mcpConfigPath = path.join(os.homedir(), '.hermes', 'config.yaml');
|
|
132
|
+
return {
|
|
133
|
+
agent,
|
|
134
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
135
|
+
skillsDir,
|
|
136
|
+
mcpRegistered: hermesMcpHasMemtrace(mcpConfigPath),
|
|
137
|
+
mcpConfigPath,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (agent === 'opencode') {
|
|
141
|
+
const skillsDir = path.join(opencodeConfigDir(), 'skills');
|
|
142
|
+
const mcpConfigPath = path.join(opencodeConfigDir(), 'opencode.json');
|
|
143
|
+
return {
|
|
144
|
+
agent,
|
|
145
|
+
skillsFound: countMemtraceSkills(skillsDir),
|
|
146
|
+
skillsDir,
|
|
147
|
+
mcpRegistered: opencodeMcpHasMemtrace(mcpConfigPath),
|
|
148
|
+
mcpConfigPath,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (agent === 'kiro') {
|
|
152
|
+
const skillsDir = path.join(os.homedir(), '.kiro', 'steering');
|
|
153
|
+
const mcpConfigPath = path.join(os.homedir(), '.kiro', 'settings', 'mcp.json');
|
|
154
|
+
return {
|
|
155
|
+
agent,
|
|
156
|
+
skillsFound: countMemtraceMarkdown(skillsDir),
|
|
157
|
+
skillsDir,
|
|
158
|
+
mcpRegistered: mcpHasMemtrace(mcpConfigPath),
|
|
159
|
+
mcpConfigPath,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
66
162
|
const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
|
|
67
163
|
const mcpConfigPath = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
68
164
|
return {
|
|
@@ -77,7 +173,7 @@ export async function runDoctorChecks() {
|
|
|
77
173
|
const binary = await checkBinary();
|
|
78
174
|
return {
|
|
79
175
|
...binary,
|
|
80
|
-
agents:
|
|
176
|
+
agents: ALL_TRANSFORMERS.map(t => checkAgent(t.name)),
|
|
81
177
|
};
|
|
82
178
|
}
|
|
83
179
|
export function formatReport(r) {
|
package/installer/dist/index.js
CHANGED
|
@@ -13,8 +13,8 @@ function parseOnly(val) {
|
|
|
13
13
|
program
|
|
14
14
|
.command('install')
|
|
15
15
|
.description('Install memtrace skills and register MCP for selected agents')
|
|
16
|
-
.option('--only <agents>', 'comma-separated agent names (claude,cursor,codex)', parseOnly)
|
|
17
|
-
.option('--local', 'install into the current project
|
|
16
|
+
.option('--only <agents>', 'comma-separated agent names (claude,cursor,codex,windsurf,vscode,hermes,opencode,kiro)', parseOnly)
|
|
17
|
+
.option('--local', 'install into the current project where the selected agent supports project scope', false)
|
|
18
18
|
.option('--global', 'install globally (~/.claude/, ~/.cursor/, ~/.agents/) [default]', false)
|
|
19
19
|
.option('--skip-mcp', 'write skills only, skip MCP server registration', false)
|
|
20
20
|
.option('--repair', 'alias for install — run after fixing a corrupt settings file')
|
|
@@ -344,6 +344,13 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
|
|
|
344
344
|
*/
|
|
345
345
|
function writeUserLevelSkills(skills) {
|
|
346
346
|
const userSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
347
|
+
if (fs.existsSync(userSkillsDir)) {
|
|
348
|
+
for (const entry of fs.readdirSync(userSkillsDir)) {
|
|
349
|
+
if (entry.startsWith('memtrace-')) {
|
|
350
|
+
fs.rmSync(path.join(userSkillsDir, entry), { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
347
354
|
for (const skill of skills) {
|
|
348
355
|
const skillName = skill.filename.replace(/\.md$/, '');
|
|
349
356
|
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
@@ -432,6 +439,13 @@ export const claudeTransformer = {
|
|
|
432
439
|
}
|
|
433
440
|
// Local scope: write skills into <cwd>/.claude/skills/
|
|
434
441
|
const skillsDir = path.join(ctx.cwd, '.claude', 'skills');
|
|
442
|
+
if (fs.existsSync(skillsDir)) {
|
|
443
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
444
|
+
if (entry.startsWith('memtrace-')) {
|
|
445
|
+
fs.rmSync(path.join(skillsDir, entry), { recursive: true, force: true });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
435
449
|
let count = 0;
|
|
436
450
|
for (const skill of skills) {
|
|
437
451
|
const skillName = skill.filename.replace(/\.md$/, '');
|
|
@@ -87,6 +87,13 @@ export const codexTransformer = {
|
|
|
87
87
|
name: 'codex',
|
|
88
88
|
async install(skills, ctx) {
|
|
89
89
|
const rootDir = skillsRoot(ctx);
|
|
90
|
+
if (fs.existsSync(rootDir)) {
|
|
91
|
+
for (const entry of fs.readdirSync(rootDir)) {
|
|
92
|
+
if (entry.startsWith('memtrace-')) {
|
|
93
|
+
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
90
97
|
for (const s of skills)
|
|
91
98
|
writeSkill(s, rootDir);
|
|
92
99
|
let mcpRegistered = false;
|
|
@@ -39,6 +39,13 @@ export const cursorTransformer = {
|
|
|
39
39
|
name: 'cursor',
|
|
40
40
|
async install(skills, ctx) {
|
|
41
41
|
const rootDir = skillsRoot(ctx);
|
|
42
|
+
if (fs.existsSync(rootDir)) {
|
|
43
|
+
for (const entry of fs.readdirSync(rootDir)) {
|
|
44
|
+
if (entry.startsWith('memtrace-')) {
|
|
45
|
+
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
for (const s of skills)
|
|
43
50
|
writeSkill(s, rootDir);
|
|
44
51
|
let mcpRegistered = false;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transformer } from './types.js';
|
|
2
|
+
export declare function hermesConfigPath(): string;
|
|
3
|
+
export declare function registerHermesMcpAt(configFile: string, binary: string): void;
|
|
4
|
+
export declare function removeHermesMcpAt(configFile: string): void;
|
|
5
|
+
export declare const hermesTransformer: Transformer;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { MEMTRACE_MCP_ENV, MCP_SERVER_NAME, removeMemtraceSkills, writeSkills, writeTextAtomic, } from './shared.js';
|
|
5
|
+
function skillsRoot(ctx) {
|
|
6
|
+
void ctx;
|
|
7
|
+
return path.join(os.homedir(), '.hermes', 'skills');
|
|
8
|
+
}
|
|
9
|
+
export function hermesConfigPath() {
|
|
10
|
+
return path.join(os.homedir(), '.hermes', 'config.yaml');
|
|
11
|
+
}
|
|
12
|
+
function yamlQuote(value) {
|
|
13
|
+
return `"${value
|
|
14
|
+
.replace(/\\/g, '\\\\')
|
|
15
|
+
.replace(/"/g, '\\"')
|
|
16
|
+
.replace(/\n/g, '\\n')
|
|
17
|
+
.replace(/\r/g, '\\r')}"`;
|
|
18
|
+
}
|
|
19
|
+
function memtraceYamlBlock(binary) {
|
|
20
|
+
return [
|
|
21
|
+
` ${MCP_SERVER_NAME}:`,
|
|
22
|
+
` command: ${yamlQuote(binary)}`,
|
|
23
|
+
' args:',
|
|
24
|
+
' - "mcp"',
|
|
25
|
+
' env:',
|
|
26
|
+
...Object.entries(MEMTRACE_MCP_ENV).map(([key, value]) => ` ${key}: ${yamlQuote(value)}`),
|
|
27
|
+
' enabled: true',
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function removeMemtraceYamlBlock(raw) {
|
|
31
|
+
const lines = raw.split(/\r?\n/);
|
|
32
|
+
const out = [];
|
|
33
|
+
let inMcpServers = false;
|
|
34
|
+
let skippingMemtrace = false;
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
if (/^\S/.test(line) && !line.startsWith('mcp_servers:')) {
|
|
37
|
+
inMcpServers = false;
|
|
38
|
+
skippingMemtrace = false;
|
|
39
|
+
}
|
|
40
|
+
if (/^mcp_servers:\s*$/.test(line)) {
|
|
41
|
+
inMcpServers = true;
|
|
42
|
+
skippingMemtrace = false;
|
|
43
|
+
out.push(line);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inMcpServers && new RegExp(`^\\s{2}${MCP_SERVER_NAME}:\\s*$`).test(line)) {
|
|
47
|
+
skippingMemtrace = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (skippingMemtrace) {
|
|
51
|
+
if (/^\s{2}\S/.test(line) || /^\S/.test(line)) {
|
|
52
|
+
skippingMemtrace = false;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!skippingMemtrace)
|
|
59
|
+
out.push(line);
|
|
60
|
+
}
|
|
61
|
+
return out.join('\n').trimEnd();
|
|
62
|
+
}
|
|
63
|
+
function removeEmptyMcpServersSection(raw) {
|
|
64
|
+
const lines = raw.split(/\r?\n/);
|
|
65
|
+
const out = [];
|
|
66
|
+
for (let i = 0; i < lines.length; i++) {
|
|
67
|
+
const line = lines[i];
|
|
68
|
+
if (!/^mcp_servers:\s*$/.test(line)) {
|
|
69
|
+
out.push(line);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let j = i + 1;
|
|
73
|
+
while (j < lines.length && /^\s*$/.test(lines[j]))
|
|
74
|
+
j++;
|
|
75
|
+
if (j >= lines.length || /^\S/.test(lines[j])) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
out.push(line);
|
|
79
|
+
}
|
|
80
|
+
return out.join('\n').trimEnd();
|
|
81
|
+
}
|
|
82
|
+
export function registerHermesMcpAt(configFile, binary) {
|
|
83
|
+
const existing = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf-8') : '';
|
|
84
|
+
const withoutMemtrace = removeMemtraceYamlBlock(existing);
|
|
85
|
+
const hasMcpServers = /^mcp_servers:\s*$/m.test(withoutMemtrace);
|
|
86
|
+
const block = memtraceYamlBlock(binary);
|
|
87
|
+
let next;
|
|
88
|
+
if (hasMcpServers) {
|
|
89
|
+
const lines = withoutMemtrace.split(/\r?\n/);
|
|
90
|
+
const index = lines.findIndex(line => /^mcp_servers:\s*$/.test(line));
|
|
91
|
+
lines.splice(index + 1, 0, ...block);
|
|
92
|
+
next = lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
next = `${withoutMemtrace}${withoutMemtrace ? '\n\n' : ''}mcp_servers:\n${block.join('\n')}`;
|
|
96
|
+
}
|
|
97
|
+
writeTextAtomic(configFile, `${next.trimEnd()}\n`);
|
|
98
|
+
}
|
|
99
|
+
export function removeHermesMcpAt(configFile) {
|
|
100
|
+
if (!fs.existsSync(configFile))
|
|
101
|
+
return;
|
|
102
|
+
const next = removeEmptyMcpServersSection(removeMemtraceYamlBlock(fs.readFileSync(configFile, 'utf-8')));
|
|
103
|
+
if (next.trim())
|
|
104
|
+
writeTextAtomic(configFile, `${next.trimEnd()}\n`);
|
|
105
|
+
else
|
|
106
|
+
fs.unlinkSync(configFile);
|
|
107
|
+
}
|
|
108
|
+
export const hermesTransformer = {
|
|
109
|
+
name: 'hermes',
|
|
110
|
+
async install(skills, ctx) {
|
|
111
|
+
const rootDir = skillsRoot(ctx);
|
|
112
|
+
const count = writeSkills(skills, rootDir);
|
|
113
|
+
const warnings = [];
|
|
114
|
+
let mcpRegistered = false;
|
|
115
|
+
const mcpConfigPath = hermesConfigPath();
|
|
116
|
+
if (!ctx.skipMcp) {
|
|
117
|
+
registerHermesMcpAt(mcpConfigPath, ctx.memtraceBinary);
|
|
118
|
+
mcpRegistered = true;
|
|
119
|
+
if (ctx.scope === 'local') {
|
|
120
|
+
warnings.push('Hermes skills and MCP config are user-level; wrote ~/.hermes/skills and ~/.hermes/config.yaml.');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
agent: 'hermes',
|
|
125
|
+
skillsWritten: count,
|
|
126
|
+
skillsDir: rootDir,
|
|
127
|
+
mcpConfigPath,
|
|
128
|
+
mcpRegistered,
|
|
129
|
+
warnings,
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
async uninstall(ctx) {
|
|
133
|
+
removeMemtraceSkills(skillsRoot(ctx));
|
|
134
|
+
removeHermesMcpAt(hermesConfigPath());
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -2,7 +2,12 @@ import { Transformer } from './types.js';
|
|
|
2
2
|
import { claudeTransformer } from './claude.js';
|
|
3
3
|
import { cursorTransformer } from './cursor.js';
|
|
4
4
|
import { codexTransformer } from './codex.js';
|
|
5
|
+
import { windsurfTransformer } from './windsurf.js';
|
|
6
|
+
import { vscodeTransformer } from './vscode.js';
|
|
7
|
+
import { hermesTransformer } from './hermes.js';
|
|
8
|
+
import { opencodeTransformer } from './opencode.js';
|
|
9
|
+
import { kiroTransformer } from './kiro.js';
|
|
5
10
|
export declare const ALL_TRANSFORMERS: Transformer[];
|
|
6
11
|
export declare function findTransformer(name: string): Transformer | undefined;
|
|
7
|
-
export { claudeTransformer, cursorTransformer, codexTransformer };
|
|
12
|
+
export { claudeTransformer, cursorTransformer, codexTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, };
|
|
8
13
|
export type { Transformer, InstallContext, InstallResult, TransformResult } from './types.js';
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { claudeTransformer } from './claude.js';
|
|
2
2
|
import { cursorTransformer } from './cursor.js';
|
|
3
3
|
import { codexTransformer } from './codex.js';
|
|
4
|
-
|
|
4
|
+
import { windsurfTransformer } from './windsurf.js';
|
|
5
|
+
import { vscodeTransformer } from './vscode.js';
|
|
6
|
+
import { hermesTransformer } from './hermes.js';
|
|
7
|
+
import { opencodeTransformer } from './opencode.js';
|
|
8
|
+
import { kiroTransformer } from './kiro.js';
|
|
9
|
+
export const ALL_TRANSFORMERS = [
|
|
10
|
+
claudeTransformer,
|
|
11
|
+
cursorTransformer,
|
|
12
|
+
codexTransformer,
|
|
13
|
+
windsurfTransformer,
|
|
14
|
+
vscodeTransformer,
|
|
15
|
+
hermesTransformer,
|
|
16
|
+
opencodeTransformer,
|
|
17
|
+
kiroTransformer,
|
|
18
|
+
];
|
|
5
19
|
export function findTransformer(name) {
|
|
6
20
|
return ALL_TRANSFORMERS.find(t => t.name === name);
|
|
7
21
|
}
|
|
8
|
-
export { claudeTransformer, cursorTransformer, codexTransformer };
|
|
22
|
+
export { claudeTransformer, cursorTransformer, codexTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { registerMcpServersJsonAt, removeMcpServersJsonAt, skillName, } from './shared.js';
|
|
5
|
+
function steeringRoot(ctx) {
|
|
6
|
+
return ctx.scope === 'global'
|
|
7
|
+
? path.join(os.homedir(), '.kiro', 'steering')
|
|
8
|
+
: path.join(ctx.cwd, '.kiro', 'steering');
|
|
9
|
+
}
|
|
10
|
+
function mcpPath(ctx) {
|
|
11
|
+
return ctx.scope === 'global'
|
|
12
|
+
? path.join(os.homedir(), '.kiro', 'settings', 'mcp.json')
|
|
13
|
+
: path.join(ctx.cwd, '.kiro', 'settings', 'mcp.json');
|
|
14
|
+
}
|
|
15
|
+
function writeSteering(skill, rootDir) {
|
|
16
|
+
const name = skillName(skill);
|
|
17
|
+
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
18
|
+
const content = [
|
|
19
|
+
'---',
|
|
20
|
+
'inclusion: auto',
|
|
21
|
+
`name: ${name}`,
|
|
22
|
+
`description: "${safeDesc}"`,
|
|
23
|
+
'---',
|
|
24
|
+
'',
|
|
25
|
+
skill.body,
|
|
26
|
+
].join('\n');
|
|
27
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
28
|
+
fs.writeFileSync(path.join(rootDir, `${name}.md`), content);
|
|
29
|
+
}
|
|
30
|
+
function removeMemtraceSteering(rootDir) {
|
|
31
|
+
if (!fs.existsSync(rootDir))
|
|
32
|
+
return;
|
|
33
|
+
for (const entry of fs.readdirSync(rootDir)) {
|
|
34
|
+
if (entry.startsWith('memtrace-') && entry.endsWith('.md')) {
|
|
35
|
+
fs.rmSync(path.join(rootDir, entry), { force: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export const kiroTransformer = {
|
|
40
|
+
name: 'kiro',
|
|
41
|
+
async install(skills, ctx) {
|
|
42
|
+
const rootDir = steeringRoot(ctx);
|
|
43
|
+
removeMemtraceSteering(rootDir);
|
|
44
|
+
for (const skill of skills)
|
|
45
|
+
writeSteering(skill, rootDir);
|
|
46
|
+
let mcpRegistered = false;
|
|
47
|
+
const warnings = [];
|
|
48
|
+
const mcpConfigPath = mcpPath(ctx);
|
|
49
|
+
if (!ctx.skipMcp) {
|
|
50
|
+
const result = registerMcpServersJsonAt(mcpConfigPath, ctx.memtraceBinary);
|
|
51
|
+
mcpRegistered = result.registered;
|
|
52
|
+
if (!mcpRegistered && result.backupPath) {
|
|
53
|
+
warnings.push(`Kiro MCP config was malformed; backed up to ${result.backupPath}.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
agent: 'kiro',
|
|
58
|
+
skillsWritten: skills.length,
|
|
59
|
+
skillsDir: rootDir,
|
|
60
|
+
mcpConfigPath,
|
|
61
|
+
mcpRegistered,
|
|
62
|
+
warnings,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
async uninstall(ctx) {
|
|
66
|
+
removeMemtraceSteering(steeringRoot(ctx));
|
|
67
|
+
removeMcpServersJsonAt(mcpPath(ctx));
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { registerOpenCodeMcpAt, removeMemtraceSkills, removeOpenCodeMcpAt, writeSkills, } from './shared.js';
|
|
4
|
+
function opencodeConfigDir() {
|
|
5
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
6
|
+
return path.join(xdg, 'opencode');
|
|
7
|
+
}
|
|
8
|
+
function skillsRoot(ctx) {
|
|
9
|
+
return ctx.scope === 'global'
|
|
10
|
+
? path.join(opencodeConfigDir(), 'skills')
|
|
11
|
+
: path.join(ctx.cwd, '.opencode', 'skills');
|
|
12
|
+
}
|
|
13
|
+
function mcpPath(ctx) {
|
|
14
|
+
return ctx.scope === 'global'
|
|
15
|
+
? path.join(opencodeConfigDir(), 'opencode.json')
|
|
16
|
+
: path.join(ctx.cwd, 'opencode.json');
|
|
17
|
+
}
|
|
18
|
+
export const opencodeTransformer = {
|
|
19
|
+
name: 'opencode',
|
|
20
|
+
async install(skills, ctx) {
|
|
21
|
+
const rootDir = skillsRoot(ctx);
|
|
22
|
+
const count = writeSkills(skills, rootDir, { compatibility: 'opencode' });
|
|
23
|
+
let mcpRegistered = false;
|
|
24
|
+
const warnings = [];
|
|
25
|
+
const mcpConfigPath = mcpPath(ctx);
|
|
26
|
+
if (!ctx.skipMcp) {
|
|
27
|
+
const result = registerOpenCodeMcpAt(mcpConfigPath, ctx.memtraceBinary);
|
|
28
|
+
mcpRegistered = result.registered;
|
|
29
|
+
if (!mcpRegistered && result.backupPath) {
|
|
30
|
+
warnings.push(`OpenCode config was malformed; backed up to ${result.backupPath}.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
agent: 'opencode',
|
|
35
|
+
skillsWritten: count,
|
|
36
|
+
skillsDir: rootDir,
|
|
37
|
+
mcpConfigPath,
|
|
38
|
+
mcpRegistered,
|
|
39
|
+
warnings,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
async uninstall(ctx) {
|
|
43
|
+
removeMemtraceSkills(skillsRoot(ctx));
|
|
44
|
+
removeOpenCodeMcpAt(mcpPath(ctx));
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Skill } from '../skills.js';
|
|
2
|
+
export declare const MCP_SERVER_NAME = "memtrace";
|
|
3
|
+
export declare const MEMTRACE_MCP_ENV: {
|
|
4
|
+
MEMTRACE_ARCADEDB_BOLT_URL: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function skillName(skill: Skill): string;
|
|
7
|
+
export declare function skillMarkdown(skill: Skill, extraFrontmatter?: Record<string, string>): string;
|
|
8
|
+
export declare function writeSkills(skills: Skill[], rootDir: string, extraFrontmatter?: Record<string, string>): number;
|
|
9
|
+
export declare function removeMemtraceSkills(rootDir: string): void;
|
|
10
|
+
export declare function writeTextAtomic(filePath: string, content: string): void;
|
|
11
|
+
export interface JsonMcpResult {
|
|
12
|
+
registered: boolean;
|
|
13
|
+
backupPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerMcpServersJsonAt(filePath: string, binary: string): JsonMcpResult;
|
|
16
|
+
export declare function removeMcpServersJsonAt(filePath: string): void;
|
|
17
|
+
export declare function registerVsCodeMcpAt(filePath: string, binary: string): JsonMcpResult;
|
|
18
|
+
export declare function removeVsCodeMcpAt(filePath: string): void;
|
|
19
|
+
export declare function registerOpenCodeMcpAt(filePath: string, binary: string): JsonMcpResult;
|
|
20
|
+
export declare function removeOpenCodeMcpAt(filePath: string): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
4
|
+
export const MCP_SERVER_NAME = 'memtrace';
|
|
5
|
+
export const MEMTRACE_MCP_ENV = { MEMTRACE_ARCADEDB_BOLT_URL: 'bolt://localhost:7687' };
|
|
6
|
+
export function skillName(skill) {
|
|
7
|
+
return skill.filename.replace(/\.md$/, '');
|
|
8
|
+
}
|
|
9
|
+
export function skillMarkdown(skill, extraFrontmatter = {}) {
|
|
10
|
+
const name = skillName(skill);
|
|
11
|
+
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
12
|
+
const extra = Object.entries(extraFrontmatter)
|
|
13
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
return `---\nname: ${name}\ndescription: "${safeDesc}"${extra ? `\n${extra}` : ''}\n---\n\n${skill.body}`;
|
|
16
|
+
}
|
|
17
|
+
export function writeSkills(skills, rootDir, extraFrontmatter = {}) {
|
|
18
|
+
removeMemtraceSkills(rootDir);
|
|
19
|
+
for (const skill of skills) {
|
|
20
|
+
const name = skillName(skill);
|
|
21
|
+
const outDir = path.join(rootDir, name);
|
|
22
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(outDir, 'SKILL.md'), skillMarkdown(skill, extraFrontmatter));
|
|
24
|
+
}
|
|
25
|
+
return skills.length;
|
|
26
|
+
}
|
|
27
|
+
export function removeMemtraceSkills(rootDir) {
|
|
28
|
+
if (!fs.existsSync(rootDir))
|
|
29
|
+
return;
|
|
30
|
+
for (const entry of fs.readdirSync(rootDir)) {
|
|
31
|
+
if (entry.startsWith('memtrace-')) {
|
|
32
|
+
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function writeTextAtomic(filePath, content) {
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
39
|
+
fs.writeFileSync(tmpPath, content);
|
|
40
|
+
fs.renameSync(tmpPath, filePath);
|
|
41
|
+
}
|
|
42
|
+
export function registerMcpServersJsonAt(filePath, binary) {
|
|
43
|
+
const { value, corrupted, backupPath } = safeReadJson(filePath);
|
|
44
|
+
if (corrupted)
|
|
45
|
+
return { registered: false, backupPath };
|
|
46
|
+
const cfg = (value ?? {});
|
|
47
|
+
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
48
|
+
cfg.mcpServers[MCP_SERVER_NAME] = {
|
|
49
|
+
command: binary,
|
|
50
|
+
args: ['mcp'],
|
|
51
|
+
env: MEMTRACE_MCP_ENV,
|
|
52
|
+
};
|
|
53
|
+
writeJsonAtomic(filePath, cfg);
|
|
54
|
+
return { registered: true };
|
|
55
|
+
}
|
|
56
|
+
export function removeMcpServersJsonAt(filePath) {
|
|
57
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
58
|
+
if (corrupted || !value?.mcpServers?.[MCP_SERVER_NAME])
|
|
59
|
+
return;
|
|
60
|
+
delete value.mcpServers[MCP_SERVER_NAME];
|
|
61
|
+
if (Object.keys(value.mcpServers).length === 0)
|
|
62
|
+
delete value.mcpServers;
|
|
63
|
+
if (Object.keys(value).length === 0)
|
|
64
|
+
fs.unlinkSync(filePath);
|
|
65
|
+
else
|
|
66
|
+
writeJsonAtomic(filePath, value);
|
|
67
|
+
}
|
|
68
|
+
export function registerVsCodeMcpAt(filePath, binary) {
|
|
69
|
+
const { value, corrupted, backupPath } = safeReadJson(filePath);
|
|
70
|
+
if (corrupted)
|
|
71
|
+
return { registered: false, backupPath };
|
|
72
|
+
const cfg = (value ?? {});
|
|
73
|
+
cfg.servers = cfg.servers ?? {};
|
|
74
|
+
cfg.servers[MCP_SERVER_NAME] = {
|
|
75
|
+
type: 'stdio',
|
|
76
|
+
command: binary,
|
|
77
|
+
args: ['mcp'],
|
|
78
|
+
env: MEMTRACE_MCP_ENV,
|
|
79
|
+
};
|
|
80
|
+
writeJsonAtomic(filePath, cfg);
|
|
81
|
+
return { registered: true };
|
|
82
|
+
}
|
|
83
|
+
export function removeVsCodeMcpAt(filePath) {
|
|
84
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
85
|
+
if (corrupted || !value?.servers?.[MCP_SERVER_NAME])
|
|
86
|
+
return;
|
|
87
|
+
delete value.servers[MCP_SERVER_NAME];
|
|
88
|
+
if (Object.keys(value.servers).length === 0)
|
|
89
|
+
delete value.servers;
|
|
90
|
+
if (Object.keys(value).length === 0)
|
|
91
|
+
fs.unlinkSync(filePath);
|
|
92
|
+
else
|
|
93
|
+
writeJsonAtomic(filePath, value);
|
|
94
|
+
}
|
|
95
|
+
export function registerOpenCodeMcpAt(filePath, binary) {
|
|
96
|
+
const { value, corrupted, backupPath } = safeReadJson(filePath);
|
|
97
|
+
if (corrupted)
|
|
98
|
+
return { registered: false, backupPath };
|
|
99
|
+
const cfg = (value ?? {});
|
|
100
|
+
cfg.$schema = cfg.$schema ?? 'https://opencode.ai/config.json';
|
|
101
|
+
cfg.mcp = cfg.mcp ?? {};
|
|
102
|
+
cfg.mcp[MCP_SERVER_NAME] = {
|
|
103
|
+
type: 'local',
|
|
104
|
+
command: [binary, 'mcp'],
|
|
105
|
+
enabled: true,
|
|
106
|
+
environment: MEMTRACE_MCP_ENV,
|
|
107
|
+
};
|
|
108
|
+
writeJsonAtomic(filePath, cfg);
|
|
109
|
+
return { registered: true };
|
|
110
|
+
}
|
|
111
|
+
export function removeOpenCodeMcpAt(filePath) {
|
|
112
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
113
|
+
if (corrupted || !value?.mcp?.[MCP_SERVER_NAME])
|
|
114
|
+
return;
|
|
115
|
+
delete value.mcp[MCP_SERVER_NAME];
|
|
116
|
+
if (Object.keys(value.mcp).length === 0)
|
|
117
|
+
delete value.mcp;
|
|
118
|
+
const remainingKeys = Object.keys(value);
|
|
119
|
+
if (remainingKeys.length === 0 || (remainingKeys.length === 1 && remainingKeys[0] === '$schema')) {
|
|
120
|
+
fs.unlinkSync(filePath);
|
|
121
|
+
}
|
|
122
|
+
else
|
|
123
|
+
writeJsonAtomic(filePath, value);
|
|
124
|
+
}
|
|
@@ -22,18 +22,19 @@ export interface InstallContext {
|
|
|
22
22
|
skipMcp?: boolean;
|
|
23
23
|
}
|
|
24
24
|
export interface InstallResult {
|
|
25
|
-
agent:
|
|
25
|
+
agent: AgentName;
|
|
26
26
|
skillsWritten: number;
|
|
27
27
|
skillsDir: string;
|
|
28
28
|
mcpConfigPath?: string;
|
|
29
29
|
mcpRegistered: boolean;
|
|
30
30
|
warnings: string[];
|
|
31
31
|
}
|
|
32
|
+
export type AgentName = 'claude' | 'cursor' | 'codex' | 'windsurf' | 'vscode' | 'hermes' | 'opencode' | 'kiro';
|
|
32
33
|
/**
|
|
33
34
|
* One transformer per supported AI coding agent.
|
|
34
35
|
*/
|
|
35
36
|
export interface Transformer {
|
|
36
|
-
name:
|
|
37
|
+
name: AgentName;
|
|
37
38
|
install(skills: Skill[], ctx: InstallContext): Promise<InstallResult>;
|
|
38
39
|
uninstall(ctx: InstallContext): Promise<void>;
|
|
39
40
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { registerVsCodeMcpAt, removeMemtraceSkills, removeVsCodeMcpAt, writeSkills, } from './shared.js';
|
|
4
|
+
function skillsRoot(ctx) {
|
|
5
|
+
return ctx.scope === 'global'
|
|
6
|
+
? path.join(os.homedir(), '.copilot', 'skills')
|
|
7
|
+
: path.join(ctx.cwd, '.github', 'skills');
|
|
8
|
+
}
|
|
9
|
+
export function vscodeUserMcpPath() {
|
|
10
|
+
if (process.platform === 'win32') {
|
|
11
|
+
const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
|
|
12
|
+
return path.join(appData, 'Code', 'User', 'mcp.json');
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === 'darwin') {
|
|
15
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
|
|
16
|
+
}
|
|
17
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
18
|
+
return path.join(xdg, 'Code', 'User', 'mcp.json');
|
|
19
|
+
}
|
|
20
|
+
function mcpPath(ctx) {
|
|
21
|
+
return ctx.scope === 'global'
|
|
22
|
+
? vscodeUserMcpPath()
|
|
23
|
+
: path.join(ctx.cwd, '.vscode', 'mcp.json');
|
|
24
|
+
}
|
|
25
|
+
export const vscodeTransformer = {
|
|
26
|
+
name: 'vscode',
|
|
27
|
+
async install(skills, ctx) {
|
|
28
|
+
const rootDir = skillsRoot(ctx);
|
|
29
|
+
const count = writeSkills(skills, rootDir);
|
|
30
|
+
let mcpRegistered = false;
|
|
31
|
+
const warnings = [];
|
|
32
|
+
const mcpConfigPath = mcpPath(ctx);
|
|
33
|
+
if (!ctx.skipMcp) {
|
|
34
|
+
const result = registerVsCodeMcpAt(mcpConfigPath, ctx.memtraceBinary);
|
|
35
|
+
mcpRegistered = result.registered;
|
|
36
|
+
if (!mcpRegistered && result.backupPath) {
|
|
37
|
+
warnings.push(`VS Code MCP config was malformed; backed up to ${result.backupPath}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
agent: 'vscode',
|
|
42
|
+
skillsWritten: count,
|
|
43
|
+
skillsDir: rootDir,
|
|
44
|
+
mcpConfigPath,
|
|
45
|
+
mcpRegistered,
|
|
46
|
+
warnings,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
async uninstall(ctx) {
|
|
50
|
+
removeMemtraceSkills(skillsRoot(ctx));
|
|
51
|
+
removeVsCodeMcpAt(mcpPath(ctx));
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { registerMcpServersJsonAt, removeMcpServersJsonAt, removeMemtraceSkills, writeSkills, } from './shared.js';
|
|
4
|
+
function skillsRoot(ctx) {
|
|
5
|
+
return ctx.scope === 'global'
|
|
6
|
+
? path.join(os.homedir(), '.codeium', 'windsurf', 'skills')
|
|
7
|
+
: path.join(ctx.cwd, '.windsurf', 'skills');
|
|
8
|
+
}
|
|
9
|
+
export function windsurfMcpPath() {
|
|
10
|
+
return path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
11
|
+
}
|
|
12
|
+
export const windsurfTransformer = {
|
|
13
|
+
name: 'windsurf',
|
|
14
|
+
async install(skills, ctx) {
|
|
15
|
+
const rootDir = skillsRoot(ctx);
|
|
16
|
+
const count = writeSkills(skills, rootDir);
|
|
17
|
+
const warnings = [];
|
|
18
|
+
let mcpRegistered = false;
|
|
19
|
+
const mcpConfigPath = windsurfMcpPath();
|
|
20
|
+
if (!ctx.skipMcp) {
|
|
21
|
+
const result = registerMcpServersJsonAt(mcpConfigPath, ctx.memtraceBinary);
|
|
22
|
+
mcpRegistered = result.registered;
|
|
23
|
+
if (!mcpRegistered && result.backupPath) {
|
|
24
|
+
warnings.push(`Windsurf MCP config was malformed; backed up to ${result.backupPath}.`);
|
|
25
|
+
}
|
|
26
|
+
if (ctx.scope === 'local') {
|
|
27
|
+
warnings.push('Windsurf MCP config is user-level only; wrote ~/.codeium/windsurf/mcp_config.json.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
agent: 'windsurf',
|
|
32
|
+
skillsWritten: count,
|
|
33
|
+
skillsDir: rootDir,
|
|
34
|
+
mcpConfigPath,
|
|
35
|
+
mcpRegistered,
|
|
36
|
+
warnings,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
async uninstall(ctx) {
|
|
40
|
+
removeMemtraceSkills(skillsRoot(ctx));
|
|
41
|
+
removeMcpServersJsonAt(windsurfMcpPath());
|
|
42
|
+
},
|
|
43
|
+
};
|
package/installer/package.json
CHANGED
|
@@ -62,6 +62,23 @@ Save the symbol `id` from results — pass it to:
|
|
|
62
62
|
- `get_symbol_context` for a 360-degree view
|
|
63
63
|
- `get_impact` to assess blast radius before changes
|
|
64
64
|
|
|
65
|
+
### Multi-word natural-language queries
|
|
66
|
+
|
|
67
|
+
When your query is 3+ words and feels descriptive (e.g. "validate auth token", "find HTTP server error"), don't stop at the first `find_code` call:
|
|
68
|
+
|
|
69
|
+
1. First try the verbatim query.
|
|
70
|
+
2. If results look generic or the right doc isn't at rank 1, fan out **in parallel** with up to 3 identifier-shaped reshapes:
|
|
71
|
+
- camelCase: "validate auth token" → `validateAuthToken`
|
|
72
|
+
- snake_case: → `validate_auth_token`
|
|
73
|
+
- Domain-likely identifiers: → `auth_token`, `tokenValidator`, `verifyToken`
|
|
74
|
+
3. Memtrace's tokenizer splits camelCase / snake / kebab at index time, so reshaped queries hit identifier names directly.
|
|
75
|
+
4. Take the union of top-5 from each call, dedupe by `file_path:start_line`.
|
|
76
|
+
|
|
77
|
+
**Worked examples** (verbatim → reshapes to try in parallel):
|
|
78
|
+
- "validate auth token" → `validateAuthToken`, `validate_auth_token`, `verifyToken`
|
|
79
|
+
- "find http server error" → `findHttpServerError`, `http_error`, `serverError`
|
|
80
|
+
- "render value panel" → `renderValuePanel`, `ValuePanel`, `value_panel`
|
|
81
|
+
|
|
65
82
|
## Common Mistakes
|
|
66
83
|
|
|
67
84
|
| Mistake | Reality |
|
|
@@ -9,12 +9,15 @@ description: "Always use first for indexed source-code repos before searching fi
|
|
|
9
9
|
|
|
10
10
|
```
|
|
11
11
|
IF THE REPO IS INDEXED IN MEMTRACE → USE MEMTRACE TOOLS FIRST.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
After a search hit, route to GRAPH tools (get_symbol_context, get_impact,
|
|
13
|
+
analyze_relationships) — that's what Memtrace uniquely provides. Read source
|
|
14
|
+
ONLY when you're about to edit or quote, and read only the bounded span
|
|
15
|
+
returned by Memtrace (start_line .. end_line + small context). Do not
|
|
16
|
+
Grep/Glob/Find to "locate" anything already in the graph, and do not read
|
|
17
|
+
the whole file when Memtrace has given you exact lines.
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
Memtrace is the memory layer of the codebase. It has the full knowledge graph
|
|
20
|
+
Memtrace is the **memory layer** of the codebase, not a search engine that returns code. It has the full knowledge graph — every symbol, call, import, community, process, and API — with a time dimension. The point is to navigate that graph: who calls this, what's the blast radius, when did this change, what community is it part of. File tools are blind to all of that.
|
|
18
21
|
|
|
19
22
|
**97% better accuracy. 83% fewer wasted tokens. No exceptions for what's in the graph.**
|
|
20
23
|
|
|
@@ -56,7 +59,7 @@ These are the ONLY cases where file tools beat memtrace:
|
|
|
56
59
|
(`.git`, `node_modules`, `target`, `dist`) are examples Memtrace cannot see.
|
|
57
60
|
- **Non-source artifacts.** `.env`, `package.json`, build scripts, top-level `README.md`, raw config files. Memtrace indexes parseable code, not configuration text.
|
|
58
61
|
- **Pure file-inventory questions.** "How many `*.test.ts` files exist", "list every Markdown file in `docs/`". You're asking for a file count, not a symbol search.
|
|
59
|
-
- **Reading at a known path.**
|
|
62
|
+
- **Reading at a known path outside Memtrace.** For configs, docs, or non-source artifacts that Memtrace cannot index, file `Read` is fine. For source-code spans returned by Memtrace, read the precise line range (your harness's `Read` with offset/limit, or `get_source_window` if your harness lacks bounded reads). Do not whole-file Read when you have a span.
|
|
60
63
|
|
|
61
64
|
For everything else inside the indexed repo, memtrace is the right tool.
|
|
62
65
|
|
|
@@ -64,16 +67,18 @@ For everything else inside the indexed repo, memtrace is the right tool.
|
|
|
64
67
|
|
|
65
68
|
| Question Claude is asking | Right tool |
|
|
66
69
|
|---|---|
|
|
67
|
-
| "Where is symbol `foo` defined?" | `find_symbol(name="foo")` →
|
|
70
|
+
| "Where is symbol `foo` defined?" | `find_symbol(name="foo")` → then `get_symbol_context` for callers/callees/community, NOT a source read unless you're editing. |
|
|
68
71
|
| "What calls `foo`?" | `get_symbol_context(name="foo")` → callers with file:line each. |
|
|
69
|
-
| "How does authentication work?" | `find_code(query="authentication")` →
|
|
72
|
+
| "How does authentication work?" | `find_code(query="authentication")` → `get_symbol_context` on the top hit, NOT a source read. |
|
|
73
|
+
| "Find behavior X" with multi-word phrase (3+ words) | `find_code(verbatim)` first; if low confidence, fan out with identifier-shaped reshapes (camelCase / snake_case). |
|
|
70
74
|
| "Find the function that uses `STRIPE_KEY_FOO_BAR`" | `find_code(query="STRIPE_KEY_FOO_BAR")` → semantic finds it inside any embedded body. |
|
|
71
75
|
| "Where's that error message `'connection refused for tenant'`?" | `find_code(query="connection refused for tenant")` → semantic catches it. |
|
|
72
76
|
| "What breaks if I change `foo`?" | `get_impact(name="foo")` → blast radius with file:line. |
|
|
73
77
|
| "What changed in `auth.ts` last week?" | `get_evolution(file_path="auth.ts", from="7d ago")`. |
|
|
74
78
|
| "List all `*.test.ts` files." | `Glob` (file inventory, not symbol search). |
|
|
75
79
|
| "Find this string in my `.env`." | `Grep` (non-source artifact). |
|
|
76
|
-
| "
|
|
80
|
+
| "I'm about to edit `foo` — show me its source." | Bounded `Read(file_path, offset=start_line, limit=end_line-start_line+8)`, or `get_source_window` if your harness lacks bounded reads. Never whole-file. |
|
|
81
|
+
| "Read config/doc file I already have the path of." | `Read` (non-source artifact, path is known). |
|
|
77
82
|
|
|
78
83
|
## Parameter Types — Read This Before Calling Any Tool
|
|
79
84
|
|
|
@@ -101,7 +106,7 @@ If not indexed → offer to index with `mcp__memtrace__index_directory`, then fo
|
|
|
101
106
|
| What you need | Use instead of Grep/Glob/Read |
|
|
102
107
|
|---|---|
|
|
103
108
|
| Find a function / class / symbol | `find_symbol` or `find_code` |
|
|
104
|
-
| Understand how something works | `get_symbol_context` |
|
|
109
|
+
| Understand how something works | `get_symbol_context` (the default next step) |
|
|
105
110
|
| Find all callers of a function | `get_symbol_context` (callers field) |
|
|
106
111
|
| Find all callees / dependencies | `get_symbol_context` (callees field) |
|
|
107
112
|
| Trace a request / execution path | `get_process_flow` |
|
|
@@ -116,14 +121,15 @@ If not indexed → offer to index with `mcp__memtrace__index_directory`, then fo
|
|
|
116
121
|
| Dependency between two symbols | `find_dependency_path` |
|
|
117
122
|
| What files change together? | `get_cochange_context` |
|
|
118
123
|
| Architecture overview | `list_communities` + `find_central_symbols` |
|
|
124
|
+
| About to edit / quote — need exact lines | Bounded `Read(file, offset=start_line, limit=N)` (preferred), or `get_source_window` for path-resolution parity |
|
|
119
125
|
|
|
120
126
|
## Standard Workflows
|
|
121
127
|
|
|
122
128
|
### "How does X work?" / "Explain X"
|
|
123
129
|
1. `find_symbol` or `find_code` → locate the symbol
|
|
124
|
-
2. `get_symbol_context` → callers, callees, community, processes
|
|
130
|
+
2. `get_symbol_context` → callers, callees, community, processes (this usually answers "how it works")
|
|
125
131
|
3. `get_process_flow` (if it's a process/request path)
|
|
126
|
-
4.
|
|
132
|
+
4. Only if you need to quote source: bounded `Read` at start_line..end_line, or `get_source_window`
|
|
127
133
|
|
|
128
134
|
### Debugging "X is broken"
|
|
129
135
|
1. `find_symbol` → locate the broken thing
|
|
@@ -135,7 +141,7 @@ If not indexed → offer to index with `mcp__memtrace__index_directory`, then fo
|
|
|
135
141
|
### "Where is X defined / called?"
|
|
136
142
|
1. `find_symbol` with `fuzzy: true`
|
|
137
143
|
2. `get_symbol_context` for full caller/callee map
|
|
138
|
-
3.
|
|
144
|
+
3. Only if you need source text: bounded `Read` at start_line..end_line, or `get_source_window`
|
|
139
145
|
|
|
140
146
|
### Before any code modification
|
|
141
147
|
1. `find_symbol` → confirm you have the right target
|
|
@@ -150,7 +156,7 @@ You are violating this skill if you think:
|
|
|
150
156
|
|---|---|
|
|
151
157
|
| "Let me grep for this" | `find_code` or `find_symbol` is faster and structurally aware |
|
|
152
158
|
| "Let me glob for the file" | `find_symbol` returns exact location with context |
|
|
153
|
-
| "Let me read the whole file" | `get_symbol_context`
|
|
159
|
+
| "Let me read the whole file" | `get_symbol_context` for the WHY (callers/callees/community); a bounded source read at start_line..end_line for the WHAT |
|
|
154
160
|
| "It's just a quick search" | Grep has no understanding of call graphs, communities, or time |
|
|
155
161
|
| "I don't know if it's indexed" | Check with `list_indexed_repositories` first — takes 1 second |
|
|
156
162
|
| "Memtrace returned 0 results" | Broaden the Memtrace query, check repo_id/path coverage, then reindex if needed |
|
|
@@ -161,10 +167,15 @@ You are violating this skill if you think:
|
|
|
161
167
|
## When File Tools Are Still Correct
|
|
162
168
|
|
|
163
169
|
Use Grep/Glob/Read ONLY for:
|
|
164
|
-
-
|
|
170
|
+
- Non-source files or paths outside every indexed source repo
|
|
165
171
|
- Files that are config, data, or docs (not source code symbols)
|
|
166
172
|
- Repos or paths confirmed outside every Memtrace indexed root
|
|
167
173
|
|
|
174
|
+
For source-code spans already located by Memtrace, use a **bounded** read —
|
|
175
|
+
your harness's `Read(file, offset, limit)` with the returned `start_line` /
|
|
176
|
+
`end_line`, or `get_source_window` if your harness lacks bounded reads. Do
|
|
177
|
+
not read the whole file.
|
|
178
|
+
|
|
168
179
|
Never use file tools as a **discovery** mechanism when Memtrace is available.
|
|
169
180
|
|
|
170
181
|
## Skill Priority
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"fs-extra": "^11.0.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@memtrace/darwin-arm64": "0.3.
|
|
41
|
-
"@memtrace/linux-x64": "0.3.
|
|
42
|
-
"@memtrace/win32-x64": "0.3.
|
|
40
|
+
"@memtrace/darwin-arm64": "0.3.34",
|
|
41
|
+
"@memtrace/linux-x64": "0.3.34",
|
|
42
|
+
"@memtrace/win32-x64": "0.3.34"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=18"
|