memtrace-skills 1.2.8 → 1.2.10
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/dist/rail-hook.d.ts +7 -1
- package/dist/rail-hook.js +176 -57
- package/dist/rail-install.js +3 -6
- package/dist/rail-lifecycle.js +6 -3
- package/dist/rail-runtime.d.ts +37 -3
- package/dist/rail-runtime.js +79 -7
- package/dist/tool-names.js +6 -0
- package/dist/transformers/claude.d.ts +14 -2
- package/dist/transformers/claude.js +50 -23
- package/dist/transformers/opencode.js +6 -6
- package/dist/transformers/rail-hooks.d.ts +6 -1
- package/dist/transformers/rail-hooks.js +31 -9
- package/dist/transformers/shared.d.ts +2 -1
- package/dist/transformers/shared.js +22 -10
- package/dist/transformers/vscode.js +3 -1
- package/package.json +2 -2
- package/plugins/memtrace-skills/references/mcp-parameters.md +14 -3
- package/plugins/memtrace-skills/skills/memtrace-codebase-exploration/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-daily/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-first/SKILL.md +14 -8
- package/plugins/memtrace-skills/skills/memtrace-graph/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-guided-refactor/SKILL.md +88 -0
- package/plugins/memtrace-skills/skills/memtrace-impact/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-index/SKILL.md +21 -0
- package/plugins/memtrace-skills/skills/memtrace-preflight/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-quality/SKILL.md +16 -2
- package/plugins/memtrace-skills/skills/memtrace-refactoring-guide/SKILL.md +4 -0
- package/plugins/memtrace-skills/skills/memtrace-search/SKILL.md +1 -0
- package/plugins/memtrace-skills/skills/memtrace-session-continuity/SKILL.md +4 -0
- package/skills/commands/memtrace-daily.md +4 -0
- package/skills/commands/memtrace-graph.md +4 -0
- package/skills/commands/memtrace-impact.md +4 -0
- package/skills/commands/memtrace-index.md +22 -0
- package/skills/commands/memtrace-preflight.md +4 -0
- package/skills/commands/memtrace-quality.md +19 -2
- package/skills/commands/memtrace-search.md +1 -0
- package/skills/workflows/memtrace-codebase-exploration.md +4 -0
- package/skills/workflows/memtrace-first.md +14 -8
- package/skills/workflows/memtrace-guided-refactor.md +89 -0
- package/skills/workflows/memtrace-refactoring-guide.md +4 -0
- package/skills/workflows/memtrace-session-continuity.md +4 -0
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { copyCanonicalPluginAssets, copyCanonicalPluginManifest, readCanonicalPluginVersion, } from '../../plugin-assets/index.js';
|
|
6
6
|
import { execCommand, commandExists } from '../utils.js';
|
|
7
7
|
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
8
|
-
import { railIsActive, railHookInvocation, railHookCommand, RAIL_GUARD_MARKER } from '../rail-runtime.js';
|
|
8
|
+
import { railIsActive, railHookInvocation, railHookCommand, RAIL_GUARD_MARKER, enrichEnabled, railMode } from '../rail-runtime.js';
|
|
9
9
|
import { LEGACY_MCP_SERVER_NAME, MEMTRACE_MCP_ENV, removeLegacyMcpServer, } from './shared.js';
|
|
10
10
|
const PLUGIN_NAME = 'memtrace-skills';
|
|
11
11
|
const MARKETPLACE_NAME = 'memtrace';
|
|
@@ -27,6 +27,17 @@ const MARKETPLACE_SETTING_CONTAINERS = [
|
|
|
27
27
|
function isRecord(value) {
|
|
28
28
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
29
|
}
|
|
30
|
+
function existingMcpArgs(entry) {
|
|
31
|
+
if (!isRecord(entry) || !Array.isArray(entry.args) || entry.args.length === 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
return entry.args.every((arg) => typeof arg === 'string') ? [...entry.args] : undefined;
|
|
34
|
+
}
|
|
35
|
+
function readExistingMemtraceArgs(filePath) {
|
|
36
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
37
|
+
if (corrupted || !isRecord(value?.mcpServers))
|
|
38
|
+
return undefined;
|
|
39
|
+
return existingMcpArgs(value.mcpServers.memtrace);
|
|
40
|
+
}
|
|
30
41
|
/**
|
|
31
42
|
* Read the existing env block (if any) for the memtrace MCP entry in a Claude
|
|
32
43
|
* settings/mcp.json file. Returns an empty object if the file or entry is
|
|
@@ -82,7 +93,8 @@ async function tryMcpAddJson(memtraceBinary) {
|
|
|
82
93
|
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
83
94
|
const config = JSON.stringify({
|
|
84
95
|
command: memtraceBinary,
|
|
85
|
-
args:
|
|
96
|
+
args: readExistingMemtraceArgs(claudeUserConfigFile())
|
|
97
|
+
?? readExistingMemtraceArgs(settingsFile) ?? ['mcp'],
|
|
86
98
|
env: mergedEnv,
|
|
87
99
|
});
|
|
88
100
|
// Shell-quote the JSON. Use single quotes; escape any embedded single quote.
|
|
@@ -185,7 +197,7 @@ async function tryClaudeCliInstall() {
|
|
|
185
197
|
* Merge-add the memtrace MCP server into a Claude settings.json file.
|
|
186
198
|
* Never overwrites a malformed file — backs it up and returns registered=false.
|
|
187
199
|
*/
|
|
188
|
-
export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv = {}) {
|
|
200
|
+
export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv = {}, legacyArgs) {
|
|
189
201
|
const { value, corrupted, backupPath } = safeReadJson(settingsPath);
|
|
190
202
|
if (corrupted) {
|
|
191
203
|
console.warn(`memtrace: ${settingsPath} is malformed; backed up to ${backupPath}. Skipped MCP registration for Claude.`);
|
|
@@ -209,7 +221,9 @@ export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv
|
|
|
209
221
|
// text can overcome. alwaysLoad opts the memtrace tools back into turn one.
|
|
210
222
|
settings.mcpServers['memtrace'] = {
|
|
211
223
|
command: memtraceBinary,
|
|
212
|
-
|
|
224
|
+
// Workspace and transport flags belong to the user. Replacing these on
|
|
225
|
+
// upgrade can send every editor into its own store instead of the owner.
|
|
226
|
+
args: existingMcpArgs(existing) ?? legacyArgs ?? ['mcp'],
|
|
213
227
|
env: mergedEnv,
|
|
214
228
|
alwaysLoad: true,
|
|
215
229
|
};
|
|
@@ -257,7 +271,7 @@ export function claudeRailHookCommand(_memtraceBinary, platform = process.platfo
|
|
|
257
271
|
* is replaced, and unrelated PreToolUse hooks are preserved. Never overwrites
|
|
258
272
|
* a malformed file.
|
|
259
273
|
*/
|
|
260
|
-
export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platform = process.platform) {
|
|
274
|
+
export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platform = process.platform, options = {}) {
|
|
261
275
|
if (!railIsActive()) {
|
|
262
276
|
removeRailHookFromSettingsAt(settingsPath);
|
|
263
277
|
return { registered: false };
|
|
@@ -270,26 +284,39 @@ export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platf
|
|
|
270
284
|
const settings = (value ?? {});
|
|
271
285
|
settings.hooks = isRecord(settings.hooks) ? settings.hooks : {};
|
|
272
286
|
pruneMemtraceOwnedHookEntries(settings.hooks);
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
hooks: [
|
|
281
|
-
{
|
|
282
|
-
type: 'command',
|
|
283
|
-
...(platform === 'win32'
|
|
284
|
-
? railHookInvocation('claude-code')
|
|
285
|
-
: { command: claudeRailHookCommand(memtraceBinary, platform) }),
|
|
286
|
-
},
|
|
287
|
-
],
|
|
287
|
+
const enrich = options.enrich ?? enrichEnabled();
|
|
288
|
+
const mode = options.mode ?? railMode();
|
|
289
|
+
const hookFor = (extra) => ({
|
|
290
|
+
type: 'command',
|
|
291
|
+
...(platform === 'win32'
|
|
292
|
+
? railHookInvocation('claude-code', extra)
|
|
293
|
+
: { command: railHookCommand('claude-code', platform, extra) }),
|
|
288
294
|
});
|
|
289
|
-
|
|
295
|
+
const append = (event, entry) => {
|
|
296
|
+
const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
297
|
+
// Drop any prior memtrace entry so re-install doesn't duplicate it.
|
|
298
|
+
settings.hooks[event] = [...existing.filter((e) => !isMemtraceOwnedHookEntry(e)), entry];
|
|
299
|
+
};
|
|
300
|
+
// With grep enrichment on, the post hook carries the work and the Rail
|
|
301
|
+
// PreToolUse router is only needed when it can deny (rail/strict): each
|
|
302
|
+
// Grep/Bash call then costs exactly one node process (design section 3).
|
|
303
|
+
if (!enrich || mode === 'rail' || mode === 'strict') {
|
|
304
|
+
append('PreToolUse', { matcher: RAIL_HOOK_MATCHER, hooks: [hookFor([])] });
|
|
305
|
+
}
|
|
306
|
+
if (enrich) {
|
|
307
|
+
append('PostToolUse', { matcher: ENRICH_HOOK_MATCHER, hooks: [{ ...hookFor(['--phase', 'post']), timeout: ENRICH_HOOK_TIMEOUT_S }] });
|
|
308
|
+
// Compacted context no longer holds earlier blocks: reset the dedupe.
|
|
309
|
+
append('PreCompact', { hooks: [{ ...hookFor(['--event', 'precompact']), timeout: ENRICH_HOOK_TIMEOUT_S }] });
|
|
310
|
+
}
|
|
290
311
|
writeJsonAtomic(settingsPath, settings);
|
|
291
312
|
return { registered: true };
|
|
292
313
|
}
|
|
314
|
+
/** Grep enrichment rides PostToolUse on these tools (design section 9). Claude Code on
|
|
315
|
+
* Windows runs shell commands through its PowerShell tool, so rg/grep/git grep there
|
|
316
|
+
* arrive as PowerShell calls. */
|
|
317
|
+
export const ENRICH_HOOK_MATCHER = 'Grep|Bash|PowerShell';
|
|
318
|
+
/** Claude hook timeout for the enrichment hooks, in seconds. */
|
|
319
|
+
export const ENRICH_HOOK_TIMEOUT_S = 2;
|
|
293
320
|
/** True when a PreToolUse entry is a memtrace-owned Rail hook (any generation). */
|
|
294
321
|
function isRailHookEntry(entry) {
|
|
295
322
|
return hookCommands(entry).some((command) => command.includes(RAIL_GUARD_MARKER) || RAIL_HOOK_MARKERS.some((marker) => command.includes(marker)));
|
|
@@ -510,7 +537,7 @@ export async function registerMcpServer(memtraceBinaryPath) {
|
|
|
510
537
|
// Desktop-only installs have no `claude` CLI. Claude Code reads user MCP
|
|
511
538
|
// registrations from ~/.claude.json, not ~/.claude/settings.json (hooks).
|
|
512
539
|
// Migrate legacy env values without overriding the authoritative user entry.
|
|
513
|
-
registerMcpInSettingsAt(claudeUserConfigFile(), memtraceBinaryPath, readExistingMemtraceEnv(settingsFile));
|
|
540
|
+
registerMcpInSettingsAt(claudeUserConfigFile(), memtraceBinaryPath, readExistingMemtraceEnv(settingsFile), readExistingMemtraceArgs(settingsFile));
|
|
514
541
|
// Retain staging's mirror for older tooling after migrating the user entry.
|
|
515
542
|
registerMcpInSettingsAt(settingsFile, memtraceBinaryPath);
|
|
516
543
|
}
|
|
@@ -663,7 +690,7 @@ function writeClaudeLocalMcp(mcpPath, binary) {
|
|
|
663
690
|
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
664
691
|
cfg.mcpServers['memtrace'] = {
|
|
665
692
|
command: binary,
|
|
666
|
-
args: ['mcp'],
|
|
693
|
+
args: existingMcpArgs(existing) ?? ['mcp'],
|
|
667
694
|
env: mergedEnv,
|
|
668
695
|
};
|
|
669
696
|
writeJsonAtomic(mcpPath, cfg);
|
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import { railIsActive } from '../rail-runtime.js';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { registerOpenCodeMcpAt, removeMemtraceSkills, removeOpenCodeMcpAt, writeSkills, } from './shared.js';
|
|
5
|
-
import { openCodeConfigDir, openCodeRailPluginPaths, openCodePluginSource, } from './rail-hooks.js';
|
|
5
|
+
import { openCodeConfigDir, openCodeRailPluginPaths, openCodePluginSource, writeOpenCodeRailPlugin, } from './rail-hooks.js';
|
|
6
6
|
function skillsRoot(ctx) {
|
|
7
7
|
return ctx.scope === 'global'
|
|
8
8
|
? path.join(openCodeConfigDir(ctx), 'skills')
|
|
@@ -32,13 +32,13 @@ export const opencodeTransformer = {
|
|
|
32
32
|
// has no stdio hook). Defaults to observe; fail-open. Best effort.
|
|
33
33
|
try {
|
|
34
34
|
const src = openCodePluginSource(ctx.memtraceBinary);
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (railIsActive()) {
|
|
36
|
+
writeOpenCodeRailPlugin(ctx, src);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const pluginPath of openCodeRailPluginPaths(ctx)) {
|
|
37
40
|
fs.rmSync(pluginPath, { force: true });
|
|
38
|
-
continue;
|
|
39
41
|
}
|
|
40
|
-
fs.mkdirSync(path.dirname(pluginPath), { recursive: true });
|
|
41
|
-
fs.writeFileSync(pluginPath, src);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
catch (e) {
|
|
@@ -18,6 +18,7 @@ export declare function openCodeHomeDir(options?: OpenCodePathOptions): string;
|
|
|
18
18
|
export declare function openCodeGlobalConfigDir(options?: OpenCodePathOptions): string;
|
|
19
19
|
export declare function openCodeConfigDir(ctx: OpenCodePathContext, options?: OpenCodePathOptions): string;
|
|
20
20
|
export declare function openCodeRailPluginPaths(ctx: OpenCodePathContext, options?: OpenCodePathOptions): string[];
|
|
21
|
+
export declare function writeOpenCodeRailPlugin(ctx: OpenCodePathContext, source: string): string;
|
|
21
22
|
export declare function isRailHookEntry(entry: unknown): boolean;
|
|
22
23
|
export interface HookResult {
|
|
23
24
|
registered: boolean;
|
|
@@ -40,7 +41,11 @@ export declare function removeSettingsHook(settingsPath: string, eventName: stri
|
|
|
40
41
|
* schema: `{version, hooks: {beforeShellExecution: [{command}]}}`. We intercept
|
|
41
42
|
* both shell execution (where rg/grep/find run) and file reads.
|
|
42
43
|
*/
|
|
43
|
-
export declare function registerCursorRailHook(hooksPath: string, binary: string
|
|
44
|
+
export declare function registerCursorRailHook(hooksPath: string, binary: string, options?: {
|
|
45
|
+
enrich?: boolean;
|
|
46
|
+
}): HookResult;
|
|
47
|
+
export declare const CURSOR_ENRICH_EVENT = "postToolUse";
|
|
48
|
+
export declare const CURSOR_ENRICH_MATCHER = "Shell|Grep";
|
|
44
49
|
/** Remove the Cursor Rail hook entries. */
|
|
45
50
|
/** Windsurf Cascade hooks path (user-level). */
|
|
46
51
|
export declare function windsurfHooksPath(): string;
|
|
@@ -19,7 +19,7 @@ import fs from 'fs';
|
|
|
19
19
|
import os from 'os';
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
22
|
-
import { railIsActive, railHookCommand, railHookInvocation, isRailCommand } from '../rail-runtime.js';
|
|
22
|
+
import { railIsActive, railHookCommand, railHookInvocation, isRailCommand, enrichEnabled } from '../rail-runtime.js';
|
|
23
23
|
/** Substring identifying a memtrace-owned Rail hook entry. */
|
|
24
24
|
export const RAIL_HOOK_MARKER = 'route --hook';
|
|
25
25
|
/** The discovery tools each host surfaces. */
|
|
@@ -68,10 +68,8 @@ export function openCodeConfigDir(ctx, options = {}) {
|
|
|
68
68
|
? openCodeGlobalConfigDir(options)
|
|
69
69
|
: path.join(ctx.cwd, '.opencode');
|
|
70
70
|
}
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// (singular). We write the plugin to BOTH so it loads regardless of version;
|
|
74
|
-
// uninstall removes both. (Identical content — whichever the runtime reads.)
|
|
71
|
+
// Enumerate both locations for upgrade/uninstall cleanup. OpenCode loads both,
|
|
72
|
+
// so installation must write only the current plural directory (public #82).
|
|
75
73
|
export function openCodeRailPluginPaths(ctx, options = {}) {
|
|
76
74
|
const base = openCodeConfigDir(ctx, options);
|
|
77
75
|
return [
|
|
@@ -79,6 +77,13 @@ export function openCodeRailPluginPaths(ctx, options = {}) {
|
|
|
79
77
|
path.join(base, 'plugins', 'memtrace-rail.js'),
|
|
80
78
|
];
|
|
81
79
|
}
|
|
80
|
+
export function writeOpenCodeRailPlugin(ctx, source) {
|
|
81
|
+
const [legacy, current] = openCodeRailPluginPaths(ctx);
|
|
82
|
+
fs.mkdirSync(path.dirname(current), { recursive: true });
|
|
83
|
+
fs.writeFileSync(current, source);
|
|
84
|
+
fs.rmSync(legacy, { force: true });
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
82
87
|
function isRecord(v) {
|
|
83
88
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
84
89
|
}
|
|
@@ -154,7 +159,7 @@ export function removeSettingsHook(settingsPath, eventName) {
|
|
|
154
159
|
* schema: `{version, hooks: {beforeShellExecution: [{command}]}}`. We intercept
|
|
155
160
|
* both shell execution (where rg/grep/find run) and file reads.
|
|
156
161
|
*/
|
|
157
|
-
export function registerCursorRailHook(hooksPath, binary) {
|
|
162
|
+
export function registerCursorRailHook(hooksPath, binary, options = {}) {
|
|
158
163
|
if (!railIsActive()) {
|
|
159
164
|
removeCursorRailHook(hooksPath);
|
|
160
165
|
return { registered: false };
|
|
@@ -176,9 +181,26 @@ export function registerCursorRailHook(hooksPath, binary) {
|
|
|
176
181
|
const preserved = existing.filter((e) => !(isRecord(e) && typeof e.command === 'string' && isRailCommand(e.command)));
|
|
177
182
|
preserved.push({ command: cmd });
|
|
178
183
|
cfg.hooks[event] = preserved;
|
|
184
|
+
// Grep enrichment: Cursor `postToolUse` on its Shell and Grep tools answers
|
|
185
|
+
// with `additional_context` (design section 9). Opt-in like everywhere.
|
|
186
|
+
const post = Array.isArray(cfg.hooks[CURSOR_ENRICH_EVENT]) ? cfg.hooks[CURSOR_ENRICH_EVENT] : [];
|
|
187
|
+
const postPreserved = post.filter((e) => !(isRecord(e) && typeof e.command === 'string' && isRailCommand(e.command)));
|
|
188
|
+
if (options.enrich ?? enrichEnabled()) {
|
|
189
|
+
postPreserved.push({
|
|
190
|
+
matcher: CURSOR_ENRICH_MATCHER,
|
|
191
|
+
command: railHookCommand('cursor', process.platform, ['--phase', 'post']),
|
|
192
|
+
timeout: 2,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (postPreserved.length)
|
|
196
|
+
cfg.hooks[CURSOR_ENRICH_EVENT] = postPreserved;
|
|
197
|
+
else
|
|
198
|
+
delete cfg.hooks[CURSOR_ENRICH_EVENT];
|
|
179
199
|
writeJsonAtomic(hooksPath, cfg);
|
|
180
200
|
return { registered: true };
|
|
181
201
|
}
|
|
202
|
+
export const CURSOR_ENRICH_EVENT = 'postToolUse';
|
|
203
|
+
export const CURSOR_ENRICH_MATCHER = 'Shell|Grep';
|
|
182
204
|
/** Remove the Cursor Rail hook entries. */
|
|
183
205
|
/** Windsurf Cascade hooks path (user-level). */
|
|
184
206
|
export function windsurfHooksPath() {
|
|
@@ -305,7 +327,7 @@ export function removeCursorRailHook(hooksPath) {
|
|
|
305
327
|
if (!isRecord(cfg.hooks))
|
|
306
328
|
return { changed: false };
|
|
307
329
|
let changed = false;
|
|
308
|
-
for (const event of ['beforeShellExecution', 'beforeReadFile']) {
|
|
330
|
+
for (const event of ['beforeShellExecution', 'beforeReadFile', CURSOR_ENRICH_EVENT]) {
|
|
309
331
|
if (!Array.isArray(cfg.hooks[event]))
|
|
310
332
|
continue;
|
|
311
333
|
const before = cfg.hooks[event];
|
|
@@ -342,14 +364,14 @@ import { execFileSync } from 'node:child_process';
|
|
|
342
364
|
const BIN = ${JSON.stringify(hook.command)};
|
|
343
365
|
const ARGS = ${JSON.stringify([...hook.args, '--json'])};
|
|
344
366
|
|
|
345
|
-
export const MemtraceRail = async ({
|
|
367
|
+
export const MemtraceRail = async ({ directory }) => ({
|
|
346
368
|
'tool.execute.before': async (input, output) => {
|
|
347
369
|
try {
|
|
348
370
|
const tool = input?.tool;
|
|
349
371
|
if (tool !== 'bash' && tool !== 'grep' && tool !== 'glob') return;
|
|
350
372
|
const attempt = {
|
|
351
373
|
host: 'open-code',
|
|
352
|
-
cwd: output?.args?.cwd || process.cwd(),
|
|
374
|
+
cwd: output?.args?.cwd || directory || process.cwd(),
|
|
353
375
|
tool: tool === 'bash' ? 'Bash' : tool === 'grep' ? 'Grep' : 'Glob',
|
|
354
376
|
input: output?.args || {},
|
|
355
377
|
session_id: input?.sessionID || 'opencode',
|
|
@@ -14,7 +14,8 @@ export interface JsonMcpResult {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function registerMcpServersJsonAt(filePath: string, binary: string): JsonMcpResult;
|
|
16
16
|
export declare function removeMcpServersJsonAt(filePath: string): void;
|
|
17
|
-
export
|
|
17
|
+
export type VsCodeMcpScope = 'user' | 'workspace';
|
|
18
|
+
export declare function registerVsCodeMcpAt(filePath: string, binary: string, scope?: VsCodeMcpScope): JsonMcpResult;
|
|
18
19
|
export declare function removeVsCodeMcpAt(filePath: string): void;
|
|
19
20
|
export declare function registerOpenCodeMcpAt(filePath: string, binary: string): JsonMcpResult;
|
|
20
21
|
export declare function removeOpenCodeMcpAt(filePath: string): void;
|
|
@@ -78,27 +78,39 @@ export function removeMcpServersJsonAt(filePath) {
|
|
|
78
78
|
else
|
|
79
79
|
writeJsonAtomic(filePath, value);
|
|
80
80
|
}
|
|
81
|
-
export function registerVsCodeMcpAt(filePath, binary) {
|
|
81
|
+
export function registerVsCodeMcpAt(filePath, binary, scope = 'workspace') {
|
|
82
82
|
const { value, corrupted, backupPath } = safeReadJson(filePath);
|
|
83
83
|
if (corrupted)
|
|
84
84
|
return { registered: false, backupPath };
|
|
85
85
|
const cfg = (value ?? {});
|
|
86
86
|
cfg.servers = cfg.servers ?? {};
|
|
87
87
|
removeLegacyMcpServer(cfg.servers);
|
|
88
|
-
|
|
88
|
+
// MEMBIZ-50 D2: `cwd` is the ONE anchor that drives both store resolution
|
|
89
|
+
// and repository discovery in `memtrace mcp`. Without it the server spawns
|
|
90
|
+
// in the editor's own working directory (often the user profile), where it
|
|
91
|
+
// can resolve a stale user-level `.memdb` instead of the project's.
|
|
92
|
+
//
|
|
93
|
+
// MEMBIZ-95: but VS Code only resolves `${workspaceFolder}` in a
|
|
94
|
+
// WORKSPACE-scoped file. In the user-level `Code/User/mcp.json` it refuses
|
|
95
|
+
// outright —
|
|
96
|
+
//
|
|
97
|
+
// Error starting memtrace: CodeExpectedError:
|
|
98
|
+
// Variable workspaceFolder can not be resolved. Please open a folder.
|
|
99
|
+
//
|
|
100
|
+
// — and Memtrace never launches at all, in any window. A server that cannot
|
|
101
|
+
// start anchors nothing, so keeping the variable there trades a narrow
|
|
102
|
+
// wrong-store risk for a total outage. Omitted at user scope: a server
|
|
103
|
+
// spawned without an anchor joins the unique live `memtrace start` instead
|
|
104
|
+
// (see `mcp_from_home_cwd_lists_the_unique_live_estate`), which is the
|
|
105
|
+
// normal case and what the field workaround relied on.
|
|
106
|
+
const entry = {
|
|
89
107
|
type: 'stdio',
|
|
90
108
|
command: binary,
|
|
91
109
|
args: ['mcp'],
|
|
92
|
-
|
|
93
|
-
// resolution and repository discovery in `memtrace mcp`. Without it a
|
|
94
|
-
// user-scope entry spawns the server in the editor's own working
|
|
95
|
-
// directory (often the user profile), where it resolves a stale
|
|
96
|
-
// user-level `.memdb` instead of the project's — every project then
|
|
97
|
-
// shares one wrong, empty store. VS Code substitutes the variable per
|
|
98
|
-
// window, so one global entry serves every project correctly.
|
|
99
|
-
cwd: '${workspaceFolder}',
|
|
110
|
+
...(scope === 'workspace' ? { cwd: '${workspaceFolder}' } : {}),
|
|
100
111
|
env: MEMTRACE_MCP_ENV,
|
|
101
112
|
};
|
|
113
|
+
cfg.servers[MCP_SERVER_NAME] = entry;
|
|
102
114
|
writeJsonAtomic(filePath, cfg);
|
|
103
115
|
return { registered: true };
|
|
104
116
|
}
|
|
@@ -31,7 +31,9 @@ export const vscodeTransformer = {
|
|
|
31
31
|
const warnings = [];
|
|
32
32
|
const mcpConfigPath = mcpPath(ctx);
|
|
33
33
|
if (!ctx.skipMcp) {
|
|
34
|
-
|
|
34
|
+
// MEMBIZ-95: the user-level file cannot carry `${workspaceFolder}` —
|
|
35
|
+
// VS Code refuses to resolve it there and never starts the server.
|
|
36
|
+
const result = registerVsCodeMcpAt(mcpConfigPath, ctx.memtraceBinary, ctx.scope === 'global' ? 'user' : 'workspace');
|
|
35
37
|
mcpRegistered = result.registered;
|
|
36
38
|
if (!mcpRegistered && result.backupPath) {
|
|
37
39
|
warnings.push(`VS Code MCP config was malformed; backed up to ${result.backupPath}.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace-skills",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "Memtrace skills for AI coding agents — codebase exploration, temporal evolution, impact analysis, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"prebuild": "node scripts/copy-skills.js",
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"prepublishOnly": "npm run build",
|
|
20
|
-
"test": "vitest run && tsc && node --test tests/runtime/rail-lifecycle.test.mjs",
|
|
20
|
+
"test": "vitest run && tsc && node --test tests/runtime/rail-lifecycle.test.mjs tests/runtime/rail-enrich-hook.test.mjs",
|
|
21
21
|
"test:watch": "vitest"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
@@ -91,6 +91,9 @@ the repository.
|
|
|
91
91
|
| `include_diagnostics` | boolean | no | `false` | Include `id`, `score` in results |
|
|
92
92
|
| `include_dependency_checks` | boolean | no | `true` | With diagnostics, set false for scores without pre-edit risk checks |
|
|
93
93
|
| `include_context` | boolean | no | auto | Bounded processes, communities and selected callers/callees; default on for concepts, off for identifiers |
|
|
94
|
+
| `verbose` | boolean | no | `false` | Full uncompacted answer (row provenance, ranking constants); `context.next_calls` is kept either way |
|
|
95
|
+
|
|
96
|
+
Rows are `name`, `kind`, `file_path`, `start_line`, `end_line` (`symbol_start_line`/`symbol_end_line` when a long symbol was narrowed). `context.processes[].id` is the `process` argument for `get_process_flow`.
|
|
94
97
|
|
|
95
98
|
No `kind` param — use `find_symbol(kind=...)` instead.
|
|
96
99
|
|
|
@@ -131,10 +134,13 @@ No parameters. Call once at session start to get `repo_id` values.
|
|
|
131
134
|
| `as_of` | string | no | now |
|
|
132
135
|
| `view` | string | no | `"live"` |
|
|
133
136
|
| `include_content` | boolean | no | `false` |
|
|
134
|
-
| `limit` | integer | no | `
|
|
137
|
+
| `limit` | integer | no | `5` (max `200`; `20` with `verbose`) |
|
|
135
138
|
| `offset` | integer | no | `0` |
|
|
139
|
+
| `include_references` | boolean | no | `false` |
|
|
140
|
+
| `include_details` | boolean | no | `false` |
|
|
141
|
+
| `verbose` | boolean | no | `false` |
|
|
136
142
|
|
|
137
|
-
Returns
|
|
143
|
+
Returns the subject (location, signature, doc, `node_id`), callers and callees with edge evidence (`via`: `rel`, `reason`, `confidence`, call-site `lines`), process memberships and community. Source bodies require `include_content: true`, or use `get_source_window`. `pagination` lists only sections with more rows: `available`, `next_offset`, and `lower_bound` when an internal graph cap was reached; the offset applies to every section. References are counted in `omitted.references` and returned with `include_references: true`. `verbose: true` returns the full uncompacted answer. An answer over the response budget drops whole rows, sets `truncated: true` and names each shortened list in `omitted` with how to fetch it.
|
|
138
144
|
|
|
139
145
|
### `analyze_relationships`
|
|
140
146
|
| Field | Type | Required | Default | Notes |
|
|
@@ -276,8 +282,13 @@ Returns `cochanged_files[]` with `file_path`, `cochange_count`, `last_cochanged_
|
|
|
276
282
|
| `branch` | string | no | `"main"` |
|
|
277
283
|
| `limit` | integer | no | `20` (max `200`) |
|
|
278
284
|
| `offset` | integer | no | `0` |
|
|
285
|
+
| `focus_symbol` | string | no | — |
|
|
286
|
+
| `query` | string | no | — |
|
|
287
|
+
| `from_symbol` | boolean | no | `false` |
|
|
288
|
+
| `path_nodes` | array of string | no | — |
|
|
289
|
+
| `verbose` | boolean | no | `false` |
|
|
279
290
|
|
|
280
|
-
Returns ordered steps, `total_steps`, `returned_steps`, `next_offset`, and `scan_truncated`. Follow `next_offset` to continue.
|
|
291
|
+
Returns ordered steps, `total_steps`, `returned_steps`, `next_offset`, and `scan_truncated`. Follow `next_offset` to continue. `focus_symbol` (a member `node_id`) returns its call neighborhood; `query` searches the members and returns matches with short previews. Without a catalog process pass `from_symbol: true` and a callable `node_id` as `process`. To search one `flow_paths.choices` path pass `from_symbol: true`, `process` = `flow_paths.anchor_symbol`, `path_nodes` = the choice's `nodes`, and `query`. `connections` name their endpoints with `rel`, `reason`, `confidence` and call-site `lines`. `verbose: true` returns the full uncompacted answer.
|
|
281
292
|
|
|
282
293
|
### `find_dependency_path`
|
|
283
294
|
| Field | Type | Required | Default | Notes |
|
|
@@ -5,6 +5,10 @@ description: "Map an indexed source-code repo into a structured overview — sca
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
+
### Choose the scope
|
|
9
|
+
|
|
10
|
+
Use this entry for a narrative overview and onboarding. For a specific algorithm such as PageRank, bridge detection, communities, or dependency paths, use `memtrace-graph`. A broad overview and an algorithm query remain distinct entry points.
|
|
11
|
+
|
|
8
12
|
Full codebase exploration workflow — from indexing through architectural understanding. Chains indexing, graph algorithms, community detection, and temporal analysis into a structured onboarding experience. Use this when someone is new to a codebase and needs to build a mental model.
|
|
9
13
|
|
|
10
14
|
## Steps
|
|
@@ -5,6 +5,10 @@ description: "Orient at the start of a coding session, review what recently chan
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
+
### Choose the scope
|
|
9
|
+
|
|
10
|
+
Use this entry for the fixed last-24-hour briefing, hotspots, and session self-audit. To resume from your own saved anchor, use `memtrace-session-continuity`; do not substitute a 24-hour window for time since the user's last session.
|
|
11
|
+
|
|
8
12
|
Session bookends: orient before you start, audit before you finish. All
|
|
9
13
|
three tools read the bi-temporal version history — every save is a change
|
|
10
14
|
event with complexity deltas, so "what happened" includes "did it make the
|
|
@@ -10,8 +10,9 @@ matters. Retrieval is evidence, not a guarantee of complete coverage.
|
|
|
10
10
|
|
|
11
11
|
## Establish scope
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Select the actual repository and branch; pass `repo_id` explicitly when
|
|
14
|
+
possible. When the task already names the repository, use it directly; call
|
|
15
|
+
`list_indexed_repositories` only when the repository or branch is unknown. It lists this session's
|
|
15
16
|
store only, so a repository missing from it may still be indexed and live in
|
|
16
17
|
another store. Do not index a parent containing unrelated repositories. For a
|
|
17
18
|
deliberately shared workspace, verify its workspace marker and scope before
|
|
@@ -52,7 +53,7 @@ answer from stale repos.
|
|
|
52
53
|
## Search, then investigate
|
|
53
54
|
|
|
54
55
|
- Exact symbol: `find_symbol(name=..., repo_id=...)`.
|
|
55
|
-
- Behavior or concept: `find_code(query=..., repo_id
|
|
56
|
+
- Behavior or concept: `find_code(query=..., repo_id=..., limit=10)` (default 20 rows).
|
|
56
57
|
- Relationships: `get_symbol_context` or `analyze_relationships` on the useful hit.
|
|
57
58
|
- Change impact: `get_impact`; confirm relevant callers against source.
|
|
58
59
|
- Execution path: `get_process_flow`; follow its `next_offset` for further steps.
|
|
@@ -68,7 +69,8 @@ Treat partial context, truncation and unavailable overlay context as incomplete
|
|
|
68
69
|
evidence; selected static flows are not complete runtime traces.
|
|
69
70
|
|
|
70
71
|
`context.next_calls` provides scoped follow-up arguments for the useful symbol
|
|
71
|
-
and process
|
|
72
|
+
and process; copy them rather than rebuilding arguments. A `context.processes[].id`
|
|
73
|
+
is the `process` for `get_process_flow`. Before changing shared behavior, inspect relevant callers and
|
|
72
74
|
callees to identify contracts the patch must preserve. Use process IDs for
|
|
73
75
|
flow navigation. No sampled flow is not proof that no call path exists.
|
|
74
76
|
|
|
@@ -79,8 +81,11 @@ Run related existing tests to detect regressions before claiming completion.
|
|
|
79
81
|
|
|
80
82
|
Read the bounded source span when verifying behavior, editing or quoting. Do not
|
|
81
83
|
fetch whole files or entire process traces when a local span answers the question.
|
|
82
|
-
`get_symbol_context`
|
|
83
|
-
|
|
84
|
+
`get_symbol_context` returns 5 rows per relationship section and omits source
|
|
85
|
+
bodies and references by default: `pagination` names sections with more
|
|
86
|
+
(`limit`/`offset`), `omitted` names what was left out (`include_references: true`,
|
|
87
|
+
`include_details: true`), and `include_content: true` returns the symbol's body whole
|
|
88
|
+
with short neighbour previews. An omitted
|
|
84
89
|
risk diagnostic means unknown, not zero callers or low risk. Check truncation and
|
|
85
90
|
pagination metadata before describing a neighborhood as complete.
|
|
86
91
|
|
|
@@ -208,8 +213,9 @@ when choosing among competing local idioms.
|
|
|
208
213
|
|
|
209
214
|
Use JSON numbers for limits/depths and booleans for switches. Use symbol names
|
|
210
215
|
for `get_symbol_context.symbol`, `get_impact.target` and relationship targets;
|
|
211
|
-
use IDs only where the specific schema asks for one.
|
|
212
|
-
|
|
216
|
+
use IDs only where the specific schema asks for one. When a call is rejected,
|
|
217
|
+
check that tool's schema rather than retrying from memory; do not pre-read schemas.
|
|
218
|
+
`find_code` has no `kind` filter; use `find_symbol` for it.
|
|
213
219
|
Search without an explicit repository uses safe scope inference, not an implicit
|
|
214
220
|
search of every repository. `get_evolution` uses `from`, not `days`.
|
|
215
221
|
|
|
@@ -5,6 +5,10 @@ description: "Map source-code architecture with graph algorithms — PageRank ce
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
+
### Choose the scope
|
|
9
|
+
|
|
10
|
+
Use this entry for a specific graph algorithm or structural question: centrality, bridges, communities, or dependency paths. For a narrative introduction spanning scale, architecture, APIs, and recent activity, use `memtrace-codebase-exploration`.
|
|
11
|
+
|
|
8
12
|
Graph algorithms over the knowledge graph — communities (Louvain), PageRank centrality, bridge symbols (articulation points), shortest paths, and execution flows.
|
|
9
13
|
|
|
10
14
|
## Quick Reference
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memtrace-guided-refactor
|
|
3
|
+
description: "Run a saved, staged refactor shared with Memtrace's Quality page. Use for splitting tangled functions, extracting helpers, or continuing a guided refactor run with fixed file scope and checks between edits."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the `guided_refactor` MCP tool to track the same run shown in Quality. This
|
|
7
|
+
skill coordinates the agent's edits and language tools; Memtrace does not execute
|
|
8
|
+
shell commands or rewrite source on behalf of the browser. Never invent check
|
|
9
|
+
results. Recorded checks are agent-reported evidence, not proof of equivalence.
|
|
10
|
+
|
|
11
|
+
## Diagnose
|
|
12
|
+
|
|
13
|
+
1. Resolve the repository and exact symbol/file with `find_symbol` and
|
|
14
|
+
`get_symbol_context`. Use the runtime attached to that repository.
|
|
15
|
+
2. If a run ID was supplied, call `guided_refactor` with `action: "get"`,
|
|
16
|
+
`repo_id`, and `run_id`. Otherwise call `action: "list"` and reuse a relevant
|
|
17
|
+
unfinished run; create one with `file_path` and `symbol` only when needed.
|
|
18
|
+
3. Inspect function complexity, affected callers, evolution/churn, module
|
|
19
|
+
boundaries, existing tests, and decision-memory constraints. Use
|
|
20
|
+
`get_function_quality_metrics`, `get_impact`, `get_evolution`, and
|
|
21
|
+
`get_symbol_context` as available; read the selected source. Record missing
|
|
22
|
+
evidence explicitly. LOC alone is never a reason to split code.
|
|
23
|
+
4. Respect the returned classification. `Manual` runs permit diagnosis and
|
|
24
|
+
blueprint only. In this first version, staged source work is available for
|
|
25
|
+
Rust, TypeScript, JavaScript, Python, and Go in Git checkouts. Other indexed
|
|
26
|
+
languages and configuration files still get a saved manual plan. Nothing is
|
|
27
|
+
classified Automatic.
|
|
28
|
+
|
|
29
|
+
## Blueprint
|
|
30
|
+
|
|
31
|
+
Call `get` before running baseline checks and retain its `snapshot`. Run the
|
|
32
|
+
project's compiler/type checker and relevant behavior tests. Compare the exported
|
|
33
|
+
API and affected callers. If a check fails or is unavailable, record that fact;
|
|
34
|
+
do not label it passed or begin source work.
|
|
35
|
+
|
|
36
|
+
Submit `action: "blueprint"` with the returned `revision`, retained `snapshot`,
|
|
37
|
+
diagnostic evidence in `note`, baseline `checks`, and 1–12 ordered `steps`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"title": "Capture diagnostic behavior",
|
|
42
|
+
"operation": "capture_tests",
|
|
43
|
+
"files": ["src/parser.rs", "tests/parser.rs"],
|
|
44
|
+
"boundary": "Add cases for existing diagnostics; preserve parsing behavior"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The only staged operations are `capture_tests` and `extract_helper`. Name every
|
|
49
|
+
file a step can change, including planned new files, with repository-relative
|
|
50
|
+
paths. Keep each step small enough to review. Do not change generated code,
|
|
51
|
+
ownership or lifetime contracts, macros, import side effects, public APIs, or
|
|
52
|
+
configuration semantics as an incidental extraction. If those boundaries cannot
|
|
53
|
+
be justified, pause and explain the manual decision needed.
|
|
54
|
+
|
|
55
|
+
Every check has `kind` (`compiler`, `tests`, or `api`), `command`, `passed`, and
|
|
56
|
+
`evidence`. All three kinds are required. Evidence should include exact commands,
|
|
57
|
+
results/counts, and uncertainty. API evidence can be a documented source/caller
|
|
58
|
+
comparison when the project has no API-check command.
|
|
59
|
+
|
|
60
|
+
## Execute one step
|
|
61
|
+
|
|
62
|
+
1. Call `get`, inspect saved state, and send `action: "begin"` with `revision`,
|
|
63
|
+
`snapshot`, and one stable `agent_id`. The coordinator refuses overlapping
|
|
64
|
+
active runs and source changes between steps. Do not delegate overlapping edits.
|
|
65
|
+
2. Make only the active step's bounded change. Preserve existing unrelated edits.
|
|
66
|
+
Do not commit during a run: a changed Git HEAD requires a fresh diagnosis.
|
|
67
|
+
3. Call `get` after editing and **before** running the checks. Retain that snapshot,
|
|
68
|
+
run the compiler, tests, and API comparison, then send `action: "checkpoint"`
|
|
69
|
+
with that same `snapshot`, current `revision`, `agent_id`, `checks`, and `note`.
|
|
70
|
+
Never refresh the snapshot after tests merely to get past a stale-source gate.
|
|
71
|
+
4. Inspect the response. A paused run is not a passed checkpoint. Stop editing
|
|
72
|
+
until the failure is understood. Keep failed evidence visible. To resume after
|
|
73
|
+
fixing the active step, obtain a fresh snapshot, rerun all checks, and send
|
|
74
|
+
`action: "resume"` with checks and an explanation, then checkpoint the step.
|
|
75
|
+
Restore an accidental scope escape without discarding someone else's work;
|
|
76
|
+
otherwise create a new diagnosis with an explicitly reviewed scope.
|
|
77
|
+
5. Begin the next step only after the preceding checkpoint is accepted.
|
|
78
|
+
|
|
79
|
+
## Verify
|
|
80
|
+
|
|
81
|
+
After every step is checked, the run enters Verify. Compare before/after
|
|
82
|
+
complexity, callers, public API, test results, and the actual diff. Get a snapshot,
|
|
83
|
+
run final checks, then submit `action: "complete"` with `revision`, `snapshot`,
|
|
84
|
+
`agent_id`, `checks`, and the comparison in `note`. State what remains unproven.
|
|
85
|
+
|
|
86
|
+
The Quality page shows saved stages, agent, scope, current tracked-file diff,
|
|
87
|
+
failed checks, and the final comparison. Closing the browser does not discard the
|
|
88
|
+
run. Use `pause` with a reason whenever the next safe step needs a decision.
|
|
@@ -5,6 +5,10 @@ description: "Compute the blast radius of modifying a symbol through transitive
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
+
### Choose the scope
|
|
9
|
+
|
|
10
|
+
Use this entry for a quick upstream/downstream blast radius. For the bundled pre-edit gate, including co-change, complexity, churn, and a verification checklist, use `memtrace-preflight`. Keep the narrower impact query available when a full preflight is unnecessary.
|
|
11
|
+
|
|
8
12
|
Compute the blast radius of changing a specific symbol. Traces upstream (what depends on this) and downstream (what this depends on) through the knowledge graph to quantify risk before making modifications.
|
|
9
13
|
|
|
10
14
|
## Quick Reference
|