@sdsrs/code-graph 0.77.0 → 0.77.2
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 +26 -1
- package/claude-plugin/scripts/statusline-composite.test.js +65 -0
- package/claude-plugin/scripts/statusline.js +9 -1
- package/claude-plugin/scripts/statusline.test.js +44 -0
- package/package.json +6 -6
|
@@ -71,16 +71,41 @@ function runProvider(command, needsStdin, stdin) {
|
|
|
71
71
|
// swallowed below, silently dropping the user's original statusline.
|
|
72
72
|
const argv = parts.map(expandTilde);
|
|
73
73
|
|
|
74
|
+
// Forward Claude Code's authoritative current dir (from the stdin payload) as
|
|
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.
|
|
79
|
+
const cwd = cwdFromStdin(stdin);
|
|
80
|
+
const env = cwd ? { ...process.env, CODE_GRAPH_STATUSLINE_CWD: cwd } : process.env;
|
|
81
|
+
|
|
74
82
|
const out = execFileSync(argv[0], argv.slice(1), {
|
|
75
83
|
timeout: 3000,
|
|
76
84
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
85
|
input: needsStdin ? stdin : '',
|
|
86
|
+
env,
|
|
78
87
|
}).toString().trim();
|
|
79
88
|
|
|
80
89
|
return out || null;
|
|
81
90
|
} catch { return null; }
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
// Extract Claude Code's current working directory from the stdin JSON context.
|
|
94
|
+
// Prefer the top-level `cwd`, then `workspace.current_dir`; both track the
|
|
95
|
+
// session's working dir (after the model runs `cd`). Returns null for empty,
|
|
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.
|
|
100
|
+
function cwdFromStdin(stdin) {
|
|
101
|
+
if (!stdin) return null;
|
|
102
|
+
try {
|
|
103
|
+
const ctx = JSON.parse(stdin);
|
|
104
|
+
const v = ctx && (ctx.cwd || (ctx.workspace && ctx.workspace.current_dir));
|
|
105
|
+
return typeof v === 'string' && v ? v : null;
|
|
106
|
+
} catch { return null; }
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
function parseCommand(cmd) {
|
|
85
110
|
// Handle: node "/path/to/script.js"
|
|
86
111
|
const match = cmd.match(/^(\S+)\s+"([^"]+)"(.*)$/);
|
|
@@ -109,4 +134,4 @@ function codeGraphCommand() {
|
|
|
109
134
|
return `node "${path.join(__dirname, 'statusline.js')}"`;
|
|
110
135
|
}
|
|
111
136
|
|
|
112
|
-
module.exports = { run, runProvider, parseCommand, expandTilde };
|
|
137
|
+
module.exports = { run, runProvider, parseCommand, expandTilde, cwdFromStdin };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The composite is the registered statusLine command: it receives Claude Code's
|
|
3
|
+
// JSON context on stdin and fans out to each provider. This pins the cwd bridge:
|
|
4
|
+
// the code-graph provider keys its gate on process.cwd(), but Claude Code may
|
|
5
|
+
// spawn the statusline from a cwd unrelated to the session. The composite must
|
|
6
|
+
// extract the authoritative cwd from stdin and forward it (CODE_GRAPH_STATUSLINE_CWD)
|
|
7
|
+
// so the provider resolves the right project regardless of the spawn's cwd.
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { cwdFromStdin, runProvider } = require('./statusline-composite');
|
|
14
|
+
|
|
15
|
+
test('cwdFromStdin reads the top-level cwd field', () => {
|
|
16
|
+
assert.equal(cwdFromStdin('{"cwd":"/a/b"}'), '/a/b');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('cwdFromStdin falls back to workspace.current_dir', () => {
|
|
20
|
+
assert.equal(cwdFromStdin('{"workspace":{"current_dir":"/c/d"}}'), '/c/d');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('cwdFromStdin prefers top-level cwd over workspace.current_dir', () => {
|
|
24
|
+
assert.equal(cwdFromStdin('{"cwd":"/a","workspace":{"current_dir":"/c"}}'), '/a');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('cwdFromStdin returns null for empty / non-JSON / cwd-less payloads', () => {
|
|
28
|
+
assert.equal(cwdFromStdin(''), null);
|
|
29
|
+
assert.equal(cwdFromStdin('not json'), null);
|
|
30
|
+
assert.equal(cwdFromStdin('{}'), null);
|
|
31
|
+
assert.equal(cwdFromStdin('{"workspace":{}}'), null);
|
|
32
|
+
});
|
|
33
|
+
|
|
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) => {
|
|
44
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-composite-'));
|
|
45
|
+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
46
|
+
const fixture = path.join(dir, 'echo-cwd.js');
|
|
47
|
+
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.CODE_GRAPH_STATUSLINE_CWD||'NONE'));");
|
|
48
|
+
const out = runProvider(`node ${JSON.stringify(fixture)}`, false, '{"cwd":"/x/y"}');
|
|
49
|
+
assert.equal(out, 'CWD=/x/y');
|
|
50
|
+
});
|
|
51
|
+
|
|
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; });
|
|
59
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-composite-'));
|
|
60
|
+
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
61
|
+
const fixture = path.join(dir, 'echo-cwd.js');
|
|
62
|
+
fs.writeFileSync(fixture, "process.stdout.write('CWD='+(process.env.CODE_GRAPH_STATUSLINE_CWD||'NONE'));");
|
|
63
|
+
const out = runProvider(`node ${JSON.stringify(fixture)}`, false, '');
|
|
64
|
+
assert.equal(out, 'CWD=NONE');
|
|
65
|
+
});
|
|
@@ -33,7 +33,15 @@ if (disabledCleanup.cleaned) process.exit(0);
|
|
|
33
33
|
// "oscillating" between root/backend/frontend node counts) or, in a clean subdir
|
|
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 (CODE_GRAPH_STATUSLINE_CWD,
|
|
38
|
+
// forwarded by the composite from its stdin payload) rather than process.cwd().
|
|
39
|
+
// The spawned statusline's process.cwd() is an implementation detail of how
|
|
40
|
+
// Claude Code launches the command and need not track the session's working dir;
|
|
41
|
+
// the stdin `cwd` always does. Fall back to process.cwd() when unset (direct
|
|
42
|
+
// invocation, tests).
|
|
43
|
+
const startDir = process.env.CODE_GRAPH_STATUSLINE_CWD || process.cwd();
|
|
44
|
+
const root = resolveProjectRoot(startDir);
|
|
37
45
|
if (!root) {
|
|
38
46
|
process.exit(0);
|
|
39
47
|
}
|
|
@@ -69,6 +69,17 @@ function runStatusline(home, projectDir) {
|
|
|
69
69
|
}).trim();
|
|
70
70
|
}
|
|
71
71
|
|
|
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 (CODE_GRAPH_STATUSLINE_CWD,
|
|
74
|
+
// forwarded by the composite from the stdin payload), NOT the spawn's cwd.
|
|
75
|
+
function runStatuslineIn(home, processCwd, extraEnv) {
|
|
76
|
+
return execFileSync('node', [STATUSLINE], {
|
|
77
|
+
cwd: processCwd,
|
|
78
|
+
env: { ...process.env, HOME: home, ...extraEnv },
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
}).trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
function mkProject(home) {
|
|
73
84
|
const dir = path.join(home, 'project');
|
|
74
85
|
const cg = path.join(dir, '.code-graph');
|
|
@@ -164,3 +175,36 @@ test('version-stale index shows rebuilding marker', (t) => {
|
|
|
164
175
|
});
|
|
165
176
|
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files | ↻ rebuilding');
|
|
166
177
|
});
|
|
178
|
+
|
|
179
|
+
// CODE_GRAPH_STATUSLINE_CWD is Claude Code's authoritative current dir, forwarded by
|
|
180
|
+
// the composite from its stdin payload. The gate must trust it over process.cwd():
|
|
181
|
+
// Claude Code may spawn the statusline from a cwd unrelated to the session (the
|
|
182
|
+
// classic regression — the segment vanished when the shell sat in a subdir whose
|
|
183
|
+
// process.cwd() didn't resolve to the project root).
|
|
184
|
+
test('CODE_GRAPH_STATUSLINE_CWD overrides process.cwd() for the gate', (t) => {
|
|
185
|
+
const home = mkHome(t);
|
|
186
|
+
const project = mkProject(home);
|
|
187
|
+
installStubBinary(home, {
|
|
188
|
+
report: { healthy: true, nodes: 3145, files: 205 },
|
|
189
|
+
exitCode: 0,
|
|
190
|
+
});
|
|
191
|
+
// process.cwd() = home (no .code-graph → resolves null → would be blank), but
|
|
192
|
+
// the authoritative cwd points at the project → must render the health line.
|
|
193
|
+
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: project });
|
|
194
|
+
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('CODE_GRAPH_STATUSLINE_CWD in a subdir walks up to the project root', (t) => {
|
|
198
|
+
const home = mkHome(t);
|
|
199
|
+
const project = mkProject(home);
|
|
200
|
+
const subdir = path.join(project, 'claude-plugin', 'scripts');
|
|
201
|
+
fs.mkdirSync(subdir, { recursive: true });
|
|
202
|
+
installStubBinary(home, {
|
|
203
|
+
report: { healthy: true, nodes: 3145, files: 205 },
|
|
204
|
+
exitCode: 0,
|
|
205
|
+
});
|
|
206
|
+
// The reported symptom: shell in <root>/claude-plugin/scripts. The subdir has
|
|
207
|
+
// no .code-graph of its own; resolveProjectRoot must walk up to <root>.
|
|
208
|
+
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: subdir });
|
|
209
|
+
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
210
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.77.
|
|
3
|
+
"version": "0.77.2",
|
|
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.77.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.77.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.77.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.77.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.77.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.77.2",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.77.2",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.77.2",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.77.2",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.77.2"
|
|
43
43
|
}
|
|
44
44
|
}
|