memtrace 0.3.34 → 0.3.36
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/bin/memtrace.js +7 -1
- package/hooks/posttool-mcp-telemetry.sh +56 -0
- package/hooks/userprompt-claude.sh +102 -0
- package/install.js +35 -1
- package/lib/claude-integration.js +447 -0
- package/lib/skill-metadata.js +303 -0
- package/lib/spawn-helper.js +63 -0
- package/lib/upgrade-skills.js +72 -0
- package/package.json +6 -4
- package/uninstall.js +107 -39
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Skill frontmatter upgrader.
|
|
4
|
+
//
|
|
5
|
+
// Anthropic's Claude Code docs (code.claude.com/docs/en/skills.md) name two
|
|
6
|
+
// frontmatter fields that drive auto-invocation reliability:
|
|
7
|
+
// * `description` — leads with the use case, ideally in user-prompt language
|
|
8
|
+
// * `when_to_use` — appended to description, holds concrete trigger phrases
|
|
9
|
+
//
|
|
10
|
+
// Combined cap is 1,536 chars. Our skills shipped with `description` only,
|
|
11
|
+
// leading with "Always use…" — strong directive but missing the trigger-phrase
|
|
12
|
+
// signal Claude reaches for when deciding whether to auto-invoke.
|
|
13
|
+
//
|
|
14
|
+
// This module:
|
|
15
|
+
// 1. parses a skill file's YAML frontmatter without depending on a yaml lib
|
|
16
|
+
// (the frontmatter we ship is a tightly-controlled subset — top-level
|
|
17
|
+
// key/value pairs plus one `allowed-tools` list)
|
|
18
|
+
// 2. upgrades it: adds `when_to_use`, adds `paths`, leaves existing keys
|
|
19
|
+
// untouched
|
|
20
|
+
// 3. round-trips byte-identical for a skill that's already been upgraded
|
|
21
|
+
//
|
|
22
|
+
// Tested in test/skill-metadata.test.js.
|
|
23
|
+
|
|
24
|
+
// ── Parser ────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse a skill markdown file with YAML frontmatter.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} content
|
|
30
|
+
* @returns {{frontmatter: object, frontmatterRaw: string, body: string}}
|
|
31
|
+
*/
|
|
32
|
+
function parseSkillFile(content) {
|
|
33
|
+
if (typeof content !== "string") {
|
|
34
|
+
throw new TypeError("parseSkillFile: content must be a string");
|
|
35
|
+
}
|
|
36
|
+
if (!content.startsWith("---")) {
|
|
37
|
+
return { frontmatter: {}, frontmatterRaw: "", body: content };
|
|
38
|
+
}
|
|
39
|
+
// Find the closing `---` for the frontmatter.
|
|
40
|
+
const endMarker = content.indexOf("\n---", 3);
|
|
41
|
+
if (endMarker === -1) {
|
|
42
|
+
return { frontmatter: {}, frontmatterRaw: "", body: content };
|
|
43
|
+
}
|
|
44
|
+
const frontmatterRaw = content.slice(3, endMarker).replace(/^\n/, "");
|
|
45
|
+
// Body starts after `\n---\n`. Some files have `\n---\r\n`; handle both.
|
|
46
|
+
const afterClose = endMarker + 4;
|
|
47
|
+
let bodyStart = afterClose;
|
|
48
|
+
if (content[afterClose] === "\r") bodyStart += 1;
|
|
49
|
+
if (content[bodyStart] === "\n") bodyStart += 1;
|
|
50
|
+
const body = content.slice(bodyStart);
|
|
51
|
+
const frontmatter = parseFrontmatter(frontmatterRaw);
|
|
52
|
+
return { frontmatter, frontmatterRaw, body };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Minimal YAML subset parser for our skills' frontmatter shape.
|
|
57
|
+
* Handles:
|
|
58
|
+
* - quoted and unquoted scalar values
|
|
59
|
+
* - YAML list values (`allowed-tools:` followed by ` - item` lines)
|
|
60
|
+
* - keeps unknown keys intact in insertion order
|
|
61
|
+
*
|
|
62
|
+
* @param {string} raw
|
|
63
|
+
* @returns {Record<string, string|string[]|boolean>}
|
|
64
|
+
*/
|
|
65
|
+
function parseFrontmatter(raw) {
|
|
66
|
+
const out = {};
|
|
67
|
+
const lines = raw.split(/\r?\n/);
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < lines.length) {
|
|
70
|
+
const line = lines[i];
|
|
71
|
+
if (!line || /^\s*#/.test(line)) {
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const m = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
76
|
+
if (!m) {
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const key = m[1];
|
|
81
|
+
const valueRaw = m[2];
|
|
82
|
+
if (valueRaw === "") {
|
|
83
|
+
// Look ahead for a list
|
|
84
|
+
const list = [];
|
|
85
|
+
i++;
|
|
86
|
+
while (i < lines.length && /^\s+-\s+/.test(lines[i])) {
|
|
87
|
+
list.push(lines[i].replace(/^\s+-\s+/, "").trim());
|
|
88
|
+
i++;
|
|
89
|
+
}
|
|
90
|
+
out[key] = list;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
out[key] = parseScalar(valueRaw);
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseScalar(raw) {
|
|
100
|
+
const t = raw.trim();
|
|
101
|
+
if (t === "true") return true;
|
|
102
|
+
if (t === "false") return false;
|
|
103
|
+
if ((t.startsWith('"') && t.endsWith('"')) ||
|
|
104
|
+
(t.startsWith("'") && t.endsWith("'"))) {
|
|
105
|
+
return t.slice(1, -1);
|
|
106
|
+
}
|
|
107
|
+
return t;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Serializer ────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Serialize back to `--- ... ---\n\nbody`. Order:
|
|
114
|
+
* 1. name (always first if present)
|
|
115
|
+
* 2. description
|
|
116
|
+
* 3. when_to_use
|
|
117
|
+
* 4. paths
|
|
118
|
+
* 5. allowed-tools (list)
|
|
119
|
+
* 6. user-invocable
|
|
120
|
+
* 7. anything else, in original order
|
|
121
|
+
*
|
|
122
|
+
* Quoting rule: scalar values containing colons, hashes, or newlines get
|
|
123
|
+
* double-quoted; lists and bare identifiers don't.
|
|
124
|
+
*
|
|
125
|
+
* @param {{frontmatter: object, body: string}} parsed
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
function serializeSkill(parsed) {
|
|
129
|
+
const fm = parsed.frontmatter || {};
|
|
130
|
+
const body = parsed.body || "";
|
|
131
|
+
const orderedKeys = [
|
|
132
|
+
"name",
|
|
133
|
+
"description",
|
|
134
|
+
"when_to_use",
|
|
135
|
+
"paths",
|
|
136
|
+
"allowed-tools",
|
|
137
|
+
"user-invocable",
|
|
138
|
+
];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
const lines = ["---"];
|
|
141
|
+
for (const k of orderedKeys) {
|
|
142
|
+
if (k in fm) {
|
|
143
|
+
lines.push(...formatPair(k, fm[k]));
|
|
144
|
+
seen.add(k);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const k of Object.keys(fm)) {
|
|
148
|
+
if (!seen.has(k)) {
|
|
149
|
+
lines.push(...formatPair(k, fm[k]));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
lines.push("---");
|
|
153
|
+
return lines.join("\n") + (body.startsWith("\n") ? "" : "\n") + body;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatPair(key, value) {
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
return [`${key}:`, ...value.map((v) => ` - ${v}`)];
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === "boolean") {
|
|
161
|
+
return [`${key}: ${value}`];
|
|
162
|
+
}
|
|
163
|
+
const s = String(value);
|
|
164
|
+
if (s.includes(":") || s.includes("#") || s.includes("\n") || s.includes('"')) {
|
|
165
|
+
return [`${key}: "${s.replace(/"/g, '\\"')}"`];
|
|
166
|
+
}
|
|
167
|
+
return [`${key}: ${s}`];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Upgrader ──────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Upgrade a skill's frontmatter to v0.3.35 best practices.
|
|
174
|
+
*
|
|
175
|
+
* - Adds `when_to_use` from the trigger map (does not overwrite existing).
|
|
176
|
+
* - Adds `paths` (does not overwrite existing).
|
|
177
|
+
* - Leaves `description` as-is — operators are responsible for reframing
|
|
178
|
+
* the description text manually since it requires per-skill judgement.
|
|
179
|
+
*
|
|
180
|
+
* @param {object} parsed output of parseSkillFile
|
|
181
|
+
* @param {string} skillName e.g. "memtrace-first"
|
|
182
|
+
* @param {object} triggers { [skillName]: { whenToUse, paths } }
|
|
183
|
+
* @returns {object} updated parsed
|
|
184
|
+
*/
|
|
185
|
+
function upgradeSkill(parsed, skillName, triggers) {
|
|
186
|
+
const fm = { ...(parsed.frontmatter || {}) };
|
|
187
|
+
const t = triggers[skillName];
|
|
188
|
+
if (!t) {
|
|
189
|
+
return parsed; // unknown skill — leave alone
|
|
190
|
+
}
|
|
191
|
+
if (!fm.when_to_use && t.whenToUse) {
|
|
192
|
+
fm.when_to_use = t.whenToUse;
|
|
193
|
+
}
|
|
194
|
+
if (!fm.paths && t.paths) {
|
|
195
|
+
fm.paths = t.paths;
|
|
196
|
+
}
|
|
197
|
+
return { ...parsed, frontmatter: fm };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Default trigger map ───────────────────────────────────────────────
|
|
201
|
+
//
|
|
202
|
+
// Concrete trigger phrases per skill. The pool is biased toward language
|
|
203
|
+
// users actually type, plus the imperatives the agent's planner uses
|
|
204
|
+
// internally ("trace through", "find the function that", etc.).
|
|
205
|
+
|
|
206
|
+
const CODE_PATHS = "**/*.{py,js,jsx,ts,tsx,mjs,cjs,rs,go,java,rb,c,cc,cpp,cxx,h,hpp,hh,cs,php,swift,kt,kts,scala,clj,cljs,ex,exs,erl,hrl,ml,mli,fs,fsx,r,jl,lua,dart,m,mm,asm,sql,gql,graphql,proto,sh,bash,zsh,fish,vue,svelte}";
|
|
207
|
+
|
|
208
|
+
const DEFAULT_TRIGGERS = {
|
|
209
|
+
"memtrace-first": {
|
|
210
|
+
whenToUse:
|
|
211
|
+
"Triggered when user says 'where is', 'how does', 'what calls', 'why does', 'find the function that', 'trace through', 'show me', 'explain this code', 'investigate', 'debug', 'understand', 'audit', 'fix bug', 'refactor', 'review', 'check for'. Use BEFORE Read/Grep/Glob/find/rg for any code-discovery question in an indexed repo. Treat zero results as a query-broadening or reindex prompt — never as permission to grep.",
|
|
212
|
+
paths: CODE_PATHS,
|
|
213
|
+
},
|
|
214
|
+
"memtrace-search": {
|
|
215
|
+
whenToUse:
|
|
216
|
+
"Triggered by 'find function', 'where is X defined', 'locate', 'look up', 'search for', 'find class', 'find type', 'find struct', 'find interface'. Also when user references a constant, error string, or magic value and asks where it's used.",
|
|
217
|
+
paths: CODE_PATHS,
|
|
218
|
+
},
|
|
219
|
+
"memtrace-relationships": {
|
|
220
|
+
whenToUse:
|
|
221
|
+
"Triggered by 'who calls', 'callers of', 'callees', 'what does X call', 'who imports', 'where is X used', 'references to', 'inheritance', 'overrides', 'implementations of'. Use to walk the call graph rather than greping for symbol names.",
|
|
222
|
+
paths: CODE_PATHS,
|
|
223
|
+
},
|
|
224
|
+
"memtrace-impact": {
|
|
225
|
+
whenToUse:
|
|
226
|
+
"Triggered by 'what breaks if', 'blast radius', 'impact of changing', 'safe to remove', 'safe to rename', 'dependencies on', 'who depends on', 'before refactor', 'before delete', 'risk of changing'.",
|
|
227
|
+
paths: CODE_PATHS,
|
|
228
|
+
},
|
|
229
|
+
"memtrace-evolution": {
|
|
230
|
+
whenToUse:
|
|
231
|
+
"Triggered by 'when did this change', 'what changed in', 'recent changes', 'evolution of', 'history of', 'who modified', 'why does this look like', 'before refactor X', 'after Y was added', 'last week's changes'.",
|
|
232
|
+
paths: CODE_PATHS,
|
|
233
|
+
},
|
|
234
|
+
"memtrace-cochange": {
|
|
235
|
+
whenToUse:
|
|
236
|
+
"Triggered by 'what changes together', 'co-change', 'historical coupling', 'hidden dependency', 'usually moves with', 'should I also touch', 'what else needs updating'.",
|
|
237
|
+
paths: CODE_PATHS,
|
|
238
|
+
},
|
|
239
|
+
"memtrace-graph": {
|
|
240
|
+
whenToUse:
|
|
241
|
+
"Triggered by 'most important', 'central to', 'critical functions', 'chokepoints', 'bridges', 'communities', 'modules', 'subsystems', 'hubs', 'depends on most', 'most depended on', 'architecture overview'.",
|
|
242
|
+
paths: CODE_PATHS,
|
|
243
|
+
},
|
|
244
|
+
"memtrace-quality": {
|
|
245
|
+
whenToUse:
|
|
246
|
+
"Triggered by 'dead code', 'unused functions', 'zero callers', 'complex functions', 'hotspots', 'cyclomatic complexity', 'cognitive complexity', 'code smells', 'refactoring candidates', 'tech debt'.",
|
|
247
|
+
paths: CODE_PATHS,
|
|
248
|
+
},
|
|
249
|
+
"memtrace-api-topology": {
|
|
250
|
+
whenToUse:
|
|
251
|
+
"Triggered by 'API endpoints', 'HTTP routes', 'who calls /endpoint', 'cross-service calls', 'service dependency', 'route handlers', 'fetch calls', 'REST surface', 'service diagram', 'call from frontend to backend'.",
|
|
252
|
+
paths: CODE_PATHS,
|
|
253
|
+
},
|
|
254
|
+
"memtrace-index": {
|
|
255
|
+
whenToUse:
|
|
256
|
+
"Triggered by 'index this repo', 'add to memtrace', 'parse this codebase', 'reindex', 'watch directory', 'enable live indexing', 'set up memtrace for'. Use when bringing a new project under code-intelligence coverage.",
|
|
257
|
+
paths: CODE_PATHS,
|
|
258
|
+
},
|
|
259
|
+
"memtrace-change-impact-analysis": {
|
|
260
|
+
whenToUse:
|
|
261
|
+
"Triggered before edits, refactors, API changes, renames, removals, or PR review when user asks about 'what will break', 'what does this affect', 'is this safe', 'callers I need to update', 'before I merge'. Use to combine search + impact + change detection.",
|
|
262
|
+
paths: CODE_PATHS,
|
|
263
|
+
},
|
|
264
|
+
"memtrace-codebase-exploration": {
|
|
265
|
+
whenToUse:
|
|
266
|
+
"Triggered by 'tour of this codebase', 'how is this organized', 'where do I start', 'onboard me', 'explain the architecture', 'main flows', 'main modules', 'key classes', 'I just cloned this'.",
|
|
267
|
+
paths: CODE_PATHS,
|
|
268
|
+
},
|
|
269
|
+
"memtrace-continuous-memory": {
|
|
270
|
+
whenToUse:
|
|
271
|
+
"Triggered by 'watch this folder', 'live indexing', 'incremental memtrace', 'always-on memory', 'keep up to date', 'pick up my saves', 'reflect uncommitted changes', 'why isn't memtrace seeing my edits'.",
|
|
272
|
+
paths: CODE_PATHS,
|
|
273
|
+
},
|
|
274
|
+
"memtrace-episode-replay": {
|
|
275
|
+
whenToUse:
|
|
276
|
+
"Triggered by 'replay history', 'why does this look like this', 'evolution of this design', 'past attempts at', 'we tried X before', 'reverted approach', 'how did we get here'.",
|
|
277
|
+
paths: CODE_PATHS,
|
|
278
|
+
},
|
|
279
|
+
"memtrace-incident-investigation": {
|
|
280
|
+
whenToUse:
|
|
281
|
+
"Triggered by 'production broke', 'incident', 'regression', 'something is failing', 'why is X slow', 'root cause', 'what changed when', 'last working state', 'bisect', 'broken since deploy'.",
|
|
282
|
+
paths: CODE_PATHS,
|
|
283
|
+
},
|
|
284
|
+
"memtrace-refactoring-guide": {
|
|
285
|
+
whenToUse:
|
|
286
|
+
"Triggered by 'refactor this', 'reduce complexity', 'split this function', 'extract module', 'clean up tech debt', 'reorganize', 'where should I start refactoring', 'priority for cleanup'.",
|
|
287
|
+
paths: CODE_PATHS,
|
|
288
|
+
},
|
|
289
|
+
"memtrace-session-continuity": {
|
|
290
|
+
whenToUse:
|
|
291
|
+
"Triggered at session start or resume — 'catch me up', 'what changed while I was away', 'resume', 'pick up where I left off', 'recap', 'orient', 'summary since yesterday', 'since last commit'.",
|
|
292
|
+
paths: CODE_PATHS,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
parseSkillFile,
|
|
298
|
+
parseFrontmatter,
|
|
299
|
+
serializeSkill,
|
|
300
|
+
upgradeSkill,
|
|
301
|
+
DEFAULT_TRIGGERS,
|
|
302
|
+
CODE_PATHS,
|
|
303
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Shared spawn helpers for the npm shim.
|
|
4
|
+
//
|
|
5
|
+
// Why this exists: Node 18.20+ / 20.12+ / 21.7+ refuse to spawn `.cmd`
|
|
6
|
+
// and `.bat` files on Windows without `shell: true`, as part of the
|
|
7
|
+
// CVE-2024-27980 mitigation. The release notes from Node phrase this
|
|
8
|
+
// as a "policy change" but in practice it surfaces as
|
|
9
|
+
// `Error: spawn npm.cmd EINVAL` on first install — which is exactly
|
|
10
|
+
// what Orbit hit on Windows during a fresh global install.
|
|
11
|
+
//
|
|
12
|
+
// The fix is small: when targeting `.cmd` / `.bat` on Windows, pass
|
|
13
|
+
// `shell: true`. macOS and Linux don't need it (npm is a real binary
|
|
14
|
+
// on PATH there).
|
|
15
|
+
//
|
|
16
|
+
// Pulled into its own module so we can TDD it without spawning real
|
|
17
|
+
// processes. The unit + property tests live in
|
|
18
|
+
// `npm/memtrace/test/spawn-helper.test.js`.
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pick the platform-specific binary name for a tool that ships as a
|
|
22
|
+
* `.cmd` shim on Windows. Used for `npm`, `memtrace` itself, and
|
|
23
|
+
* anything else that has the same dual-name shape.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} unixName - the bare tool name on macOS/Linux ("npm")
|
|
26
|
+
* @param {string} platform - usually `process.platform`
|
|
27
|
+
* @returns {string} - "npm" or "npm.cmd"
|
|
28
|
+
*/
|
|
29
|
+
function platformBinary(unixName, platform) {
|
|
30
|
+
if (typeof unixName !== "string" || unixName.length === 0) {
|
|
31
|
+
throw new TypeError("platformBinary: unixName must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
return platform === "win32" ? unixName + ".cmd" : unixName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build a spawn options object that's safe for the given platform.
|
|
38
|
+
*
|
|
39
|
+
* On Windows, returns options with `shell: true` set, which is
|
|
40
|
+
* mandatory for spawning `.cmd` / `.bat` files since the
|
|
41
|
+
* CVE-2024-27980 mitigation. On non-Windows platforms, returns the
|
|
42
|
+
* options unchanged so we don't accidentally re-tokenise arguments
|
|
43
|
+
* through a shell that doesn't need it.
|
|
44
|
+
*
|
|
45
|
+
* Pure: does not mutate `baseOpts`. Always returns a new object.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} platform - usually `process.platform`
|
|
48
|
+
* @param {object} [baseOpts] - any options to merge in
|
|
49
|
+
* @returns {object}
|
|
50
|
+
*/
|
|
51
|
+
function spawnOptionsForPlatform(platform, baseOpts) {
|
|
52
|
+
const base = baseOpts && typeof baseOpts === "object" ? baseOpts : {};
|
|
53
|
+
const merged = Object.assign({}, base);
|
|
54
|
+
if (platform === "win32") {
|
|
55
|
+
merged.shell = true;
|
|
56
|
+
}
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
platformBinary,
|
|
62
|
+
spawnOptionsForPlatform,
|
|
63
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// One-shot upgrader: walks every skill file under both
|
|
5
|
+
// `npm/memtrace/skills/` and `npm/memtrace/installer/skills/` and
|
|
6
|
+
// adds `when_to_use` + `paths` from the trigger map. Idempotent;
|
|
7
|
+
// safe to re-run.
|
|
8
|
+
//
|
|
9
|
+
// Usage: node npm/memtrace/lib/upgrade-skills.js
|
|
10
|
+
//
|
|
11
|
+
// Exit code 0 on success.
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const {
|
|
16
|
+
parseSkillFile,
|
|
17
|
+
serializeSkill,
|
|
18
|
+
upgradeSkill,
|
|
19
|
+
DEFAULT_TRIGGERS,
|
|
20
|
+
} = require("./skill-metadata");
|
|
21
|
+
|
|
22
|
+
const ROOTS = [
|
|
23
|
+
path.join(__dirname, "..", "skills"),
|
|
24
|
+
path.join(__dirname, "..", "installer", "skills"),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
let totalScanned = 0;
|
|
28
|
+
let totalUpgraded = 0;
|
|
29
|
+
let totalSkipped = 0;
|
|
30
|
+
|
|
31
|
+
function walk(root) {
|
|
32
|
+
if (!fs.existsSync(root)) return;
|
|
33
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
34
|
+
for (const e of entries) {
|
|
35
|
+
const full = path.join(root, e.name);
|
|
36
|
+
if (e.isDirectory()) {
|
|
37
|
+
walk(full);
|
|
38
|
+
} else if (e.isFile() && e.name.endsWith(".md")) {
|
|
39
|
+
processSkillFile(full);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function processSkillFile(filePath) {
|
|
45
|
+
totalScanned++;
|
|
46
|
+
const original = fs.readFileSync(filePath, "utf8");
|
|
47
|
+
const parsed = parseSkillFile(original);
|
|
48
|
+
const skillName = parsed.frontmatter.name || path.basename(filePath, ".md");
|
|
49
|
+
|
|
50
|
+
if (!DEFAULT_TRIGGERS[skillName]) {
|
|
51
|
+
totalSkipped++;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const upgraded = upgradeSkill(parsed, skillName, DEFAULT_TRIGGERS);
|
|
56
|
+
const serialized = serializeSkill(upgraded);
|
|
57
|
+
|
|
58
|
+
if (serialized === original) {
|
|
59
|
+
totalSkipped++;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fs.writeFileSync(filePath, serialized);
|
|
64
|
+
totalUpgraded++;
|
|
65
|
+
console.log(`upgraded: ${path.relative(process.cwd(), filePath)}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const root of ROOTS) {
|
|
69
|
+
walk(root);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`\nscanned ${totalScanned} skill files · upgraded ${totalUpgraded} · skipped ${totalSkipped}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrace",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.36",
|
|
4
4
|
"description": "Code intelligence graph — MCP server + AI agent skills + visualization UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"hooks/",
|
|
25
27
|
"skills/",
|
|
26
28
|
"installer/",
|
|
27
29
|
"install.js",
|
|
@@ -37,9 +39,9 @@
|
|
|
37
39
|
"fs-extra": "^11.0.0"
|
|
38
40
|
},
|
|
39
41
|
"optionalDependencies": {
|
|
40
|
-
"@memtrace/darwin-arm64": "0.3.
|
|
41
|
-
"@memtrace/linux-x64": "0.3.
|
|
42
|
-
"@memtrace/win32-x64": "0.3.
|
|
42
|
+
"@memtrace/darwin-arm64": "0.3.36",
|
|
43
|
+
"@memtrace/linux-x64": "0.3.36",
|
|
44
|
+
"@memtrace/win32-x64": "0.3.36"
|
|
43
45
|
},
|
|
44
46
|
"engines": {
|
|
45
47
|
"node": ">=18"
|