@sdsrs/code-graph 0.24.1 → 0.25.1

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.
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "sdsrs"
6
6
  },
7
- "version": "0.24.1",
7
+ "version": "0.25.1",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -24,6 +24,16 @@
24
24
  "timeout": 4
25
25
  }
26
26
  ]
27
+ },
28
+ {
29
+ "matcher": "tool == \"Bash\"",
30
+ "hooks": [
31
+ {
32
+ "type": "command",
33
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-grep-guide.js\"",
34
+ "timeout": 3
35
+ }
36
+ ]
27
37
  }
28
38
  ],
29
39
  "PostToolUse": [
@@ -87,13 +87,33 @@ function isNativeBinary(candidate) {
87
87
  }
88
88
  }
89
89
 
90
+ /**
91
+ * Decide whether a cached binary path is fresh enough to skip the full
92
+ * discovery walk. Matches the auto-update cache logic at ~:185-188 —
93
+ * cache wins only when its binary's version >= pkg version. Without this
94
+ * check, a stale cache entry (e.g. dev checkout's `bin/code-graph-mcp`
95
+ * recorded once, then never refreshed) shadows newer auto-update or
96
+ * platform-pkg binaries forever (see mem #8454).
97
+ *
98
+ * Permissive on unknown values: missing pkg version or unreadable binary
99
+ * version → trust cache (don't refuse the only path we know about).
100
+ */
101
+ function isCachedBinaryFresh(cachedPath, pkgVersion) {
102
+ if (!isNativeBinary(cachedPath)) return false;
103
+ if (!pkgVersion) return true;
104
+ const cacheVer = readBinaryVersion(cachedPath);
105
+ if (!cacheVer) return true;
106
+ return compareVersions(cacheVer, pkgVersion) >= 0;
107
+ }
108
+
90
109
  /**
91
110
  * Locate the code-graph-mcp binary using multiple strategies.
92
111
  * Results are cached to disk so repeated calls (e.g. per-hook) are fast.
93
112
  *
94
113
  * Priority:
95
- * cache (if valid) → dev-mode (target/release) → auto-update cache
96
- * → platform npm pkg → bundled (bin/) → cargo install → PATH → npx cache
114
+ * cache (if valid + version >= pkg) → dev-mode (target/release) →
115
+ * auto-update cache → platform npm pkg → bundled (bin/) →
116
+ * cargo install → PATH → npx cache
97
117
  *
98
118
  * Returns the absolute path or null if not found.
99
119
  */
@@ -101,7 +121,7 @@ function findBinary() {
101
121
  // Try disk cache first (avoids spawning `which` on hot paths)
102
122
  try {
103
123
  const cached = fs.readFileSync(CACHE_FILE, 'utf8').trim();
104
- if (isNativeBinary(cached)) return cached;
124
+ if (isCachedBinaryFresh(cached, getPackageVersion())) return cached;
105
125
  if (cached) clearCache();
106
126
  } catch { /* no cache or stale */ }
107
127
 
@@ -242,7 +262,7 @@ function clearCache() {
242
262
  module.exports = {
243
263
  findBinary, findBinaryUncached, clearCache,
244
264
  globalNodeModulesCandidates, findPlatformBinary,
245
- getPackageVersion, compareVersions,
265
+ getPackageVersion, compareVersions, isCachedBinaryFresh,
246
266
  CACHE_FILE, BINARY_NAME, PLATFORM_PKG,
247
267
  };
248
268
 
@@ -6,7 +6,7 @@ const os = require('os');
6
6
  const path = require('path');
7
7
 
8
8
  const { globalNodeModulesCandidates, findPlatformBinary, BINARY_NAME,
9
- compareVersions, getPackageVersion } = require('./find-binary');
9
+ compareVersions, getPackageVersion, isCachedBinaryFresh } = require('./find-binary');
10
10
 
11
11
  function mkDir(t, prefix) {
12
12
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
@@ -145,3 +145,77 @@ test('getPackageVersion reads root package.json', () => {
145
145
  const v = getPackageVersion();
146
146
  assert.match(v, /^\d+\.\d+\.\d+$/, `expected semver-ish, got: ${v}`);
147
147
  });
148
+
149
+ // ─── isCachedBinaryFresh: disk cache version-check (mem #8454) ────────────
150
+ //
151
+ // Builds a fake binary that responds to `--version` with a controllable
152
+ // string. process.execPath (node itself) won't do — we need a binary
153
+ // whose --version line we control. Smallest approach: shell wrapper.
154
+
155
+ function buildFakeBinary(t, versionLine) {
156
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cgmcp-fake-bin-'));
157
+ t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
158
+ const binPath = path.join(dir, BINARY_NAME);
159
+ // readBinaryVersion parses "code-graph-mcp X.Y.Z" via the binary's first
160
+ // stdout line on `--version`. Shell wrapper is simpler than compiling.
161
+ const script = process.platform === 'win32'
162
+ ? `@echo off\r\necho ${versionLine}\r\n`
163
+ : `#!/bin/sh\necho '${versionLine}'\n`;
164
+ fs.writeFileSync(binPath, script);
165
+ if (process.platform !== 'win32') fs.chmodSync(binPath, 0o755);
166
+ return binPath;
167
+ }
168
+
169
+ test('isCachedBinaryFresh: cache binary version >= pkg → fresh', (t) => {
170
+ const bin = buildFakeBinary(t, 'code-graph-mcp 9.9.9');
171
+ assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true);
172
+ });
173
+
174
+ test('isCachedBinaryFresh: cache binary version equals pkg → fresh', (t) => {
175
+ const bin = buildFakeBinary(t, 'code-graph-mcp 0.25.0');
176
+ assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true);
177
+ });
178
+
179
+ test('isCachedBinaryFresh: cache binary version < pkg → stale (THE BUG)', (t) => {
180
+ // Reproduces mem #8454: cache pointed at bin/code-graph-mcp v0.5.28
181
+ // while pkg was v0.25.0 → cache was returned silently with no
182
+ // version-check, shadowing the installed 0.25.0 platform binary.
183
+ // After this fix, returns false → caller clears cache + falls through.
184
+ const bin = buildFakeBinary(t, 'code-graph-mcp 0.5.28');
185
+ assert.equal(isCachedBinaryFresh(bin, '0.25.0'), false);
186
+ });
187
+
188
+ test('isCachedBinaryFresh: missing pkg version → permissive (trust cache)', (t) => {
189
+ // Caller couldn't read package.json; refusing the cache would leave us
190
+ // with nothing. Better to trust the one path we have.
191
+ const bin = buildFakeBinary(t, 'code-graph-mcp 0.5.28');
192
+ assert.equal(isCachedBinaryFresh(bin, null), true);
193
+ assert.equal(isCachedBinaryFresh(bin, ''), true);
194
+ });
195
+
196
+ test('isCachedBinaryFresh: unreadable cache binary version → permissive', (t) => {
197
+ // Old binary that doesn't support `--version`, or output we can't
198
+ // parse. Same permissive path as missing pkg version.
199
+ const bin = buildFakeBinary(t, 'whatever garbage no semver here');
200
+ assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true);
201
+ });
202
+
203
+ test('isCachedBinaryFresh: cache path does not exist → not fresh', () => {
204
+ assert.equal(isCachedBinaryFresh('/nonexistent/path/code-graph-mcp', '0.25.0'), false);
205
+ });
206
+
207
+ test('isCachedBinaryFresh: empty/null cache path → not fresh', () => {
208
+ assert.equal(isCachedBinaryFresh('', '0.25.0'), false);
209
+ assert.equal(isCachedBinaryFresh(null, '0.25.0'), false);
210
+ assert.equal(isCachedBinaryFresh(undefined, '0.25.0'), false);
211
+ });
212
+
213
+ test('isCachedBinaryFresh: file basename mismatch → not fresh', (t) => {
214
+ // realpathSync.basename check inside isNativeBinary — wrong name = not ours.
215
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cgmcp-wrongname-'));
216
+ t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
217
+ const wrongName = path.join(dir, 'other-tool');
218
+ fs.writeFileSync(wrongName, '#!/bin/sh\necho wrong\n');
219
+ if (process.platform !== 'win32') fs.chmodSync(wrongName, 0o755);
220
+ assert.equal(isCachedBinaryFresh(wrongName, '0.25.0'), false);
221
+ });
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // PreToolUse(Bash) hook: detect raw `grep`/`rg`/`ag` on the indexed source tree
4
+ // and suggest code-graph CLI alternatives. Closes the "Bash comfort zone" leak —
5
+ // pre-training bias has Claude reach for `grep -rn` ~13× more than the indexed
6
+ // CLI on bash-heavy days (15-day baseline: 429 raw grep vs 191 functional CLI).
7
+ //
8
+ // Fires when ALL conditions met:
9
+ // 1. Command HEAD is grep/rg/ag (NOT piped — pipe-greps are output filters)
10
+ // 2. Args include an indexed source-tree path (src/ tests/ lib/ scripts/ ...)
11
+ // 3. Not searching only a config/lockfile (Cargo.toml/.gitignore/*.md/*.json)
12
+ // 4. Command doesn't already invoke code-graph-mcp (no double-suggest)
13
+ // 5. .code-graph/index.db exists in CWD
14
+ // 6. Same command-hash not hinted within last 60s (per-command cooldown)
15
+ //
16
+ // Exits silently otherwise — zero noise for build greps, log filters, config
17
+ // lookups, or the rare legitimate use of raw grep on indexed source.
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('os');
22
+ const crypto = require('crypto');
23
+
24
+ // --- Pure logic (testable) ---
25
+
26
+ const GREP_HEAD = /^\s*(?:env\s+\S+=\S+\s+)*(grep|rg|ag)\b/;
27
+ const SRC_PATH = /(?:^|\s|["'])(src|tests|lib|scripts|claude-plugin|tools|pkg|cmd|internal|app|components?|server|client|crates|packages)\//;
28
+ const PIPE_INTO_GREP = /\|\s*(?:grep|rg|ag)\b/;
29
+ const CG_INVOKED = /\bcode-graph-mcp\b/;
30
+ // A file argument that ends in a config/lockfile extension AND no source-tree
31
+ // path appears elsewhere → grep is searching config, not code.
32
+ const CONFIG_TARGET_ONLY =
33
+ /(?:^|\s)[^\s|<>]*\.(toml|md|json|yml|yaml|lock|txt|cfg|env|gitignore|properties)(?:\s|$)/i;
34
+
35
+ function shouldHint(cmd) {
36
+ if (!cmd || typeof cmd !== 'string') return false;
37
+ if (cmd.length > 1000) return false; // sanity — oversize commands are noise
38
+ if (CG_INVOKED.test(cmd)) return false; // already using cg
39
+ if (PIPE_INTO_GREP.test(cmd)) return false; // `cargo test | grep FAILED` is output filter
40
+ if (!GREP_HEAD.test(cmd)) return false; // not a search command
41
+ if (!SRC_PATH.test(cmd)) return false; // not against indexed source tree
42
+ // If a config file appears AND no source path remains after stripping it, skip.
43
+ if (CONFIG_TARGET_ONLY.test(cmd)) {
44
+ const stripped = cmd.replace(CONFIG_TARGET_ONLY, ' ');
45
+ if (!SRC_PATH.test(stripped)) return false;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ function commandHash(cmd) {
51
+ return crypto.createHash('sha1').update(cmd).digest('hex').slice(0, 12);
52
+ }
53
+
54
+ function isOnCooldown(cmd, now = Date.now(), windowMs = 60000) {
55
+ const flag = path.join(os.tmpdir(), `.code-graph-bash-${commandHash(cmd)}`);
56
+ try {
57
+ return now - fs.statSync(flag).mtimeMs < windowMs;
58
+ } catch { return false; }
59
+ }
60
+
61
+ function markCooldown(cmd) {
62
+ const flag = path.join(os.tmpdir(), `.code-graph-bash-${commandHash(cmd)}`);
63
+ try { fs.writeFileSync(flag, ''); } catch { /* ok */ }
64
+ }
65
+
66
+ function buildHint() {
67
+ // Terse, no banner spam. Single message budget ~600 bytes.
68
+ return [
69
+ '[code-graph] Raw `grep`/`rg` on indexed source — consider AST-aware equivalents:',
70
+ ' • code-graph-mcp grep "<pat>" <path> # grep + containing fn/module per hit',
71
+ ' • code-graph-mcp ast-search "<pat>" --type fn # filter by type/returns/params',
72
+ ' • code-graph-mcp callgraph SYMBOL # callers + callees, repo-wide',
73
+ ' • code-graph-mcp show SYMBOL # one symbol: signature + source',
74
+ 'Repo-wide index (LSP only sees open files). Skip this hint if you specifically need raw-text regex.',
75
+ ].join('\n');
76
+ }
77
+
78
+ // --- Main execution (only when run directly) ---
79
+
80
+ // Kill switch: matches user-prompt-context.js convention. =1 forces silence
81
+ // even when the rest of the hook tier is noisy. Default (unset) is noisy here
82
+ // — this hook only fires on raw grep against the source tree, which is the
83
+ // exact comfort-zone leak it was designed to catch.
84
+ function isSilenced(env = process.env) {
85
+ return env.CODE_GRAPH_QUIET_HOOKS === '1';
86
+ }
87
+
88
+ function runMain() {
89
+ if (isSilenced()) return;
90
+ const cwd = process.cwd();
91
+ const dbPath = path.join(cwd, '.code-graph', 'index.db');
92
+ if (!fs.existsSync(dbPath)) return; // no index — no hint
93
+
94
+ let input;
95
+ try {
96
+ input = JSON.parse(fs.readFileSync('/dev/stdin', 'utf8'));
97
+ } catch { return; }
98
+
99
+ const cmd = (input.tool_input && input.tool_input.command) || '';
100
+ if (!shouldHint(cmd)) return;
101
+ if (isOnCooldown(cmd)) return;
102
+
103
+ markCooldown(cmd);
104
+ process.stdout.write(buildHint() + '\n');
105
+ }
106
+
107
+ if (require.main === module) {
108
+ runMain();
109
+ }
110
+
111
+ module.exports = {
112
+ shouldHint,
113
+ buildHint,
114
+ commandHash,
115
+ isOnCooldown,
116
+ markCooldown,
117
+ isSilenced,
118
+ };
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+ const test = require('node:test');
3
+ const assert = require('node:assert/strict');
4
+ const { shouldHint, buildHint, commandHash, isSilenced } = require('./pre-grep-guide');
5
+
6
+ // ── Should fire: bare grep/rg/ag on indexed source tree ─────────────
7
+
8
+ test('shouldHint: grep -rn on src/', () => {
9
+ assert.equal(shouldHint('grep -rn "fn fts5_search" src/storage/'), true);
10
+ });
11
+
12
+ test('shouldHint: rg on tests/', () => {
13
+ assert.equal(shouldHint('rg "expand_acronym" tests/'), true);
14
+ });
15
+
16
+ test('shouldHint: grep -n on single file in src/', () => {
17
+ assert.equal(shouldHint('grep -n "fn split_identifier" src/search/tokenizer.rs'), true);
18
+ });
19
+
20
+ test('shouldHint: grep -rn on claude-plugin/', () => {
21
+ assert.equal(shouldHint('grep -rn "computeQuietHooks" claude-plugin/scripts/'), true);
22
+ });
23
+
24
+ test('shouldHint: grep with alternation against src/', () => {
25
+ assert.equal(shouldHint('grep -rn "set_hook\\|panic_handler" src/main.rs src/lib.rs'), true);
26
+ });
27
+
28
+ test('shouldHint: grep with stderr redirect + head pipe (still a source search)', () => {
29
+ // head/tail/sort pipes don't disqualify — the SEARCH operation is grep on src/
30
+ assert.equal(shouldHint('grep -rn "fn fts5_search\\|MATCH" src/storage/ 2>&1 | head -10'), true);
31
+ });
32
+
33
+ test('shouldHint: ag on lib/', () => {
34
+ assert.equal(shouldHint('ag "TODO" lib/'), true);
35
+ });
36
+
37
+ test('shouldHint: env-prefixed grep on src/', () => {
38
+ assert.equal(shouldHint('env LANG=C grep -rn "Foo" src/'), true);
39
+ });
40
+
41
+ // ── Should NOT fire: pipe-grep (output filter, not search) ──────────
42
+
43
+ test('shouldHint: pipe-grep on cargo test output', () => {
44
+ assert.equal(shouldHint('cargo test 2>&1 | grep "test result"'), false);
45
+ });
46
+
47
+ test('shouldHint: pipe-grep with -E flag', () => {
48
+ assert.equal(shouldHint("cargo test --no-default-features 2>&1 | grep -E 'test result|FAILED'"), false);
49
+ });
50
+
51
+ test('shouldHint: pipe-rg', () => {
52
+ assert.equal(shouldHint("cargo build 2>&1 | rg 'warning|error'"), false);
53
+ });
54
+
55
+ test('shouldHint: pipe-grep with src/ in pattern (still output filter)', () => {
56
+ assert.equal(shouldHint("cargo build 2>&1 | grep 'src/main.rs'"), false);
57
+ });
58
+
59
+ // ── Should NOT fire: already using code-graph-mcp ───────────────────
60
+
61
+ test('shouldHint: code-graph-mcp grep itself', () => {
62
+ assert.equal(shouldHint('code-graph-mcp grep "fn parse" src/'), false);
63
+ });
64
+
65
+ test('shouldHint: pipe through code-graph-mcp', () => {
66
+ assert.equal(shouldHint('code-graph-mcp show foo | grep src/'), false);
67
+ });
68
+
69
+ // ── Should NOT fire: not source-tree paths ──────────────────────────
70
+
71
+ test('shouldHint: grep on Cargo.toml only', () => {
72
+ assert.equal(shouldHint('grep "^version" Cargo.toml'), false);
73
+ });
74
+
75
+ test('shouldHint: grep -i docs on .gitignore', () => {
76
+ assert.equal(shouldHint('grep -i docs .gitignore'), false);
77
+ });
78
+
79
+ test('shouldHint: grep on package.json', () => {
80
+ assert.equal(shouldHint('grep "version" package.json'), false);
81
+ });
82
+
83
+ test('shouldHint: grep on a markdown changelog', () => {
84
+ assert.equal(shouldHint('grep "v0.24" CHANGELOG.md'), false);
85
+ });
86
+
87
+ // ── Should NOT fire: not search tools ───────────────────────────────
88
+
89
+ test('shouldHint: ls src/', () => {
90
+ assert.equal(shouldHint('ls src/storage/'), false);
91
+ });
92
+
93
+ test('shouldHint: cat src/main.rs', () => {
94
+ assert.equal(shouldHint('cat src/main.rs'), false);
95
+ });
96
+
97
+ test('shouldHint: git log on src/', () => {
98
+ assert.equal(shouldHint('git log --oneline -10 src/'), false);
99
+ });
100
+
101
+ test('shouldHint: find on src/ (file path tool, not content search)', () => {
102
+ // find is path-based, not pattern-based. Out of scope for this hook.
103
+ assert.equal(shouldHint('find src/ -name "*.rs"'), false);
104
+ });
105
+
106
+ // ── Edge cases ──────────────────────────────────────────────────────
107
+
108
+ test('shouldHint: empty command', () => {
109
+ assert.equal(shouldHint(''), false);
110
+ });
111
+
112
+ test('shouldHint: non-string input', () => {
113
+ assert.equal(shouldHint(null), false);
114
+ assert.equal(shouldHint(undefined), false);
115
+ assert.equal(shouldHint(42), false);
116
+ });
117
+
118
+ test('shouldHint: oversize command (>1000 chars)', () => {
119
+ assert.equal(shouldHint('grep -rn "x" src/ ' + 'y'.repeat(1100)), false);
120
+ });
121
+
122
+ // ── Hint content ────────────────────────────────────────────────────
123
+
124
+ test('buildHint: includes all four code-graph subcommands', () => {
125
+ const out = buildHint();
126
+ assert.match(out, /code-graph-mcp grep/);
127
+ assert.match(out, /code-graph-mcp ast-search/);
128
+ assert.match(out, /code-graph-mcp callgraph/);
129
+ assert.match(out, /code-graph-mcp show/);
130
+ });
131
+
132
+ test('buildHint: stays under 700-byte budget (~175 tokens)', () => {
133
+ const out = buildHint();
134
+ assert.ok(out.length < 700, `hint length ${out.length} exceeds budget`);
135
+ });
136
+
137
+ test('buildHint: mentions repo-wide / LSP boundary', () => {
138
+ assert.match(buildHint(), /Repo-wide index|LSP/);
139
+ });
140
+
141
+ // ── Cooldown hash ───────────────────────────────────────────────────
142
+
143
+ test('commandHash: deterministic + 12-char', () => {
144
+ const h1 = commandHash('grep -rn "foo" src/');
145
+ const h2 = commandHash('grep -rn "foo" src/');
146
+ assert.equal(h1, h2);
147
+ assert.equal(h1.length, 12);
148
+ });
149
+
150
+ test('commandHash: different commands → different hashes', () => {
151
+ assert.notEqual(commandHash('grep -rn "foo" src/'), commandHash('grep -rn "bar" src/'));
152
+ });
153
+
154
+ // ── Kill switch ─────────────────────────────────────────────────────
155
+
156
+ test('isSilenced: default (no env) → not silenced (noisy)', () => {
157
+ assert.equal(isSilenced({}), false);
158
+ });
159
+
160
+ test('isSilenced: CODE_GRAPH_QUIET_HOOKS=1 → silenced', () => {
161
+ assert.equal(isSilenced({ CODE_GRAPH_QUIET_HOOKS: '1' }), true);
162
+ });
163
+
164
+ test('isSilenced: CODE_GRAPH_QUIET_HOOKS=0 → not silenced', () => {
165
+ assert.equal(isSilenced({ CODE_GRAPH_QUIET_HOOKS: '0' }), false);
166
+ });
167
+
168
+ test('isSilenced: VERBOSE_HOOKS=1 alone → not silenced (noisy by default already)', () => {
169
+ // pre-grep-guide is noisy-by-default; VERBOSE is irrelevant here.
170
+ assert.equal(isSilenced({ CODE_GRAPH_VERBOSE_HOOKS: '1' }), false);
171
+ });
172
+
173
+ // ── Regression cases from real session telemetry (2026-05-11) ───────
174
+
175
+ test('regression: grep -n "Error\\|anyhow" src/main.rs (sess 5052e2a1)', () => {
176
+ assert.equal(shouldHint('grep -n "Error\\|anyhow\\|context" src/main.rs'), true);
177
+ });
178
+
179
+ test('regression: grep -rn "fn fts5_search" src/storage/ (sess 25fa8050)', () => {
180
+ assert.equal(shouldHint('grep -rn "fn fts5_search\\|MATCH\\|fts.*tokenize" src/storage/'), true);
181
+ });
182
+
183
+ test('regression: grep multi-extension MEMORY.md tag search (sess 5052e2a1)', () => {
184
+ // This one targets MEMORY.md files — should NOT fire because the --include flags
185
+ // are for non-source extensions and there's no `src/` etc. in the args.
186
+ assert.equal(shouldHint("grep -rn 'callgraph, impact' --include='*.md'"), false);
187
+ });
188
+
189
+ test('regression: cargo test pipe filter NOT fires (sess 45691293)', () => {
190
+ assert.equal(shouldHint('cargo test --no-default-features 2>&1 | grep -E "test result|FAILED|error\\[" | tail -15'), false);
191
+ });
192
+
193
+ test('regression: grep -m1 "^version" Cargo.toml NOT fires', () => {
194
+ assert.equal(shouldHint('grep -m1 "^version" Cargo.toml'), false);
195
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdsrs/code-graph",
3
- "version": "0.24.1",
3
+ "version": "0.25.1",
4
4
  "description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,10 +35,10 @@
35
35
  "node": ">=16"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@sdsrs/code-graph-linux-x64": "0.24.1",
39
- "@sdsrs/code-graph-linux-arm64": "0.24.1",
40
- "@sdsrs/code-graph-darwin-x64": "0.24.1",
41
- "@sdsrs/code-graph-darwin-arm64": "0.24.1",
42
- "@sdsrs/code-graph-win32-x64": "0.24.1"
38
+ "@sdsrs/code-graph-linux-x64": "0.25.1",
39
+ "@sdsrs/code-graph-linux-arm64": "0.25.1",
40
+ "@sdsrs/code-graph-darwin-x64": "0.25.1",
41
+ "@sdsrs/code-graph-darwin-arm64": "0.25.1",
42
+ "@sdsrs/code-graph-win32-x64": "0.25.1"
43
43
  }
44
44
  }