@sdsrs/code-graph
Advanced tools
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "0.25.0", | ||
| "version": "0.25.1", | ||
| "keywords": [ | ||
@@ -10,0 +10,0 @@ "code-graph", |
@@ -91,2 +91,21 @@ #!/usr/bin/env node | ||
| /** | ||
| * Decide whether a cached binary path is fresh enough to skip the full | ||
| * discovery walk. Matches the auto-update cache logic at ~:185-188 — | ||
| * cache wins only when its binary's version >= pkg version. Without this | ||
| * check, a stale cache entry (e.g. dev checkout's `bin/code-graph-mcp` | ||
| * recorded once, then never refreshed) shadows newer auto-update or | ||
| * platform-pkg binaries forever (see mem #8454). | ||
| * | ||
| * Permissive on unknown values: missing pkg version or unreadable binary | ||
| * version → trust cache (don't refuse the only path we know about). | ||
| */ | ||
| function isCachedBinaryFresh(cachedPath, pkgVersion) { | ||
| if (!isNativeBinary(cachedPath)) return false; | ||
| if (!pkgVersion) return true; | ||
| const cacheVer = readBinaryVersion(cachedPath); | ||
| if (!cacheVer) return true; | ||
| return compareVersions(cacheVer, pkgVersion) >= 0; | ||
| } | ||
| /** | ||
| * Locate the code-graph-mcp binary using multiple strategies. | ||
@@ -96,4 +115,5 @@ * Results are cached to disk so repeated calls (e.g. per-hook) are fast. | ||
| * Priority: | ||
| * cache (if valid) → dev-mode (target/release) → auto-update cache | ||
| * → platform npm pkg → bundled (bin/) → cargo install → PATH → npx cache | ||
| * cache (if valid + version >= pkg) → dev-mode (target/release) → | ||
| * auto-update cache → platform npm pkg → bundled (bin/) → | ||
| * cargo install → PATH → npx cache | ||
| * | ||
@@ -106,3 +126,3 @@ * Returns the absolute path or null if not found. | ||
| const cached = fs.readFileSync(CACHE_FILE, 'utf8').trim(); | ||
| if (isNativeBinary(cached)) return cached; | ||
| if (isCachedBinaryFresh(cached, getPackageVersion())) return cached; | ||
| if (cached) clearCache(); | ||
@@ -248,3 +268,3 @@ } catch { /* no cache or stale */ } | ||
| globalNodeModulesCandidates, findPlatformBinary, | ||
| getPackageVersion, compareVersions, | ||
| getPackageVersion, compareVersions, isCachedBinaryFresh, | ||
| CACHE_FILE, BINARY_NAME, PLATFORM_PKG, | ||
@@ -251,0 +271,0 @@ }; |
@@ -9,3 +9,3 @@ 'use strict'; | ||
| const { globalNodeModulesCandidates, findPlatformBinary, BINARY_NAME, | ||
| compareVersions, getPackageVersion } = require('./find-binary'); | ||
| compareVersions, getPackageVersion, isCachedBinaryFresh } = require('./find-binary'); | ||
@@ -149,1 +149,75 @@ function mkDir(t, prefix) { | ||
| }); | ||
| // ─── isCachedBinaryFresh: disk cache version-check (mem #8454) ──────────── | ||
| // | ||
| // Builds a fake binary that responds to `--version` with a controllable | ||
| // string. process.execPath (node itself) won't do — we need a binary | ||
| // whose --version line we control. Smallest approach: shell wrapper. | ||
| function buildFakeBinary(t, versionLine) { | ||
| const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cgmcp-fake-bin-')); | ||
| t.after(() => fs.rmSync(dir, { recursive: true, force: true })); | ||
| const binPath = path.join(dir, BINARY_NAME); | ||
| // readBinaryVersion parses "code-graph-mcp X.Y.Z" via the binary's first | ||
| // stdout line on `--version`. Shell wrapper is simpler than compiling. | ||
| const script = process.platform === 'win32' | ||
| ? `@echo off\r\necho ${versionLine}\r\n` | ||
| : `#!/bin/sh\necho '${versionLine}'\n`; | ||
| fs.writeFileSync(binPath, script); | ||
| if (process.platform !== 'win32') fs.chmodSync(binPath, 0o755); | ||
| return binPath; | ||
| } | ||
| test('isCachedBinaryFresh: cache binary version >= pkg → fresh', (t) => { | ||
| const bin = buildFakeBinary(t, 'code-graph-mcp 9.9.9'); | ||
| assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true); | ||
| }); | ||
| test('isCachedBinaryFresh: cache binary version equals pkg → fresh', (t) => { | ||
| const bin = buildFakeBinary(t, 'code-graph-mcp 0.25.0'); | ||
| assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true); | ||
| }); | ||
| test('isCachedBinaryFresh: cache binary version < pkg → stale (THE BUG)', (t) => { | ||
| // Reproduces mem #8454: cache pointed at bin/code-graph-mcp v0.5.28 | ||
| // while pkg was v0.25.0 → cache was returned silently with no | ||
| // version-check, shadowing the installed 0.25.0 platform binary. | ||
| // After this fix, returns false → caller clears cache + falls through. | ||
| const bin = buildFakeBinary(t, 'code-graph-mcp 0.5.28'); | ||
| assert.equal(isCachedBinaryFresh(bin, '0.25.0'), false); | ||
| }); | ||
| test('isCachedBinaryFresh: missing pkg version → permissive (trust cache)', (t) => { | ||
| // Caller couldn't read package.json; refusing the cache would leave us | ||
| // with nothing. Better to trust the one path we have. | ||
| const bin = buildFakeBinary(t, 'code-graph-mcp 0.5.28'); | ||
| assert.equal(isCachedBinaryFresh(bin, null), true); | ||
| assert.equal(isCachedBinaryFresh(bin, ''), true); | ||
| }); | ||
| test('isCachedBinaryFresh: unreadable cache binary version → permissive', (t) => { | ||
| // Old binary that doesn't support `--version`, or output we can't | ||
| // parse. Same permissive path as missing pkg version. | ||
| const bin = buildFakeBinary(t, 'whatever garbage no semver here'); | ||
| assert.equal(isCachedBinaryFresh(bin, '0.25.0'), true); | ||
| }); | ||
| test('isCachedBinaryFresh: cache path does not exist → not fresh', () => { | ||
| assert.equal(isCachedBinaryFresh('/nonexistent/path/code-graph-mcp', '0.25.0'), false); | ||
| }); | ||
| test('isCachedBinaryFresh: empty/null cache path → not fresh', () => { | ||
| assert.equal(isCachedBinaryFresh('', '0.25.0'), false); | ||
| assert.equal(isCachedBinaryFresh(null, '0.25.0'), false); | ||
| assert.equal(isCachedBinaryFresh(undefined, '0.25.0'), false); | ||
| }); | ||
| test('isCachedBinaryFresh: file basename mismatch → not fresh', (t) => { | ||
| // realpathSync.basename check inside isNativeBinary — wrong name = not ours. | ||
| const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cgmcp-wrongname-')); | ||
| t.after(() => fs.rmSync(dir, { recursive: true, force: true })); | ||
| const wrongName = path.join(dir, 'other-tool'); | ||
| fs.writeFileSync(wrongName, '#!/bin/sh\necho wrong\n'); | ||
| if (process.platform !== 'win32') fs.chmodSync(wrongName, 0o755); | ||
| assert.equal(isCachedBinaryFresh(wrongName, '0.25.0'), false); | ||
| }); |
+6
-6
| { | ||
| "name": "@sdsrs/code-graph", | ||
| "version": "0.25.0", | ||
| "version": "0.25.1", | ||
| "description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing", | ||
@@ -38,8 +38,8 @@ "license": "MIT", | ||
| "optionalDependencies": { | ||
| "@sdsrs/code-graph-linux-x64": "0.25.0", | ||
| "@sdsrs/code-graph-linux-arm64": "0.25.0", | ||
| "@sdsrs/code-graph-darwin-x64": "0.25.0", | ||
| "@sdsrs/code-graph-darwin-arm64": "0.25.0", | ||
| "@sdsrs/code-graph-win32-x64": "0.25.0" | ||
| "@sdsrs/code-graph-linux-x64": "0.25.1", | ||
| "@sdsrs/code-graph-linux-arm64": "0.25.1", | ||
| "@sdsrs/code-graph-darwin-x64": "0.25.1", | ||
| "@sdsrs/code-graph-darwin-arm64": "0.25.1", | ||
| "@sdsrs/code-graph-win32-x64": "0.25.1" | ||
| } | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
306486
1.49%6172
1.36%