Skip to content

Commit 55bc573

Browse files
sdsrssclaude
andcommitted
fix: prevent .code-graph pollution + sync update-state + test leak cleanup (v0.8.4)
- cli: resolve_project_root() climbs to .git before using cwd, stops orphan .code-graph/ indexes when CLI runs from subdirs - cli: cleanup_legacy_db_files() auto-removes 0-byte legacy code-graph.db/code_graph.db/graph.db on index open - auto-update: always sync installedVersion from manifest.version so /plugin update (which bypasses auto-update.js) doesn't leave state stale - tests: add t.after(rmSync) cleanup to 7 test files to stop ~/.claude/tmp/ from accumulating mkdtempSync leftovers across runs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent af14ec9 commit 55bc573

File tree

18 files changed

+206
-74
lines changed

18 files changed

+206
-74
lines changed

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
},
66
"metadata": {
77
"description": "AST knowledge graph plugin for Claude Code — semantic search, call graph, HTTP tracing, impact analysis",
8-
"version": "0.8.3"
8+
"version": "0.8.4"
99
},
1010
"plugins": [
1111
{
1212
"name": "code-graph-mcp",
1313
"source": "./claude-plugin",
1414
"description": "AST knowledge graph for intelligent code navigation — auto-indexes your codebase and provides semantic search, call graph traversal, HTTP route tracing, and impact analysis via MCP tools",
15-
"version": "0.8.3",
15+
"version": "0.8.4",
1616
"author": {
1717
"name": "sdsrs"
1818
},

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "code-graph-mcp"
3-
version = "0.8.3"
3+
version = "0.8.4"
44
edition = "2021"
55

66
[features]

claude-plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"author": {
55
"name": "sdsrs"
66
},
7-
"version": "0.8.3",
7+
"version": "0.8.4",
88
"keywords": [
99
"code-graph",
1010
"ast",

claude-plugin/scripts/auto-update.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,33 +310,38 @@ async function checkForUpdate() {
310310
if (isDevMode()) return null;
311311

312312
const state = readState();
313+
// manifest.version is authoritative — /plugin update writes it directly and
314+
// bypasses auto-update.js, so re-sync state.installedVersion every call.
315+
const installedVersion = readManifest().version || '0.0.0';
313316

314317
// Time-based throttle
315318
if (!shouldCheck(state)) {
316-
if (state.updateAvailable && state.latestVersion) {
317-
return { updateAvailable: true, from: state.installedVersion, to: state.latestVersion };
319+
if (state.installedVersion !== installedVersion) {
320+
saveState({ ...state, installedVersion });
321+
}
322+
if (state.updateAvailable && state.latestVersion
323+
&& compareVersions(state.latestVersion, installedVersion) > 0) {
324+
return { updateAvailable: true, from: installedVersion, to: state.latestVersion };
318325
}
319326
return null;
320327
}
321328

322329
// Check GitHub for latest release
323330
const latest = await fetchLatestRelease();
324331
if (!latest) {
325-
saveState({ ...state, lastCheck: new Date().toISOString() });
332+
saveState({ ...state, installedVersion, lastCheck: new Date().toISOString() });
326333
return null;
327334
}
328335

329336
// Compare versions
330-
const manifest = readManifest();
331-
const currentVersion = manifest.version || '0.0.0';
332-
const hasUpdate = compareVersions(latest.version, currentVersion) > 0;
337+
const hasUpdate = compareVersions(latest.version, installedVersion) > 0;
333338

334339
if (hasUpdate) {
335340
const result = await downloadAndInstall(latest);
336341
const success = result.pluginUpdated;
337342
const newState = {
338343
lastCheck: new Date().toISOString(),
339-
installedVersion: success ? latest.version : currentVersion,
344+
installedVersion: success ? latest.version : installedVersion,
340345
latestVersion: latest.version,
341346
updateAvailable: !success,
342347
lastUpdate: success ? new Date().toISOString() : state.lastUpdate,
@@ -349,14 +354,15 @@ async function checkForUpdate() {
349354
updateAvailable: !success,
350355
updated: success,
351356
binaryUpdated: result.binaryUpdated,
352-
from: currentVersion,
357+
from: installedVersion,
353358
to: latest.version,
354359
};
355360
}
356361

357362
// No update needed
358363
saveState({
359364
...state,
365+
installedVersion,
360366
lastCheck: new Date().toISOString(),
361367
latestVersion: latest.version,
362368
updateAvailable: false,

claude-plugin/scripts/auto-update.test.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ const {
1414
promoteVerifiedBinary,
1515
} = require('./auto-update');
1616

17-
function mkDir(prefix) {
18-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
17+
function mkDir(t, prefix) {
18+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
19+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
20+
return dir;
1921
}
2022

21-
test('getExtractedPluginVersion reads extracted plugin manifest version', () => {
22-
const root = mkDir('code-graph-plugin-');
23+
test('getExtractedPluginVersion reads extracted plugin manifest version', (t) => {
24+
const root = mkDir(t, 'code-graph-plugin-');
2325
const manifest = path.join(root, '.claude-plugin', 'plugin.json');
2426
fs.mkdirSync(path.dirname(manifest), { recursive: true });
2527
fs.writeFileSync(manifest, JSON.stringify({ version: '1.2.3' }, null, 2));
@@ -41,8 +43,8 @@ function writeFakeBinary(filePath, version) {
4143
fs.chmodSync(filePath, 0o755);
4244
}
4345

44-
test('promoteVerifiedBinary accepts a runnable binary with the expected version', () => {
45-
const dir = mkDir('code-graph-bin-');
46+
test('promoteVerifiedBinary accepts a runnable binary with the expected version', (t) => {
47+
const dir = mkDir(t, 'code-graph-bin-');
4648
const tmp = path.join(dir, 'code-graph-mcp.tmp');
4749
const dst = path.join(dir, 'code-graph-mcp');
4850
writeFakeBinary(tmp, '1.2.3');
@@ -53,8 +55,8 @@ test('promoteVerifiedBinary accepts a runnable binary with the expected version'
5355
assert.equal(fs.existsSync(dst), true);
5456
});
5557

56-
test('promoteVerifiedBinary rejects binaries with mismatched version', () => {
57-
const dir = mkDir('code-graph-bin-');
58+
test('promoteVerifiedBinary rejects binaries with mismatched version', (t) => {
59+
const dir = mkDir(t, 'code-graph-bin-');
5860
const tmp = path.join(dir, 'code-graph-mcp.tmp');
5961
const dst = path.join(dir, 'code-graph-mcp');
6062
writeFakeBinary(tmp, '1.2.2');

claude-plugin/scripts/lifecycle.e2e.test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ const lifecycleCli = path.join(__dirname, 'lifecycle.js');
1212
const compositeCli = path.join(__dirname, 'statusline-composite.js');
1313
const currentVersion = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).version;
1414

15-
function mkHome() {
16-
return fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-e2e-'));
15+
function mkHome(t) {
16+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-e2e-'));
17+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
18+
return dir;
1719
}
1820

1921
function writeJson(filePath, value) {
@@ -38,8 +40,8 @@ function runScript(homeDir, scriptPath, args = [], options = {}) {
3840
}).toString();
3941
}
4042

41-
test('lifecycle CLI handles install, disable self-heal, re-enable, and uninstall', () => {
42-
const homeDir = mkHome();
43+
test('lifecycle CLI handles install, disable self-heal, re-enable, and uninstall', (t) => {
44+
const homeDir = mkHome(t);
4345
const settingsPath = path.join(homeDir, '.claude', 'settings.json');
4446
const installedPath = path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json');
4547
const registryPath = path.join(homeDir, '.cache', 'code-graph', 'statusline-registry.json');

claude-plugin/scripts/lifecycle.test.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ const { execFileSync } = require('child_process');
99
const lifecyclePath = path.join(__dirname, 'lifecycle.js');
1010
const statuslinePath = path.join(__dirname, 'statusline.js');
1111

12-
function mkHome() {
13-
return fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-home-'));
12+
function mkHome(t) {
13+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-home-'));
14+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
15+
return dir;
1416
}
1517

1618
function writeJson(filePath, value) {
@@ -48,8 +50,8 @@ function seedOrphanedComposite(homeDir) {
4850
return { settingsPath, registryPath };
4951
}
5052

51-
test('cleanupDisabledStatusline restores previous statusline and removes registry', () => {
52-
const homeDir = mkHome();
53+
test('cleanupDisabledStatusline restores previous statusline and removes registry', (t) => {
54+
const homeDir = mkHome(t);
5355
const { settingsPath, registryPath } = seedDisabledComposite(homeDir);
5456

5557
const out = execFileSync(process.execPath, ['-e', `
@@ -63,10 +65,11 @@ test('cleanupDisabledStatusline restores previous statusline and removes registr
6365
assert.equal(fs.existsSync(registryPath), false);
6466
});
6567

66-
test('statusline exits cleanly and self-heals when plugin is disabled', () => {
67-
const homeDir = mkHome();
68+
test('statusline exits cleanly and self-heals when plugin is disabled', (t) => {
69+
const homeDir = mkHome(t);
6870
const { settingsPath, registryPath } = seedDisabledComposite(homeDir);
6971
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-project-'));
72+
t.after(() => fs.rmSync(projectDir, { recursive: true, force: true }));
7073
fs.mkdirSync(path.join(projectDir, '.code-graph'), { recursive: true });
7174
fs.writeFileSync(path.join(projectDir, '.code-graph', 'index.db'), '');
7275

@@ -81,8 +84,8 @@ test('statusline exits cleanly and self-heals when plugin is disabled', () => {
8184
assert.equal(fs.existsSync(registryPath), false);
8285
});
8386

84-
test('cleanupDisabledStatusline also heals orphaned statusline after uninstall', () => {
85-
const homeDir = mkHome();
87+
test('cleanupDisabledStatusline also heals orphaned statusline after uninstall', (t) => {
88+
const homeDir = mkHome(t);
8689
const { settingsPath, registryPath } = seedOrphanedComposite(homeDir);
8790

8891
const out = execFileSync(process.execPath, ['-e', `
@@ -175,8 +178,8 @@ test('removeHooksFromSettings strips our entries but keeps unrelated hooks', ()
175178
assert.ok(!s.hooks.PostToolUse, 'empty event key should be deleted');
176179
});
177180

178-
test('install() removes legacy code-graph hooks from settings.json without re-registering', () => {
179-
const homeDir = mkHome();
181+
test('install() removes legacy code-graph hooks from settings.json without re-registering', (t) => {
182+
const homeDir = mkHome(t);
180183
const settingsPath = path.join(homeDir, '.claude', 'settings.json');
181184
writeJson(settingsPath, {
182185
statusLine: { type: 'command', command: 'echo previous-status' },

claude-plugin/scripts/session-init.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,10 @@ test('consistencyCheck returns empty array when binary version matches plugin',
8383
assert.ok(Array.isArray(result));
8484
});
8585

86-
test('consistencyCheck returns version-mismatch when versions differ', () => {
86+
test('consistencyCheck returns version-mismatch when versions differ', (t) => {
8787
const os = require('os');
8888
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-'));
89+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
8990
const bin = path.join(dir, 'code-graph-mcp');
9091
fs.writeFileSync(bin, [
9192
'#!/usr/bin/env bash',

claude-plugin/scripts/version-utils.test.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ const fs = require('fs');
55
const os = require('os');
66
const path = require('path');
77

8-
function mkDir(prefix) {
9-
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
8+
function mkDir(t, prefix) {
9+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
10+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
11+
return dir;
1012
}
1113

1214
// ── readBinaryVersion ──
1315

14-
test('readBinaryVersion returns version from valid binary', () => {
16+
test('readBinaryVersion returns version from valid binary', (t) => {
1517
const { readBinaryVersion } = require('./version-utils');
16-
const dir = mkDir('vu-');
18+
const dir = mkDir(t, 'vu-');
1719
const bin = path.join(dir, 'code-graph-mcp');
1820
fs.writeFileSync(bin, [
1921
'#!/usr/bin/env bash',
@@ -32,9 +34,9 @@ test('readBinaryVersion returns null for non-existent binary', () => {
3234
assert.equal(readBinaryVersion('/tmp/does-not-exist-binary'), null);
3335
});
3436

35-
test('readBinaryVersion returns null for binary with unexpected output', () => {
37+
test('readBinaryVersion returns null for binary with unexpected output', (t) => {
3638
const { readBinaryVersion } = require('./version-utils');
37-
const dir = mkDir('vu-');
39+
const dir = mkDir(t, 'vu-');
3840
const bin = path.join(dir, 'code-graph-mcp');
3941
fs.writeFileSync(bin, '#!/usr/bin/env bash\necho "something else"');
4042
fs.chmodSync(bin, 0o755);
@@ -56,9 +58,9 @@ test('getNewestMtime returns 0 for non-existent directory', () => {
5658
assert.equal(getNewestMtime('/tmp/no-such-dir-xyz'), 0);
5759
});
5860

59-
test('getNewestMtime finds newest .rs file mtime', () => {
61+
test('getNewestMtime finds newest .rs file mtime', (t) => {
6062
const { getNewestMtime } = require('./version-utils');
61-
const dir = mkDir('vu-mtime-');
63+
const dir = mkDir(t, 'vu-mtime-');
6264
const sub = path.join(dir, 'sub');
6365
fs.mkdirSync(sub);
6466

@@ -75,9 +77,9 @@ test('getNewestMtime finds newest .rs file mtime', () => {
7577
assert.equal(result, newerMtime, 'should return exactly the newest file mtime');
7678
});
7779

78-
test('getNewestMtime ignores non-matching extensions', () => {
80+
test('getNewestMtime ignores non-matching extensions', (t) => {
7981
const { getNewestMtime } = require('./version-utils');
80-
const dir = mkDir('vu-ext-');
82+
const dir = mkDir(t, 'vu-ext-');
8183
fs.writeFileSync(path.join(dir, 'file.js'), 'hello');
8284
assert.equal(getNewestMtime(dir, '.rs'), 0);
8385
});

0 commit comments

Comments
 (0)