memtrace 0.3.31 → 0.3.33

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
@@ -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 + 12 skills + MCP server — one command
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 Cursor (v2.4+) pick up the skills and MCP tools automatically.
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
- ## 12 Agent Skills
178
+ ## 17 Agent Skills
179
179
 
180
- Memtrace ships skills that teach Claude *how* to use the graph. They fire automatically based on what you ask — no prompt engineering required.
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 **4 workflow skills** that chain multiple tools with decision logic:
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 (12) | Install |
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
- | **Windsurf** | ✅ | Coming soon | Add MCP server manually |
225
- | **VS Code (Copilot)** | ✅ | | Add MCP server manually |
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 Claude Code, Claude Desktop, and Cursor (v2.4+) all load them natively from the same `SKILL.md` format.
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, 12 skills, MCP server, plugin, and marketplace all register in one command for both Claude Code and Claude Desktop.
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
- - **12 skills + 4 workflows** → `~/.cursor/skills/memtrace-*/SKILL.md`
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
- ### Other Editors (Windsurf, VS Code, Cline)
268
+ ### Codex, Windsurf, VS Code, Hermes, OpenCode, and Kiro
261
269
 
262
- After `npm install -g memtrace`, add the MCP server to your editor's config:
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>
@@ -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: [checkAgent('claude'), checkAgent('cursor'), checkAgent('codex')],
176
+ agents: ALL_TRANSFORMERS.map(t => checkAgent(t.name)),
81
177
  };
82
178
  }
83
179
  export function formatReport(r) {
@@ -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 (./.claude/, ./.cursor/, ./.agents/)', false)
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
- export const ALL_TRANSFORMERS = [claudeTransformer, cursorTransformer, codexTransformer];
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,2 @@
1
+ import { Transformer } from './types.js';
2
+ export declare const kiroTransformer: Transformer;
@@ -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,2 @@
1
+ import { Transformer } from './types.js';
2
+ export declare const opencodeTransformer: Transformer;
@@ -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: string;
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: 'claude' | 'cursor' | 'codex';
37
+ name: AgentName;
37
38
  install(skills: Skill[], ctx: InstallContext): Promise<InstallResult>;
38
39
  uninstall(ctx: InstallContext): Promise<void>;
39
40
  }
@@ -0,0 +1,3 @@
1
+ import { Transformer } from './types.js';
2
+ export declare function vscodeUserMcpPath(): string;
3
+ export declare const vscodeTransformer: Transformer;
@@ -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,3 @@
1
+ import { Transformer } from './types.js';
2
+ export declare function windsurfMcpPath(): string;
3
+ export declare const windsurfTransformer: Transformer;
@@ -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
+ };
@@ -27,6 +27,7 @@
27
27
  "@types/fs-extra": "^11.0.0",
28
28
  "@types/node": "^20.0.0",
29
29
  "@vitest/coverage-v8": "^4.1.4",
30
+ "fast-check": "^4.7.0",
30
31
  "typescript": "^5.4.0",
31
32
  "vitest": "^4.1.4"
32
33
  },
@@ -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
- Memtrace returns exact file_path + start_line + end_line for every result.
13
- Read the file at THAT location. Do not Grep/Glob/Find to "locate" anything
14
- already in the graph.
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: every symbol, call, import, community, process, and API — with time dimension. File tools are blind to this structure.
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.** Once memtrace has handed you `file_path:start_line:end_line`, use `Read` never substitute `Grep` for `Read`.
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")` → file:line. Then `Read` that range. |
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")` → ranked symbols with file:line. |
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
- | "Read file I already have the path of." | `Read` (path is known). |
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. Read source ONLY for the exact lines you need to quote
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. Read specific file only after locating exact line
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` gives you only what matters |
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
- - Reading the **exact source lines** of a symbol you already located via Memtrace
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.31",
3
+ "version": "0.3.33",
4
4
  "description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
5
5
  "keywords": [
6
6
  "mcp",