@sdsrs/code-graph 0.94.0 → 0.95.1
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/auto-update.js +69 -9
- package/package.json +8 -7
- package/claude-plugin/scripts/adopt.test.js +0 -679
- package/claude-plugin/scripts/auto-update.test.js +0 -515
- package/claude-plugin/scripts/cg-answer.test.js +0 -309
- package/claude-plugin/scripts/claude-config.test.js +0 -58
- package/claude-plugin/scripts/covering-tests.test.js +0 -78
- package/claude-plugin/scripts/doctor.test.js +0 -215
- package/claude-plugin/scripts/find-binary.test.js +0 -246
- package/claude-plugin/scripts/hook-fire.test.js +0 -117
- package/claude-plugin/scripts/hooks.test.js +0 -230
- package/claude-plugin/scripts/incremental-index.test.js +0 -102
- package/claude-plugin/scripts/lifecycle.e2e.test.js +0 -179
- package/claude-plugin/scripts/lifecycle.test.js +0 -786
- package/claude-plugin/scripts/mcp-launcher.test.js +0 -162
- package/claude-plugin/scripts/mcp-stub.test.js +0 -207
- package/claude-plugin/scripts/post-grep-inject.test.js +0 -531
- package/claude-plugin/scripts/pr-impact-comment.test.js +0 -110
- package/claude-plugin/scripts/pre-edit-guide.test.js +0 -218
- package/claude-plugin/scripts/pre-grep-guide.test.js +0 -1682
- package/claude-plugin/scripts/pre-read-guide.test.js +0 -363
- package/claude-plugin/scripts/project-detect.test.js +0 -95
- package/claude-plugin/scripts/recommendation-log.test.js +0 -79
- package/claude-plugin/scripts/session-init.test.js +0 -479
- package/claude-plugin/scripts/statusline-composite.test.js +0 -65
- package/claude-plugin/scripts/statusline.test.js +0 -235
- package/claude-plugin/scripts/tmp-dir.test.js +0 -50
- package/claude-plugin/scripts/user-prompt-context.test.js +0 -743
- package/claude-plugin/scripts/version-utils.test.js +0 -141
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// statusline.js renders code-graph health into the Claude Code status line.
|
|
3
|
-
// The states are easy to confuse, so each is pinned here with a stub binary:
|
|
4
|
-
//
|
|
5
|
-
// ✓/✗ N nodes | M files — health-check produced a report (exit 0 OR exit 1)
|
|
6
|
-
// ↻ updating — no report + (schema-version error OR pending update)
|
|
7
|
-
// offline — no report + genuine failure, nothing pending
|
|
8
|
-
//
|
|
9
|
-
// The critical regression this guards: health-check exits NON-ZERO on an
|
|
10
|
-
// empty/unhealthy index but still emits the full JSON report. statusline must
|
|
11
|
-
// render that report ("✗ 0 nodes"), NOT collapse it into "offline".
|
|
12
|
-
const test = require('node:test');
|
|
13
|
-
const assert = require('node:assert/strict');
|
|
14
|
-
const { execFileSync } = require('child_process');
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const os = require('os');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
|
|
19
|
-
const STATUSLINE = path.join(__dirname, 'statusline.js');
|
|
20
|
-
|
|
21
|
-
function mkHome(t) {
|
|
22
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-statusline-'));
|
|
23
|
-
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
24
|
-
return dir;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Write an executable shell stub named `code-graph-mcp` (find-binary's
|
|
28
|
-
// isNativeBinary requires that exact basename) and register it in the binary
|
|
29
|
-
// cache so findBinary() returns it before any dev/platform discovery.
|
|
30
|
-
// --version → prints `version` (must be >= pkg version to be "fresh")
|
|
31
|
-
// health-check ... → prints `report` to stdout, exits with `exitCode`
|
|
32
|
-
function installStubBinary(home, { report, stderr = '', exitCode = 0, version = '9.9.9' }) {
|
|
33
|
-
const binDir = path.join(home, 'stub-bin');
|
|
34
|
-
fs.mkdirSync(binDir, { recursive: true });
|
|
35
|
-
const binPath = path.join(binDir, 'code-graph-mcp');
|
|
36
|
-
const sh = (s) => String(s).replace(/'/g, `'\\''`);
|
|
37
|
-
const payload = typeof report === 'string' ? report : JSON.stringify(report);
|
|
38
|
-
const lines = [
|
|
39
|
-
'#!/usr/bin/env bash',
|
|
40
|
-
'if [ "$1" = "--version" ]; then',
|
|
41
|
-
` echo "${version}"; exit 0`,
|
|
42
|
-
'fi',
|
|
43
|
-
];
|
|
44
|
-
if (payload) lines.push(`printf '%s' '${sh(payload)}'`);
|
|
45
|
-
if (stderr) lines.push(`printf '%s' '${sh(stderr)}' >&2`);
|
|
46
|
-
lines.push(`exit ${exitCode}`, '');
|
|
47
|
-
fs.writeFileSync(binPath, lines.join('\n'));
|
|
48
|
-
fs.chmodSync(binPath, 0o755);
|
|
49
|
-
|
|
50
|
-
const cacheDir = path.join(home, '.cache', 'code-graph');
|
|
51
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
52
|
-
fs.writeFileSync(path.join(cacheDir, 'binary-path'), binPath);
|
|
53
|
-
return binPath;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function setUpdateState(home, state) {
|
|
57
|
-
const cacheDir = path.join(home, '.cache', 'code-graph');
|
|
58
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
59
|
-
fs.writeFileSync(path.join(cacheDir, 'update-state.json'), JSON.stringify(state));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Run statusline.js in `projectDir` (must contain .code-graph/index.db) with a
|
|
63
|
-
// sandboxed HOME, and return its stdout. PATH retains bash for the stub.
|
|
64
|
-
function runStatusline(home, projectDir) {
|
|
65
|
-
return execFileSync('node', [STATUSLINE], {
|
|
66
|
-
cwd: projectDir,
|
|
67
|
-
env: { ...process.env, HOME: home },
|
|
68
|
-
encoding: 'utf8',
|
|
69
|
-
}).trim();
|
|
70
|
-
}
|
|
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
|
-
|
|
83
|
-
function mkProject(home) {
|
|
84
|
-
const dir = path.join(home, 'project');
|
|
85
|
-
const cg = path.join(dir, '.code-graph');
|
|
86
|
-
fs.mkdirSync(cg, { recursive: true });
|
|
87
|
-
fs.writeFileSync(path.join(cg, 'index.db'), 'stub'); // existence is all statusline checks
|
|
88
|
-
return dir;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
test('healthy index → ✓ with node/file counts', (t) => {
|
|
92
|
-
const home = mkHome(t);
|
|
93
|
-
const project = mkProject(home);
|
|
94
|
-
installStubBinary(home, {
|
|
95
|
-
report: { healthy: true, nodes: 3145, files: 205, watching: true },
|
|
96
|
-
exitCode: 0,
|
|
97
|
-
});
|
|
98
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✓ 3145 nodes | 205 files | watching');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('empty index, health-check EXIT 1 with JSON → ✗ 0 nodes (not offline)', (t) => {
|
|
102
|
-
// The core regression: non-zero exit must not mask a valid report.
|
|
103
|
-
const home = mkHome(t);
|
|
104
|
-
const project = mkProject(home);
|
|
105
|
-
installStubBinary(home, {
|
|
106
|
-
report: { healthy: false, nodes: 0, files: 0, watching: false },
|
|
107
|
-
exitCode: 1,
|
|
108
|
-
});
|
|
109
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✗ 0 nodes | 0 files');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('no report + update pending → updating', (t) => {
|
|
113
|
-
const home = mkHome(t);
|
|
114
|
-
const project = mkProject(home);
|
|
115
|
-
installStubBinary(home, { report: 'boom', exitCode: 1 });
|
|
116
|
-
setUpdateState(home, { updateAvailable: true });
|
|
117
|
-
assert.equal(runStatusline(home, project), 'code-graph: ↻ updating');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('stuck update (updateAttempts exhausted) → offline, not updating', (t) => {
|
|
121
|
-
// A persistently-failing update must not pin "↻ updating" forever. Past
|
|
122
|
-
// STUCK_UPDATE_ATTEMPTS the statusline drops the optimistic state and surfaces
|
|
123
|
-
// the real one (here: no report → offline).
|
|
124
|
-
const home = mkHome(t);
|
|
125
|
-
const project = mkProject(home);
|
|
126
|
-
installStubBinary(home, { report: 'boom', exitCode: 1 });
|
|
127
|
-
setUpdateState(home, { updateAvailable: true, updateAttempts: 5 });
|
|
128
|
-
assert.equal(runStatusline(home, project), 'code-graph: offline');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('schema-version error on stderr (no report) → updating', (t) => {
|
|
132
|
-
const home = mkHome(t);
|
|
133
|
-
const project = mkProject(home);
|
|
134
|
-
// The binary writes schema errors to stderr; no parseable report on stdout.
|
|
135
|
-
installStubBinary(home, {
|
|
136
|
-
report: '',
|
|
137
|
-
stderr: 'Error: Database schema version v9 is newer than supported v8',
|
|
138
|
-
exitCode: 1,
|
|
139
|
-
});
|
|
140
|
-
assert.equal(runStatusline(home, project), 'code-graph: ↻ updating');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('schema-too-new MARKER on stderr → updating (keys on the stable token, not prose)', (t) => {
|
|
144
|
-
// The binary appends domain::SCHEMA_TOO_NEW_MARKER; the statusline must detect
|
|
145
|
-
// the post-update window via that token even when the surrounding prose is
|
|
146
|
-
// reworded/translated (the old `/schema version/i` regex would miss this).
|
|
147
|
-
const home = mkHome(t);
|
|
148
|
-
const project = mkProject(home);
|
|
149
|
-
installStubBinary(home, {
|
|
150
|
-
report: '',
|
|
151
|
-
stderr: 'Error: totally reworded wording here [code-graph:schema-too-new]',
|
|
152
|
-
exitCode: 1,
|
|
153
|
-
});
|
|
154
|
-
assert.equal(runStatusline(home, project), 'code-graph: ↻ updating');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('genuine crash (no report, nothing pending) → offline', (t) => {
|
|
158
|
-
const home = mkHome(t);
|
|
159
|
-
const project = mkProject(home);
|
|
160
|
-
installStubBinary(home, { report: 'segfault, core dumped', exitCode: 139 });
|
|
161
|
-
assert.equal(runStatusline(home, project), 'code-graph: offline');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('partial embeddings show vector coverage', (t) => {
|
|
165
|
-
const home = mkHome(t);
|
|
166
|
-
const project = mkProject(home);
|
|
167
|
-
installStubBinary(home, {
|
|
168
|
-
report: { healthy: true, nodes: 14119, files: 922, embedding_status: 'partial', embedding_coverage_pct: 60 },
|
|
169
|
-
exitCode: 0,
|
|
170
|
-
});
|
|
171
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files | 60% vec');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('pending embeddings show "vec pending"', (t) => {
|
|
175
|
-
const home = mkHome(t);
|
|
176
|
-
const project = mkProject(home);
|
|
177
|
-
installStubBinary(home, {
|
|
178
|
-
report: { healthy: true, nodes: 14119, files: 922, embedding_status: 'pending', embedding_coverage_pct: 0 },
|
|
179
|
-
exitCode: 0,
|
|
180
|
-
});
|
|
181
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files | vec pending');
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test('complete embeddings add no vector suffix', (t) => {
|
|
185
|
-
const home = mkHome(t);
|
|
186
|
-
const project = mkProject(home);
|
|
187
|
-
installStubBinary(home, {
|
|
188
|
-
report: { healthy: true, nodes: 14119, files: 922, embedding_status: 'complete', embedding_coverage_pct: 100 },
|
|
189
|
-
exitCode: 0,
|
|
190
|
-
});
|
|
191
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('version-stale index shows rebuilding marker', (t) => {
|
|
195
|
-
const home = mkHome(t);
|
|
196
|
-
const project = mkProject(home);
|
|
197
|
-
installStubBinary(home, {
|
|
198
|
-
report: { healthy: true, nodes: 14119, files: 922, embedding_status: 'complete', index_version_stale: true },
|
|
199
|
-
exitCode: 0,
|
|
200
|
-
});
|
|
201
|
-
assert.equal(runStatusline(home, project), 'code-graph: ✓ 14119 nodes | 922 files | ↻ rebuilding');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// CODE_GRAPH_STATUSLINE_CWD is Claude Code's authoritative current dir, forwarded by
|
|
205
|
-
// the composite from its stdin payload. The gate must trust it over process.cwd():
|
|
206
|
-
// Claude Code may spawn the statusline from a cwd unrelated to the session (the
|
|
207
|
-
// classic regression — the segment vanished when the shell sat in a subdir whose
|
|
208
|
-
// process.cwd() didn't resolve to the project root).
|
|
209
|
-
test('CODE_GRAPH_STATUSLINE_CWD overrides process.cwd() for the gate', (t) => {
|
|
210
|
-
const home = mkHome(t);
|
|
211
|
-
const project = mkProject(home);
|
|
212
|
-
installStubBinary(home, {
|
|
213
|
-
report: { healthy: true, nodes: 3145, files: 205 },
|
|
214
|
-
exitCode: 0,
|
|
215
|
-
});
|
|
216
|
-
// process.cwd() = home (no .code-graph → resolves null → would be blank), but
|
|
217
|
-
// the authoritative cwd points at the project → must render the health line.
|
|
218
|
-
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: project });
|
|
219
|
-
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('CODE_GRAPH_STATUSLINE_CWD in a subdir walks up to the project root', (t) => {
|
|
223
|
-
const home = mkHome(t);
|
|
224
|
-
const project = mkProject(home);
|
|
225
|
-
const subdir = path.join(project, 'claude-plugin', 'scripts');
|
|
226
|
-
fs.mkdirSync(subdir, { recursive: true });
|
|
227
|
-
installStubBinary(home, {
|
|
228
|
-
report: { healthy: true, nodes: 3145, files: 205 },
|
|
229
|
-
exitCode: 0,
|
|
230
|
-
});
|
|
231
|
-
// The reported symptom: shell in <root>/claude-plugin/scripts. The subdir has
|
|
232
|
-
// no .code-graph of its own; resolveProjectRoot must walk up to <root>.
|
|
233
|
-
const out = runStatuslineIn(home, home, { CODE_GRAPH_STATUSLINE_CWD: subdir });
|
|
234
|
-
assert.equal(out, 'code-graph: ✓ 3145 nodes | 205 files');
|
|
235
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const test = require('node:test');
|
|
3
|
-
const assert = require('node:assert');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
const { cgTmpDir, CG_TMP_DIR } = require('./tmp-dir');
|
|
9
|
-
|
|
10
|
-
test('CG_TMP_DIR is a "code-graph-mcp" subdir of os.tmpdir()', () => {
|
|
11
|
-
assert.strictEqual(path.basename(CG_TMP_DIR), 'code-graph-mcp');
|
|
12
|
-
assert.strictEqual(path.dirname(CG_TMP_DIR), os.tmpdir());
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('cgTmpDir() returns the same path and creates the directory', () => {
|
|
16
|
-
// Pre-condition: nuke it if it exists from a prior run, to prove cgTmpDir()
|
|
17
|
-
// actually creates it on demand (not just reports a pre-existing path).
|
|
18
|
-
try { fs.rmSync(CG_TMP_DIR, { recursive: true, force: true }); } catch { /* ok */ }
|
|
19
|
-
assert.ok(!fs.existsSync(CG_TMP_DIR), 'pre-condition: dir must be absent');
|
|
20
|
-
|
|
21
|
-
const p = cgTmpDir();
|
|
22
|
-
assert.strictEqual(p, CG_TMP_DIR);
|
|
23
|
-
assert.ok(fs.existsSync(p), 'cgTmpDir() must create the directory');
|
|
24
|
-
assert.ok(fs.statSync(p).isDirectory(), 'created entry must be a directory');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('cgTmpDir() is idempotent — second call does not throw on existing dir', () => {
|
|
28
|
-
cgTmpDir();
|
|
29
|
-
// Should not throw even though dir now exists.
|
|
30
|
-
assert.doesNotThrow(() => cgTmpDir());
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('cgTmpDir() does not leak files into os.tmpdir() root', () => {
|
|
34
|
-
// Regression guard: the v0.32.x bug was hook artifacts landing directly
|
|
35
|
-
// in os.tmpdir() (= ~/.claude/tmp/ under Claude Code's $TMPDIR override),
|
|
36
|
-
// colliding with transcript subdirs. After the fix, no `.code-graph-bash-*`
|
|
37
|
-
// / `.cg-impact-*` / `.code-graph-readfan-*` filename should ever appear
|
|
38
|
-
// outside CG_TMP_DIR — only inside it.
|
|
39
|
-
const dir = cgTmpDir();
|
|
40
|
-
const flag = path.join(dir, '.code-graph-bash-test');
|
|
41
|
-
fs.writeFileSync(flag, '');
|
|
42
|
-
try {
|
|
43
|
-
// The sibling of CG_TMP_DIR (= os.tmpdir()) must NOT now contain the flag.
|
|
44
|
-
const parent = path.dirname(dir);
|
|
45
|
-
const stray = path.join(parent, '.code-graph-bash-test');
|
|
46
|
-
assert.ok(!fs.existsSync(stray), 'flag must not exist in os.tmpdir() root');
|
|
47
|
-
} finally {
|
|
48
|
-
try { fs.unlinkSync(flag); } catch { /* ok */ }
|
|
49
|
-
}
|
|
50
|
-
});
|