@sdsrs/code-graph 0.7.3 → 0.7.5
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/README.md +25 -0
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/lifecycle.js +39 -1
- package/claude-plugin/scripts/mcp-launcher.js +1 -13
- package/claude-plugin/scripts/session-init.js +38 -1
- package/claude-plugin/scripts/session-init.test.js +17 -1
- package/claude-plugin/scripts/user-prompt-context.js +4 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -234,6 +234,31 @@ npm uninstall -g @sdsrs/code-graph
|
|
|
234
234
|
| `find_references` | Find all references to a symbol (callers, importers, inheritors). Supports `compact` mode |
|
|
235
235
|
| `find_dead_code` | Find unused code — orphan symbols and exported-but-unused public APIs |
|
|
236
236
|
|
|
237
|
+
## CLI Commands
|
|
238
|
+
|
|
239
|
+
All tools are also available as CLI subcommands for shell scripts, hooks, and terminal workflows:
|
|
240
|
+
|
|
241
|
+
| Command | MCP Equivalent | Description |
|
|
242
|
+
|---------|---------------|-------------|
|
|
243
|
+
| `search <query>` | `semantic_code_search` | FTS5 search by concept |
|
|
244
|
+
| `ast-search [query]` | `ast_search` | Structural search with `--type`/`--returns`/`--params` filters |
|
|
245
|
+
| `callgraph <symbol>` | `get_call_graph` | Show call graph (callers/callees) |
|
|
246
|
+
| `impact <symbol>` | `impact_analysis` | Impact analysis (callers, routes, risk level) |
|
|
247
|
+
| `show <symbol>` | `get_ast_node` | Show symbol details (code, type, signature) |
|
|
248
|
+
| `map` | `project_map` | Project architecture map |
|
|
249
|
+
| `overview <path>` | `module_overview` | Module symbols grouped by file and type |
|
|
250
|
+
| `deps <file>` | `dependency_graph` | File-level dependency graph |
|
|
251
|
+
| `trace <route>` | `trace_http_chain` | Trace HTTP route → handler → downstream calls |
|
|
252
|
+
| `similar <symbol>` | `find_similar_code` | Find semantically similar code (requires embeddings) |
|
|
253
|
+
| `refs <symbol>` | `find_references` | Find all references to a symbol |
|
|
254
|
+
| `dead-code [path]` | `find_dead_code` | Find unused code (orphans and exported-unused) |
|
|
255
|
+
| `grep <pattern>` | — | AST-context grep (ripgrep + containing function/class) |
|
|
256
|
+
| `incremental-index` | — | Run incremental index update (auto-creates DB if needed) |
|
|
257
|
+
| `health-check` | `get_index_status` | Query index status and freshness |
|
|
258
|
+
| `benchmark` | — | Benchmark index speed, query latency, token savings |
|
|
259
|
+
|
|
260
|
+
Common options: `--json` (JSON output), `--compact` (compact output), `--limit N`, `--depth N`, `--file <path>`.
|
|
261
|
+
|
|
237
262
|
## Plugin Slash Commands
|
|
238
263
|
|
|
239
264
|
Available when installed as a Claude Code plugin:
|
|
@@ -364,15 +364,53 @@ function update() {
|
|
|
364
364
|
manifest.updatedAt = new Date().toISOString();
|
|
365
365
|
writeManifest(manifest);
|
|
366
366
|
|
|
367
|
+
// 6. Clean up old cached versions (keep latest 3)
|
|
368
|
+
cleanupOldCacheVersions(3);
|
|
369
|
+
|
|
367
370
|
return { oldVersion, version, settingsChanged };
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Remove old plugin cache versions, keeping the N most recent.
|
|
375
|
+
* Cache layout: ~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/
|
|
376
|
+
*/
|
|
377
|
+
function cleanupOldCacheVersions(keep = 3) {
|
|
378
|
+
const cacheParent = path.join(os.homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_NAME);
|
|
379
|
+
try {
|
|
380
|
+
// List all subdirectories under the marketplace cache
|
|
381
|
+
const entries = fs.readdirSync(cacheParent, { withFileTypes: true });
|
|
382
|
+
for (const entry of entries) {
|
|
383
|
+
if (!entry.isDirectory()) continue;
|
|
384
|
+
const pluginDir = path.join(cacheParent, entry.name);
|
|
385
|
+
try {
|
|
386
|
+
const versions = fs.readdirSync(pluginDir, { withFileTypes: true })
|
|
387
|
+
.filter(d => d.isDirectory())
|
|
388
|
+
.map(d => ({
|
|
389
|
+
name: d.name,
|
|
390
|
+
path: path.join(pluginDir, d.name),
|
|
391
|
+
mtime: fs.statSync(path.join(pluginDir, d.name)).mtimeMs,
|
|
392
|
+
}))
|
|
393
|
+
.sort((a, b) => b.mtime - a.mtime); // newest first
|
|
394
|
+
|
|
395
|
+
if (versions.length <= keep) continue;
|
|
396
|
+
|
|
397
|
+
const toRemove = versions.slice(keep);
|
|
398
|
+
for (const v of toRemove) {
|
|
399
|
+
try {
|
|
400
|
+
fs.rmSync(v.path, { recursive: true, force: true });
|
|
401
|
+
} catch { /* permission error or in-use — skip */ }
|
|
402
|
+
}
|
|
403
|
+
} catch { /* can't read plugin dir — skip */ }
|
|
404
|
+
}
|
|
405
|
+
} catch { /* cache dir doesn't exist — nothing to clean */ }
|
|
406
|
+
}
|
|
407
|
+
|
|
370
408
|
module.exports = {
|
|
371
409
|
install, uninstall, update, checkScopeConflict,
|
|
372
410
|
isPluginExplicitlyDisabled, isPluginInactive, cleanupDisabledStatusline,
|
|
373
411
|
readManifest, readJson, writeJsonAtomic,
|
|
374
412
|
readRegistry, writeRegistry,
|
|
375
|
-
getPluginVersion,
|
|
413
|
+
getPluginVersion, cleanupOldCacheVersions,
|
|
376
414
|
PLUGIN_ID, OLD_PLUGIN_IDS, MARKETPLACE_NAME, CACHE_DIR, REGISTRY_FILE,
|
|
377
415
|
};
|
|
378
416
|
|
|
@@ -37,19 +37,7 @@ if (!binary) {
|
|
|
37
37
|
process.stderr.write(`[code-graph] Installed at ${binary}\n`);
|
|
38
38
|
}
|
|
39
39
|
} catch {
|
|
40
|
-
process.stderr.write('[code-graph] npm install failed
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Fallback: direct binary download from GitHub release
|
|
44
|
-
if (!binary) {
|
|
45
|
-
try {
|
|
46
|
-
const { downloadBinarySync } = require('./auto-update');
|
|
47
|
-
if (typeof downloadBinarySync === 'function') {
|
|
48
|
-
downloadBinarySync(version);
|
|
49
|
-
clearCache();
|
|
50
|
-
binary = findBinary();
|
|
51
|
-
}
|
|
52
|
-
} catch { /* not available */ }
|
|
40
|
+
process.stderr.write('[code-graph] npm install failed.\n');
|
|
53
41
|
}
|
|
54
42
|
}
|
|
55
43
|
|
|
@@ -47,6 +47,41 @@ function syncLifecycleConfig() {
|
|
|
47
47
|
return 'noop';
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Check if the index is stale by comparing git HEAD timestamp vs index.db mtime.
|
|
52
|
+
* If stale, spawn background incremental-index to refresh.
|
|
53
|
+
* Returns 'fresh' | 'refreshing' | 'skipped'.
|
|
54
|
+
*/
|
|
55
|
+
function ensureIndexFresh() {
|
|
56
|
+
const { findBinary } = require('./find-binary');
|
|
57
|
+
const bin = findBinary();
|
|
58
|
+
if (!bin) return 'skipped';
|
|
59
|
+
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
const dbPath = path.join(cwd, '.code-graph', 'index.db');
|
|
62
|
+
if (!fs.existsSync(dbPath)) return 'skipped';
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const dbMtime = fs.statSync(dbPath).mtimeMs;
|
|
66
|
+
// Compare with git HEAD commit timestamp
|
|
67
|
+
const gitTs = parseInt(
|
|
68
|
+
execSync('git log -1 --format=%ct', { cwd, timeout: 2000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
69
|
+
) * 1000;
|
|
70
|
+
if (gitTs <= dbMtime) return 'fresh';
|
|
71
|
+
|
|
72
|
+
// Index is stale — run incremental-index in background
|
|
73
|
+
const child = spawn(bin, ['incremental-index', '--quiet'], {
|
|
74
|
+
cwd,
|
|
75
|
+
detached: true,
|
|
76
|
+
stdio: 'ignore',
|
|
77
|
+
});
|
|
78
|
+
if (child && typeof child.unref === 'function') child.unref();
|
|
79
|
+
return 'refreshing';
|
|
80
|
+
} catch {
|
|
81
|
+
return 'skipped';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
50
85
|
function runSessionInit() {
|
|
51
86
|
if (isPluginInactive()) {
|
|
52
87
|
cleanupDisabledStatusline();
|
|
@@ -63,8 +98,9 @@ function runSessionInit() {
|
|
|
63
98
|
|
|
64
99
|
const lifecycle = syncLifecycleConfig();
|
|
65
100
|
const autoUpdateLaunched = launchBackgroundAutoUpdate();
|
|
101
|
+
const indexFreshness = ensureIndexFresh();
|
|
66
102
|
const mapInjected = injectProjectMap();
|
|
67
|
-
return { inactive: false, lifecycle, autoUpdateLaunched, mapInjected };
|
|
103
|
+
return { inactive: false, lifecycle, autoUpdateLaunched, indexFreshness, mapInjected };
|
|
68
104
|
}
|
|
69
105
|
|
|
70
106
|
/**
|
|
@@ -99,6 +135,7 @@ function injectProjectMap() {
|
|
|
99
135
|
module.exports = {
|
|
100
136
|
launchBackgroundAutoUpdate,
|
|
101
137
|
syncLifecycleConfig,
|
|
138
|
+
ensureIndexFresh,
|
|
102
139
|
injectProjectMap,
|
|
103
140
|
runSessionInit,
|
|
104
141
|
};
|
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
const test = require('node:test');
|
|
3
3
|
const assert = require('node:assert/strict');
|
|
4
4
|
|
|
5
|
-
const { launchBackgroundAutoUpdate, syncLifecycleConfig } = require('./session-init');
|
|
5
|
+
const { launchBackgroundAutoUpdate, syncLifecycleConfig, ensureIndexFresh } = require('./session-init');
|
|
6
6
|
|
|
7
7
|
test('syncLifecycleConfig is exported as a callable helper', () => {
|
|
8
8
|
assert.equal(typeof syncLifecycleConfig, 'function');
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
test('ensureIndexFresh is exported as a callable helper', () => {
|
|
12
|
+
assert.equal(typeof ensureIndexFresh, 'function');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('ensureIndexFresh returns skipped when no index exists', () => {
|
|
16
|
+
const origCwd = process.cwd();
|
|
17
|
+
const tmpDir = require('node:os').tmpdir();
|
|
18
|
+
process.chdir(tmpDir);
|
|
19
|
+
try {
|
|
20
|
+
const result = ensureIndexFresh();
|
|
21
|
+
assert.equal(result, 'skipped');
|
|
22
|
+
} finally {
|
|
23
|
+
process.chdir(origCwd);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
11
27
|
test('launchBackgroundAutoUpdate spawns detached silent updater', () => {
|
|
12
28
|
const calls = [];
|
|
13
29
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// UserPromptSubmit hook: inject relevant code-graph context based on user's question.
|
|
4
4
|
// Only activates when user message references code entities + has understanding intent.
|
|
5
5
|
// This is a CODE INDEX, not a memory store — only inject structural code context.
|
|
6
|
-
const {
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
@@ -106,7 +106,9 @@ if (result && result.trim()) {
|
|
|
106
106
|
// --- Helpers ---
|
|
107
107
|
|
|
108
108
|
function run(cmd) {
|
|
109
|
-
|
|
109
|
+
const parts = cmd.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
|
|
110
|
+
const args = parts.slice(1).map(a => a.replace(/^"|"$/g, ''));
|
|
111
|
+
return execFileSync(parts[0], args, {
|
|
110
112
|
cwd,
|
|
111
113
|
timeout: 3000,
|
|
112
114
|
encoding: 'utf8',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
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": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"node": ">=16"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@sdsrs/code-graph-linux-x64": "0.7.
|
|
37
|
-
"@sdsrs/code-graph-linux-arm64": "0.7.
|
|
38
|
-
"@sdsrs/code-graph-darwin-x64": "0.7.
|
|
39
|
-
"@sdsrs/code-graph-darwin-arm64": "0.7.
|
|
40
|
-
"@sdsrs/code-graph-win32-x64": "0.7.
|
|
36
|
+
"@sdsrs/code-graph-linux-x64": "0.7.5",
|
|
37
|
+
"@sdsrs/code-graph-linux-arm64": "0.7.5",
|
|
38
|
+
"@sdsrs/code-graph-darwin-x64": "0.7.5",
|
|
39
|
+
"@sdsrs/code-graph-darwin-arm64": "0.7.5",
|
|
40
|
+
"@sdsrs/code-graph-win32-x64": "0.7.5"
|
|
41
41
|
}
|
|
42
42
|
}
|