@sdsrs/code-graph 0.139.0 → 0.141.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.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/adopt.js +37 -1
- package/claude-plugin/scripts/find-binary.js +48 -5
- package/claude-plugin/scripts/incremental-index.js +25 -2
- package/claude-plugin/scripts/lifecycle.js +20 -8
- package/claude-plugin/scripts/session-init.js +32 -3
- package/claude-plugin/scripts/user-prompt-context.js +26 -2
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/claude-plugin/templates/plugin_code_graph_mcp.md +13 -0
- package/package.json +6 -6
|
@@ -71,6 +71,21 @@ function writeFileAtomic(filePath, data, { followLink = false } = {}) {
|
|
|
71
71
|
// the full table at .claude/plugin_code_graph_mcp.md (opened on demand, never
|
|
72
72
|
// auto-loaded). Project-type tailoring swaps a couple of rows (web → HTTP-route
|
|
73
73
|
// tracing; frontend → reference audits) — body of the detail doc is unchanged.
|
|
74
|
+
//
|
|
75
|
+
// Every row spends the bare name `code-graph-mcp`, and a plugin-only install has
|
|
76
|
+
// it nowhere on PATH: findBinary() resolves the plugin's own download at
|
|
77
|
+
// ~/.cache/code-graph/bin (auto-update.js `cachedBinaryPath`), and the global-npm
|
|
78
|
+
// tiers below it exist only for users who ran `npm i -g` themselves. Issue #41 is
|
|
79
|
+
// what that reads like from the other side — a table of commands the user's shell
|
|
80
|
+
// answers with "command not found". Hence the fallback line below the table.
|
|
81
|
+
//
|
|
82
|
+
// It names a path SHAPE, never a resolved one. This file goes into a git-tracked
|
|
83
|
+
// CLAUDE.md (the adopt output says so two screens down), so a resolved
|
|
84
|
+
// /home/<user>/… is right on one machine and wrong on every teammate's — and
|
|
85
|
+
// buildBlock's byte-determinism is exactly what needsRefresh diffs, so
|
|
86
|
+
// machine-varying content would rewrite the block on every clone's next
|
|
87
|
+
// SessionStart. No `.exe` variant for the same reason it is safe not to have one:
|
|
88
|
+
// platformGuard() refuses adopt on win32, so this block never exists there.
|
|
74
89
|
const BLOCK_HEADING = '## Code Graph (repo-wide AST index)';
|
|
75
90
|
|
|
76
91
|
function buildTriggerRows(projectType = 'generic') {
|
|
@@ -116,6 +131,9 @@ function buildBlock(projectType = 'generic') {
|
|
|
116
131
|
'',
|
|
117
132
|
table,
|
|
118
133
|
'',
|
|
134
|
+
'Not on PATH? A plugin-only install keeps its own copy — same commands, run',
|
|
135
|
+
'`~/.cache/code-graph/bin/code-graph-mcp` (or `npm i -g @sdsrs/code-graph` once).',
|
|
136
|
+
'',
|
|
119
137
|
"Still use Grep for literal strings/regex in non-code files; still Read files you'll edit.",
|
|
120
138
|
'Full command + MCP-tool table: `.claude/plugin_code_graph_mcp.md`',
|
|
121
139
|
].join('\n');
|
|
@@ -891,7 +909,25 @@ function formatResult(action, result) {
|
|
|
891
909
|
}
|
|
892
910
|
|
|
893
911
|
if (require.main === module) {
|
|
894
|
-
|
|
912
|
+
// Strict, because SessionStart now hands users this entry point directly
|
|
913
|
+
// (`node <…>/adopt.js unadopt`) rather than the binary, which validates its
|
|
914
|
+
// arguments in main.rs before dispatching here. The old `argv[2] === 'unadopt'
|
|
915
|
+
// ? … : 'adopt'` fell back to ADOPT for anything else, so `--help` performed
|
|
916
|
+
// an unadopt where the binary prints help, and a mistyped verb silently
|
|
917
|
+
// adopted the current project — the footgun main.rs's `reject_extra_args`
|
|
918
|
+
// exists to prevent.
|
|
919
|
+
//
|
|
920
|
+
// Both binary call sites stay valid: main.rs passes `["unadopt"]` for unadopt
|
|
921
|
+
// and no arguments at all for adopt.
|
|
922
|
+
const argv = process.argv.slice(2);
|
|
923
|
+
const action = argv.length === 0 ? 'adopt' : argv[0];
|
|
924
|
+
if (argv.length > 1 || (action !== 'adopt' && action !== 'unadopt')) {
|
|
925
|
+
process.stderr.write(
|
|
926
|
+
`[code-graph] Usage: node ${path.basename(__filename)} [adopt|unadopt]\n` +
|
|
927
|
+
' Exactly one verb and no flags. No argument means `adopt`.\n' +
|
|
928
|
+
' Refusing rather than guessing: both verbs write to your CLAUDE.md.\n');
|
|
929
|
+
process.exit(2);
|
|
930
|
+
}
|
|
895
931
|
const result = action === 'unadopt' ? unadopt() : adopt();
|
|
896
932
|
process.stdout.write(formatResult(action, result) + '\n');
|
|
897
933
|
process.exit(result.ok === false ? 1 : 0);
|
|
@@ -33,6 +33,34 @@ function pathProbeTimeoutMs(remaining = require('./hook-fail-open').remainingMs)
|
|
|
33
33
|
const ms = left === null ? PATH_PROBE_MIN_MS : Math.floor(left);
|
|
34
34
|
return Math.max(ms, PATH_PROBE_MIN_MS);
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
// `readBinaryVersion`'s own default. Kept here as the number this module asks
|
|
38
|
+
// for so the clamp below is expressed against the same figure the callee would
|
|
39
|
+
// have used on its own.
|
|
40
|
+
const VERSION_PROBE_TIMEOUT_MS = 5000;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How long ONE `--version` probe in the discovery chain may run (JS-23, audit
|
|
44
|
+
* 2026-09-07).
|
|
45
|
+
*
|
|
46
|
+
* The version gate execs the candidate binary, and `findBinary()` walks up to
|
|
47
|
+
* six candidates — so a chain of default 5 s probes is 30 s of children on the
|
|
48
|
+
* first thing every hook does, all of it BEFORE the hook has consulted its
|
|
49
|
+
* budget even once. Cold cache (first install, a version bump,
|
|
50
|
+
* `auto-update.clearCache()`) is exactly when the chain is longest and every
|
|
51
|
+
* candidate is a cold page-in.
|
|
52
|
+
*
|
|
53
|
+
* Same shape and the same two edges as `pathProbeTimeoutMs`: never 0 (node
|
|
54
|
+
* reads `timeout: 0` as no timeout), never fractional (ERR_OUT_OF_RANGE, which
|
|
55
|
+
* `readBinaryVersion`'s catch would report as "no version" → a good binary
|
|
56
|
+
* judged stale). With no deadline armed — doctor, statusline, the launcher —
|
|
57
|
+
* it returns the unchanged 5 s default.
|
|
58
|
+
*/
|
|
59
|
+
function versionProbeTimeoutMs(remaining = require('./hook-fail-open').remainingMs) {
|
|
60
|
+
const left = remaining(VERSION_PROBE_TIMEOUT_MS);
|
|
61
|
+
const ms = left === null ? PATH_PROBE_MIN_MS : Math.floor(left);
|
|
62
|
+
return Math.max(ms, PATH_PROBE_MIN_MS);
|
|
63
|
+
}
|
|
36
64
|
const PLATFORM_PKG = `@sdsrs/code-graph-${PLATFORM}-${ARCH}`;
|
|
37
65
|
|
|
38
66
|
/**
|
|
@@ -129,9 +157,13 @@ function globalNodeModulesCandidates() {
|
|
|
129
157
|
|
|
130
158
|
// 4. Last resort: ask npm directly. Slow (~50-200ms) but most accurate when
|
|
131
159
|
// user has a non-standard prefix. Cached at the disk-cache layer above.
|
|
160
|
+
// Budget-aware like the sibling probes in this file (JS-23 / pre-ship review
|
|
161
|
+
// 2026-09-07): this runs on the same `findBinaryUncached()` chain, and every
|
|
162
|
+
// hook calls `findBinary()` before it consults its own budget. `remainingMs`
|
|
163
|
+
// returns the default unchanged when nothing armed a deadline.
|
|
132
164
|
try {
|
|
133
165
|
const npm = npmInvocation(['root', '-g'], {
|
|
134
|
-
timeout:
|
|
166
|
+
timeout: pathProbeTimeoutMs(),
|
|
135
167
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
136
168
|
encoding: 'utf8',
|
|
137
169
|
});
|
|
@@ -186,7 +218,9 @@ function isCachedBinaryFresh(cachedPath, pkgVersion, knownVersion) {
|
|
|
186
218
|
// `knownVersion` lets the caller skip the `--version` spawn when the cache
|
|
187
219
|
// entry already recorded it AND the file has not changed since — see
|
|
188
220
|
// `readCacheEntry`. Absent, behaviour is exactly as before.
|
|
189
|
-
|
|
221
|
+
// Clamped: this fires whenever the cache stamp mismatches — i.e. right after
|
|
222
|
+
// an auto-update, on the hook path (JS-23 / pre-ship review 2026-09-07).
|
|
223
|
+
const cacheVer = knownVersion || readBinaryVersion(cachedPath, { timeoutMs: versionProbeTimeoutMs() });
|
|
190
224
|
if (!cacheVer) return true;
|
|
191
225
|
return compareVersions(cacheVer, pkgVersion) >= 0;
|
|
192
226
|
}
|
|
@@ -243,7 +277,9 @@ function writeCacheEntry(binPath) {
|
|
|
243
277
|
CACHE_FILE,
|
|
244
278
|
JSON.stringify({
|
|
245
279
|
path: binPath,
|
|
246
|
-
|
|
280
|
+
// Clamped: this is on the COLD-CACHE path, which is exactly the case
|
|
281
|
+
// JS-23 is about (pre-ship review 2026-09-07).
|
|
282
|
+
version: readBinaryVersion(binPath, { timeoutMs: versionProbeTimeoutMs() }) || null,
|
|
247
283
|
stamp: binaryStamp(binPath),
|
|
248
284
|
})
|
|
249
285
|
);
|
|
@@ -396,13 +432,19 @@ function findPlatformBinary() {
|
|
|
396
432
|
* gracefully on a slightly-old binary than on "offline", and the stale-binary
|
|
397
433
|
* self-heal in auto-update.js re-downloads shortly anyway.
|
|
398
434
|
*/
|
|
399
|
-
function createVersionGate(
|
|
435
|
+
function createVersionGate(
|
|
436
|
+
pkgVersion,
|
|
437
|
+
{ readVersion = readBinaryVersion, probeTimeoutMs = versionProbeTimeoutMs } = {},
|
|
438
|
+
) {
|
|
400
439
|
let bestStale = null;
|
|
401
440
|
return {
|
|
402
441
|
consider(bin) {
|
|
403
442
|
if (!isNativeBinary(bin)) return null;
|
|
404
443
|
if (!pkgVersion) return bin;
|
|
405
|
-
|
|
444
|
+
// Re-read per candidate, not once for the gate: the budget shrinks as the
|
|
445
|
+
// chain walks, and the point is that candidate six cannot still ask for a
|
|
446
|
+
// fresh 5 s (JS-23).
|
|
447
|
+
const ver = readVersion(bin, { timeoutMs: probeTimeoutMs() });
|
|
406
448
|
if (!ver || compareVersions(ver, pkgVersion) >= 0) return bin;
|
|
407
449
|
if (!bestStale || compareVersions(ver, bestStale.ver) > 0) bestStale = { bin, ver };
|
|
408
450
|
return null;
|
|
@@ -554,6 +596,7 @@ module.exports = {
|
|
|
554
596
|
detectLibc, unsupportedPlatformHint,
|
|
555
597
|
CACHE_FILE, BINARY_NAME, PLATFORM_PKG,
|
|
556
598
|
pathProbeTimeoutMs, PATH_PROBE_TIMEOUT_MS, PATH_PROBE_MIN_MS,
|
|
599
|
+
versionProbeTimeoutMs, VERSION_PROBE_TIMEOUT_MS,
|
|
557
600
|
};
|
|
558
601
|
|
|
559
602
|
// Allow direct invocation for testing
|
|
@@ -12,6 +12,24 @@ const { execFileSync } = require('child_process');
|
|
|
12
12
|
const { findBinary } = require('./find-binary');
|
|
13
13
|
const { hidden } = require('./proc-opts');
|
|
14
14
|
|
|
15
|
+
// Default allowance for this hook's one child, spent through the hook budget
|
|
16
|
+
// below. The PostToolUse deadline armed at the top of this file is 10 s.
|
|
17
|
+
const CHILD_TIMEOUT_MS = 8000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How long the indexer child may run: the default when nothing armed a deadline
|
|
21
|
+
* (a test importing this module, a manual invocation), or whatever is LEFT of
|
|
22
|
+
* the PostToolUse budget. `null` means the budget is exhausted — do not run.
|
|
23
|
+
*
|
|
24
|
+
* JS-32: the child spent a literal 8 s no matter how much of the 10 s deadline
|
|
25
|
+
* `findBinary()` had already consumed, so a cold cache overran it and Claude
|
|
26
|
+
* Code killed the hook on the user's own edit. Exported because `runMain()`
|
|
27
|
+
* cannot be reached from a test without spawning the hook (JS-18's shape).
|
|
28
|
+
*/
|
|
29
|
+
function childBudgetMs() {
|
|
30
|
+
return require('./hook-fail-open').remainingMs(CHILD_TIMEOUT_MS);
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
// v0.21 — gated default-off. v0.18.0 added query-time freshness
|
|
16
34
|
// (ensure_file_indexed) inside MCP tools that take a file_path arg, so a
|
|
17
35
|
// PostToolUse hook spawning a fresh process on every Edit/Write was redundant
|
|
@@ -37,9 +55,14 @@ function runMain() {
|
|
|
37
55
|
const bin = findBinary();
|
|
38
56
|
if (!bin) return; // silent — binary not installed yet
|
|
39
57
|
|
|
58
|
+
// Read AFTER findBinary(): that chain is what eats the budget on a cold
|
|
59
|
+
// cache, and asking before it would hand the child an allowance nobody had.
|
|
60
|
+
const budget = childBudgetMs();
|
|
61
|
+
if (budget === null) return; // exhausted — the index waits for the next edit
|
|
62
|
+
|
|
40
63
|
try {
|
|
41
64
|
execFileSync(bin, ['incremental-index', '--quiet'], hidden({
|
|
42
|
-
timeout:
|
|
65
|
+
timeout: budget,
|
|
43
66
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
44
67
|
}));
|
|
45
68
|
} catch { /* timeout or error — silent for hook */ }
|
|
@@ -49,4 +72,4 @@ if (require.main === module) {
|
|
|
49
72
|
runMain();
|
|
50
73
|
}
|
|
51
74
|
|
|
52
|
-
module.exports = { shouldRun };
|
|
75
|
+
module.exports = { shouldRun, childBudgetMs, CHILD_TIMEOUT_MS };
|
|
@@ -1616,8 +1616,18 @@ function uninstall({ purgeGlobal = false, unadoptAll = false, runNpm = defaultRu
|
|
|
1616
1616
|
}
|
|
1617
1617
|
}
|
|
1618
1618
|
|
|
1619
|
-
// 6. Remove cache directory
|
|
1620
|
-
|
|
1619
|
+
// 6. Remove cache directory — through `removeCacheResidue()`, not a bare
|
|
1620
|
+
// rmSync (JS-17, audit 2026-09-07). Without `--unadopt-all` the sweep above
|
|
1621
|
+
// does not run, so the registry is still NON-EMPTY here and a wholesale wipe
|
|
1622
|
+
// strands every managed block — while step 8's own guidance, and `cli.js`,
|
|
1623
|
+
// tell the user their next move is `--unadopt-all`, which then reads an
|
|
1624
|
+
// absent registry and reports nothing to clean. `adoptedProjects` was
|
|
1625
|
+
// captured at 5.5 for the RETURN value; that capture never protected the
|
|
1626
|
+
// file. With `--unadopt-all` the sweep leaves the registry empty (or holding
|
|
1627
|
+
// only projects whose CLAUDE.md could not be rewritten, which is exactly what
|
|
1628
|
+
// must survive), and `removeCacheResidue` preserves only a non-empty one, so
|
|
1629
|
+
// the clean case still leaves nothing behind.
|
|
1630
|
+
try { removeCacheResidue(); } catch { /* ok */ }
|
|
1621
1631
|
|
|
1622
1632
|
// 6.5. The shared tmp dir (cooldown flags, read-fanout state, interrupted
|
|
1623
1633
|
// `update-*` staging). Nothing in it outlives an uninstall, and the periodic
|
|
@@ -1954,12 +1964,14 @@ function isPluginUninstalled(settings = readJson(settingsPath()) || {}) {
|
|
|
1954
1964
|
// of which repos carry a managed CLAUDE.md block. Wiping it strands every block
|
|
1955
1965
|
// — `uninstall({unadoptAll:true})` afterwards reads an empty registry, reports
|
|
1956
1966
|
// `unadopted: []`, and the blocks stay in the user's repos with nothing left
|
|
1957
|
-
// that knows where they are.
|
|
1958
|
-
//
|
|
1959
|
-
//
|
|
1960
|
-
//
|
|
1961
|
-
//
|
|
1962
|
-
//
|
|
1967
|
+
// that knows where they are. No caller can be trusted to protect it first:
|
|
1968
|
+
// `cleanupDisabledStatusline` cannot (it is the ONE path guaranteed to run after
|
|
1969
|
+
// a CC `/plugin uninstall`, and it has no list), and `uninstall()` did not —
|
|
1970
|
+
// it captured the list for its RETURN value and then wiped the file with a bare
|
|
1971
|
+
// rmSync, which is the JS-17 data loss (audit 2026-09-07); it routes through
|
|
1972
|
+
// here now. So the preservation belongs here, at the wipe, rather than at each
|
|
1973
|
+
// caller — the same "fix it at the shared layer, not per surface" the
|
|
1974
|
+
// <external> query filter needed.
|
|
1963
1975
|
function removeCacheResidue() {
|
|
1964
1976
|
// Path comes from adopt.js rather than a second spelling of the basename —
|
|
1965
1977
|
// a literal here would silently stop matching the day adopt.js renames it,
|
|
@@ -557,6 +557,35 @@ function verifyBinary() {
|
|
|
557
557
|
return { available: true, binary };
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
/**
|
|
561
|
+
* The `unadopt` invocation to hand the user, spelled so their shell can run it.
|
|
562
|
+
*
|
|
563
|
+
* Issue #41: the reporter drives this plugin through `/plugin install` only and
|
|
564
|
+
* has no `code-graph-mcp` on PATH, so every remedy this hook printed spent a
|
|
565
|
+
* bare name their shell answers with "command not found".
|
|
566
|
+
*
|
|
567
|
+
* The obvious repair — print the binary `verifyBinary()` resolved — is WRONG,
|
|
568
|
+
* and a pre-ship review caught it as a net regression. `unadopt` is one of the
|
|
569
|
+
* three JS-dispatched subcommands (`main.rs`: doctor / adopt / unadopt): the
|
|
570
|
+
* native binary re-execs `claude-plugin/scripts/adopt.js`, which it locates via
|
|
571
|
+
* `$_FIND_BINARY_ROOT` or a path relative to its own exe. The plugin's cached
|
|
572
|
+
* binary at ~/.cache/code-graph/bin has no such neighbour, so
|
|
573
|
+
* `<cache>/code-graph-mcp unadopt` exits 1 with "adopt.js not found" —
|
|
574
|
+
* reproduced. Worse, npm-global users reach adopt.js today through the
|
|
575
|
+
* `bin/cli.js` shim that sets `_FIND_BINARY_ROOT`, so printing a resolved
|
|
576
|
+
* platform-package binary would BREAK a path that currently works.
|
|
577
|
+
*
|
|
578
|
+
* So point at the script itself. `adopt.js` carries its own
|
|
579
|
+
* `require.main === module` arm (`node adopt.js unadopt`), and this file is its
|
|
580
|
+
* neighbour in every install layout — plugin cache, npm package, dev checkout —
|
|
581
|
+
* because both are shipped inside `claude-plugin/scripts/`. `__dirname` is
|
|
582
|
+
* therefore the one locator that cannot be wrong. Quoted, since a plugin cache
|
|
583
|
+
* path under a home directory with a space is otherwise not runnable as printed.
|
|
584
|
+
*/
|
|
585
|
+
function unadoptCommand() {
|
|
586
|
+
return `node ${JSON.stringify(path.join(__dirname, 'adopt.js'))} unadopt`;
|
|
587
|
+
}
|
|
588
|
+
|
|
560
589
|
/**
|
|
561
590
|
* Lightweight consistency checks — called from runSessionInit().
|
|
562
591
|
* Returns an array of issue objects: { id, msg, fix }.
|
|
@@ -806,7 +835,7 @@ function runSessionInit({ source } = {}) {
|
|
|
806
835
|
'[code-graph] Installed code-graph block into project CLAUDE.md (plugin install → knowing consent).\n' +
|
|
807
836
|
' Detail table: .claude/plugin_code_graph_mcp.md (generated; safe to gitignore)\n' +
|
|
808
837
|
' Opt out: CODE_GRAPH_NO_AUTO_ADOPT=1 in ~/.claude/settings.json env\n' +
|
|
809
|
-
|
|
838
|
+
` Reverse: ${unadoptCommand()}\n`
|
|
810
839
|
);
|
|
811
840
|
}
|
|
812
841
|
// `adopt()` has returned `registryRecorded` since it stopped throwing on a
|
|
@@ -820,7 +849,7 @@ function runSessionInit({ source } = {}) {
|
|
|
820
849
|
process.stderr.write(
|
|
821
850
|
'[code-graph] Note: this project could not be recorded in the adopted-projects registry,\n' +
|
|
822
851
|
' so `/plugin uninstall` will NOT strip the block from this CLAUDE.md.\n' +
|
|
823
|
-
|
|
852
|
+
` Remove it by hand with \`${unadoptCommand()}\` before uninstalling.\n`
|
|
824
853
|
);
|
|
825
854
|
}
|
|
826
855
|
}
|
|
@@ -1090,7 +1119,7 @@ module.exports = {
|
|
|
1090
1119
|
indexNeedsRevalidation,
|
|
1091
1120
|
injectProjectMap,
|
|
1092
1121
|
injectRecentImpact,
|
|
1093
|
-
verifyBinary, missingBinaryMessage,
|
|
1122
|
+
verifyBinary, missingBinaryMessage, unadoptCommand,
|
|
1094
1123
|
consistencyCheck,
|
|
1095
1124
|
runSessionInit,
|
|
1096
1125
|
computeQuietHooks,
|
|
@@ -8,6 +8,21 @@
|
|
|
8
8
|
// would swallow the test's own failures.
|
|
9
9
|
if (require.main === module) require('./hook-fail-open').installHookFailOpen('UserPromptSubmit');
|
|
10
10
|
|
|
11
|
+
// Default per-child allowance, spent through the hook budget below.
|
|
12
|
+
const CHILD_TIMEOUT_MS = 3000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How long ONE child of this hook may run: the default when nothing armed a
|
|
16
|
+
* deadline (a test importing this module), or whatever is LEFT of the
|
|
17
|
+
* UserPromptSubmit budget. `null` means the budget is exhausted — do not run.
|
|
18
|
+
*
|
|
19
|
+
* Exported so the three states are testable; `run()` below is a closure over
|
|
20
|
+
* the resolved binary and cannot be reached from a test (JS-18).
|
|
21
|
+
*/
|
|
22
|
+
function childBudgetMs() {
|
|
23
|
+
return require('./hook-fail-open').remainingMs(CHILD_TIMEOUT_MS);
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
// UserPromptSubmit hook: inject relevant code-graph RESULTS based on user's intent.
|
|
12
27
|
// Strategy: PUSH structural context (not suggestions) that Grep/Read cannot provide.
|
|
13
28
|
// This is a CODE INDEX — only inject structural code context (impact, overview, callgraph).
|
|
@@ -518,9 +533,18 @@ function runMain() {
|
|
|
518
533
|
if (!binary) return;
|
|
519
534
|
|
|
520
535
|
function run(args) {
|
|
536
|
+
// JS-18 (audit 2026-09-07): spend the UserPromptSubmit budget, don't ask
|
|
537
|
+
// for a fresh 3 s. This file armed a deadline at line 1 and then never
|
|
538
|
+
// consulted it — the literal here plus the `findBinary()` chain above ran
|
|
539
|
+
// outside it, so a cold cache had the hook killed by Claude Code and the
|
|
540
|
+
// user saw a hook error on THEIR prompt. `null` means the budget is gone:
|
|
541
|
+
// do not run (node reads `timeout: 0` as no timeout at all). Returning ''
|
|
542
|
+
// lands on the same "nothing to say" path as an empty result.
|
|
543
|
+
const budget = childBudgetMs();
|
|
544
|
+
if (budget === null) return '';
|
|
521
545
|
return execFileSync(binary, args, hidden({
|
|
522
546
|
cwd,
|
|
523
|
-
timeout:
|
|
547
|
+
timeout: budget,
|
|
524
548
|
encoding: 'utf8',
|
|
525
549
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
526
550
|
env: buildRunEnv(),
|
|
@@ -553,4 +577,4 @@ if (require.main === module) {
|
|
|
553
577
|
runMain();
|
|
554
578
|
}
|
|
555
579
|
|
|
556
|
-
module.exports = { COOLDOWNS, shouldSkip, extractFilePaths, extractSymbols, detectIntents, scoreIntent, INTENT_PATTERNS, INTENT_THRESHOLD, determineQueryType, computeQuietHooks, STOP_WORDS, PLAIN_WORD_EXCLUDE, hasSymptom, SYMPTOM_PATTERNS, buildRunEnv };
|
|
580
|
+
module.exports = { COOLDOWNS, shouldSkip, extractFilePaths, extractSymbols, detectIntents, scoreIntent, INTENT_PATTERNS, INTENT_THRESHOLD, determineQueryType, computeQuietHooks, STOP_WORDS, PLAIN_WORD_EXCLUDE, hasSymptom, SYMPTOM_PATTERNS, buildRunEnv, childBudgetMs, CHILD_TIMEOUT_MS };
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
node-version: '20'
|
|
36
36
|
- name: Build snapshot
|
|
37
37
|
run: |
|
|
38
|
-
npx -y -p @sdsrs/code-graph@0.
|
|
38
|
+
npx -y -p @sdsrs/code-graph@0.141.0 code-graph-mcp snapshot create --out snapshot.db
|
|
39
39
|
zstd -9 snapshot.db -o snapshot.db.zst
|
|
40
40
|
mv snapshot.db.zst "code-graph-snapshot-${GITHUB_SHA:0:7}.db.zst"
|
|
41
41
|
- name: Upload to release
|
|
@@ -49,6 +49,19 @@ type: reference
|
|
|
49
49
|
> ToolSearch 加载),而 Bash 永远在线——真实编程夜(2026-06-12)观测到的全部
|
|
50
50
|
> 转化都是 CLI 调用。结构化查询的最快路径是 Bash 直呼
|
|
51
51
|
> `code-graph-mcp callgraph X / show X / overview <dir> / grep "pat" / impact X`。
|
|
52
|
+
>
|
|
53
|
+
> **裸名字不在 PATH 上时**(纯 `/plugin install`,没跑过 `npm i -g`):插件自己
|
|
54
|
+
> 下载的那份在 `~/.cache/code-graph/bin/code-graph-mcp`,本文下面所有查询类子命令
|
|
55
|
+
> (callgraph / impact / overview / show / search / grep / refs / similar / deps /
|
|
56
|
+
> dead-code / trace)都能直接用它跑;`npm i -g @sdsrs/code-graph` 则把裸名字放上
|
|
57
|
+
> PATH(issue #41)。
|
|
58
|
+
>
|
|
59
|
+
> **三个例外**:`doctor` / `adopt` / `unadopt` 是 JS 派发的——二进制会去自己旁边
|
|
60
|
+
> 找对应脚本(前者 `doctor.js`,后两者 `adopt.js`),而缓存目录里没有那些邻居,
|
|
61
|
+
> 所以从缓存路径调它们会报 `<脚本名> not found`。直接跑脚本即可:
|
|
62
|
+
> `node <插件目录>/claude-plugin/scripts/adopt.js unadopt`(SessionStart 的提示行
|
|
63
|
+
> 会印出这台机器上的完整路径)、`node <插件目录>/claude-plugin/scripts/doctor.js`。
|
|
64
|
+
>
|
|
52
65
|
> `grep` 是 drop-in 替代:`-F` 字面 / `-i` / `-w` / `-l` / `-c` 计数 / `-t <lang>` 按语言筛 /
|
|
53
66
|
> `-g <glob>` 路径过滤 / `-A/-B/-C N` 上下文 / `-M N` 行宽上限(默认 512,防长行刷屏)/
|
|
54
67
|
> 多路径 / `-m 0` 取消每文件上限,退出码兼容 grep(0/1/2),召回达 git-grep 级
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.141.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.141.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.141.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.141.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.141.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.141.0"
|
|
43
43
|
}
|
|
44
44
|
}
|