memtrace-skills 0.8.3 → 0.8.5
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/commands/doctor.js +11 -0
- package/dist/index.js +1 -1
- package/dist/rail-install.js +4 -15
- package/dist/transformers/codex.js +7 -80
- package/dist/transformers/index.d.ts +2 -1
- package/dist/transformers/index.js +3 -1
- package/dist/transformers/opencode.js +5 -21
- package/dist/transformers/rail-hooks.d.ts +13 -0
- package/dist/transformers/rail-hooks.js +50 -0
- package/dist/transformers/types.d.ts +1 -1
- package/dist/transformers/warp.d.ts +4 -0
- package/dist/transformers/warp.js +83 -0
- package/package.json +1 -1
- package/plugins/memtrace-skills/.claude-plugin/plugin.json +1 -1
- package/plugins/memtrace-skills/references/mcp-parameters.md +7 -4
- package/plugins/memtrace-skills/skills/memtrace-change-impact-analysis/SKILL.md +19 -7
- package/plugins/memtrace-skills/skills/memtrace-decision-memory/SKILL.md +16 -12
- package/plugins/memtrace-skills/skills/memtrace-decision-recall/SKILL.md +7 -4
- package/plugins/memtrace-skills/skills/memtrace-docs/SKILL.md +121 -0
- package/plugins/memtrace-skills/skills/memtrace-docs-ask/SKILL.md +91 -0
- package/plugins/memtrace-skills/skills/memtrace-docs-read/SKILL.md +84 -0
- package/plugins/memtrace-skills/skills/memtrace-docs-search/SKILL.md +87 -0
- package/plugins/memtrace-skills/skills/memtrace-first/SKILL.md +21 -3
- package/plugins/memtrace-skills/skills/memtrace-preflight/SKILL.md +14 -1
- package/plugins/memtrace-skills/skills/memtrace-provenance/SKILL.md +7 -4
- package/plugins/memtrace-skills/skills/memtrace-refactoring-guide/SKILL.md +21 -9
- package/skills/commands/memtrace-decision-recall.md +7 -4
- package/skills/commands/memtrace-docs-ask.md +99 -0
- package/skills/commands/memtrace-docs-read.md +91 -0
- package/skills/commands/memtrace-docs-search.md +94 -0
- package/skills/commands/memtrace-preflight.md +17 -1
- package/skills/commands/memtrace-provenance.md +7 -4
- package/skills/workflows/memtrace-change-impact-analysis.md +23 -7
- package/skills/workflows/memtrace-decision-memory.md +16 -12
- package/skills/workflows/memtrace-docs.md +129 -0
- package/skills/workflows/memtrace-first.md +25 -3
- package/skills/workflows/memtrace-refactoring-guide.md +25 -9
package/dist/commands/doctor.js
CHANGED
|
@@ -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)
|
package/dist/rail-install.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
203
|
-
`args = [
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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,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,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.
|
|
4
|
+
"version": "0.8.5",
|
|
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
|
-
|
|
376
|
-
**JSON numbers** (Cortex node ids), not
|
|
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
|
|
401
|
-
|
|
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
|
|
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
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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. **
|
|
68
|
-
7. **
|
|
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
|
|
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
|
-
##
|
|
45
|
+
## Tool availability (once per session)
|
|
46
46
|
|
|
47
|
-
These tools are
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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/
|
|
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
|
|
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.
|
|
89
|
-
2. `
|
|
90
|
-
3.
|
|
91
|
-
4.
|
|
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. |
|