@sdsrs/code-graph 0.136.0 → 0.138.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/README.md +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/cg-answer.js +7 -1
- package/claude-plugin/scripts/post-grep-inject.js +7 -0
- package/claude-plugin/scripts/pre-read-guide.js +8 -0
- package/claude-plugin/scripts/statusline-composite.js +59 -1
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -536,6 +536,7 @@ defaults are what you get by doing nothing.
|
|
|
536
536
|
| `CODE_GRAPH_HOOK_INDEX=on\|off` | Force the incremental-index hook on or off instead of letting it decide. |
|
|
537
537
|
| `CODE_GRAPH_MODEL_DIR=<dir>` | Load the embedding model from here (air-gapped installs). |
|
|
538
538
|
| `CODE_GRAPH_DISABLE_MODEL_DOWNLOAD=1` | Never fetch the model; fail instead. |
|
|
539
|
+
| `CODE_GRAPH_REQUIRE_MODEL=1` | Test-only: make the embedding tests FAIL when no model can be loaded instead of self-skipping. Set by the release gate and the cache-warm cron, which supply pinned weights — without it those tests pass by doing nothing on any runner with no cached model. |
|
|
539
540
|
| `CODE_GRAPH_MAX_FILE_SIZE=<bytes>` | Skip files larger than this (default 1 MiB). |
|
|
540
541
|
| `CODE_GRAPH_MAX_CODE_LEN=<bytes>` | Truncate stored per-node source at this length. |
|
|
541
542
|
| `CODE_GRAPH_PARSE_TIMEOUT_MS=<ms>` | Per-file parse timeout. |
|
|
@@ -589,6 +590,7 @@ plugins' entries. An installer that reads exit codes must not treat it as
|
|
|
589
590
|
| Variable | Set by | Purpose |
|
|
590
591
|
|---|---|---|
|
|
591
592
|
| `CODE_GRAPH_STATUSLINE_CWD` | statusline-composite | Forwards Claude Code's authoritative cwd to each statusline provider. |
|
|
593
|
+
| `CODE_GRAPH_STATUSLINE_DEBUG` | test suite | Makes statusline-composite print why it dropped a provider instead of swallowing the error. Off by default so a broken third-party provider stays silent in a real statusline. |
|
|
592
594
|
| `CODE_GRAPH_INSTALL_LOCK_HELD` | launcher | Tells a child that the parent already holds the install lock, so it does not deadlock. |
|
|
593
595
|
| `CODE_GRAPH_AUTO_UPDATE_SILENT` | session-init | Runs the update check without console output. |
|
|
594
596
|
| `CODE_GRAPH_NO_ANSWER_IN_DENY` | lifecycle | Keeps `cg-answer` out of the deny path. |
|
|
@@ -336,7 +336,13 @@ function runOverviewAnswer(opts = {}) {
|
|
|
336
336
|
// green against it.
|
|
337
337
|
const verdict = classifyRun(res, { exitOneIsNoHits: false });
|
|
338
338
|
if (verdict !== 'ok') {
|
|
339
|
-
|
|
339
|
+
// Same arm as the three sibling runners: an exhausted hook budget reaches
|
|
340
|
+
// `classifyRun` as a synthetic timeout, so without this it is reported as
|
|
341
|
+
// a bare `unavailable` — indistinguishable from a wedged or broken binary.
|
|
342
|
+
// This runner is the read-fanout hint's only answer path, and its budget is
|
|
343
|
+
// the one most often already spent (pre-read runs after a SessionStart that
|
|
344
|
+
// already burned six children), so it is the worst place to lose the cause.
|
|
345
|
+
return { status: verdict, ...(res.budgetExhausted ? { reason: 'budget' } : {}) };
|
|
340
346
|
}
|
|
341
347
|
const out = (res.stdout || '').trim();
|
|
342
348
|
if (isEmptyAnswer(out)) return { status: 'no-hits' };
|
|
@@ -316,6 +316,13 @@ function runMain() {
|
|
|
316
316
|
...(pattern ? { pattern } : {}),
|
|
317
317
|
fallthrough: answer.status,
|
|
318
318
|
reason: answer.status,
|
|
319
|
+
// Sub-divides `unavailable` without touching it: a starved hook and a
|
|
320
|
+
// wedged binary arrive under the same word and only one of them means
|
|
321
|
+
// something is broken. It cannot ride in `reason` — src/cli/usage.rs:448
|
|
322
|
+
// scores `reason:"unavailable"` as an inconclusive follow-up, so a new
|
|
323
|
+
// value there would silently re-file every budget skip as "the inline
|
|
324
|
+
// answer was insufficient". Same field pre-grep-guide.js:790 uses.
|
|
325
|
+
...(answer.reason ? { fallthrough_reason: answer.reason } : {}),
|
|
319
326
|
// Attribute the skip to the mode that burned the attempt — the LAST mode
|
|
320
327
|
// tried, so a callgraph miss that fell through to grep is charged to grep,
|
|
321
328
|
// matching the fallback order above. Without this the aggregator files
|
|
@@ -192,7 +192,15 @@ function trackReadAndMaybeHint(root, rel, now = Date.now()) {
|
|
|
192
192
|
// 'no-binary' (delivered overview dark — binary missing) vs 'unavailable'
|
|
193
193
|
// (binary ran but failed/timed out) vs 'no-hits'. Mirrors pre-grep-guide
|
|
194
194
|
// so the read-fanout funnel can tell a dark flagship apart from no result.
|
|
195
|
+
//
|
|
196
|
+
// `fallthrough_reason` sub-divides 'unavailable' rather than replacing it:
|
|
197
|
+
// a spent hook budget and a wedged binary are the same word here, and only
|
|
198
|
+
// the former is the healthy-hook-answers-nothing case NEW-08 traded a kill
|
|
199
|
+
// for. It cannot go in `reason` — src/cli/usage.rs:448 scores
|
|
200
|
+
// `reason:"unavailable"` as an inconclusive follow-up, so a new value there
|
|
201
|
+
// would re-file every budget skip as "the answer was insufficient".
|
|
195
202
|
...(answered ? {} : { reason: answer.status }),
|
|
203
|
+
...(answered || !answer.reason ? {} : { fallthrough_reason: answer.reason }),
|
|
196
204
|
});
|
|
197
205
|
// Compound-grep sibling sweep: emit via the PreToolUse allow+additionalContext
|
|
198
206
|
// envelope (shared hook-emit.js). Bare stdout on a PreToolUse exit-0 lands in
|
|
@@ -140,7 +140,65 @@ function runProvider(command, needsStdin, stdin, id) {
|
|
|
140
140
|
})).toString().trim();
|
|
141
141
|
|
|
142
142
|
return out || null;
|
|
143
|
-
} catch {
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// EPIPE is OUR failure to deliver, not the provider's failure to answer.
|
|
145
|
+
// `execFileSync` writes `input` into the child's stdin; a provider that
|
|
146
|
+
// exits without reading it (a static `echo`, an env-var-only line) makes
|
|
147
|
+
// that write fail — and by then the child has already produced its output
|
|
148
|
+
// in full. Discarding it drops the user's original statusline for a reason
|
|
149
|
+
// that has nothing to do with the provider. `_previous` is registered with
|
|
150
|
+
// needsStdin unconditionally true (lifecycle.js), so every such user is
|
|
151
|
+
// exposed. Measured: 10/10 EPIPE with a payload past the pipe buffer and
|
|
152
|
+
// the child's stdout complete in 10 of 10; on ordinary payloads the same
|
|
153
|
+
// discard happens as a timing race, which is what made
|
|
154
|
+
// lifecycle.e2e.test.js's "issue #24" intermittent for two releases.
|
|
155
|
+
// An empty stdout still falls through: nothing was produced, nothing to keep.
|
|
156
|
+
// Gated on a CLEAN exit. A provider that genuinely failed can EPIPE too, and
|
|
157
|
+
// its stdout is then partial by definition. Measured: a `set -e` script that
|
|
158
|
+
// prints one segment and exits 1 salvages as "SEGMENT-ONE |" — half a line,
|
|
159
|
+
// rendered as if whole, and rendering differently run to run depending on
|
|
160
|
+
// which side of the race it lands. `err.status` is 0 in the case this exists
|
|
161
|
+
// for (pre-ship delta review 2026-09-06).
|
|
162
|
+
//
|
|
163
|
+
// `status === 0`, not `!status`: a signal-killed child reports `status:
|
|
164
|
+
// null`, which `!status` would wave through — and that is a live path, not a
|
|
165
|
+
// defensive one. A provider that prints and then takes a signal while we
|
|
166
|
+
// still have stdin to write lands HERE, with `code: 'EPIPE'`, `status: null`
|
|
167
|
+
// and half a line; measured 6 runs of 6 across SIGTERM and SIGKILL. Only our
|
|
168
|
+
// OWN 3 s timeout misses this branch, surfacing as ETIMEDOUT instead.
|
|
169
|
+
// Measured set: clean → EPIPE / 0 / whole line; `exit 1` → EPIPE / 1 / half;
|
|
170
|
+
// self-signal → EPIPE / null / half; our timeout → ETIMEDOUT / null / half.
|
|
171
|
+
const salvaged =
|
|
172
|
+
err && err.code === 'EPIPE' && err.status === 0
|
|
173
|
+
? (err.stdout || '').toString().trim()
|
|
174
|
+
: '';
|
|
175
|
+
// Swallowing is correct in production and stays the default: a third-party
|
|
176
|
+
// provider that throws must not take the user's whole statusline with it.
|
|
177
|
+
// But a swallowed error is also why `lifecycle.e2e.test.js`'s "issue #24"
|
|
178
|
+
// has gone intermittently red with nothing to go on — a dropped provider and
|
|
179
|
+
// a provider that printed nothing are the same observation from outside, so
|
|
180
|
+
// every diagnosis of it so far has been a guess. Opt-in, off unless the env
|
|
181
|
+
// var is set, so no shipped behavior changes.
|
|
182
|
+
if (process.env.CODE_GRAPH_STATUSLINE_DEBUG) {
|
|
183
|
+
// Its own try/catch. On Linux this is belt-and-braces rather than
|
|
184
|
+
// load-bearing: a write to a closed stderr does NOT throw synchronously —
|
|
185
|
+
// measured both shapes, `fs.closeSync(2)` is a silent no-op and a piped
|
|
186
|
+
// reader that exits surfaces one tick later as an uncaughtException, which
|
|
187
|
+
// no try/catch here can see (hook-fail-open.js already handles that path
|
|
188
|
+
// and skips its own stderr write on EPIPE). It earns its place on Windows,
|
|
189
|
+
// where piped stderr writes are synchronous, and it costs nothing: this
|
|
190
|
+
// sits inside the catch that keeps a bad provider from taking the whole
|
|
191
|
+
// statusline down, so a debug switch must not be the thing that does.
|
|
192
|
+
try {
|
|
193
|
+
const why = err && (err.code || err.message) ? (err.code || err.message) : String(err);
|
|
194
|
+
// Report BOTH outcomes. Returning early on a salvage would make the
|
|
195
|
+
// channel silent on the exact failure it was built to diagnose.
|
|
196
|
+
const verb = salvaged ? 'recovered after' : 'dropped';
|
|
197
|
+
process.stderr.write(`[statusline] provider '${id}' ${verb}: ${why}\n`);
|
|
198
|
+
} catch { /* diagnostics are best-effort; rendering is not */ }
|
|
199
|
+
}
|
|
200
|
+
return salvaged || null;
|
|
201
|
+
}
|
|
144
202
|
}
|
|
145
203
|
|
|
146
204
|
// Extract Claude Code's current working directory from the stdin JSON context.
|
|
@@ -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.138.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.138.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.138.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.138.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.138.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.138.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.138.0"
|
|
43
43
|
}
|
|
44
44
|
}
|