memtrace-skills 0.8.3 → 0.8.4

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.
Files changed (37) hide show
  1. package/dist/commands/doctor.js +11 -0
  2. package/dist/index.js +1 -1
  3. package/dist/rail-install.js +4 -15
  4. package/dist/transformers/codex.js +7 -80
  5. package/dist/transformers/index.d.ts +2 -1
  6. package/dist/transformers/index.js +3 -1
  7. package/dist/transformers/opencode.js +5 -21
  8. package/dist/transformers/rail-hooks.d.ts +13 -0
  9. package/dist/transformers/rail-hooks.js +50 -0
  10. package/dist/transformers/types.d.ts +1 -1
  11. package/dist/transformers/warp.d.ts +4 -0
  12. package/dist/transformers/warp.js +83 -0
  13. package/package.json +1 -1
  14. package/plugins/memtrace-skills/.claude-plugin/plugin.json +1 -1
  15. package/plugins/memtrace-skills/references/mcp-parameters.md +7 -4
  16. package/plugins/memtrace-skills/skills/memtrace-change-impact-analysis/SKILL.md +19 -7
  17. package/plugins/memtrace-skills/skills/memtrace-decision-memory/SKILL.md +16 -12
  18. package/plugins/memtrace-skills/skills/memtrace-decision-recall/SKILL.md +7 -4
  19. package/plugins/memtrace-skills/skills/memtrace-docs/SKILL.md +121 -0
  20. package/plugins/memtrace-skills/skills/memtrace-docs-ask/SKILL.md +91 -0
  21. package/plugins/memtrace-skills/skills/memtrace-docs-read/SKILL.md +84 -0
  22. package/plugins/memtrace-skills/skills/memtrace-docs-search/SKILL.md +87 -0
  23. package/plugins/memtrace-skills/skills/memtrace-first/SKILL.md +21 -3
  24. package/plugins/memtrace-skills/skills/memtrace-preflight/SKILL.md +14 -1
  25. package/plugins/memtrace-skills/skills/memtrace-provenance/SKILL.md +7 -4
  26. package/plugins/memtrace-skills/skills/memtrace-refactoring-guide/SKILL.md +21 -9
  27. package/skills/commands/memtrace-decision-recall.md +7 -4
  28. package/skills/commands/memtrace-docs-ask.md +99 -0
  29. package/skills/commands/memtrace-docs-read.md +91 -0
  30. package/skills/commands/memtrace-docs-search.md +94 -0
  31. package/skills/commands/memtrace-preflight.md +17 -1
  32. package/skills/commands/memtrace-provenance.md +7 -4
  33. package/skills/workflows/memtrace-change-impact-analysis.md +23 -7
  34. package/skills/workflows/memtrace-decision-memory.md +16 -12
  35. package/skills/workflows/memtrace-docs.md +129 -0
  36. package/skills/workflows/memtrace-first.md +25 -3
  37. package/skills/workflows/memtrace-refactoring-guide.md +25 -9
@@ -159,6 +159,17 @@ function checkAgent(agent) {
159
159
  mcpConfigPath,
160
160
  };
161
161
  }
162
+ if (agent === 'warp') {
163
+ const skillsDir = path.join(os.homedir(), '.warp', 'skills');
164
+ const mcpConfigPath = path.join(os.homedir(), '.warp', '.mcp.json');
165
+ return {
166
+ agent,
167
+ skillsFound: countMemtraceSkills(skillsDir),
168
+ skillsDir,
169
+ mcpRegistered: mcpHasMemtrace(mcpConfigPath),
170
+ mcpConfigPath,
171
+ };
172
+ }
162
173
  const skillsDir = path.join(os.homedir(), '.cursor', 'skills');
163
174
  const mcpConfigPath = path.join(os.homedir(), '.cursor', 'mcp.json');
164
175
  return {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ function parseOnly(val) {
15
15
  program
16
16
  .command('install')
17
17
  .description('Install memtrace skills and register MCP for selected agents')
18
- .option('--only <agents>', 'comma-separated agent names (claude,cursor,codex,gemini,windsurf,vscode,hermes,opencode,kiro)', parseOnly)
18
+ .option('--only <agents>', 'comma-separated agent names (claude,cursor,codex,gemini,windsurf,vscode,hermes,opencode,kiro,warp)', parseOnly)
19
19
  .option('--local', 'install into the current project where the selected agent supports project scope', false)
20
20
  .option('--global', 'install globally (~/.claude/, ~/.cursor/, ~/.agents/) [default]', false)
21
21
  .option('--skip-mcp', 'write skills only, skip MCP server registration', false)
@@ -8,7 +8,7 @@ import os from 'os';
8
8
  import path from 'path';
9
9
  import { registerRailHookInSettingsAt, removeRailHookFromSettingsAt, } from './transformers/claude.js';
10
10
  import { geminiSettingsPath } from './transformers/gemini.js';
11
- import { registerCursorRailHook, removeCursorRailHook, registerSettingsHook, removeSettingsHook, registerWindsurfRailHook, removeWindsurfRailHook, registerVsCodeRailHook, removeVsCodeRailHook, windsurfHooksPath, vscodeCopilotRailHookPath, CODEX_MATCHER, GEMINI_MATCHER, openCodePluginSource, } from './transformers/rail-hooks.js';
11
+ import { registerCursorRailHook, removeCursorRailHook, registerSettingsHook, removeSettingsHook, registerWindsurfRailHook, removeWindsurfRailHook, registerVsCodeRailHook, removeVsCodeRailHook, windsurfHooksPath, vscodeCopilotRailHookPath, CODEX_MATCHER, GEMINI_MATCHER, openCodeRailPluginPaths, openCodePluginSource, } from './transformers/rail-hooks.js';
12
12
  /** Hosts with no Rail hook surface yet (MCP/skills only). */
13
13
  export const RAIL_UNSUPPORTED = [
14
14
  { host: 'hermes', reason: 'Hermes pre_tool_call adapter not wired yet' },
@@ -22,17 +22,6 @@ function cursorHooksPath(ctx) {
22
22
  const base = ctx.scope === 'global' ? os.homedir() : ctx.cwd;
23
23
  return path.join(base, '.cursor', 'hooks.json');
24
24
  }
25
- function opencodeConfigDir(ctx) {
26
- const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
27
- return ctx.scope === 'global' ? path.join(xdg, 'opencode') : path.join(ctx.cwd, '.opencode');
28
- }
29
- function opencodeRailPluginPaths(ctx) {
30
- const base = opencodeConfigDir(ctx);
31
- return [
32
- path.join(base, 'plugin', 'memtrace-rail.js'),
33
- path.join(base, 'plugins', 'memtrace-rail.js'),
34
- ];
35
- }
36
25
  /**
37
26
  * Wire Rail discovery hooks for Claude, Cursor, Codex, Gemini, and OpenCode.
38
27
  * Idempotent. Does not install skills or MCP — hooks only.
@@ -80,14 +69,14 @@ export function installRailHooks(ctx) {
80
69
  });
81
70
  try {
82
71
  const src = openCodePluginSource(bin);
83
- for (const pluginPath of opencodeRailPluginPaths(ctx)) {
72
+ for (const pluginPath of openCodeRailPluginPaths(ctx)) {
84
73
  fs.mkdirSync(path.dirname(pluginPath), { recursive: true });
85
74
  fs.writeFileSync(pluginPath, src);
86
75
  }
87
76
  results.push({
88
77
  host: 'opencode',
89
78
  registered: true,
90
- path: opencodeRailPluginPaths(ctx)[0],
79
+ path: openCodeRailPluginPaths(ctx)[0],
91
80
  });
92
81
  }
93
82
  catch (e) {
@@ -135,7 +124,7 @@ export function uninstallRailHooks(ctx) {
135
124
  const geminiPath = geminiSettingsPath(ctx);
136
125
  const gemini = removeSettingsHook(geminiPath, 'BeforeTool');
137
126
  results.push({ host: 'gemini', registered: false, path: geminiPath, skipped: gemini.changed ? undefined : 'no rail hook' });
138
- for (const p of opencodeRailPluginPaths(ctx)) {
127
+ for (const p of openCodeRailPluginPaths(ctx)) {
139
128
  try {
140
129
  fs.rmSync(p, { force: true });
141
130
  }
@@ -4,7 +4,6 @@ import path from 'path';
4
4
  import { commandExists, execCommand } from '../utils.js';
5
5
  import { registerSettingsHook, removeSettingsHook, CODEX_MATCHER, } from './rail-hooks.js';
6
6
  const MCP_SERVER_NAME = 'memtrace';
7
- const CODEX_MCP_SHIM_NAME = 'memtrace-mcp-codex-shim.cjs';
8
7
  function codexRailHooksPath(ctx) {
9
8
  const base = ctx.scope === 'global' ? path.join(os.homedir(), '.codex') : path.join(ctx.cwd, '.codex');
10
9
  return path.join(base, 'hooks.json');
@@ -38,82 +37,6 @@ function writeTextAtomic(filePath, content) {
38
37
  fs.writeFileSync(tmpPath, content);
39
38
  fs.renameSync(tmpPath, filePath);
40
39
  }
41
- function codexMcpShimSource() {
42
- return `#!/usr/bin/env node
43
- const { spawn } = require('node:child_process');
44
-
45
- const binary = process.argv[2] || 'memtrace';
46
- const child = spawn(binary, ['mcp'], { stdio: ['pipe', 'pipe', 'pipe'] });
47
-
48
- process.stdin.pipe(child.stdin);
49
- child.stderr.pipe(process.stderr);
50
-
51
- function stripAnnotations(line) {
52
- let message;
53
- try {
54
- message = JSON.parse(line);
55
- } catch {
56
- return line;
57
- }
58
-
59
- const content = message && message.result && message.result.content;
60
- if (!Array.isArray(content)) return line;
61
-
62
- const assistantItems = [];
63
- const fallbackItems = [];
64
- for (const item of content) {
65
- if (!item || typeof item !== 'object') continue;
66
- const clean = { ...item };
67
- const annotations = clean.annotations;
68
- delete clean.annotations;
69
- fallbackItems.push(clean);
70
- const audience = annotations && annotations.audience;
71
- if (Array.isArray(audience) && audience.includes('assistant')) {
72
- assistantItems.push(clean);
73
- }
74
- }
75
-
76
- message.result.content = assistantItems.length > 0 ? assistantItems : fallbackItems;
77
- return JSON.stringify(message);
78
- }
79
-
80
- let buffer = '';
81
- child.stdout.on('data', (chunk) => {
82
- buffer += chunk.toString('utf8');
83
- let index = buffer.indexOf('\\n');
84
- while (index !== -1) {
85
- const line = buffer.slice(0, index);
86
- buffer = buffer.slice(index + 1);
87
- process.stdout.write(stripAnnotations(line) + '\\n');
88
- index = buffer.indexOf('\\n');
89
- }
90
- });
91
-
92
- child.stdout.on('end', () => {
93
- if (buffer.length > 0) process.stdout.write(stripAnnotations(buffer));
94
- });
95
-
96
- child.on('exit', (code, signal) => {
97
- if (signal) process.kill(process.pid, signal);
98
- process.exit(code ?? 0);
99
- });
100
- `;
101
- }
102
- function codexMcpShimPath(configFile) {
103
- return path.join(path.dirname(configFile), CODEX_MCP_SHIM_NAME);
104
- }
105
- function writeCodexMcpShim(configFile) {
106
- const shimPath = codexMcpShimPath(configFile);
107
- writeTextAtomic(shimPath, codexMcpShimSource());
108
- try {
109
- fs.chmodSync(shimPath, 0o755);
110
- }
111
- catch {
112
- // Windows and locked-down filesystems may reject chmod; Codex invokes it
113
- // through Node, so executable bits are a convenience, not a requirement.
114
- }
115
- return shimPath;
116
- }
117
40
  function isMemtraceMcpTable(tableName) {
118
41
  return tableName === `mcp_servers.${MCP_SERVER_NAME}`
119
42
  || tableName === `mcp_servers."${MCP_SERVER_NAME}"`
@@ -196,11 +119,15 @@ export function registerCodexMcpAt(configFile, binary) {
196
119
  // after the freshly written parent block for readability.
197
120
  const stripped = stripCodexMcpServer(existing);
198
121
  const { rest, envBlock } = extractMemtraceEnvBlock(stripped);
199
- const shimPath = writeCodexMcpShim(configFile);
122
+ // Bare `memtrace mcp` — no shim. The server now emits a Codex-safe, plain
123
+ // (annotation-free) tool response when the client identifies as Codex at
124
+ // `initialize` (MemtraceMcpServer / shape_tool_content), so Codex talks to the
125
+ // binary directly. This removes the Node shim that hand-configuring users
126
+ // (who wrote `command = "memtrace"` themselves) used to miss.
200
127
  const block = [
201
128
  `[mcp_servers.${MCP_SERVER_NAME}]`,
202
- `command = ${tomlString(process.execPath)}`,
203
- `args = [${[shimPath, binary].map(tomlString).join(', ')}]`,
129
+ `command = ${tomlString(binary)}`,
130
+ `args = ["mcp"]`,
204
131
  ].join('\n');
205
132
  const parts = [];
206
133
  if (rest)
@@ -8,7 +8,8 @@ import { vscodeTransformer } from './vscode.js';
8
8
  import { hermesTransformer } from './hermes.js';
9
9
  import { opencodeTransformer } from './opencode.js';
10
10
  import { kiroTransformer } from './kiro.js';
11
+ import { warpTransformer } from './warp.js';
11
12
  export declare const ALL_TRANSFORMERS: Transformer[];
12
13
  export declare function findTransformer(name: string): Transformer | undefined;
13
- export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, };
14
+ export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, };
14
15
  export type { Transformer, InstallContext, InstallResult, TransformResult } from './types.js';
@@ -7,6 +7,7 @@ import { vscodeTransformer } from './vscode.js';
7
7
  import { hermesTransformer } from './hermes.js';
8
8
  import { opencodeTransformer } from './opencode.js';
9
9
  import { kiroTransformer } from './kiro.js';
10
+ import { warpTransformer } from './warp.js';
10
11
  export const ALL_TRANSFORMERS = [
11
12
  claudeTransformer,
12
13
  cursorTransformer,
@@ -17,8 +18,9 @@ export const ALL_TRANSFORMERS = [
17
18
  hermesTransformer,
18
19
  opencodeTransformer,
19
20
  kiroTransformer,
21
+ warpTransformer,
20
22
  ];
21
23
  export function findTransformer(name) {
22
24
  return ALL_TRANSFORMERS.find(t => t.name === name);
23
25
  }
24
- export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, };
26
+ export { claudeTransformer, cursorTransformer, codexTransformer, geminiTransformer, windsurfTransformer, vscodeTransformer, hermesTransformer, opencodeTransformer, kiroTransformer, warpTransformer, };
@@ -1,33 +1,17 @@
1
1
  import fs from 'fs';
2
- import os from 'os';
3
2
  import path from 'path';
4
3
  import { registerOpenCodeMcpAt, removeMemtraceSkills, removeOpenCodeMcpAt, writeSkills, } from './shared.js';
5
- import { openCodePluginSource } from './rail-hooks.js';
6
- function opencodeConfigDir() {
7
- const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
8
- return path.join(xdg, 'opencode');
9
- }
4
+ import { openCodeConfigDir, openCodeRailPluginPaths, openCodePluginSource, } from './rail-hooks.js';
10
5
  function skillsRoot(ctx) {
11
6
  return ctx.scope === 'global'
12
- ? path.join(opencodeConfigDir(), 'skills')
7
+ ? path.join(openCodeConfigDir(ctx), 'skills')
13
8
  : path.join(ctx.cwd, '.opencode', 'skills');
14
9
  }
15
10
  function mcpPath(ctx) {
16
11
  return ctx.scope === 'global'
17
- ? path.join(opencodeConfigDir(), 'opencode.json')
12
+ ? path.join(openCodeConfigDir(ctx), 'opencode.json')
18
13
  : path.join(ctx.cwd, 'opencode.json');
19
14
  }
20
- // OpenCode's plugin directory name changed across versions: current official
21
- // docs use `plugins/` (plural) while older/community builds load `plugin/`
22
- // (singular). We write the plugin to BOTH so it loads regardless of version;
23
- // uninstall removes both. (Identical content — whichever the runtime reads.)
24
- function railPluginPaths(ctx) {
25
- const base = ctx.scope === 'global' ? opencodeConfigDir() : path.join(ctx.cwd, '.opencode');
26
- return [
27
- path.join(base, 'plugin', 'memtrace-rail.js'),
28
- path.join(base, 'plugins', 'memtrace-rail.js'),
29
- ];
30
- }
31
15
  export const opencodeTransformer = {
32
16
  name: 'opencode',
33
17
  async install(skills, ctx) {
@@ -47,7 +31,7 @@ export const opencodeTransformer = {
47
31
  // has no stdio hook). Defaults to observe; fail-open. Best effort.
48
32
  try {
49
33
  const src = openCodePluginSource(ctx.memtraceBinary);
50
- for (const pluginPath of railPluginPaths(ctx)) {
34
+ for (const pluginPath of openCodeRailPluginPaths(ctx)) {
51
35
  fs.mkdirSync(path.dirname(pluginPath), { recursive: true });
52
36
  fs.writeFileSync(pluginPath, src);
53
37
  }
@@ -67,7 +51,7 @@ export const opencodeTransformer = {
67
51
  async uninstall(ctx) {
68
52
  removeMemtraceSkills(skillsRoot(ctx));
69
53
  removeOpenCodeMcpAt(mcpPath(ctx));
70
- for (const p of railPluginPaths(ctx)) {
54
+ for (const p of openCodeRailPluginPaths(ctx)) {
71
55
  try {
72
56
  fs.rmSync(p, { force: true });
73
57
  }
@@ -5,6 +5,19 @@ export declare const CLAUDE_MATCHER = "Grep|Glob|Bash";
5
5
  export declare const CODEX_MATCHER = "Bash";
6
6
  export declare const GEMINI_MATCHER = "run_shell_command|read_file|read_many_files|glob|search_file_content";
7
7
  export declare function railCommand(binary: string, host: string): string;
8
+ export interface OpenCodePathContext {
9
+ scope: 'global' | 'local';
10
+ cwd: string;
11
+ }
12
+ export interface OpenCodePathOptions {
13
+ env?: NodeJS.ProcessEnv;
14
+ platform?: NodeJS.Platform;
15
+ homedir?: string;
16
+ }
17
+ export declare function openCodeHomeDir(options?: OpenCodePathOptions): string;
18
+ export declare function openCodeGlobalConfigDir(options?: OpenCodePathOptions): string;
19
+ export declare function openCodeConfigDir(ctx: OpenCodePathContext, options?: OpenCodePathOptions): string;
20
+ export declare function openCodeRailPluginPaths(ctx: OpenCodePathContext, options?: OpenCodePathOptions): string[];
8
21
  export declare function isRailHookEntry(entry: unknown): boolean;
9
22
  export interface HookResult {
10
23
  registered: boolean;
@@ -28,6 +28,56 @@ export const GEMINI_MATCHER = 'run_shell_command|read_file|read_many_files|glob|
28
28
  export function railCommand(binary, host) {
29
29
  return `${binary} route --hook --host ${host}`;
30
30
  }
31
+ function trimmed(value) {
32
+ const clean = value?.trim();
33
+ return clean ? clean : undefined;
34
+ }
35
+ function windowsProfileHome(env) {
36
+ const userProfile = trimmed(env.USERPROFILE);
37
+ if (userProfile)
38
+ return userProfile;
39
+ const drive = trimmed(env.HOMEDRIVE);
40
+ const homePath = trimmed(env.HOMEPATH);
41
+ return drive && homePath ? `${drive}${homePath}` : undefined;
42
+ }
43
+ export function openCodeHomeDir(options = {}) {
44
+ const env = options.env ?? process.env;
45
+ const platform = options.platform ?? process.platform;
46
+ const fallbackHome = trimmed(options.homedir ?? os.homedir());
47
+ const posixHome = trimmed(env.HOME);
48
+ const winHome = windowsProfileHome(env);
49
+ if (platform === 'win32') {
50
+ return winHome ?? posixHome ?? fallbackHome ?? '';
51
+ }
52
+ return posixHome ?? fallbackHome ?? winHome ?? '';
53
+ }
54
+ export function openCodeGlobalConfigDir(options = {}) {
55
+ const env = options.env ?? process.env;
56
+ const xdg = trimmed(env.XDG_CONFIG_HOME);
57
+ if (xdg)
58
+ return path.join(xdg, 'opencode');
59
+ const home = openCodeHomeDir(options);
60
+ if (!home) {
61
+ throw new Error('OpenCode config path unavailable: HOME or USERPROFILE is not set');
62
+ }
63
+ return path.join(home, '.config', 'opencode');
64
+ }
65
+ export function openCodeConfigDir(ctx, options = {}) {
66
+ return ctx.scope === 'global'
67
+ ? openCodeGlobalConfigDir(options)
68
+ : path.join(ctx.cwd, '.opencode');
69
+ }
70
+ // OpenCode's plugin directory name changed across versions: current official
71
+ // docs use `plugins/` (plural) while older/community builds load `plugin/`
72
+ // (singular). We write the plugin to BOTH so it loads regardless of version;
73
+ // uninstall removes both. (Identical content — whichever the runtime reads.)
74
+ export function openCodeRailPluginPaths(ctx, options = {}) {
75
+ const base = openCodeConfigDir(ctx, options);
76
+ return [
77
+ path.join(base, 'plugin', 'memtrace-rail.js'),
78
+ path.join(base, 'plugins', 'memtrace-rail.js'),
79
+ ];
80
+ }
31
81
  function isRecord(v) {
32
82
  return typeof v === 'object' && v !== null && !Array.isArray(v);
33
83
  }
@@ -29,7 +29,7 @@ export interface InstallResult {
29
29
  mcpRegistered: boolean;
30
30
  warnings: string[];
31
31
  }
32
- export type AgentName = 'claude' | 'cursor' | 'codex' | 'gemini' | 'windsurf' | 'vscode' | 'hermes' | 'opencode' | 'kiro';
32
+ export type AgentName = 'claude' | 'cursor' | 'codex' | 'gemini' | 'windsurf' | 'vscode' | 'hermes' | 'opencode' | 'kiro' | 'warp';
33
33
  /**
34
34
  * One transformer per supported AI coding agent.
35
35
  */
@@ -0,0 +1,4 @@
1
+ import { Transformer } from './types.js';
2
+ /** Global Warp MCP config: `~/.warp/.mcp.json` (auto-spawns by default). */
3
+ export declare function warpMcpPath(): string;
4
+ export declare const warpTransformer: Transformer;
@@ -0,0 +1,83 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import { registerMcpServersJsonAt, removeMcpServersJsonAt, removeMemtraceSkills, writeSkills, } from './shared.js';
4
+ // Warp (https://docs.warp.dev/agent-platform/capabilities/mcp/) is a terminal
5
+ // with a built-in agent that reads the open Agent Skills format (SKILL.md +
6
+ // name/description frontmatter) and stdio MCP servers from JSON config.
7
+ //
8
+ // Two things make Warp's wiring different from the other transformers:
9
+ //
10
+ // 1. Skills + MCP both live under `~/.warp/` (global) or `<cwd>/.warp/`
11
+ // (project). We write to Warp's NATIVE provider paths rather than the
12
+ // `~/.agents/` "Other agents" paths, because the Warp provider row
13
+ // auto-spawns global servers by default — the "Other agents" row
14
+ // requires the user to toggle "Auto-spawn servers from third-party
15
+ // agents" on. Native paths = MCP works on first launch, zero toggling.
16
+ //
17
+ // 2. The MCP config file is a dotfile: `~/.warp/.mcp.json` (note the
18
+ // leading dot on the filename). The shared JSON helpers don't care
19
+ // about the basename — they read/merge/write atomically — so the only
20
+ // thing we customize is the path.
21
+ //
22
+ // Warp uses the same `mcpServers` schema as Claude Code / Windsurf / Kiro
23
+ // (each entry: `{ command, args, env? }`), so we reuse
24
+ // `registerMcpServersJsonAt` / `removeMcpServersJsonAt` from shared.ts —
25
+ // corrupted-file backup, unrelated-server preservation, and empty-file
26
+ // deletion on uninstall all come for free.
27
+ function warpRoot(ctx) {
28
+ return ctx.scope === 'global'
29
+ ? path.join(os.homedir(), '.warp')
30
+ : path.join(ctx.cwd, '.warp');
31
+ }
32
+ function skillsRoot(ctx) {
33
+ return path.join(warpRoot(ctx), 'skills');
34
+ }
35
+ /** Global Warp MCP config: `~/.warp/.mcp.json` (auto-spawns by default). */
36
+ export function warpMcpPath() {
37
+ return path.join(os.homedir(), '.warp', '.mcp.json');
38
+ }
39
+ function mcpPath(ctx) {
40
+ // Global: the Warp provider file (auto-spawns). Local: project-scoped
41
+ // `.warp/.mcp.json` at the repo root — Warp detects these but NEVER
42
+ // auto-spawns them (security: a cloned repo can't start arbitrary local
43
+ // commands). The install warning below tells the user to toggle it on.
44
+ return ctx.scope === 'global'
45
+ ? warpMcpPath()
46
+ : path.join(ctx.cwd, '.warp', '.mcp.json');
47
+ }
48
+ export const warpTransformer = {
49
+ name: 'warp',
50
+ async install(skills, ctx) {
51
+ const rootDir = skillsRoot(ctx);
52
+ const count = writeSkills(skills, rootDir);
53
+ const warnings = [];
54
+ let mcpRegistered = false;
55
+ const mcpConfigPath = mcpPath(ctx);
56
+ if (!ctx.skipMcp) {
57
+ const result = registerMcpServersJsonAt(mcpConfigPath, ctx.memtraceBinary);
58
+ mcpRegistered = result.registered;
59
+ if (!mcpRegistered && result.backupPath) {
60
+ warnings.push(`Warp MCP config was malformed; backed up to ${result.backupPath}.`);
61
+ }
62
+ // Project-scoped Warp servers never auto-spawn — the user must toggle
63
+ // the server on from Settings > Agents > MCP servers after install.
64
+ // Surface that so a `--local` install doesn't look like a no-op.
65
+ if (ctx.scope === 'local') {
66
+ warnings.push('Warp project-scoped MCP servers never auto-spawn — ' +
67
+ 'enable "memtrace" under Settings > Agents > MCP servers in Warp.');
68
+ }
69
+ }
70
+ return {
71
+ agent: 'warp',
72
+ skillsWritten: count,
73
+ skillsDir: rootDir,
74
+ mcpConfigPath,
75
+ mcpRegistered,
76
+ warnings,
77
+ };
78
+ },
79
+ async uninstall(ctx) {
80
+ removeMemtraceSkills(skillsRoot(ctx));
81
+ removeMcpServersJsonAt(mcpPath(ctx));
82
+ },
83
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memtrace-skills",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Memtrace skills for AI coding agents — codebase exploration, temporal evolution, impact analysis, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memtrace-skills",
3
3
  "description": "Memtrace skills for codebase exploration, code search, relationship analysis, temporal evolution, blast radius impact, code quality, graph algorithms, API topology, GitHub PR code review, multi-agent fleet coordination (branch-scoped intents, conflict classification, and agent-judged Class C resolution), and multi-step workflows for change impact, incident investigation, refactoring, session continuity, co-change analysis, and episode replay, Cortex decision memory (decision recall, provenance, intent verification), session bookends (daily briefing, hotspots, self-audit), and single-symbol pre-edit preflight checks.",
4
- "version": "0.8.3",
4
+ "version": "0.8.4",
5
5
  "author": {
6
6
  "name": "Syncable",
7
7
  "email": "support@syncable.dev"
@@ -372,8 +372,9 @@ No parameters. Returns `{ watches: [{ path, repo_id, branch, started_at, origin
372
372
 
373
373
  ## Cortex decision memory
374
374
 
375
- Proxied to memcortex-serve; available when the Cortex store is enabled. Ids are
376
- **JSON numbers** (Cortex node ids), not strings — `decision_id: 1`, never `"1"`.
375
+ Exposed on the normal `memtrace` MCP server and proxied to the local MemCortex
376
+ sidecar when Cortex is enabled. Ids are **JSON numbers** (Cortex node ids), not
377
+ strings — `decision_id: 1`, never `"1"`.
377
378
  An unknown or empty query/id returns an explicit **CannotProve**, never a
378
379
  fabricated result.
379
380
 
@@ -381,6 +382,8 @@ fabricated result.
381
382
  | Field | Type | Required | Notes |
382
383
  |---|---|---|---|
383
384
  | `query` | string | yes | Free text; drives the lexical lane, decisions ranked first |
385
+ | `top_k` | integer | no | Optional result cap; must be > 0 |
386
+ | `min_score` | number | no | Optional finite score floor |
384
387
 
385
388
  (A tier-based decision cap is applied server-side; it is not a caller parameter.)
386
389
 
@@ -397,7 +400,7 @@ fabricated result.
397
400
  ## When this file is wrong
398
401
 
399
402
  It's drift-prone. Regenerate by grepping `crates/memtrace-mcp/src/tools/*.rs`
400
- for `^pub struct.*Params` — the Rust declarations are the source of truth
401
- (Cortex decision tools: `MemCortex/crates/memcortex-mcp/src/request.rs`).
403
+ and `crates/memtrace-mcp/src/cortex_client.rs` for `Params` declarations the
404
+ Rust declarations are the source of truth.
402
405
  If a live tool call rejects with `-32602`, trust the Rust struct over this
403
406
  doc and file a fix PR.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: memtrace-change-impact-analysis
3
- description: "Compute what a planned source-code change will break — blast radius, affected processes, cross-repo callers — and produce a risk-rated change plan. Use when the user needs to know what will break before a change: edits, refactors, API changes, renames, removals, PR reviews, or risk assessments. Do not manually grep references or browse files for impact; this workflow uses Memtrace graph context, impact, and change history. For a quick blast-radius check on one symbol, use memtrace-impact."
3
+ description: "Compute what a planned source-code change will break — blast radius, affected processes, cross-repo callers, temporal stability, and Cortex decision-memory constraints — and produce a risk-rated change plan. Use before edits, refactors, API changes, renames, removals, PR reviews, or risk assessments, especially when changing established behavior or deleting code. Do not manually grep references or browse files for impact; use Memtrace graph context, change history, and decision recall/provenance."
4
4
  ---
5
5
 
6
6
  ## Overview
7
7
 
8
- Pre-change risk assessment workflow. Before modifying code, this workflow maps the full blast radius, identifies affected processes, checks recent change history for instability signals, and produces a risk-rated change plan.
8
+ Pre-change risk assessment workflow. Before modifying code, this workflow maps the full blast radius, identifies affected processes, checks recent change history for instability signals, checks Cortex decision memory for recorded decisions/bans/contracts, and produces a risk-rated change plan.
9
9
 
10
10
  ## Steps
11
11
 
@@ -42,20 +42,30 @@ For each target, call `get_impact` (`repo_id`, `target`) with `direction: "both"
42
42
  | High | Plan incremental migration; consider feature flags |
43
43
  | Critical | Full migration strategy; backward-compatible changes required |
44
44
 
45
- ### 4. Check temporal stability
45
+ ### 4. Check Cortex decision memory
46
+
47
+ For each target or subsystem, call `recall_decision("<target/subsystem/approach>")`.
48
+ If the graph result exposes a numeric `symbol_id`, also call
49
+ `why_is_this_here(symbol_id)` and `governing_contracts(symbol_id)`.
50
+
51
+ - Matching decision/ban/convention → include it in the plan before recommending a change.
52
+ - Decision you intend to rely on → run `verify_intent(decision_id)` first.
53
+ - CannotProve → record "no decision proven"; do not treat it as approval.
54
+
55
+ ### 5. Check temporal stability
46
56
 
47
57
  Call `get_evolution` (`repo_id`, `from: "30d ago"`, `mode: "compound"`), then `get_timeline` on each target symbol (requires `scope_path` + `file_path` from `find_symbol`):
48
58
  - Sparse timeline history + you're about to change it → structurally surprising; extra scrutiny warranted.
49
59
  - Target appears in `top_touched_symbols` → high churn + high impact = volatile hotspot.
50
60
 
51
- ### 5. Map affected execution flows
61
+ ### 6. Map affected execution flows
52
62
 
53
63
  From step 2, you already know which processes are affected. For critical changes, use `analyze_relationships` (`repo_id`, `target`) with `query_type: "find_callers"` at `depth: 3` to trace the full transitive caller chain.
54
64
 
55
65
  `depth: 3` is a JSON number, not a string — the validator rejects `"3"`.
56
66
  Full parameter spec for every Memtrace tool: `references/mcp-parameters.md` (bundled at the memtrace-skills plugin root).
57
67
 
58
- ### 6. Produce the risk assessment
68
+ ### 7. Produce the risk assessment
59
69
 
60
70
  Synthesize into a change plan:
61
71
 
@@ -64,8 +74,9 @@ Synthesize into a change plan:
64
74
  3. **Affected Processes** — which execution flows will be impacted
65
75
  4. **Cross-Service Impact** — any external callers or consumers
66
76
  5. **Stability Signal** — sparse `get_timeline` history (stable) vs frequent appearance in `top_touched_symbols` (volatile)
67
- 6. **Recommended Approach** — based on risk: direct change, incremental migration, or backward-compatible evolution
68
- 7. **Test Coverage** — which callers/processes to verify after the change
77
+ 6. **Decision Memory** — relevant decisions/bans/contracts, or CannotProve as unknown
78
+ 7. **Recommended Approach** — based on risk and decision constraints: direct change, incremental migration, or backward-compatible evolution
79
+ 8. **Test Coverage** — which callers/processes to verify after the change
69
80
 
70
81
  ## Decision Points
71
82
 
@@ -76,6 +87,7 @@ Synthesize into a change plan:
76
87
  | Symbol has sparse timeline but high impact | Extra review — this rarely changes; make sure the change is intentional |
77
88
  | Multiple processes affected | List each affected flow; recommend testing each one |
78
89
  | Symbol is a bridge point | Change may disconnect parts of the architecture — verify alternative paths exist |
90
+ | Cortex returns a held ban/contract | Do not recommend contradicting it without explicit user sign-off |
79
91
 
80
92
  ## Output
81
93
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: memtrace-decision-memory
3
- description: "Check Cortex decision memory — the umbrella entry point for decision recall, provenance (why is this here), intent verification, and governing contracts. Use before assuming WHY code exists, before making a choice that may contradict a past one, or before deleting/rewriting code that looks odd or redundant. Route: free-text what-was-decided → memtrace-decision-recall; why does this symbol exist / what constrains it → memtrace-provenance; did the decision hold → memtrace-intent-verification. Do not guess rationale from the diff or git log; Cortex remembers the decision, the ban, and whether it still holds."
3
+ description: "Check Cortex decision memory through the normal Memtrace MCP tools — the umbrella entry point for decision recall, provenance (why is this here), intent verification, and governing contracts. Use before assuming WHY code exists, before any non-trivial edit/refactor/delete of existing code, before re-picking a library/pattern/architecture, or before contradicting an apparent convention. Route: free-text decisions/bans/conventions → memtrace-decision-recall; symbol lineage/contracts → memtrace-provenance; did the decision hold → memtrace-intent-verification. Do not guess rationale from the diff or git log."
4
4
  ---
5
5
 
6
6
  # Decision Memory First
@@ -42,12 +42,14 @@ These five tools are **deterministic, zero-LLM**. Every call returns a labeled
42
42
  `CannotProve` is a real, trustworthy answer ("memory has nothing on this"), not a
43
43
  failure and not a green light. Never invent a rationale to fill the gap.
44
44
 
45
- ## Server check (once per session)
45
+ ## Tool availability (once per session)
46
46
 
47
- These tools are served by the **`memcortex`** MCP server (decision memory), separate
48
- from the `memtrace` code-graph server. If `recall_decision` isn't available, decision
49
- memory isn't wired in this environment say so and use `memtrace-first` instead;
50
- don't fabricate decisions.
47
+ These tools are exposed on the normal **`memtrace` MCP server**:
48
+ `recall_decision`, `why_is_this_here`, `verify_intent`, `get_arc`, and
49
+ `governing_contracts`. Hosts do not need a second MemCortex MCP connection. If a
50
+ tool call returns CannotProve because Cortex is unavailable (for example native
51
+ Windows without WSL2), say decision memory was unavailable/unknown and continue
52
+ with `memtrace-first`; do not fabricate decisions.
51
53
 
52
54
  ## The decision rule
53
55
 
@@ -55,9 +57,9 @@ don't fabricate decisions.
55
57
  |---|---|---|
56
58
  | "Did we already decide/choose/reject X?" "What's our convention on Y?" | `recall_decision("X")` | `memtrace-decision-recall` |
57
59
  | "Is there a ban / a 'don't do this' on Z?" | `recall_decision("Z")` — bans surface as decisions | `memtrace-decision-recall` |
58
- | About to re-pick a library/pattern/approach | `recall_decision` FIRST — don't re-litigate a settled call | `memtrace-decision-recall` |
60
+ | About to edit behavior, re-pick a library/pattern/architecture, or change a subsystem policy | `recall_decision` FIRST — don't re-litigate a settled call | `memtrace-decision-recall` |
59
61
  | "Why is this code here?" "Why is it done this odd way?" | `why_is_this_here(symbol_id)` | `memtrace-provenance` |
60
- | About to delete/refactor code that looks unused or strange | `why_is_this_here` + `governing_contracts` before touching it | `memtrace-provenance` |
62
+ | About to delete/refactor/clean up existing code, especially odd or "dead" code | `why_is_this_here` + `governing_contracts` before touching it | `memtrace-provenance` |
61
63
  | "What rules/contracts constrain this symbol?" | `governing_contracts(symbol_id)` | `memtrace-provenance` |
62
64
  | "Did decision D actually hold, or did we drift?" | `verify_intent(decision_id)` | `memtrace-intent-verification` |
63
65
  | "What commits/episodes implemented decision D?" | `get_arc(decision_id)` | `memtrace-intent-verification` |
@@ -85,10 +87,11 @@ have a name or a free-text question, start with `recall_decision`.** Do not inve
85
87
  ## Standard workflows
86
88
 
87
89
  ### "Why does this code exist / can I delete it?"
88
- 1. `why_is_this_here(symbol_id)` the governing decision, if any
89
- 2. `governing_contracts(symbol_id)` → constraints that must survive a rewrite
90
- 3. If a decision governs it → `verify_intent(decision_id)` to see if it still holds
91
- 4. **CannotProve on all three safe to delete** — confirm with `memtrace-impact` (blast radius) and the user
90
+ 1. If you only have a name/free-text target, `recall_decision("<symbol/subsystem>")` first.
91
+ 2. If you have a `symbol_id`, `why_is_this_here(symbol_id)` → the governing decision, if any.
92
+ 3. `governing_contracts(symbol_id)` constraints that must survive a rewrite.
93
+ 4. If a decision governs it `verify_intent(decision_id)` to see if it still holds.
94
+ 5. **CannotProve on all checks ≠ safe to delete** — confirm with `memtrace-impact` (blast radius) and the user.
92
95
 
93
96
  ### "Should I do X?" (about to make a choice)
94
97
  1. `recall_decision("X")` → did we already decide or ban this?
@@ -104,6 +107,7 @@ have a name or a free-text question, start with `recall_decision`.** Do not inve
104
107
  | Thought | Reality |
105
108
  |---|---|
106
109
  | "This code looks unused, I'll delete it" | `why_is_this_here` first — a decision may govern it; deletion may reopen a closed issue |
110
+ | "I'll just edit/refactor this existing behavior" | `recall_decision("<behavior/subsystem>")` first — the change may violate a recorded decision or ban |
107
111
  | "I'll just use library/pattern X" | `recall_decision("X")` — you may be undoing a deliberate ban |
108
112
  | "The diff/git log will tell me why" | Git shows *what changed*, not *what was decided or rejected*. Decision memory has the rationale and the bans. |
109
113
  | "CannotProve, so it's fine/approved" | CannotProve = unknown, not approved. Don't treat absence of a record as permission. |