@sdsrs/code-graph 0.134.0 → 0.135.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/cg-answer.js +41 -2
- package/claude-plugin/scripts/hook-fail-open.js +87 -1
- package/claude-plugin/scripts/lifecycle.js +69 -11
- package/claude-plugin/scripts/pre-edit-guide.js +16 -3
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/package.json +6 -6
|
@@ -105,11 +105,41 @@ function resolveAnswerBinary(opts) {
|
|
|
105
105
|
return binary || null;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* One spawn, one options block, one `CODE_GRAPH_INTERNAL` stamp.
|
|
110
|
+
*
|
|
111
|
+
* The timeout is the SMALLER of this call's own budget and whatever is left of
|
|
112
|
+
* the hook process's registered budget, because the callers run these in
|
|
113
|
+
* series: post-grep-inject loops callgraph over every symbol in the pattern
|
|
114
|
+
* before falling back to show and then grep, so three 2 s answers overran a 5 s
|
|
115
|
+
* hook and Claude Code killed it — which the user sees as a hook error on their
|
|
116
|
+
* own tool call, not as a missing hint (audit 2026-09-05 JS-03). Out of budget
|
|
117
|
+
* returns a synthetic timeout the exit-code table already reads as
|
|
118
|
+
* `unavailable`, so the caller degrades to the static path exactly as it does
|
|
119
|
+
* for a real timeout.
|
|
120
|
+
*
|
|
121
|
+
* `killSignal: 'SIGKILL'`: the child we are giving up on is most often one
|
|
122
|
+
* wedged waiting on `index.lock`, and node's `timeout` sends SIGTERM and then
|
|
123
|
+
* WAITS. It reads no stdin and holds no lock file worth unwinding — the same
|
|
124
|
+
* reasoning statusline.js and doctor.js already apply (see proc-opts.js).
|
|
125
|
+
*/
|
|
109
126
|
function runCg(binary, args, { cwd, timeoutMs }) {
|
|
127
|
+
const budget = require('./hook-fail-open').remainingMs(timeoutMs);
|
|
128
|
+
if (budget === null) {
|
|
129
|
+
// `error` is what classifyRun reads (→ `unavailable`); `budgetExhausted` is
|
|
130
|
+
// for the one caller that loops and must not mistake this for "that symbol
|
|
131
|
+
// did not resolve" — see runShowAnswer.
|
|
132
|
+
return {
|
|
133
|
+
error: new Error('hook budget exhausted'),
|
|
134
|
+
budgetExhausted: true,
|
|
135
|
+
status: null,
|
|
136
|
+
stdout: '',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
110
139
|
return spawnSync(binary, args, hidden({
|
|
111
140
|
cwd,
|
|
112
|
-
timeout:
|
|
141
|
+
timeout: budget,
|
|
142
|
+
killSignal: 'SIGKILL',
|
|
113
143
|
encoding: 'utf8',
|
|
114
144
|
maxBuffer: 4 * 1024 * 1024,
|
|
115
145
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -230,6 +260,15 @@ function runShowAnswer(opts = {}) {
|
|
|
230
260
|
for (const sym of symbols.slice(0, 3)) {
|
|
231
261
|
if (typeof sym !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(sym)) continue;
|
|
232
262
|
const res = runCg(binary, ['show', sym], { cwd, timeoutMs });
|
|
263
|
+
// Out of hook budget is not "this symbol did not resolve". This loop
|
|
264
|
+
// `continue`s past every failure and reports `no-hits` when none of the
|
|
265
|
+
// three produced output, so without this arm an exhausted budget reached
|
|
266
|
+
// recordRecommendation as a genuine empty result — and the whole reason
|
|
267
|
+
// `no-binary` is kept distinct from `unavailable` (see this function's
|
|
268
|
+
// docs) is that the deny funnel has to tell those causes apart. Nothing
|
|
269
|
+
// later in the loop can succeed either: the budget is gone for all three
|
|
270
|
+
// (pre-ship review 2026-09-05).
|
|
271
|
+
if (res.budgetExhausted) return { status: 'unavailable' };
|
|
233
272
|
// A symbol that did not resolve is SKIPPED, not fatal — exit 1 included,
|
|
234
273
|
// which is why this asks for `exitOneIsNoHits: false` and then treats
|
|
235
274
|
// every non-`ok` verdict the same way.
|
|
@@ -19,7 +19,87 @@
|
|
|
19
19
|
//
|
|
20
20
|
// EPIPE is silent by design. It means the consumer closed the pipe — Claude
|
|
21
21
|
// Code moved on, or a `| head` upstream exited. There is nobody left to tell.
|
|
22
|
+
// The budget Claude Code kills each hook at, in SECONDS — the single source for
|
|
23
|
+
// both halves of that contract: `lifecycle.js` registers these numbers into
|
|
24
|
+
// settings.json, and `remainingMs` below spends against them. `hooks.test.js`
|
|
25
|
+
// pins both registration sites to this table so a bump in one cannot drift from
|
|
26
|
+
// the other.
|
|
27
|
+
//
|
|
28
|
+
// `session-init.js` is here for its REGISTRATION only. It is registered from
|
|
29
|
+
// `claude-plugin/hooks/hooks.json` (SessionStart is the one event Claude Code
|
|
30
|
+
// loads from plugin-cache), it predates `installHookFailOpen` and wraps its own
|
|
31
|
+
// main in a try/catch, so nothing arms a deadline for it and none of its six
|
|
32
|
+
// child spawns clamp — 15.5 s of serial timeouts against the 5 s below. That is
|
|
33
|
+
// the largest overrun of the seven and it is NOT fixed here; pre-ship review
|
|
34
|
+
// 2026-09-05 finding 1, carried in the audit report as NEW-05. Wiring it means
|
|
35
|
+
// deciding, per child, whether an out-of-budget SessionStart should skip a
|
|
36
|
+
// binary health check or a quarantine probe, which is not a mechanical change.
|
|
37
|
+
const HOOK_TIMEOUT_SECONDS = {
|
|
38
|
+
'pre-edit-guide.js': 4,
|
|
39
|
+
'pre-grep-guide.js': 3,
|
|
40
|
+
'pre-read-guide.js': 3,
|
|
41
|
+
'incremental-index.js': 10,
|
|
42
|
+
'post-grep-inject.js': 5,
|
|
43
|
+
'user-prompt-context.js': 5,
|
|
44
|
+
'session-init.js': 5,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Left for the hook to render its answer and exit after the last child returns.
|
|
48
|
+
const WRITE_RESERVE_MS = 400;
|
|
49
|
+
|
|
50
|
+
// Wall-clock instant this process must be finished by, or null when nothing
|
|
51
|
+
// armed one (a hook `require`d by a test, or an unlisted script).
|
|
52
|
+
let deadlineAt = null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Arm the process deadline from the registered budget of `script`.
|
|
56
|
+
*
|
|
57
|
+
* The hooks' internal timeouts were each sized in isolation and run in SERIES:
|
|
58
|
+
* pre-edit-guide alone could spend 5 × 2000 ms of candidate greps plus 2500 ms
|
|
59
|
+
* of impact against a 4 s budget, and post-grep-inject looped callgraph over
|
|
60
|
+
* every symbol in the pattern before its show/grep fallbacks — 2–3× the budget
|
|
61
|
+
* either way. Nothing enforced the sum, so a binary wedged on `index.lock` got
|
|
62
|
+
* the hook killed by Claude Code, which surfaces to the user as a hook error on
|
|
63
|
+
* THEIR tool call (audit 2026-09-05 JS-03).
|
|
64
|
+
*
|
|
65
|
+
* `process.uptime()` is subtracted because the budget starts when Claude Code
|
|
66
|
+
* spawns us, not when this line runs: cold node startup is real time already
|
|
67
|
+
* spent, and on a loaded machine it is hundreds of milliseconds.
|
|
68
|
+
*/
|
|
69
|
+
function armHookDeadline(script) {
|
|
70
|
+
const seconds = HOOK_TIMEOUT_SECONDS[script];
|
|
71
|
+
if (!seconds) return;
|
|
72
|
+
deadlineAt = Math.floor(Date.now() + seconds * 1000 - process.uptime() * 1000 - WRITE_RESERVE_MS);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* How long a child may run: `defaultMs`, or whatever is left of the budget.
|
|
77
|
+
*
|
|
78
|
+
* Returns `null` when there is nothing left — meaning DO NOT RUN, not "run
|
|
79
|
+
* unbounded". Callers must branch on it: node reads `timeout: 0` as no timeout
|
|
80
|
+
* at all, so a numeric zero here would turn the last child into the unbounded
|
|
81
|
+
* one, which is the exact failure this exists to prevent.
|
|
82
|
+
*
|
|
83
|
+
* Always an INTEGER. `child_process` validates `timeout` with
|
|
84
|
+
* `validateTimeout` and throws `ERR_OUT_OF_RANGE` on a fraction — and the one
|
|
85
|
+
* fractional term here (`process.uptime()`) made every spawn throw, which the
|
|
86
|
+
* runners' own try/catch turned into a silent `unavailable`: the hooks stopped
|
|
87
|
+
* answering and still exited 0. Caught by pre-grep-guide's e2e suite.
|
|
88
|
+
*/
|
|
89
|
+
function remainingMs(defaultMs) {
|
|
90
|
+
if (deadlineAt === null) return Math.floor(defaultMs);
|
|
91
|
+
const left = deadlineAt - Date.now();
|
|
92
|
+
if (left <= 0) return null;
|
|
93
|
+
return Math.floor(Math.min(defaultMs, left));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Test seam: drop any armed deadline so one test file can't leak into another. */
|
|
97
|
+
function resetHookDeadline() {
|
|
98
|
+
deadlineAt = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
22
101
|
function installHookFailOpen(label) {
|
|
102
|
+
armHookDeadline(require('path').basename(process.argv[1] || ''));
|
|
23
103
|
const bail = (err) => {
|
|
24
104
|
const code = (err && err.code) || (err && err.name) || 'Error';
|
|
25
105
|
if (code !== 'EPIPE') {
|
|
@@ -46,4 +126,10 @@ function installHookFailOpen(label) {
|
|
|
46
126
|
process.on('unhandledRejection', bail);
|
|
47
127
|
}
|
|
48
128
|
|
|
49
|
-
module.exports = {
|
|
129
|
+
module.exports = {
|
|
130
|
+
installHookFailOpen,
|
|
131
|
+
HOOK_TIMEOUT_SECONDS,
|
|
132
|
+
armHookDeadline,
|
|
133
|
+
remainingMs,
|
|
134
|
+
resetHookDeadline,
|
|
135
|
+
};
|
|
@@ -142,6 +142,41 @@ function readJson(filePath) {
|
|
|
142
142
|
/** How many `.corrupt-*` copies of one file to keep. */
|
|
143
143
|
const MAX_CORRUPT_BACKUPS = 5;
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Owner-only. What every file this module creates under `~/.claude` gets when
|
|
147
|
+
* there is no prior file whose bits to preserve, and what a `.corrupt-*` copy
|
|
148
|
+
* gets unconditionally: settings.json routinely carries an `env` block with API
|
|
149
|
+
* keys, and a backup of one is the same secret in a second file (audit
|
|
150
|
+
* 2026-09-05 JS-01).
|
|
151
|
+
*/
|
|
152
|
+
const SECRET_FILE_MODE = 0o600;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Pin `target` at exactly `mode`, because neither route that creates our files
|
|
156
|
+
* does it on its own: `writeFileSync`'s `mode` option goes to `open(O_CREAT)`
|
|
157
|
+
* and is masked by umask (and ignored outright when the file already exists),
|
|
158
|
+
* and `copyFileSync` carries the SOURCE's bits.
|
|
159
|
+
*
|
|
160
|
+
* Reported, not swallowed: the whole point of the call is that the file may
|
|
161
|
+
* hold a key, so a permission we could not set is exactly the thing the user
|
|
162
|
+
* needs to hear about. The write itself has already succeeded — a failure here
|
|
163
|
+
* is a disclosure, not a reason to unwind it.
|
|
164
|
+
*/
|
|
165
|
+
function restrictMode(target, mode) {
|
|
166
|
+
try {
|
|
167
|
+
fs.chmodSync(target, mode);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// No return value: both call sites act on the stderr line, not on a bool,
|
|
170
|
+
// and a discarded success flag is an invitation to branch on it later
|
|
171
|
+
// without noticing that nothing ever did (pre-ship review 2026-09-05).
|
|
172
|
+
console.error(
|
|
173
|
+
`[code-graph] wrote ${target} but could not set its permissions to ` +
|
|
174
|
+
`0${mode.toString(8)} (${err.code || err.name}). If it contains an API key, ` +
|
|
175
|
+
`it may be readable by other users on this machine.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
145
180
|
/**
|
|
146
181
|
* Delete all but the newest `MAX_CORRUPT_BACKUPS` copies of `filePath`.
|
|
147
182
|
*
|
|
@@ -180,9 +215,14 @@ function backupCorruptFile(filePath, raw) {
|
|
|
180
215
|
const dest = `${filePath}.corrupt-${stamp}`;
|
|
181
216
|
try {
|
|
182
217
|
// Buffer, not string: see readJsonResult. A string here would re-encode.
|
|
183
|
-
if (Buffer.isBuffer(raw)) fs.writeFileSync(dest, raw);
|
|
184
|
-
else if (typeof raw === 'string')
|
|
185
|
-
|
|
218
|
+
if (Buffer.isBuffer(raw)) fs.writeFileSync(dest, raw, { mode: SECRET_FILE_MODE });
|
|
219
|
+
else if (typeof raw === 'string') {
|
|
220
|
+
fs.writeFileSync(dest, Buffer.from(raw, 'utf8'), { mode: SECRET_FILE_MODE });
|
|
221
|
+
} else fs.copyFileSync(filePath, dest);
|
|
222
|
+
// Owner-only regardless of what the original was: up to MAX_CORRUPT_BACKUPS
|
|
223
|
+
// of these accumulate beside settings.json with its `env` block copied
|
|
224
|
+
// verbatim, so a 0644 original must not mint 0644 duplicates.
|
|
225
|
+
restrictMode(dest, SECRET_FILE_MODE);
|
|
186
226
|
pruneCorruptBackups(filePath);
|
|
187
227
|
return dest;
|
|
188
228
|
} catch {
|
|
@@ -261,11 +301,23 @@ function readSettingsForWrite(pre) {
|
|
|
261
301
|
return { settings: {}, backedUpTo: backup };
|
|
262
302
|
}
|
|
263
303
|
|
|
304
|
+
// Replace-by-rename loses the permission bits unless they are carried over
|
|
305
|
+
// deliberately: the new inode is the TMP file's, created under our umask, so a
|
|
306
|
+
// settings.json the user had put at 0600 came back 0644 on the first
|
|
307
|
+
// install()/update()/cleanupDisabledStatusline() — with its `env` API keys in
|
|
308
|
+
// it, silently, on a file we were only meant to add two hook entries to (audit
|
|
309
|
+
// 2026-09-05 JS-01). Stat the original and restore its exact mode; when there
|
|
310
|
+
// is no original, create owner-only rather than at whatever umask says.
|
|
264
311
|
function writeJsonAtomic(filePath, data) {
|
|
265
312
|
const dir = path.dirname(filePath);
|
|
266
313
|
fs.mkdirSync(dir, { recursive: true });
|
|
314
|
+
let mode = null;
|
|
315
|
+
try { mode = fs.statSync(filePath).mode & 0o777; } catch { /* new file */ }
|
|
267
316
|
const tmp = filePath + '.tmp.' + process.pid;
|
|
268
|
-
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n');
|
|
317
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: mode ?? SECRET_FILE_MODE });
|
|
318
|
+
// umask can only have cleared bits the original had, so chmod is what makes
|
|
319
|
+
// this preservation rather than tightening.
|
|
320
|
+
restrictMode(tmp, mode ?? SECRET_FILE_MODE);
|
|
269
321
|
fs.renameSync(tmp, filePath);
|
|
270
322
|
}
|
|
271
323
|
|
|
@@ -907,7 +959,13 @@ function removeHooksFromSettings(settings) {
|
|
|
907
959
|
|
|
908
960
|
function buildSettingsHookEntries() {
|
|
909
961
|
const root = PLUGIN_ROOT;
|
|
910
|
-
|
|
962
|
+
// The budget is read from the table the hooks THEMSELVES spend against
|
|
963
|
+
// (hook-fail-open.js), not written twice: a number registered here that the
|
|
964
|
+
// hook does not know about is the JS-03 shape — the hook overruns a limit it
|
|
965
|
+
// cannot see and Claude Code kills it mid-tool-call.
|
|
966
|
+
const { HOOK_TIMEOUT_SECONDS } = require('./hook-fail-open');
|
|
967
|
+
const scriptCmd = (name) => {
|
|
968
|
+
const timeout = HOOK_TIMEOUT_SECONDS[name];
|
|
911
969
|
const script = path.join(root, 'scripts', name);
|
|
912
970
|
// POSIX: existence-guarded. After `/plugin uninstall`, CC may delete the
|
|
913
971
|
// plugin-cache dir before our statusline teardown gets to strip these
|
|
@@ -924,16 +982,16 @@ function buildSettingsHookEntries() {
|
|
|
924
982
|
|
|
925
983
|
return {
|
|
926
984
|
PreToolUse: [
|
|
927
|
-
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Edit', hooks: [scriptCmd('pre-edit-guide.js'
|
|
928
|
-
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Bash', hooks: [scriptCmd('pre-grep-guide.js'
|
|
929
|
-
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Read', hooks: [scriptCmd('pre-read-guide.js'
|
|
985
|
+
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Edit', hooks: [scriptCmd('pre-edit-guide.js')] },
|
|
986
|
+
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Bash', hooks: [scriptCmd('pre-grep-guide.js')] },
|
|
987
|
+
{ description: SETTINGS_HOOK_DESC.preToolUse, matcher: 'Read', hooks: [scriptCmd('pre-read-guide.js')] },
|
|
930
988
|
],
|
|
931
989
|
PostToolUse: [
|
|
932
|
-
{ description: SETTINGS_HOOK_DESC.postToolUseEdit, matcher: 'Write|Edit', hooks: [scriptCmd('incremental-index.js'
|
|
933
|
-
{ description: SETTINGS_HOOK_DESC.postToolUseInject, matcher: 'Bash', hooks: [scriptCmd('post-grep-inject.js'
|
|
990
|
+
{ description: SETTINGS_HOOK_DESC.postToolUseEdit, matcher: 'Write|Edit', hooks: [scriptCmd('incremental-index.js')] },
|
|
991
|
+
{ description: SETTINGS_HOOK_DESC.postToolUseInject, matcher: 'Bash', hooks: [scriptCmd('post-grep-inject.js')] },
|
|
934
992
|
],
|
|
935
993
|
UserPromptSubmit: [
|
|
936
|
-
{ description: SETTINGS_HOOK_DESC.userPromptSubmit, matcher: '', hooks: [scriptCmd('user-prompt-context.js'
|
|
994
|
+
{ description: SETTINGS_HOOK_DESC.userPromptSubmit, matcher: '', hooks: [scriptCmd('user-prompt-context.js')] },
|
|
937
995
|
],
|
|
938
996
|
};
|
|
939
997
|
}
|
|
@@ -24,6 +24,7 @@ const { recordRecommendation } = require('./recommendation-log');
|
|
|
24
24
|
const { formatCoveringTests } = require('./covering-tests');
|
|
25
25
|
const { emitPreToolContext } = require('./hook-emit');
|
|
26
26
|
const { hidden } = require('./proc-opts');
|
|
27
|
+
const { remainingMs } = require('./hook-fail-open');
|
|
27
28
|
|
|
28
29
|
// v0.49 — walk up from the shell cwd (subdir-cwd fix). The per-cwd index.db
|
|
29
30
|
// gate kept this hook dark for entire sessions after `cd backend/` — daagu
|
|
@@ -114,10 +115,17 @@ if (!symbol || symbol.length < 3) {
|
|
|
114
115
|
const candidates = [...new Set(identifiers)]
|
|
115
116
|
.filter(id => !skipWords.has(id.toLowerCase()))
|
|
116
117
|
.sort((a, b) => b.length - a.length);
|
|
117
|
-
|
|
118
|
+
// Two candidates, not five. They are sorted most-specific-first, so the
|
|
119
|
+
// 3rd–5th were the least likely to resolve AND the ones that pushed this
|
|
120
|
+
// loop past the hook's whole 4 s budget (5 × 2000 ms here + 2500 ms of
|
|
121
|
+
// impact below); the impact query they starve is the hook's actual output
|
|
122
|
+
// (audit 2026-09-05 JS-03).
|
|
123
|
+
for (const candidate of candidates.slice(0, 2)) {
|
|
124
|
+
const budget = remainingMs(2000);
|
|
125
|
+
if (budget === null) break;
|
|
118
126
|
try {
|
|
119
127
|
const raw = execFileSync(binary, ['grep', candidate, filePath, '--json'], hidden({
|
|
120
|
-
cwd, timeout:
|
|
128
|
+
cwd, timeout: budget, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
121
129
|
env: internalEnv,
|
|
122
130
|
}));
|
|
123
131
|
const grepResult = JSON.parse(raw);
|
|
@@ -170,6 +178,11 @@ try {
|
|
|
170
178
|
const editedFile = (input.tool_input && input.tool_input.file_path) || '';
|
|
171
179
|
const relFile = editedFile ? path.relative(cwd, editedFile) : '';
|
|
172
180
|
let jsonResult;
|
|
181
|
+
// Whatever is left of the registered 4 s, capped at this call's own 2500 ms.
|
|
182
|
+
// `null` = the candidate loop above already spent the budget; running anyway is
|
|
183
|
+
// what got the hook killed by Claude Code mid-Edit (audit 2026-09-05 JS-03).
|
|
184
|
+
const impactBudget = remainingMs(2500);
|
|
185
|
+
if (impactBudget === null) process.exit(0);
|
|
173
186
|
try {
|
|
174
187
|
const args = ['impact', symbol, '--json'];
|
|
175
188
|
if (relFile && !relFile.startsWith('..')) args.push('--file', relFile);
|
|
@@ -177,7 +190,7 @@ try {
|
|
|
177
190
|
// diverging from the findBinary() result the rest of the hook trusts).
|
|
178
191
|
const raw = execFileSync(binary, args, hidden({
|
|
179
192
|
cwd,
|
|
180
|
-
timeout:
|
|
193
|
+
timeout: impactBudget,
|
|
181
194
|
encoding: 'utf8',
|
|
182
195
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
183
196
|
env: internalEnv,
|
|
@@ -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.135.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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.135.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.135.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.135.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.135.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.135.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.135.0"
|
|
43
43
|
}
|
|
44
44
|
}
|