@sdsrs/code-graph 0.81.1 → 0.82.0
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.
|
@@ -756,10 +756,13 @@ function update() {
|
|
|
756
756
|
manifest.updatedAt = new Date().toISOString();
|
|
757
757
|
writeManifest(manifest);
|
|
758
758
|
|
|
759
|
-
// 7. Clean up old cached versions (keep
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
|
|
759
|
+
// 7. Clean up old cached versions (keep the newest few). NOTE: older cache
|
|
760
|
+
// dirs are NOT always inert — a running MCP server's launcher path
|
|
761
|
+
// (<version>/scripts/mcp-launcher.js) is resolved + cached by Claude Code
|
|
762
|
+
// for the whole session, so pruning the version a live process is bound to
|
|
763
|
+
// breaks `/mcp` reconnect with -32000 (MODULE_NOT_FOUND). cleanupOldCacheVersions
|
|
764
|
+
// therefore skips any version still referenced by a live process cmdline.
|
|
765
|
+
cleanupOldCacheVersions(5);
|
|
763
766
|
|
|
764
767
|
return { oldVersion, version, settingsChanged, hooksRegistered };
|
|
765
768
|
}
|
|
@@ -768,8 +771,16 @@ function update() {
|
|
|
768
771
|
* Remove old plugin cache versions, keeping the N most recent.
|
|
769
772
|
* Cache layout: ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/
|
|
770
773
|
*/
|
|
771
|
-
function cleanupOldCacheVersions(
|
|
772
|
-
|
|
774
|
+
function cleanupOldCacheVersions(
|
|
775
|
+
keep = 5,
|
|
776
|
+
getActiveCmdlines = readActiveProcessCmdlines,
|
|
777
|
+
cacheParent = path.join(pluginsCacheDir(), MARKETPLACE_NAME),
|
|
778
|
+
) {
|
|
779
|
+
// Command lines of all live processes — used by the in-use guard below to
|
|
780
|
+
// avoid deleting a version a running MCP server is still bound to. Failure to
|
|
781
|
+
// enumerate ⇒ [] ⇒ pruning falls back to recency-only (pre-guard behavior).
|
|
782
|
+
let cmdlines;
|
|
783
|
+
try { cmdlines = getActiveCmdlines() || []; } catch { cmdlines = []; }
|
|
773
784
|
try {
|
|
774
785
|
// List all subdirectories under the marketplace cache
|
|
775
786
|
const entries = fs.readdirSync(cacheParent, { withFileTypes: true });
|
|
@@ -788,17 +799,51 @@ function cleanupOldCacheVersions(keep = 3) {
|
|
|
788
799
|
|
|
789
800
|
if (versions.length <= keep) continue;
|
|
790
801
|
|
|
791
|
-
const
|
|
792
|
-
|
|
802
|
+
for (const v of versions.slice(keep)) {
|
|
803
|
+
// In-use guard: never delete a version dir a live process is running
|
|
804
|
+
// from. Claude Code caches the resolved launcher path
|
|
805
|
+
// (<version>/scripts/mcp-launcher.js) for the session; deleting that
|
|
806
|
+
// dir breaks `/mcp` reconnect with -32000. Trailing separator stops
|
|
807
|
+
// `0.8` from matching `0.80.x`.
|
|
808
|
+
if (cmdlines.some(c => c.includes(v.path + path.sep))) continue;
|
|
793
809
|
try {
|
|
794
810
|
fs.rmSync(v.path, { recursive: true, force: true });
|
|
795
|
-
} catch { /* permission error
|
|
811
|
+
} catch { /* permission error — skip */ }
|
|
796
812
|
}
|
|
797
813
|
} catch { /* can't read plugin dir — skip */ }
|
|
798
814
|
}
|
|
799
815
|
} catch { /* cache dir doesn't exist — nothing to clean */ }
|
|
800
816
|
}
|
|
801
817
|
|
|
818
|
+
/**
|
|
819
|
+
* Best-effort list of running process command lines, for cleanupOldCacheVersions'
|
|
820
|
+
* in-use guard. Linux reads /proc/<pid>/cmdline; macOS/BSD shells out to `ps`;
|
|
821
|
+
* any other platform or failure returns [] (pruning then falls back to
|
|
822
|
+
* recency-only — the same behavior as before the guard existed).
|
|
823
|
+
*/
|
|
824
|
+
function readActiveProcessCmdlines() {
|
|
825
|
+
try {
|
|
826
|
+
if (process.platform === 'linux' && fs.existsSync('/proc')) {
|
|
827
|
+
const out = [];
|
|
828
|
+
for (const pid of fs.readdirSync('/proc')) {
|
|
829
|
+
if (!/^\d+$/.test(pid)) continue;
|
|
830
|
+
try {
|
|
831
|
+
const raw = fs.readFileSync(path.join('/proc', pid, 'cmdline'), 'utf8');
|
|
832
|
+
if (raw) out.push(raw.replace(/\0/g, ' '));
|
|
833
|
+
} catch { /* pid exited or unreadable — skip */ }
|
|
834
|
+
}
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
} catch { /* fall through to ps */ }
|
|
838
|
+
try {
|
|
839
|
+
const { execFileSync } = require('child_process');
|
|
840
|
+
return execFileSync('ps', ['-axww', '-o', 'command='], {
|
|
841
|
+
encoding: 'utf8', maxBuffer: 8 * 1024 * 1024,
|
|
842
|
+
}).split('\n').filter(Boolean);
|
|
843
|
+
} catch { /* unsupported platform — caller falls back to recency-only */ }
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
|
|
802
847
|
// --- Health Check ---
|
|
803
848
|
// Validates all registered paths in settings.json point to existing scripts.
|
|
804
849
|
// Returns { healthy, issues, repaired, remaining }.
|
|
@@ -733,3 +733,54 @@ test('isStaleRelicContext: relic in plugins cache defers to a different active i
|
|
|
733
733
|
existsSync: () => false,
|
|
734
734
|
}), false);
|
|
735
735
|
});
|
|
736
|
+
|
|
737
|
+
test('cleanupOldCacheVersions keeps an in-use version even beyond the keep window', (t) => {
|
|
738
|
+
const { cleanupOldCacheVersions } = require('./lifecycle.js');
|
|
739
|
+
const cacheParent = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-cache-'));
|
|
740
|
+
t.after(() => fs.rmSync(cacheParent, { recursive: true, force: true }));
|
|
741
|
+
const pluginDir = path.join(cacheParent, 'code-graph-mcp');
|
|
742
|
+
// Seven versions, oldest -> newest by mtime.
|
|
743
|
+
const vers = ['0.78.0', '0.80.2', '0.80.3', '0.81.0', '0.81.1', '0.81.2', '0.81.3'];
|
|
744
|
+
vers.forEach((v, i) => {
|
|
745
|
+
const scripts = path.join(pluginDir, v, 'scripts');
|
|
746
|
+
fs.mkdirSync(scripts, { recursive: true });
|
|
747
|
+
fs.writeFileSync(path.join(scripts, 'mcp-launcher.js'), '// stub');
|
|
748
|
+
const ts = (i + 1) * 3600; // distinct, increasing mtimes
|
|
749
|
+
fs.utimesSync(path.join(pluginDir, v), ts, ts);
|
|
750
|
+
});
|
|
751
|
+
// A live MCP server is running from the OLDEST version (beyond keep=5) — this
|
|
752
|
+
// is the v0.80.2 reconnect-(-32000) scenario.
|
|
753
|
+
const inUse = path.join(pluginDir, '0.78.0');
|
|
754
|
+
const fakeCmdlines = [`node ${path.join(inUse, 'scripts', 'mcp-launcher.js')} `];
|
|
755
|
+
|
|
756
|
+
cleanupOldCacheVersions(5, () => fakeCmdlines, cacheParent);
|
|
757
|
+
|
|
758
|
+
assert.equal(fs.existsSync(inUse), true,
|
|
759
|
+
'in-use version must survive prune even when it is the oldest');
|
|
760
|
+
assert.equal(fs.existsSync(path.join(pluginDir, '0.80.2')), false,
|
|
761
|
+
'a non-in-use version beyond the keep window is still pruned');
|
|
762
|
+
assert.equal(fs.existsSync(path.join(pluginDir, '0.81.3')), true,
|
|
763
|
+
'newest version (within keep window) is kept');
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('cleanupOldCacheVersions prunes beyond keep when nothing is in use', (t) => {
|
|
767
|
+
const { cleanupOldCacheVersions } = require('./lifecycle.js');
|
|
768
|
+
const cacheParent = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-cache-'));
|
|
769
|
+
t.after(() => fs.rmSync(cacheParent, { recursive: true, force: true }));
|
|
770
|
+
const pluginDir = path.join(cacheParent, 'code-graph-mcp');
|
|
771
|
+
const vers = ['0.78.0', '0.80.2', '0.80.3', '0.81.0', '0.81.1', '0.81.2', '0.81.3'];
|
|
772
|
+
vers.forEach((v, i) => {
|
|
773
|
+
fs.mkdirSync(path.join(pluginDir, v), { recursive: true });
|
|
774
|
+
const ts = (i + 1) * 3600;
|
|
775
|
+
fs.utimesSync(path.join(pluginDir, v), ts, ts);
|
|
776
|
+
});
|
|
777
|
+
// No live process references any version → recency-only pruning (pre-guard).
|
|
778
|
+
cleanupOldCacheVersions(5, () => [], cacheParent);
|
|
779
|
+
|
|
780
|
+
assert.equal(fs.existsSync(path.join(pluginDir, '0.78.0')), false, 'oldest pruned');
|
|
781
|
+
assert.equal(fs.existsSync(path.join(pluginDir, '0.80.2')), false, '2nd-oldest pruned');
|
|
782
|
+
assert.equal(fs.existsSync(path.join(pluginDir, '0.80.3')), true, 'within keep window kept');
|
|
783
|
+
assert.equal(
|
|
784
|
+
fs.readdirSync(pluginDir).filter(n => fs.statSync(path.join(pluginDir, n)).isDirectory()).length,
|
|
785
|
+
5, 'exactly keep=5 versions remain');
|
|
786
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
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.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.82.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.82.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.82.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.82.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.82.0"
|
|
43
43
|
}
|
|
44
44
|
}
|