@sdsrs/code-graph 0.77.1 → 0.78.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/statusline-composite.js +10 -5
- package/claude-plugin/scripts/statusline-composite.test.js +20 -5
- package/claude-plugin/scripts/statusline.js +2 -2
- package/claude-plugin/scripts/statusline.test.js +6 -6
- package/package.json +6 -6
|
@@ -72,11 +72,12 @@ function runProvider(command, needsStdin, stdin) {
|
|
|
72
72
|
const argv = parts.map(expandTilde);
|
|
73
73
|
|
|
74
74
|
// Forward Claude Code's authoritative current dir (from the stdin payload) as
|
|
75
|
-
//
|
|
76
|
-
// process.cwd(), which need not track the session's working dir. Harmless
|
|
77
|
-
// `_previous`/third-party providers, which ignore the unknown var.
|
|
75
|
+
// a plugin-scoped env var. The code-graph provider gates on it instead of its
|
|
76
|
+
// own process.cwd(), which need not track the session's working dir. Harmless
|
|
77
|
+
// to `_previous`/third-party providers, which ignore the unknown var. The
|
|
78
|
+
// CODE_GRAPH_ prefix (not CLAUDE_) keeps it out of Claude Code's own namespace.
|
|
78
79
|
const cwd = cwdFromStdin(stdin);
|
|
79
|
-
const env = cwd ? { ...process.env,
|
|
80
|
+
const env = cwd ? { ...process.env, CODE_GRAPH_STATUSLINE_CWD: cwd } : process.env;
|
|
80
81
|
|
|
81
82
|
const out = execFileSync(argv[0], argv.slice(1), {
|
|
82
83
|
timeout: 3000,
|
|
@@ -93,11 +94,15 @@ function runProvider(command, needsStdin, stdin) {
|
|
|
93
94
|
// Prefer the top-level `cwd`, then `workspace.current_dir`; both track the
|
|
94
95
|
// session's working dir (after the model runs `cd`). Returns null for empty,
|
|
95
96
|
// non-JSON, or cwd-less payloads (e.g. the stdin-timeout fallback passes '').
|
|
97
|
+
// Only a non-empty STRING is accepted: a malformed `cwd` (number/object) would
|
|
98
|
+
// otherwise be coerced to a bogus env path that resolves nowhere and silently
|
|
99
|
+
// blanks the segment — null keeps the gate on the safe process.cwd() fallback.
|
|
96
100
|
function cwdFromStdin(stdin) {
|
|
97
101
|
if (!stdin) return null;
|
|
98
102
|
try {
|
|
99
103
|
const ctx = JSON.parse(stdin);
|
|
100
|
-
|
|
104
|
+
const v = ctx && (ctx.cwd || (ctx.workspace && ctx.workspace.current_dir));
|
|
105
|
+
return typeof v === 'string' && v ? v : null;
|
|
101
106
|
} catch { return null; }
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// JSON context on stdin and fans out to each provider. This pins the cwd bridge:
|
|
4
4
|
// the code-graph provider keys its gate on process.cwd(), but Claude Code may
|
|
5
5
|
// spawn the statusline from a cwd unrelated to the session. The composite must
|
|
6
|
-
// extract the authoritative cwd from stdin and forward it (
|
|
6
|
+
// extract the authoritative cwd from stdin and forward it (CODE_GRAPH_STATUSLINE_CWD)
|
|
7
7
|
// so the provider resolves the right project regardless of the spawn's cwd.
|
|
8
8
|
const test = require('node:test');
|
|
9
9
|
const assert = require('node:assert/strict');
|
|
@@ -31,20 +31,35 @@ test('cwdFromStdin returns null for empty / non-JSON / cwd-less payloads', () =>
|
|
|
31
31
|
assert.equal(cwdFromStdin('{"workspace":{}}'), null);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test('
|
|
34
|
+
test('cwdFromStdin returns null for a non-string cwd (no bogus env path)', () => {
|
|
35
|
+
// A malformed payload must not coerce a number/object into an env path that
|
|
36
|
+
// resolves to nowhere and silently blanks the segment. Only a real string wins.
|
|
37
|
+
assert.equal(cwdFromStdin('{"cwd":123}'), null);
|
|
38
|
+
assert.equal(cwdFromStdin('{"cwd":{"x":1}}'), null);
|
|
39
|
+
assert.equal(cwdFromStdin('{"cwd":""}'), null);
|
|
40
|
+
assert.equal(cwdFromStdin('{"workspace":{"current_dir":42}}'), null);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('runProvider forwards the stdin cwd to the provider as CODE_GRAPH_STATUSLINE_CWD', (t) => {
|
|
35
44
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-composite-'));
|
|
36
45
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
37
46
|
const fixture = path.join(dir, 'echo-cwd.js');
|
|
38
|
-
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.
|
|
47
|
+
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.CODE_GRAPH_STATUSLINE_CWD||'NONE'));");
|
|
39
48
|
const out = runProvider(`node ${JSON.stringify(fixture)}`, false, '{"cwd":"/x/y"}');
|
|
40
49
|
assert.equal(out, 'CWD=/x/y');
|
|
41
50
|
});
|
|
42
51
|
|
|
43
|
-
test('runProvider leaves
|
|
52
|
+
test('runProvider leaves CODE_GRAPH_STATUSLINE_CWD unset when stdin carries no cwd', (t) => {
|
|
53
|
+
// Hermetic against an ambient var: with no stdin cwd, runProvider passes
|
|
54
|
+
// process.env through unchanged, so a value inherited by the test runner would
|
|
55
|
+
// leak into the child. Clear it for this case, restore after.
|
|
56
|
+
const saved = process.env.CODE_GRAPH_STATUSLINE_CWD;
|
|
57
|
+
delete process.env.CODE_GRAPH_STATUSLINE_CWD;
|
|
58
|
+
t.after(() => { if (saved !== undefined) process.env.CODE_GRAPH_STATUSLINE_CWD = saved; });
|
|
44
59
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-composite-'));
|
|
45
60
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
46
61
|
const fixture = path.join(dir, 'echo-cwd.js');
|
|
47
|
-
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.
|
|
62
|
+
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.CODE_GRAPH_STATUSLINE_CWD||'NONE'));");
|
|
48
63
|
const out = runProvider(`node ${JSON.stringify(fixture)}`, false, '');
|
|
49
64
|
assert.equal(out, 'CWD=NONE');
|
|
50
65
|
});
|
|
@@ -34,13 +34,13 @@ if (disabledCleanup.cleaned) process.exit(0);
|
|
|
34
34
|
// with no local index, showed nothing at all. The resolver skips stray nested
|
|
35
35
|
// indexes, so the statusline tracks one DB — the project root — from any subdir.
|
|
36
36
|
//
|
|
37
|
-
// Start from Claude Code's AUTHORITATIVE current dir (
|
|
37
|
+
// Start from Claude Code's AUTHORITATIVE current dir (CODE_GRAPH_STATUSLINE_CWD,
|
|
38
38
|
// forwarded by the composite from its stdin payload) rather than process.cwd().
|
|
39
39
|
// The spawned statusline's process.cwd() is an implementation detail of how
|
|
40
40
|
// Claude Code launches the command and need not track the session's working dir;
|
|
41
41
|
// the stdin `cwd` always does. Fall back to process.cwd() when unset (direct
|
|
42
42
|
// invocation, tests).
|
|
43
|
-
const startDir = process.env.
|
|
43
|
+
const startDir = process.env.CODE_GRAPH_STATUSLINE_CWD || process.cwd();
|
|
44
44
|
const root = resolveProjectRoot(startDir);
|
|
45
45
|
if (!root) {
|
|
46
46
|
process.exit(0);
|
|
@@ -70,7 +70,7 @@ function runStatusline(home, projectDir) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Run statusline.js from an arbitrary process.cwd() with extra env vars. Used to
|
|
73
|
-
// prove the gate keys on Claude Code's authoritative cwd (
|
|
73
|
+
// prove the gate keys on Claude Code's authoritative cwd (CODE_GRAPH_STATUSLINE_CWD,
|
|
74
74
|
// forwarded by the composite from the stdin payload), NOT the spawn's cwd.
|
|
75
75
|
function runStatuslineIn(home, processCwd, extraEnv) {
|
|
76
76
|
return execFileSync('node', [STATUSLINE], {
|
|
@@ -176,12 +176,12 @@ test('version-stale index shows rebuilding marker', (t) => {
|
|
|
176
176
|
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files | ↻ rebuilding');
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
//
|
|
179
|
+
// CODE_GRAPH_STATUSLINE_CWD is Claude Code's authoritative current dir, forwarded by
|
|
180
180
|
// the composite from its stdin payload. The gate must trust it over process.cwd():
|
|
181
181
|
// Claude Code may spawn the statusline from a cwd unrelated to the session (the
|
|
182
182
|
// classic regression — the segment vanished when the shell sat in a subdir whose
|
|
183
183
|
// process.cwd() didn't resolve to the project root).
|
|
184
|
-
test('
|
|
184
|
+
test('CODE_GRAPH_STATUSLINE_CWD overrides process.cwd() for the gate', (t) => {
|
|
185
185
|
const home = mkHome(t);
|
|
186
186
|
const project = mkProject(home);
|
|
187
187
|
installStubBinary(home, {
|
|
@@ -190,11 +190,11 @@ test('CLAUDE_STATUSLINE_CWD overrides process.cwd() for the gate', (t) => {
|
|
|
190
190
|
});
|
|
191
191
|
// process.cwd() = home (no .code-graph → resolves null → would be blank), but
|
|
192
192
|
// the authoritative cwd points at the project → must render the health line.
|
|
193
|
-
const out = runStatuslineIn(home, home, {
|
|
193
|
+
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: project });
|
|
194
194
|
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
test('
|
|
197
|
+
test('CODE_GRAPH_STATUSLINE_CWD in a subdir walks up to the project root', (t) => {
|
|
198
198
|
const home = mkHome(t);
|
|
199
199
|
const project = mkProject(home);
|
|
200
200
|
const subdir = path.join(project, 'claude-plugin', 'scripts');
|
|
@@ -205,6 +205,6 @@ test('CLAUDE_STATUSLINE_CWD in a subdir walks up to the project root', (t) => {
|
|
|
205
205
|
});
|
|
206
206
|
// The reported symptom: shell in <root>/claude-plugin/scripts. The subdir has
|
|
207
207
|
// no .code-graph of its own; resolveProjectRoot must walk up to <root>.
|
|
208
|
-
const out = runStatuslineIn(home, home, {
|
|
208
|
+
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: subdir });
|
|
209
209
|
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
210
210
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.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.78.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.78.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.78.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.78.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.78.0"
|
|
43
43
|
}
|
|
44
44
|
}
|