memtrace-skills 1.2.9 → 1.2.11
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/managed-skills.d.ts +4 -0
- package/dist/managed-skills.js +95 -0
- package/dist/rail-hook.d.ts +7 -1
- package/dist/rail-hook.js +177 -57
- package/dist/rail-install.js +3 -6
- package/dist/rail-lifecycle.js +6 -3
- package/dist/rail-runtime.d.ts +38 -3
- package/dist/rail-runtime.js +88 -7
- package/dist/tool-names.js +6 -0
- package/dist/transformers/claude.d.ts +16 -4
- package/dist/transformers/claude.js +69 -82
- package/dist/transformers/codex.js +5 -20
- package/dist/transformers/cursor.js +5 -20
- package/dist/transformers/kiro.js +5 -14
- package/dist/transformers/opencode.js +6 -6
- package/dist/transformers/pi.js +5 -5
- package/dist/transformers/rail-hooks.d.ts +6 -1
- package/dist/transformers/rail-hooks.js +72 -13
- package/dist/transformers/shared.d.ts +2 -1
- package/dist/transformers/shared.js +28 -23
- 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 +55 -24
- 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/managed-stock-sha256.txt +364 -0
- package/skills/workflows/memtrace-codebase-exploration.md +4 -0
- package/skills/workflows/memtrace-first.md +55 -24
- 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
|
@@ -19,7 +19,7 @@ export interface SettingsMutationResult {
|
|
|
19
19
|
* Merge-add the memtrace MCP server into a Claude settings.json file.
|
|
20
20
|
* Never overwrites a malformed file — backs it up and returns registered=false.
|
|
21
21
|
*/
|
|
22
|
-
export declare function registerMcpInSettingsAt(settingsPath: string, memtraceBinary: string, legacyEnv?: Record<string, string
|
|
22
|
+
export declare function registerMcpInSettingsAt(settingsPath: string, memtraceBinary: string, legacyEnv?: Record<string, string>, legacyArgs?: string[]): SettingsMutationResult;
|
|
23
23
|
/**
|
|
24
24
|
* Every host enters a local liveness guard before contacting Memtrace. The
|
|
25
25
|
* daemon lifecycle registers/removes this command; a cached registration is
|
|
@@ -32,8 +32,8 @@ export declare function claudeRailHookCommand(_memtraceBinary: string, platform?
|
|
|
32
32
|
* This is what makes Rail "part of memtrace" rather than a manual install:
|
|
33
33
|
* `memtrace install` wires a PreToolUse hook that pipes every Grep/Glob/Bash
|
|
34
34
|
* attempt through the Rail router (see {@link claudeRailHookCommand} for the
|
|
35
|
-
* transport). While a daemon is running the hook defaults to **
|
|
36
|
-
*
|
|
35
|
+
* transport). While a daemon is running the hook defaults to **nudge** —
|
|
36
|
+
* context alongside ordinary tools. Explicit modes are preserved. Mode is intentionally NOT
|
|
37
37
|
* baked into the command so the user can change it via env without
|
|
38
38
|
* re-installing.
|
|
39
39
|
*
|
|
@@ -41,7 +41,19 @@ export declare function claudeRailHookCommand(_memtraceBinary: string, platform?
|
|
|
41
41
|
* is replaced, and unrelated PreToolUse hooks are preserved. Never overwrites
|
|
42
42
|
* a malformed file.
|
|
43
43
|
*/
|
|
44
|
-
export declare function registerRailHookInSettingsAt(settingsPath: string, memtraceBinary: string, platform?: NodeJS.Platform): SettingsMutationResult;
|
|
44
|
+
export declare function registerRailHookInSettingsAt(settingsPath: string, memtraceBinary: string, platform?: NodeJS.Platform, options?: RailHookOptions): SettingsMutationResult;
|
|
45
|
+
/** Grep enrichment rides PostToolUse on these tools (design section 9). Claude Code on
|
|
46
|
+
* Windows runs shell commands through its PowerShell tool, so rg/grep/git grep there
|
|
47
|
+
* arrive as PowerShell calls. */
|
|
48
|
+
export declare const ENRICH_HOOK_MATCHER = "Grep|Bash|PowerShell";
|
|
49
|
+
/** Claude hook timeout for the enrichment hooks, in seconds. */
|
|
50
|
+
export declare const ENRICH_HOOK_TIMEOUT_S = 2;
|
|
51
|
+
export interface RailHookOptions {
|
|
52
|
+
/** Register grep-enrichment hooks. Explicit env/config wins; implicit on only in Nudge. */
|
|
53
|
+
enrich?: boolean;
|
|
54
|
+
/** The Rail mode the PreToolUse decision depends on. Default: env, then rail.json. */
|
|
55
|
+
mode?: string;
|
|
56
|
+
}
|
|
45
57
|
/**
|
|
46
58
|
* Remove the Memtrace Rail PreToolUse hook from a Claude settings.json.
|
|
47
59
|
* Preserves unrelated hooks; prunes empty containers. Never touches a
|
|
@@ -3,9 +3,10 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { copyCanonicalPluginAssets, copyCanonicalPluginManifest, readCanonicalPluginVersion, } from '../../plugin-assets/index.js';
|
|
6
|
+
import { writeManagedSkill, removeManagedSkills } from '../managed-skills.js';
|
|
6
7
|
import { execCommand, commandExists } from '../utils.js';
|
|
7
8
|
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
8
|
-
import { railIsActive, railHookInvocation, railHookCommand, RAIL_GUARD_MARKER } from '../rail-runtime.js';
|
|
9
|
+
import { railIsActive, railHookInvocation, railHookCommand, RAIL_GUARD_MARKER, enrichEnabled, railMode, railConfig } from '../rail-runtime.js';
|
|
9
10
|
import { LEGACY_MCP_SERVER_NAME, MEMTRACE_MCP_ENV, removeLegacyMcpServer, } from './shared.js';
|
|
10
11
|
const PLUGIN_NAME = 'memtrace-skills';
|
|
11
12
|
const MARKETPLACE_NAME = 'memtrace';
|
|
@@ -27,6 +28,17 @@ const MARKETPLACE_SETTING_CONTAINERS = [
|
|
|
27
28
|
function isRecord(value) {
|
|
28
29
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
30
|
}
|
|
31
|
+
function existingMcpArgs(entry) {
|
|
32
|
+
if (!isRecord(entry) || !Array.isArray(entry.args) || entry.args.length === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
return entry.args.every((arg) => typeof arg === 'string') ? [...entry.args] : undefined;
|
|
35
|
+
}
|
|
36
|
+
function readExistingMemtraceArgs(filePath) {
|
|
37
|
+
const { value, corrupted } = safeReadJson(filePath);
|
|
38
|
+
if (corrupted || !isRecord(value?.mcpServers))
|
|
39
|
+
return undefined;
|
|
40
|
+
return existingMcpArgs(value.mcpServers.memtrace);
|
|
41
|
+
}
|
|
30
42
|
/**
|
|
31
43
|
* Read the existing env block (if any) for the memtrace MCP entry in a Claude
|
|
32
44
|
* settings/mcp.json file. Returns an empty object if the file or entry is
|
|
@@ -82,7 +94,8 @@ async function tryMcpAddJson(memtraceBinary) {
|
|
|
82
94
|
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
83
95
|
const config = JSON.stringify({
|
|
84
96
|
command: memtraceBinary,
|
|
85
|
-
args:
|
|
97
|
+
args: readExistingMemtraceArgs(claudeUserConfigFile())
|
|
98
|
+
?? readExistingMemtraceArgs(settingsFile) ?? ['mcp'],
|
|
86
99
|
env: mergedEnv,
|
|
87
100
|
});
|
|
88
101
|
// Shell-quote the JSON. Use single quotes; escape any embedded single quote.
|
|
@@ -185,7 +198,7 @@ async function tryClaudeCliInstall() {
|
|
|
185
198
|
* Merge-add the memtrace MCP server into a Claude settings.json file.
|
|
186
199
|
* Never overwrites a malformed file — backs it up and returns registered=false.
|
|
187
200
|
*/
|
|
188
|
-
export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv = {}) {
|
|
201
|
+
export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv = {}, legacyArgs) {
|
|
189
202
|
const { value, corrupted, backupPath } = safeReadJson(settingsPath);
|
|
190
203
|
if (corrupted) {
|
|
191
204
|
console.warn(`memtrace: ${settingsPath} is malformed; backed up to ${backupPath}. Skipped MCP registration for Claude.`);
|
|
@@ -209,7 +222,9 @@ export function registerMcpInSettingsAt(settingsPath, memtraceBinary, legacyEnv
|
|
|
209
222
|
// text can overcome. alwaysLoad opts the memtrace tools back into turn one.
|
|
210
223
|
settings.mcpServers['memtrace'] = {
|
|
211
224
|
command: memtraceBinary,
|
|
212
|
-
|
|
225
|
+
// Workspace and transport flags belong to the user. Replacing these on
|
|
226
|
+
// upgrade can send every editor into its own store instead of the owner.
|
|
227
|
+
args: existingMcpArgs(existing) ?? legacyArgs ?? ['mcp'],
|
|
213
228
|
env: mergedEnv,
|
|
214
229
|
alwaysLoad: true,
|
|
215
230
|
};
|
|
@@ -248,8 +263,8 @@ export function claudeRailHookCommand(_memtraceBinary, platform = process.platfo
|
|
|
248
263
|
* This is what makes Rail "part of memtrace" rather than a manual install:
|
|
249
264
|
* `memtrace install` wires a PreToolUse hook that pipes every Grep/Glob/Bash
|
|
250
265
|
* attempt through the Rail router (see {@link claudeRailHookCommand} for the
|
|
251
|
-
* transport). While a daemon is running the hook defaults to **
|
|
252
|
-
*
|
|
266
|
+
* transport). While a daemon is running the hook defaults to **nudge** —
|
|
267
|
+
* context alongside ordinary tools. Explicit modes are preserved. Mode is intentionally NOT
|
|
253
268
|
* baked into the command so the user can change it via env without
|
|
254
269
|
* re-installing.
|
|
255
270
|
*
|
|
@@ -257,7 +272,7 @@ export function claudeRailHookCommand(_memtraceBinary, platform = process.platfo
|
|
|
257
272
|
* is replaced, and unrelated PreToolUse hooks are preserved. Never overwrites
|
|
258
273
|
* a malformed file.
|
|
259
274
|
*/
|
|
260
|
-
export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platform = process.platform) {
|
|
275
|
+
export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platform = process.platform, options = {}) {
|
|
261
276
|
if (!railIsActive()) {
|
|
262
277
|
removeRailHookFromSettingsAt(settingsPath);
|
|
263
278
|
return { registered: false };
|
|
@@ -270,26 +285,40 @@ export function registerRailHookInSettingsAt(settingsPath, memtraceBinary, platf
|
|
|
270
285
|
const settings = (value ?? {});
|
|
271
286
|
settings.hooks = isRecord(settings.hooks) ? settings.hooks : {};
|
|
272
287
|
pruneMemtraceOwnedHookEntries(settings.hooks);
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
{
|
|
282
|
-
type: 'command',
|
|
283
|
-
...(platform === 'win32'
|
|
284
|
-
? railHookInvocation('claude-code')
|
|
285
|
-
: { command: claudeRailHookCommand(memtraceBinary, platform) }),
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
+
const config = railConfig();
|
|
289
|
+
const mode = options.mode ?? railMode(process.env, config);
|
|
290
|
+
const enrich = options.enrich ?? enrichEnabled(process.env, { ...config, mode });
|
|
291
|
+
const hookFor = (extra) => ({
|
|
292
|
+
type: 'command',
|
|
293
|
+
...(platform === 'win32'
|
|
294
|
+
? railHookInvocation('claude-code', extra)
|
|
295
|
+
: { command: railHookCommand('claude-code', platform, extra) }),
|
|
288
296
|
});
|
|
289
|
-
|
|
297
|
+
const append = (event, entry) => {
|
|
298
|
+
const existing = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
299
|
+
// Drop any prior memtrace entry so re-install doesn't duplicate it.
|
|
300
|
+
settings.hooks[event] = [...existing.filter((e) => !isMemtraceOwnedHookEntry(e)), entry];
|
|
301
|
+
};
|
|
302
|
+
// With grep enrichment on, the post hook carries the work and the Rail
|
|
303
|
+
// PreToolUse router is only needed when it can deny (rail/strict): each
|
|
304
|
+
// Grep/Bash call then costs exactly one node process (design section 3).
|
|
305
|
+
if (!enrich || mode === 'rail' || mode === 'strict') {
|
|
306
|
+
append('PreToolUse', { matcher: RAIL_HOOK_MATCHER, hooks: [hookFor([])] });
|
|
307
|
+
}
|
|
308
|
+
if (enrich) {
|
|
309
|
+
append('PostToolUse', { matcher: ENRICH_HOOK_MATCHER, hooks: [{ ...hookFor(['--phase', 'post']), timeout: ENRICH_HOOK_TIMEOUT_S }] });
|
|
310
|
+
// Compacted context no longer holds earlier blocks: reset the dedupe.
|
|
311
|
+
append('PreCompact', { hooks: [{ ...hookFor(['--event', 'precompact']), timeout: ENRICH_HOOK_TIMEOUT_S }] });
|
|
312
|
+
}
|
|
290
313
|
writeJsonAtomic(settingsPath, settings);
|
|
291
314
|
return { registered: true };
|
|
292
315
|
}
|
|
316
|
+
/** Grep enrichment rides PostToolUse on these tools (design section 9). Claude Code on
|
|
317
|
+
* Windows runs shell commands through its PowerShell tool, so rg/grep/git grep there
|
|
318
|
+
* arrive as PowerShell calls. */
|
|
319
|
+
export const ENRICH_HOOK_MATCHER = 'Grep|Bash|PowerShell';
|
|
320
|
+
/** Claude hook timeout for the enrichment hooks, in seconds. */
|
|
321
|
+
export const ENRICH_HOOK_TIMEOUT_S = 2;
|
|
293
322
|
/** True when a PreToolUse entry is a memtrace-owned Rail hook (any generation). */
|
|
294
323
|
function isRailHookEntry(entry) {
|
|
295
324
|
return hookCommands(entry).some((command) => command.includes(RAIL_GUARD_MARKER) || RAIL_HOOK_MARKERS.some((marker) => command.includes(marker)));
|
|
@@ -510,7 +539,7 @@ export async function registerMcpServer(memtraceBinaryPath) {
|
|
|
510
539
|
// Desktop-only installs have no `claude` CLI. Claude Code reads user MCP
|
|
511
540
|
// registrations from ~/.claude.json, not ~/.claude/settings.json (hooks).
|
|
512
541
|
// Migrate legacy env values without overriding the authoritative user entry.
|
|
513
|
-
registerMcpInSettingsAt(claudeUserConfigFile(), memtraceBinaryPath, readExistingMemtraceEnv(settingsFile));
|
|
542
|
+
registerMcpInSettingsAt(claudeUserConfigFile(), memtraceBinaryPath, readExistingMemtraceEnv(settingsFile), readExistingMemtraceArgs(settingsFile));
|
|
514
543
|
// Retain staging's mirror for older tooling after migrating the user entry.
|
|
515
544
|
registerMcpInSettingsAt(settingsFile, memtraceBinaryPath);
|
|
516
545
|
}
|
|
@@ -526,6 +555,9 @@ export function claudeUserConfigFile() {
|
|
|
526
555
|
* 3. Register MCP server for memtrace tools
|
|
527
556
|
*/
|
|
528
557
|
export async function installClaudePlugin(skills, memtraceBinaryPath) {
|
|
558
|
+
// Retire known stock bare copies when moving to plugin delivery. Customized
|
|
559
|
+
// strict policies/opt-outs survive; names alone never prove ownership.
|
|
560
|
+
removeManagedSkills(path.join(os.homedir(), '.claude', 'skills'));
|
|
529
561
|
// Step 1: make sure Claude sees the public HTTPS marketplace source before
|
|
530
562
|
// the CLI attempts to clone/update it.
|
|
531
563
|
enablePluginInSettings();
|
|
@@ -555,15 +587,12 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
|
|
|
555
587
|
// Step 5: Write plugin assets and skills
|
|
556
588
|
copyBundledPluginAssetsAt(cacheDir);
|
|
557
589
|
const skillsDir = path.join(cacheDir, 'skills');
|
|
558
|
-
|
|
559
|
-
fs.rmSync(skillsDir, { recursive: true });
|
|
560
|
-
}
|
|
590
|
+
let skillCount = 0;
|
|
561
591
|
for (const skill of skills) {
|
|
562
592
|
const results = transformForClaude(skill);
|
|
563
593
|
for (const { relativePath, content } of results) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
fs.writeFileSync(fullPath, content);
|
|
594
|
+
if (writeManagedSkill(skillsDir, path.basename(path.dirname(relativePath)), content))
|
|
595
|
+
skillCount++;
|
|
567
596
|
}
|
|
568
597
|
}
|
|
569
598
|
enablePluginInSettings();
|
|
@@ -572,27 +601,16 @@ export async function installClaudePlugin(skills, memtraceBinaryPath) {
|
|
|
572
601
|
// Skills ship via the memtrace-skills@memtrace plugin only. Writing
|
|
573
602
|
// bare copies to ~/.claude/skills/memtrace-* duplicates every skill
|
|
574
603
|
// in Claude Code when the plugin is also enabled (issue #11).
|
|
575
|
-
return { cacheDir, skillCount
|
|
604
|
+
return { cacheDir, skillCount };
|
|
576
605
|
}
|
|
577
606
|
/**
|
|
578
607
|
* Write skills to ~/.claude/skills/ for SDK-based integrations.
|
|
579
608
|
*/
|
|
580
609
|
function writeUserLevelSkills(skills) {
|
|
581
610
|
const userSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
582
|
-
if (fs.existsSync(userSkillsDir)) {
|
|
583
|
-
for (const entry of fs.readdirSync(userSkillsDir)) {
|
|
584
|
-
if (entry.startsWith('memtrace-')) {
|
|
585
|
-
fs.rmSync(path.join(userSkillsDir, entry), { recursive: true, force: true });
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
611
|
for (const skill of skills) {
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
const content = `---\nname: ${skillName}\ndescription: "${safeDesc}"\n---\n\n${skill.body}`;
|
|
593
|
-
const outDir = path.join(userSkillsDir, skillName);
|
|
594
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
595
|
-
fs.writeFileSync(path.join(outDir, 'SKILL.md'), content);
|
|
612
|
+
const content = transformForClaude(skill)[0].content;
|
|
613
|
+
writeManagedSkill(userSkillsDir, skill.filename.replace(/\.md$/, ''), content);
|
|
596
614
|
}
|
|
597
615
|
}
|
|
598
616
|
/**
|
|
@@ -625,22 +643,8 @@ export async function uninstallClaudePlugin() {
|
|
|
625
643
|
}
|
|
626
644
|
removeClaudeMarketplaceCacheDirs();
|
|
627
645
|
removeClaudeInstalledPluginMetadata();
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
if (fs.existsSync(userSkillsDir)) {
|
|
631
|
-
for (const entry of fs.readdirSync(userSkillsDir)) {
|
|
632
|
-
if (entry.startsWith('memtrace-')) {
|
|
633
|
-
const entryPath = path.join(userSkillsDir, entry);
|
|
634
|
-
const stat = fs.statSync(entryPath);
|
|
635
|
-
if (stat.isDirectory()) {
|
|
636
|
-
fs.rmSync(entryPath, { recursive: true });
|
|
637
|
-
}
|
|
638
|
-
else if (entry.endsWith('.md')) {
|
|
639
|
-
fs.unlinkSync(entryPath);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
646
|
+
// Customized skills and their extra assets are not installer-owned.
|
|
647
|
+
removeManagedSkills(path.join(os.homedir(), '.claude', 'skills'));
|
|
644
648
|
}
|
|
645
649
|
/**
|
|
646
650
|
* Write/merge the memtrace MCP server entry into a project-local .mcp.json.
|
|
@@ -663,7 +667,7 @@ function writeClaudeLocalMcp(mcpPath, binary) {
|
|
|
663
667
|
const mergedEnv = { ...existingEnv, ...MEMTRACE_MCP_ENV };
|
|
664
668
|
cfg.mcpServers['memtrace'] = {
|
|
665
669
|
command: binary,
|
|
666
|
-
args: ['mcp'],
|
|
670
|
+
args: existingMcpArgs(existing) ?? ['mcp'],
|
|
667
671
|
env: mergedEnv,
|
|
668
672
|
};
|
|
669
673
|
writeJsonAtomic(mcpPath, cfg);
|
|
@@ -685,22 +689,11 @@ export const claudeTransformer = {
|
|
|
685
689
|
}
|
|
686
690
|
// Local scope: write skills into <cwd>/.claude/skills/
|
|
687
691
|
const skillsDir = path.join(ctx.cwd, '.claude', 'skills');
|
|
688
|
-
if (fs.existsSync(skillsDir)) {
|
|
689
|
-
for (const entry of fs.readdirSync(skillsDir)) {
|
|
690
|
-
if (entry.startsWith('memtrace-')) {
|
|
691
|
-
fs.rmSync(path.join(skillsDir, entry), { recursive: true, force: true });
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
692
|
let count = 0;
|
|
696
693
|
for (const skill of skills) {
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
701
|
-
const content = `---\nname: ${skillName}\ndescription: "${safeDesc}"\n---\n\n${skill.body}`;
|
|
702
|
-
fs.writeFileSync(path.join(outDir, 'SKILL.md'), content);
|
|
703
|
-
count++;
|
|
694
|
+
const content = transformForClaude(skill)[0].content;
|
|
695
|
+
if (writeManagedSkill(skillsDir, skill.filename.replace(/\.md$/, ''), content))
|
|
696
|
+
count++;
|
|
704
697
|
}
|
|
705
698
|
// MCP for local scope: .mcp.json at project root (Claude's project-level convention)
|
|
706
699
|
const mcpConfigPath = path.join(ctx.cwd, '.mcp.json');
|
|
@@ -724,13 +717,7 @@ export const claudeTransformer = {
|
|
|
724
717
|
}
|
|
725
718
|
// Local: remove <cwd>/.claude/skills/memtrace-* and the memtrace entry from <cwd>/.mcp.json
|
|
726
719
|
const skillsDir = path.join(ctx.cwd, '.claude', 'skills');
|
|
727
|
-
|
|
728
|
-
for (const entry of fs.readdirSync(skillsDir)) {
|
|
729
|
-
if (entry.startsWith('memtrace-')) {
|
|
730
|
-
fs.rmSync(path.join(skillsDir, entry), { recursive: true, force: true });
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
720
|
+
removeManagedSkills(skillsDir);
|
|
734
721
|
const mcpPath = path.join(ctx.cwd, '.mcp.json');
|
|
735
722
|
const { value, corrupted } = safeReadJson(mcpPath);
|
|
736
723
|
if (!corrupted && value?.mcpServers?.['memtrace']) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { writeManagedSkill, removeManagedSkills } from '../managed-skills.js';
|
|
4
5
|
import { commandExists, execCommand } from '../utils.js';
|
|
5
6
|
import { registerSettingsHook, removeSettingsHook, CODEX_MATCHER, } from './rail-hooks.js';
|
|
6
7
|
const MCP_SERVER_NAME = 'memtrace';
|
|
@@ -19,11 +20,9 @@ export function codexConfigPath(ctx) {
|
|
|
19
20
|
}
|
|
20
21
|
function writeSkill(skill, rootDir) {
|
|
21
22
|
const name = skill.filename.replace(/\.md$/, '');
|
|
22
|
-
const outDir = path.join(rootDir, name);
|
|
23
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
24
23
|
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
25
24
|
const content = `---\nname: ${name}\ndescription: "${safeDesc}"\n---\n\n${skill.body}`;
|
|
26
|
-
|
|
25
|
+
return writeManagedSkill(rootDir, name, content);
|
|
27
26
|
}
|
|
28
27
|
function tomlString(value) {
|
|
29
28
|
return `"${value
|
|
@@ -168,15 +167,7 @@ export const codexTransformer = {
|
|
|
168
167
|
name: 'codex',
|
|
169
168
|
async install(skills, ctx) {
|
|
170
169
|
const rootDir = skillsRoot(ctx);
|
|
171
|
-
|
|
172
|
-
for (const entry of fs.readdirSync(rootDir)) {
|
|
173
|
-
if (entry.startsWith('memtrace-')) {
|
|
174
|
-
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
for (const s of skills)
|
|
179
|
-
writeSkill(s, rootDir);
|
|
170
|
+
const written = skills.filter(skill => writeSkill(skill, rootDir)).length;
|
|
180
171
|
let mcpRegistered = false;
|
|
181
172
|
const warnings = [];
|
|
182
173
|
const mcpConfigPath = codexConfigPath(ctx);
|
|
@@ -198,7 +189,7 @@ export const codexTransformer = {
|
|
|
198
189
|
}
|
|
199
190
|
return {
|
|
200
191
|
agent: 'codex',
|
|
201
|
-
skillsWritten:
|
|
192
|
+
skillsWritten: written,
|
|
202
193
|
skillsDir: rootDir,
|
|
203
194
|
mcpConfigPath,
|
|
204
195
|
mcpRegistered,
|
|
@@ -207,13 +198,7 @@ export const codexTransformer = {
|
|
|
207
198
|
},
|
|
208
199
|
async uninstall(ctx) {
|
|
209
200
|
const rootDir = skillsRoot(ctx);
|
|
210
|
-
|
|
211
|
-
for (const entry of fs.readdirSync(rootDir)) {
|
|
212
|
-
if (entry.startsWith('memtrace-')) {
|
|
213
|
-
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
201
|
+
removeManagedSkills(rootDir);
|
|
217
202
|
try {
|
|
218
203
|
removeSettingsHook(codexRailHooksPath(ctx), 'PreToolUse');
|
|
219
204
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { writeManagedSkill, removeManagedSkills } from '../managed-skills.js';
|
|
4
5
|
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
5
6
|
import { MEMTRACE_MCP_ENV, removeLegacyMcpServer } from './shared.js';
|
|
6
7
|
import { registerCursorRailHook, removeCursorRailHook } from './rail-hooks.js';
|
|
@@ -18,12 +19,10 @@ function railHooksPath(ctx) {
|
|
|
18
19
|
}
|
|
19
20
|
function writeSkill(skill, rootDir) {
|
|
20
21
|
const name = skill.filename.replace(/\.md$/, '');
|
|
21
|
-
const outDir = path.join(rootDir, name);
|
|
22
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
23
22
|
const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
|
|
24
23
|
// Cursor requires frontmatter `name` matching the folder name.
|
|
25
24
|
const content = `---\nname: ${name}\ndescription: "${safeDesc}"\n---\n\n${skill.body}`;
|
|
26
|
-
|
|
25
|
+
return writeManagedSkill(rootDir, name, content);
|
|
27
26
|
}
|
|
28
27
|
function isRecord(value) {
|
|
29
28
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -96,15 +95,7 @@ export const cursorTransformer = {
|
|
|
96
95
|
async install(skills, ctx) {
|
|
97
96
|
const rootDir = skillsRoot(ctx);
|
|
98
97
|
const warnings = cursorWorkspaceBoundaryWarnings(ctx);
|
|
99
|
-
|
|
100
|
-
for (const entry of fs.readdirSync(rootDir)) {
|
|
101
|
-
if (entry.startsWith('memtrace-')) {
|
|
102
|
-
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const s of skills)
|
|
107
|
-
writeSkill(s, rootDir);
|
|
98
|
+
const written = skills.filter(skill => writeSkill(skill, rootDir)).length;
|
|
108
99
|
let mcpRegistered = false;
|
|
109
100
|
if (!ctx.skipMcp) {
|
|
110
101
|
const result = registerCursorMcpAt(mcpPath(ctx), ctx.memtraceBinary);
|
|
@@ -123,7 +114,7 @@ export const cursorTransformer = {
|
|
|
123
114
|
}
|
|
124
115
|
return {
|
|
125
116
|
agent: 'cursor',
|
|
126
|
-
skillsWritten:
|
|
117
|
+
skillsWritten: written,
|
|
127
118
|
skillsDir: rootDir,
|
|
128
119
|
mcpConfigPath: mcpPath(ctx),
|
|
129
120
|
mcpRegistered,
|
|
@@ -133,13 +124,7 @@ export const cursorTransformer = {
|
|
|
133
124
|
async uninstall(ctx) {
|
|
134
125
|
// 1. Remove memtrace-* skill dirs
|
|
135
126
|
const rootDir = skillsRoot(ctx);
|
|
136
|
-
|
|
137
|
-
for (const entry of fs.readdirSync(rootDir)) {
|
|
138
|
-
if (entry.startsWith('memtrace-')) {
|
|
139
|
-
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
127
|
+
removeManagedSkills(rootDir);
|
|
143
128
|
// 1b. Remove the Rail hook.
|
|
144
129
|
try {
|
|
145
130
|
removeCursorRailHook(railHooksPath(ctx));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
3
|
+
import { writeManagedSkill, removeManagedSkills } from '../managed-skills.js';
|
|
4
4
|
import { registerMcpServersJsonAt, removeMcpServersJsonAt, skillName, } from './shared.js';
|
|
5
5
|
function steeringRoot(ctx) {
|
|
6
6
|
return ctx.scope === 'global'
|
|
@@ -24,25 +24,16 @@ function writeSteering(skill, rootDir) {
|
|
|
24
24
|
'',
|
|
25
25
|
skill.body,
|
|
26
26
|
].join('\n');
|
|
27
|
-
|
|
28
|
-
fs.writeFileSync(path.join(rootDir, `${name}.md`), content);
|
|
27
|
+
return writeManagedSkill(rootDir, name, content, true);
|
|
29
28
|
}
|
|
30
29
|
function removeMemtraceSteering(rootDir) {
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
for (const entry of fs.readdirSync(rootDir)) {
|
|
34
|
-
if (entry.startsWith('memtrace-') && entry.endsWith('.md')) {
|
|
35
|
-
fs.rmSync(path.join(rootDir, entry), { force: true });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
30
|
+
removeManagedSkills(rootDir, true);
|
|
38
31
|
}
|
|
39
32
|
export const kiroTransformer = {
|
|
40
33
|
name: 'kiro',
|
|
41
34
|
async install(skills, ctx) {
|
|
42
35
|
const rootDir = steeringRoot(ctx);
|
|
43
|
-
|
|
44
|
-
for (const skill of skills)
|
|
45
|
-
writeSteering(skill, rootDir);
|
|
36
|
+
const written = skills.filter(skill => writeSteering(skill, rootDir)).length;
|
|
46
37
|
let mcpRegistered = false;
|
|
47
38
|
const warnings = [];
|
|
48
39
|
const mcpConfigPath = mcpPath(ctx);
|
|
@@ -55,7 +46,7 @@ export const kiroTransformer = {
|
|
|
55
46
|
}
|
|
56
47
|
return {
|
|
57
48
|
agent: 'kiro',
|
|
58
|
-
skillsWritten:
|
|
49
|
+
skillsWritten: written,
|
|
59
50
|
skillsDir: rootDir,
|
|
60
51
|
mcpConfigPath,
|
|
61
52
|
mcpRegistered,
|
|
@@ -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) {
|
package/dist/transformers/pi.js
CHANGED
|
@@ -3,6 +3,7 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { removeMemtraceSkills, skillName } from './shared.js';
|
|
6
|
+
import { writeManagedSkill } from '../managed-skills.js';
|
|
6
7
|
import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
|
|
7
8
|
import { commandExists } from '../utils.js';
|
|
8
9
|
import { MEMTRACE_TOOL_NAMES } from '../tool-names.js';
|
|
@@ -80,15 +81,14 @@ function skillMarkdownForPi(skill, toolNames) {
|
|
|
80
81
|
return `---\nname: ${name}\ndescription: "${safeDesc}"${toolsLine}\n---\n\n${body}`;
|
|
81
82
|
}
|
|
82
83
|
function writeSkillsForPi(skills, rootDir) {
|
|
83
|
-
|
|
84
|
+
let written = 0;
|
|
84
85
|
const toolNames = memtraceToolNames(skills);
|
|
85
86
|
for (const skill of skills) {
|
|
86
87
|
const name = skillName(skill);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
fs.writeFileSync(path.join(outDir, 'SKILL.md'), skillMarkdownForPi(skill, toolNames));
|
|
88
|
+
if (writeManagedSkill(rootDir, name, skillMarkdownForPi(skill, toolNames)))
|
|
89
|
+
written++;
|
|
90
90
|
}
|
|
91
|
-
return
|
|
91
|
+
return written;
|
|
92
92
|
}
|
|
93
93
|
function packageEntrySource(entry) {
|
|
94
94
|
if (typeof entry === 'string')
|
|
@@ -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;
|