@sdsrs/code-graph 0.13.0 → 0.14.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.
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
// adopt / unadopt — writes plugin_code_graph_mcp.md into this project's
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Claude Code auto-memory dir (~/.claude/projects/<slug>/memory/, also
|
|
5
|
+
// read/written by claude-mem-lite) and maintains a sentinel-bracketed index
|
|
6
|
+
// entry in MEMORY.md. Idempotent. Used by invited-memory pattern with
|
|
7
|
+
// CODE_GRAPH_QUIET_HOOKS=1.
|
|
6
8
|
const fs = require('fs');
|
|
7
9
|
const path = require('path');
|
|
8
10
|
const os = require('os');
|
|
@@ -19,6 +19,10 @@ const MANIFEST_FILE = path.join(CACHE_DIR, 'install-manifest.json');
|
|
|
19
19
|
const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
20
20
|
const INSTALLED_PLUGINS_PATH = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
21
21
|
const REGISTRY_FILE = path.join(CACHE_DIR, 'statusline-registry.json');
|
|
22
|
+
// Durable mirror outside ~/.cache/ — survives cache cleanup. Captures the
|
|
23
|
+
// `_previous` snapshot (pre-install statusline) and any third-party providers
|
|
24
|
+
// (GSD, etc.). readRegistry() self-heals from this file when primary is missing.
|
|
25
|
+
const PROVIDERS_BACKUP_FILE = path.join(os.homedir(), '.claude', 'statusline-providers.json');
|
|
22
26
|
|
|
23
27
|
// --- Helpers ---
|
|
24
28
|
|
|
@@ -75,15 +79,28 @@ function isOurComposite(settings) {
|
|
|
75
79
|
// Multiple providers can register. The composite script runs them all.
|
|
76
80
|
|
|
77
81
|
function readRegistry() {
|
|
78
|
-
|
|
82
|
+
const primary = readJson(REGISTRY_FILE);
|
|
83
|
+
if (primary && Array.isArray(primary) && primary.length > 0) return primary;
|
|
84
|
+
// Self-heal: primary missing or empty (e.g. user cleaned ~/.cache/code-graph/).
|
|
85
|
+
// Durable backup in ~/.claude/ retains `_previous` + third-party providers.
|
|
86
|
+
const backup = readJson(PROVIDERS_BACKUP_FILE);
|
|
87
|
+
if (backup && Array.isArray(backup) && backup.length > 0) {
|
|
88
|
+
try { writeJsonAtomic(REGISTRY_FILE, backup); } catch { /* ok */ }
|
|
89
|
+
return backup;
|
|
90
|
+
}
|
|
91
|
+
return [];
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
function writeRegistry(registry) {
|
|
82
95
|
if (!registry || registry.length === 0) {
|
|
83
96
|
try { fs.unlinkSync(REGISTRY_FILE); } catch { /* ok */ }
|
|
97
|
+
try { fs.unlinkSync(PROVIDERS_BACKUP_FILE); } catch { /* ok */ }
|
|
84
98
|
return;
|
|
85
99
|
}
|
|
86
100
|
writeJsonAtomic(REGISTRY_FILE, registry);
|
|
101
|
+
// Mirror to durable location so cache cleanup doesn't strand `_previous`
|
|
102
|
+
// or third-party provider entries.
|
|
103
|
+
try { writeJsonAtomic(PROVIDERS_BACKUP_FILE, registry); } catch { /* ok */ }
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
function registerStatuslineProvider(id, command, needsStdin) {
|
|
@@ -535,7 +552,9 @@ module.exports = {
|
|
|
535
552
|
readRegistry, writeRegistry,
|
|
536
553
|
getPluginVersion, cleanupOldCacheVersions,
|
|
537
554
|
removeHooksFromSettings, isOurHookEntry,
|
|
555
|
+
registerStatuslineProvider, unregisterStatuslineProvider,
|
|
538
556
|
PLUGIN_ID, OLD_PLUGIN_IDS, MARKETPLACE_NAME, CACHE_DIR, REGISTRY_FILE,
|
|
557
|
+
PROVIDERS_BACKUP_FILE,
|
|
539
558
|
};
|
|
540
559
|
|
|
541
560
|
// CLI: node lifecycle.js <install|uninstall|update|health>
|
|
@@ -178,6 +178,94 @@ test('removeHooksFromSettings strips our entries but keeps unrelated hooks', ()
|
|
|
178
178
|
assert.ok(!s.hooks.PostToolUse, 'empty event key should be deleted');
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
+
test('writeRegistry mirrors entries to durable backup outside ~/.cache/', (t) => {
|
|
182
|
+
const homeDir = mkHome(t);
|
|
183
|
+
const registryPath = path.join(homeDir, '.cache', 'code-graph', 'statusline-registry.json');
|
|
184
|
+
const backupPath = path.join(homeDir, '.claude', 'statusline-providers.json');
|
|
185
|
+
|
|
186
|
+
execFileSync(process.execPath, ['-e', `
|
|
187
|
+
const { registerStatuslineProvider } = require(${JSON.stringify(lifecyclePath)});
|
|
188
|
+
registerStatuslineProvider('_previous', 'echo prev', true);
|
|
189
|
+
registerStatuslineProvider('code-graph', 'node /cg.js', false);
|
|
190
|
+
`], { env: { ...process.env, HOME: homeDir } });
|
|
191
|
+
|
|
192
|
+
const primary = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
193
|
+
const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
|
|
194
|
+
assert.deepEqual(primary, backup);
|
|
195
|
+
assert.equal(primary.length, 2);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('readRegistry self-heals primary from durable backup after cache wipe', (t) => {
|
|
199
|
+
const homeDir = mkHome(t);
|
|
200
|
+
const cacheDir = path.join(homeDir, '.cache', 'code-graph');
|
|
201
|
+
const registryPath = path.join(cacheDir, 'statusline-registry.json');
|
|
202
|
+
const backupPath = path.join(homeDir, '.claude', 'statusline-providers.json');
|
|
203
|
+
|
|
204
|
+
// Seed both files, then simulate user wiping ~/.cache/code-graph/
|
|
205
|
+
writeJson(registryPath, [
|
|
206
|
+
{ id: '_previous', command: 'echo gsd', needsStdin: true },
|
|
207
|
+
{ id: 'code-graph', command: 'node /cg.js', needsStdin: false },
|
|
208
|
+
]);
|
|
209
|
+
writeJson(backupPath, [
|
|
210
|
+
{ id: '_previous', command: 'echo gsd', needsStdin: true },
|
|
211
|
+
{ id: 'code-graph', command: 'node /cg.js', needsStdin: false },
|
|
212
|
+
]);
|
|
213
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
214
|
+
assert.equal(fs.existsSync(registryPath), false);
|
|
215
|
+
|
|
216
|
+
const out = execFileSync(process.execPath, ['-e', `
|
|
217
|
+
const { readRegistry } = require(${JSON.stringify(lifecyclePath)});
|
|
218
|
+
process.stdout.write(JSON.stringify(readRegistry()));
|
|
219
|
+
`], { env: { ...process.env, HOME: homeDir } }).toString();
|
|
220
|
+
|
|
221
|
+
const restored = JSON.parse(out);
|
|
222
|
+
assert.equal(restored.length, 2);
|
|
223
|
+
assert.equal(restored[0].id, '_previous');
|
|
224
|
+
// Primary file rebuilt from backup
|
|
225
|
+
assert.equal(fs.existsSync(registryPath), true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('writeRegistry([]) clears both primary and backup', (t) => {
|
|
229
|
+
const homeDir = mkHome(t);
|
|
230
|
+
const registryPath = path.join(homeDir, '.cache', 'code-graph', 'statusline-registry.json');
|
|
231
|
+
const backupPath = path.join(homeDir, '.claude', 'statusline-providers.json');
|
|
232
|
+
|
|
233
|
+
execFileSync(process.execPath, ['-e', `
|
|
234
|
+
const { registerStatuslineProvider, unregisterStatuslineProvider } = require(${JSON.stringify(lifecyclePath)});
|
|
235
|
+
registerStatuslineProvider('code-graph', 'node /cg.js', false);
|
|
236
|
+
unregisterStatuslineProvider('code-graph');
|
|
237
|
+
`], { env: { ...process.env, HOME: homeDir } });
|
|
238
|
+
|
|
239
|
+
assert.equal(fs.existsSync(registryPath), false);
|
|
240
|
+
assert.equal(fs.existsSync(backupPath), false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('statusline-chain CLI register/unregister/list + reserved-id guard', (t) => {
|
|
244
|
+
const homeDir = mkHome(t);
|
|
245
|
+
const chainPath = path.join(__dirname, 'statusline-chain.js');
|
|
246
|
+
const env = { ...process.env, HOME: homeDir };
|
|
247
|
+
|
|
248
|
+
const reg = execFileSync(process.execPath, [chainPath, 'register', 'gsd', 'node /gsd.cjs', '--stdin'], { env }).toString();
|
|
249
|
+
assert.match(reg, /registered gsd/);
|
|
250
|
+
|
|
251
|
+
const reRun = execFileSync(process.execPath, [chainPath, 'register', 'gsd', 'node /gsd.cjs', '--stdin'], { env }).toString();
|
|
252
|
+
assert.match(reRun, /unchanged gsd/);
|
|
253
|
+
|
|
254
|
+
const list = execFileSync(process.execPath, [chainPath, 'list'], { env }).toString();
|
|
255
|
+
assert.match(list, /gsd \[stdin\]: node \/gsd\.cjs/);
|
|
256
|
+
|
|
257
|
+
// Reserved ids rejected — both should exit 2 with stderr "reserved"
|
|
258
|
+
const { spawnSync } = require('child_process');
|
|
259
|
+
for (const rid of ['_previous', 'code-graph']) {
|
|
260
|
+
const r = spawnSync(process.execPath, [chainPath, 'register', rid, 'x'], { env });
|
|
261
|
+
assert.equal(r.status, 2, `${rid} should exit 2`);
|
|
262
|
+
assert.match(r.stderr.toString(), /reserved/);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const un = execFileSync(process.execPath, [chainPath, 'unregister', 'gsd'], { env }).toString();
|
|
266
|
+
assert.match(un, /unregistered gsd/);
|
|
267
|
+
});
|
|
268
|
+
|
|
181
269
|
test('install() removes legacy code-graph hooks from settings.json without re-registering', (t) => {
|
|
182
270
|
const homeDir = mkHome(t);
|
|
183
271
|
const settingsPath = path.join(homeDir, '.claude', 'settings.json');
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Public CLI for third-party plugins (GSD, etc.) to register a statusline
|
|
4
|
+
// provider into code-graph's composite chain.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node statusline-chain.js register <id> <command> [--stdin]
|
|
8
|
+
// node statusline-chain.js unregister <id>
|
|
9
|
+
// node statusline-chain.js list
|
|
10
|
+
//
|
|
11
|
+
// Writes to ~/.cache/code-graph/statusline-registry.json (working copy) and
|
|
12
|
+
// mirrors to ~/.claude/statusline-providers.json (durable backup). The
|
|
13
|
+
// composite script reads both.
|
|
14
|
+
//
|
|
15
|
+
// Reserved ids: "_previous" (captures pre-install statusline), "code-graph"
|
|
16
|
+
// (this plugin's own provider). Third parties should use stable ids like
|
|
17
|
+
// "gsd", "claude-mem", etc.
|
|
18
|
+
|
|
19
|
+
const { readRegistry, registerStatuslineProvider, unregisterStatuslineProvider } = require('./lifecycle');
|
|
20
|
+
|
|
21
|
+
function usage(code = 1) {
|
|
22
|
+
process.stderr.write(
|
|
23
|
+
'Usage:\n' +
|
|
24
|
+
' node statusline-chain.js register <id> <command> [--stdin]\n' +
|
|
25
|
+
' node statusline-chain.js unregister <id>\n' +
|
|
26
|
+
' node statusline-chain.js list\n'
|
|
27
|
+
);
|
|
28
|
+
process.exit(code);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runRegister(id, command, needsStdin) {
|
|
32
|
+
if (id === 'code-graph' || id === '_previous') {
|
|
33
|
+
process.stderr.write(`error: id "${id}" is reserved\n`);
|
|
34
|
+
process.exit(2);
|
|
35
|
+
}
|
|
36
|
+
if (!id || !command) usage();
|
|
37
|
+
const changed = registerStatuslineProvider(id, command, needsStdin);
|
|
38
|
+
process.stdout.write(changed ? `registered ${id}\n` : `unchanged ${id}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function runUnregister(id) {
|
|
42
|
+
if (!id) usage();
|
|
43
|
+
const changed = unregisterStatuslineProvider(id);
|
|
44
|
+
process.stdout.write(changed ? `unregistered ${id}\n` : `not-found ${id}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function runList() {
|
|
48
|
+
const registry = readRegistry();
|
|
49
|
+
if (registry.length === 0) {
|
|
50
|
+
process.stdout.write('(empty)\n');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const entry of registry) {
|
|
54
|
+
const stdin = entry.needsStdin ? ' [stdin]' : '';
|
|
55
|
+
process.stdout.write(`${entry.id}${stdin}: ${entry.command}\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (require.main === module) {
|
|
60
|
+
const [, , cmd, ...rest] = process.argv;
|
|
61
|
+
if (cmd === 'register') {
|
|
62
|
+
const needsStdin = rest.includes('--stdin');
|
|
63
|
+
const args = rest.filter((a) => a !== '--stdin');
|
|
64
|
+
runRegister(args[0], args[1], needsStdin);
|
|
65
|
+
} else if (cmd === 'unregister') {
|
|
66
|
+
runUnregister(rest[0]);
|
|
67
|
+
} else if (cmd === 'list') {
|
|
68
|
+
runList();
|
|
69
|
+
} else {
|
|
70
|
+
usage();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { runRegister, runUnregister, runList };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
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": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"node": ">=16"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@sdsrs/code-graph-linux-x64": "0.
|
|
38
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
39
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
41
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
37
|
+
"@sdsrs/code-graph-linux-x64": "0.14.1",
|
|
38
|
+
"@sdsrs/code-graph-linux-arm64": "0.14.1",
|
|
39
|
+
"@sdsrs/code-graph-darwin-x64": "0.14.1",
|
|
40
|
+
"@sdsrs/code-graph-darwin-arm64": "0.14.1",
|
|
41
|
+
"@sdsrs/code-graph-win32-x64": "0.14.1"
|
|
42
42
|
}
|
|
43
43
|
}
|