memtrace-skills 1.2.6 → 1.2.8

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/skills.js CHANGED
@@ -5,7 +5,8 @@ import path from 'path';
5
5
  * Handles the subset of YAML used in skill files (no nested objects).
6
6
  */
7
7
  function parseFrontmatter(raw) {
8
- const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
8
+ // Skill files are LF in git but CRLF in a Windows checkout; read both.
9
+ const match = raw.replace(/\r\n/g, '\n').match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
9
10
  if (!match)
10
11
  return null;
11
12
  const frontmatterRaw = match[1];
@@ -0,0 +1 @@
1
+ export declare const MEMTRACE_TOOL_NAMES: readonly string[];
@@ -0,0 +1,101 @@
1
+ // Every tool the Memtrace MCP server registers, by its bare name.
2
+ //
3
+ // Pi registers each one as `memtrace_<name>`, so the Pi installer rewrites
4
+ // these names wherever skill text mentions them. Deriving the list from the
5
+ // tools skills happen to allow missed any tool no skill allowed (e.g.
6
+ // find_code_review_issues). `installer_tool_list_matches_the_router` in
7
+ // crates/memtrace-mcp/src/server.rs pins this list to the server's router.
8
+ export const MEMTRACE_TOOL_NAMES = Object.freeze([
9
+ 'analyze_relationships',
10
+ 'ask_docs',
11
+ 'calculate_cyclomatic_complexity',
12
+ 'check_job_status',
13
+ 'cleanup_episodes',
14
+ 'cleanup_stale_records',
15
+ 'cleanup_worktrees',
16
+ 'create_anchor',
17
+ 'delete_repository',
18
+ 'detect_changes',
19
+ 'embed_diag',
20
+ 'embed_reset_breaker',
21
+ 'export_query_audit',
22
+ 'find_api_calls',
23
+ 'find_api_endpoints',
24
+ 'find_ast_review_issues',
25
+ 'find_bridge_symbols',
26
+ 'find_central_symbols',
27
+ 'find_code',
28
+ 'find_code_review_issues',
29
+ 'find_cross_module_issues',
30
+ 'find_dead_code',
31
+ 'find_dependency_path',
32
+ 'find_duplicate_code',
33
+ 'find_hotspots',
34
+ 'find_import_cycles',
35
+ 'find_most_complex_functions',
36
+ 'find_symbol',
37
+ 'find_tool_definitions',
38
+ 'find_yaml_rule_matches',
39
+ 'fleet_acquire_lease',
40
+ 'fleet_audit',
41
+ 'fleet_branch_context',
42
+ 'fleet_get_episode',
43
+ 'fleet_get_escalation',
44
+ 'fleet_get_node_state',
45
+ 'fleet_list_escalations',
46
+ 'fleet_preflight',
47
+ 'fleet_publish_intent',
48
+ 'fleet_query_episodes',
49
+ 'fleet_record_episode',
50
+ 'fleet_release_lease',
51
+ 'fleet_renew_lease',
52
+ 'fleet_resolve_escalation',
53
+ 'fleet_status',
54
+ 'fleet_submit_verdict',
55
+ 'fleet_ydoc_append',
56
+ 'fleet_ydoc_read',
57
+ 'get_api_topology',
58
+ 'get_arc',
59
+ 'get_changes_since',
60
+ 'get_cochange_context',
61
+ 'get_codebase_briefing',
62
+ 'get_daily_briefing',
63
+ 'get_directory_tree',
64
+ 'get_episode_replay',
65
+ 'get_evolution',
66
+ 'get_function_quality_metrics',
67
+ 'get_impact',
68
+ 'get_process_flow',
69
+ 'get_repository_stats',
70
+ 'get_service_diagram',
71
+ 'get_source_window',
72
+ 'get_style_fingerprint',
73
+ 'get_symbol_context',
74
+ 'get_timeline',
75
+ 'governing_contracts',
76
+ 'governing_rules',
77
+ 'index_directory',
78
+ 'ingest_graph_fragment',
79
+ 'link_repositories',
80
+ 'link_symbols',
81
+ 'list_anchors',
82
+ 'list_communities',
83
+ 'list_indexed_repositories',
84
+ 'list_jobs',
85
+ 'list_processes',
86
+ 'list_watched_paths',
87
+ 'list_worktrees',
88
+ 'mem_diag',
89
+ 'preflight_check',
90
+ 'read_doc',
91
+ 'recall_decision',
92
+ 'record_external_episode',
93
+ 'replay_history',
94
+ 'review_agent_sessions',
95
+ 'review_github_pr',
96
+ 'search_docs',
97
+ 'unwatch_directory',
98
+ 'verify_intent',
99
+ 'watch_directory',
100
+ 'why_is_this_here',
101
+ ]);
@@ -60,7 +60,7 @@ function cursorWorkspaceBoundaryWarnings(ctx) {
60
60
  if (fs.existsSync(path.join(ctx.cwd, '.memtrace-workspace')))
61
61
  return [];
62
62
  return [
63
- `Cursor install target ${ctx.cwd} contains multiple sibling git repos but is not a blessed Memtrace workspace. For separate MemDBs, install/open each repo root. To intentionally share one MemDB, run 'memtrace start --bless-workspace' from ${ctx.cwd}, then verify with 'memtrace workspace status ${ctx.cwd}'.`,
63
+ `Cursor install target ${ctx.cwd} contains multiple sibling git repos but is not a blessed Memtrace workspace. For separate MemDBs, install/open each repo root. To intentionally share one MemDB, declare the members in a workspace manifest and run 'memtrace start --workspace-file <manifest>', or use the legacy Folder Group flow: 'memtrace start --bless-workspace' from ${ctx.cwd}, then verify with 'memtrace workspace status ${ctx.cwd}'. Once a store's membership is declared, a Memtrace MCP session can never widen it — adding a repo means editing the manifest and restarting that store's daemon with 'memtrace start --workspace-file <manifest>'.`,
64
64
  ];
65
65
  }
66
66
  export function registerCursorMcpAt(mcpFile, binary) {
@@ -1,5 +1,13 @@
1
+ import { Skill } from '../skills.js';
1
2
  import { Transformer, InstallContext } from './types.js';
2
3
  export declare function piSettingsPath(ctx: InstallContext): string;
3
4
  /** Resolve the pi-package directory shipped inside the memtrace npm bundle. */
4
5
  export declare function resolveBundledPiPackage(fromDir?: string): string;
6
+ /**
7
+ * Every Memtrace tool name skill text may mention, bare (`find_code`): the
8
+ * server's full tool list, plus any tool a skill allows. The allowed tools
9
+ * alone missed tools no skill allows, which then stayed bare in Pi skills.
10
+ */
11
+ export declare function memtraceToolNames(skills: readonly Skill[]): string[];
12
+ export declare function transformForPi(text: string, toolNames: readonly string[]): string;
5
13
  export declare const piTransformer: Transformer;
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  import { removeMemtraceSkills, skillName } from './shared.js';
6
6
  import { safeReadJson, writeJsonAtomic } from '../fs-safe.js';
7
7
  import { commandExists } from '../utils.js';
8
+ import { MEMTRACE_TOOL_NAMES } from '../tool-names.js';
8
9
  // Pi (https://pi.dev/docs/latest/packages) uses native pi packages — not MCP JSON
9
10
  // config like Cursor. The bundled `pi-package/` extension spawns `memtrace mcp`
10
11
  // itself and registers every Memtrace tool as a native Pi tool (`memtrace_*`).
@@ -30,8 +31,32 @@ export function resolveBundledPiPackage(fromDir = path.dirname(fileURLToPath(imp
30
31
  }
31
32
  throw new Error('bundled pi-package not found (reinstall memtrace from npm)');
32
33
  }
33
- function transformForPi(text) {
34
- return text.replace(/mcp__memtrace__/g, 'memtrace_');
34
+ const MEMTRACE_TOOL_PREFIX = 'mcp__memtrace__';
35
+ /**
36
+ * Every Memtrace tool name skill text may mention, bare (`find_code`): the
37
+ * server's full tool list, plus any tool a skill allows. The allowed tools
38
+ * alone missed tools no skill allows, which then stayed bare in Pi skills.
39
+ */
40
+ export function memtraceToolNames(skills) {
41
+ const names = new Set(MEMTRACE_TOOL_NAMES);
42
+ for (const skill of skills) {
43
+ for (const tool of skill.frontmatter['allowed-tools'] ?? []) {
44
+ if (tool.startsWith(MEMTRACE_TOOL_PREFIX))
45
+ names.add(tool.slice(MEMTRACE_TOOL_PREFIX.length));
46
+ }
47
+ }
48
+ return [...names].sort();
49
+ }
50
+ // Pi registers each Memtrace tool as `memtrace_<name>`. Skill bodies name tools
51
+ // bare (`find_code`), so rewriting only the `mcp__memtrace__` prefix changed
52
+ // nothing in them: every Pi skill allowed `memtrace_get_evolution` while its
53
+ // prose told the agent to call `get_evolution`.
54
+ export function transformForPi(text, toolNames) {
55
+ const prefixed = text.replace(/mcp__memtrace__/g, 'memtrace_');
56
+ if (toolNames.length === 0)
57
+ return prefixed;
58
+ const bare = new RegExp(`(?<![A-Za-z0-9_])(${toolNames.join('|')})(?![A-Za-z0-9_])`, 'g');
59
+ return prefixed.replace(bare, 'memtrace_$1');
35
60
  }
36
61
  function transformAllowedTool(tool) {
37
62
  return tool
@@ -40,23 +65,28 @@ function transformAllowedTool(tool) {
40
65
  .replace(/^Grep$/, 'grep')
41
66
  .replace(/^Glob$/, 'find');
42
67
  }
43
- function skillMarkdownForPi(skill) {
68
+ function skillMarkdownForPi(skill, toolNames) {
44
69
  const name = skillName(skill);
45
- const safeDesc = skill.frontmatter.description.replace(/"/g, '\\"').trim();
70
+ // The description is what Pi routes on, so it names tools the way Pi
71
+ // registers them too.
72
+ const safeDesc = transformForPi(skill.frontmatter.description, toolNames)
73
+ .replace(/"/g, '\\"')
74
+ .trim();
46
75
  const tools = skill.frontmatter['allowed-tools'];
47
76
  const toolsLine = tools?.length
48
77
  ? `\nallowed-tools: ${tools.map(transformAllowedTool).join(' ')}`
49
78
  : '';
50
- const body = transformForPi(skill.body);
79
+ const body = transformForPi(skill.body, toolNames);
51
80
  return `---\nname: ${name}\ndescription: "${safeDesc}"${toolsLine}\n---\n\n${body}`;
52
81
  }
53
82
  function writeSkillsForPi(skills, rootDir) {
54
83
  removeMemtraceSkills(rootDir);
84
+ const toolNames = memtraceToolNames(skills);
55
85
  for (const skill of skills) {
56
86
  const name = skillName(skill);
57
87
  const outDir = path.join(rootDir, name);
58
88
  fs.mkdirSync(outDir, { recursive: true });
59
- fs.writeFileSync(path.join(outDir, 'SKILL.md'), skillMarkdownForPi(skill));
89
+ fs.writeFileSync(path.join(outDir, 'SKILL.md'), skillMarkdownForPi(skill, toolNames));
60
90
  }
61
91
  return skills.length;
62
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memtrace-skills",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "Memtrace skills for AI coding agents — codebase exploration, temporal evolution, impact analysis, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- export const PLUGIN_ASSET_PATHS: readonly ['commands', 'README.md'];
1
+ export const PLUGIN_ASSET_PATHS: readonly ['commands', 'references', 'README.md'];
2
2
  export const PLUGIN_MANIFEST_PATH: string;
3
3
  export const LEGACY_PLUGIN_ASSET_PATHS: readonly [
4
4
  'hooks',
@@ -1,7 +1,11 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
 
4
- export const PLUGIN_ASSET_PATHS = Object.freeze(['commands', 'README.md']);
4
+ // `references/` holds the parameter spec that 25 skills send the agent to
5
+ // ("bundled at the memtrace-skills plugin root"); without it here the
6
+ // plugin install never wrote the file those skills name. Only the Claude
7
+ // Code plugin carries it; other agents' skill installs do not.
8
+ export const PLUGIN_ASSET_PATHS = Object.freeze(['commands', 'references', 'README.md']);
5
9
  export const PLUGIN_MANIFEST_PATH = path.join('.claude-plugin', 'plugin.json');
6
10
  export const LEGACY_PLUGIN_ASSET_PATHS = Object.freeze([
7
11
  'hooks',
@@ -31,24 +31,66 @@ arrays as arrays. No quoting numbers, no `"true"` for booleans.
31
31
 
32
32
  | Param | Type | Meaning |
33
33
  |---|---|---|
34
- | `repo_id` | string | Repository identifier from `list_indexed_repositories` (usually the repo folder name, e.g. `"memtrace"`) |
34
+ | `repo_id` | string | Repository identifier from `list_indexed_repositories` (usually the repo folder name, e.g. `"memtrace"`). That list covers this session's store only — see "A `repo_id` this session's store does not hold" below |
35
35
  | `branch` *or* `branch_name` | string | Git branch. Default `"main"` across every tool. Both spellings occur historically — use whichever the specific tool's schema says |
36
36
  | `limit` | integer | Cap on returned results. Always a JSON number, never a string |
37
37
  | `depth` | integer | Graph traversal hops. 1–5 is reasonable; >8 explodes on wide graphs |
38
38
  | `target` / `symbol` | string | Symbol **name** for graph tools — `get_impact`/`analyze_relationships` use `target`; `get_symbol_context` uses `symbol` |
39
39
  | `from` / `to` / `since` / `until` | string | e.g. `"90d ago"`, `"2026-04-17T13:00:00Z"`, `"yesterday"` — relative strings work for temporal tools. **Never `days` on `get_evolution`.** |
40
40
 
41
+
42
+ ## A `repo_id` this session's store does not hold
43
+
44
+ Ten tools check the requested `repo_id` against this store's declared members
45
+ before they answer: `find_code`, `find_symbol`, `get_codebase_briefing`,
46
+ `find_central_symbols`, `find_dependency_path`, `find_bridge_symbols`,
47
+ `list_processes`, `get_process_flow`, `list_communities` and
48
+ `get_repository_stats`. On those, a wrong `repo_id` is never a success-shaped
49
+ zero.
50
+
51
+ - If a live runtime's store declares the repository, the read is forwarded to
52
+ it and the answer carries `_meta.answered_by: "store_owner_daemon"`,
53
+ `_meta.owner_pid`, `_meta.owner_http` and `_meta.store`.
54
+ - Otherwise the call is refused: `repo_in_store: false`,
55
+ `error_code: "repo_not_in_store"`, `count: 0`, and a `diagnostic` naming the
56
+ store that answered, its `members`, `reason` and `repo_lives_in` when a
57
+ runtime declares the repository elsewhere. Nothing was searched. A `reason`
58
+ of `ambiguous_repo_id` is the opposite case: the id matches more than one
59
+ repository in this store, so pass the exact id.
60
+
61
+ The remaining `repo_id` tools do not check membership: the relationship tools
62
+ (`get_symbol_context`, `get_impact`, `analyze_relationships`), the temporal
63
+ tools (`get_evolution`, `get_timeline`, `get_changes_since`), the topology
64
+ tools (`get_api_topology`, `find_api_endpoints`, `find_api_calls`,
65
+ `get_service_diagram`) and `get_source_window`. They read this session's store
66
+ whatever id you pass, set no `repo_in_store` key, and return an ordinary empty
67
+ result for a repository this store does not hold. A zero from one of them is
68
+ not an absence — confirm the repository is a member with one of the ten above
69
+ first.
70
+
71
+ `repo_in_store: true` means membership was proven. `repo_in_store: null` means
72
+ the session discovered no workspace, so membership was assumed rather than
73
+ proven.
74
+
75
+ Tools that change a store, a runtime, or anything outside the process
76
+ (`index_directory`, `delete_repository`, `watch_directory`, `link_*`,
77
+ `cleanup_*`, `replay_history`, the mutating `fleet_*` calls, and the rest) are
78
+ never forwarded. Run them from the session attached to the store that holds
79
+ the repository.
80
+
41
81
  ## Search & discovery
42
82
 
43
83
  ### `find_code`
44
84
  | Field | Type | Required | Default | Notes |
45
85
  |---|---|---|---|---|
46
86
  | `query` | string | yes | — | Natural-language text |
47
- | `repo_id` | string | no | all repos | Scope to one repo |
87
+ | `repo_id` | string | no | this store's members | Scope to one repo. Omitted, an unambiguous session resolves to its own repository and a multi-repo store fans out across its discovered members |
48
88
  | `file_path` | string | no | — | Path substring filter |
49
89
  | `limit` | integer | no | `20` | Max 100 |
50
90
  | `as_of` | string | no | now | Time-travel search |
51
91
  | `include_diagnostics` | boolean | no | `false` | Include `id`, `score` in results |
92
+ | `include_dependency_checks` | boolean | no | `true` | With diagnostics, set false for scores without pre-edit risk checks |
93
+ | `include_context` | boolean | no | auto | Bounded processes, communities and selected callers/callees; default on for concepts, off for identifiers |
52
94
 
53
95
  No `kind` param — use `find_symbol(kind=...)` instead.
54
96
 
@@ -58,7 +100,7 @@ No `kind` param — use `find_symbol(kind=...)` instead.
58
100
  | `name` | string | yes | — | Exact or partial identifier |
59
101
  | `fuzzy` | boolean | no | `false` | Exact-match today; field kept for API parity |
60
102
  | `edit_distance` | integer | no | `2` | Max 2. Only used when `fuzzy: true` |
61
- | `repo_id` | string | no | all repos | |
103
+ | `repo_id` | string | no | inferred | One repository is inferred only when that is unambiguous; otherwise the call returns `repo_scope_required` |
62
104
  | `kind` | string | no | — | Same enum as `find_code` |
63
105
  | `file_path` | string | no | — | |
64
106
  | `limit` | integer | no | `10` | Capped at `50` |
@@ -88,8 +130,11 @@ No parameters. Call once at session start to get `repo_id` values.
88
130
  | `branch` | string | no | `"main"` |
89
131
  | `as_of` | string | no | now |
90
132
  | `view` | string | no | `"live"` |
133
+ | `include_content` | boolean | no | `false` |
134
+ | `limit` | integer | no | `20` (max `200`) |
135
+ | `offset` | integer | no | `0` |
91
136
 
92
- Returns: symbol, callers, callees, type_references, community, processes, api_callers_cross_repo.
137
+ Returns compact symbol locations, relationship sections, community and process context. Source bodies require `include_content: true`, or use `get_source_window`. Each section has `pagination` metadata with `available`, `returned`, `offset`, and `next_offset`. The offset applies independently to each section; repeat with that section's `next_offset` to continue it. These pages cover the bounded graph result: `graph_truncated` and `available_is_lower_bound` remain true when an internal graph cap was reached.
93
138
 
94
139
  ### `analyze_relationships`
95
140
  | Field | Type | Required | Default | Notes |
@@ -229,6 +274,10 @@ Returns `cochanged_files[]` with `file_path`, `cochange_count`, `last_cochanged_
229
274
  | `repo_id` | string | yes | — |
230
275
  | `process` | string | yes | — |
231
276
  | `branch` | string | no | `"main"` |
277
+ | `limit` | integer | no | `20` (max `200`) |
278
+ | `offset` | integer | no | `0` |
279
+
280
+ Returns ordered steps, `total_steps`, `returned_steps`, `next_offset`, and `scan_truncated`. Follow `next_offset` to continue. The existing internal scan cap still applies; `scan_truncated` signals when pagination cannot expose the entire process.
232
281
 
233
282
  ### `find_dependency_path`
234
283
  | Field | Type | Required | Default | Notes |
@@ -15,6 +15,11 @@ Call `list_indexed_repositories` first. If the repo is already indexed, skip to
15
15
 
16
16
  Otherwise, call `index_directory` with the project path, then poll `check_job_status` until completion.
17
17
 
18
+ `list_indexed_repositories` covers this session's store only. If a later call
19
+ answers `error_code: "repo_not_in_store"`, the repository is indexed in the
20
+ store named in `diagnostic.repo_lives_in` — route the question there instead
21
+ of indexing a second copy here.
22
+
18
23
  **Success criteria:** Repo appears in `list_indexed_repositories` with non-zero node/edge counts.
19
24
 
20
25
  ### 2. Get the lay of the land
@@ -116,6 +121,18 @@ The deliverable is the 7-part overview above. Skeleton (one headline per part):
116
121
  6. Recent Activity — 31 episodes in 30d; hottest file per `top_changed_files`
117
122
  7. Technical Debt — top-10 complex functions, highest complexity first
118
123
 
124
+ ## A `busy` answer is a wait, not an absence
125
+
126
+ Heavy graph work runs one repository-sized fold at a time, because holding
127
+ two in memory is what the bound protects against. When the lane is already
128
+ held, a caller is not queued indefinitely: after a bounded wait it gets a
129
+ successful answer carrying `"busy": true`, `"lane": "graph_materialization"`,
130
+ `"retryable": true`, and a `holder` block naming the operation that holds the
131
+ lane and how long it has held it. Report it as a wait, retry once the holder
132
+ is likely done, or narrow the call (one `repo_id`, a smaller `limit`, a
133
+ shorter window) so it does not need the lane. Do not read it as an empty
134
+ graph and do not fall back to file search.
135
+
119
136
  ## Common Mistakes
120
137
 
121
138
  | Mistake | Reality |