@sdsrs/code-graph 0.5.27 → 0.5.28
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 +1 -1
- package/bin/cli.js +8 -47
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/hooks/hooks.json +2 -2
- package/claude-plugin/scripts/auto-update.js +167 -51
- package/claude-plugin/scripts/auto-update.test.js +97 -0
- package/claude-plugin/scripts/find-binary.js +92 -29
- package/claude-plugin/scripts/lifecycle.e2e.test.js +91 -0
- package/claude-plugin/scripts/lifecycle.js +69 -16
- package/claude-plugin/scripts/lifecycle.test.js +97 -0
- package/claude-plugin/scripts/session-init.js +46 -160
- package/claude-plugin/scripts/session-init.test.js +35 -0
- package/claude-plugin/scripts/statusline-composite.js +4 -1
- package/claude-plugin/scripts/statusline.js +4 -0
- package/package.json +6 -6
|
@@ -1,180 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const {
|
|
4
|
-
const fs = require('fs');
|
|
3
|
+
const { spawn } = require('child_process');
|
|
5
4
|
const path = require('path');
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const {
|
|
6
|
+
install, update, readManifest, getPluginVersion, checkScopeConflict,
|
|
7
|
+
cleanupDisabledStatusline, isPluginInactive,
|
|
8
|
+
} = require('./lifecycle');
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// --- 0b. Retry pending binary update from previous failed auto-update ---
|
|
14
|
-
{
|
|
15
|
-
const updateState = readUpdateState();
|
|
16
|
-
if (updateState.pendingBinaryUpdate) {
|
|
17
|
-
const pendingVer = updateState.pendingBinaryUpdate;
|
|
18
|
-
try {
|
|
19
|
-
execFileSync('npm', ['install', '-g', `@sdsrs/code-graph@${pendingVer}`], {
|
|
20
|
-
timeout: 30000, stdio: 'pipe'
|
|
21
|
-
});
|
|
22
|
-
try { fs.unlinkSync(path.join(os.homedir(), '.cache', 'code-graph', 'binary-path')); } catch {}
|
|
23
|
-
// Clear pending flag
|
|
24
|
-
const { writeJsonAtomic, CACHE_DIR } = require('./lifecycle');
|
|
25
|
-
const s = readUpdateState();
|
|
26
|
-
delete s.pendingBinaryUpdate;
|
|
27
|
-
writeJsonAtomic(path.join(CACHE_DIR, 'update-state.json'), s);
|
|
28
|
-
process.stderr.write(`[code-graph] Binary retry succeeded: v${pendingVer}\n`);
|
|
29
|
-
BIN = findBinary(); // refresh
|
|
30
|
-
} catch { /* npm still not available — will retry next session */ }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// --- 0. Auto-install binary if missing ---
|
|
35
|
-
if (!BIN) {
|
|
36
|
-
const version = getPluginVersion();
|
|
37
|
-
process.stderr.write(`[code-graph] Binary not found, installing @sdsrs/code-graph@${version}...\n`);
|
|
10
|
+
function launchBackgroundAutoUpdate(spawnFn = spawn, env = process.env) {
|
|
38
11
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
12
|
+
const child = spawnFn(process.execPath, [path.join(__dirname, 'auto-update.js'), 'check', '--silent'], {
|
|
13
|
+
detached: true,
|
|
14
|
+
stdio: 'ignore',
|
|
15
|
+
env: { ...env, CODE_GRAPH_AUTO_UPDATE_SILENT: '1' },
|
|
41
16
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
BIN = findBinary();
|
|
45
|
-
if (BIN) {
|
|
46
|
-
process.stderr.write(`[code-graph] Installed v${version} at ${BIN}\n`);
|
|
47
|
-
} else {
|
|
48
|
-
process.stderr.write('[code-graph] Install succeeded but binary not found in PATH. Try: npx @sdsrs/code-graph@latest\n');
|
|
49
|
-
}
|
|
17
|
+
if (child && typeof child.unref === 'function') child.unref();
|
|
18
|
+
return true;
|
|
50
19
|
} catch {
|
|
51
|
-
|
|
52
|
-
`[code-graph] Auto-install failed. Run manually: npm install -g @sdsrs/code-graph@${version}\n`
|
|
53
|
-
);
|
|
20
|
+
return false;
|
|
54
21
|
}
|
|
55
22
|
}
|
|
56
23
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const out = execFileSync(BIN, ['health-check', '--format', 'oneline'], {
|
|
62
|
-
timeout: 2000,
|
|
63
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
64
|
-
}).toString().trim();
|
|
65
|
-
if (out) process.stdout.write(out);
|
|
66
|
-
// Parse node count for empty-index detection
|
|
67
|
-
const m = out.match(/(\d+)\s*nodes/);
|
|
68
|
-
if (m) healthNodes = parseInt(m[1], 10);
|
|
69
|
-
} catch { /* timeout — silent */ }
|
|
70
|
-
}
|
|
24
|
+
function syncLifecycleConfig() {
|
|
25
|
+
const manifest = readManifest();
|
|
26
|
+
const currentVersion = getPluginVersion();
|
|
71
27
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
process.stderr.write('[code-graph] Empty index detected, running initial indexing...\n');
|
|
80
|
-
const result = execFileSync(BIN, ['incremental-index', '--quiet'], {
|
|
81
|
-
timeout: 15000, // 15s max (SessionStart hook has 20s budget)
|
|
82
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
-
}).toString().trim();
|
|
84
|
-
if (result) process.stderr.write(`[code-graph] ${result}\n`);
|
|
85
|
-
// Re-run health check to update statusline with new counts
|
|
86
|
-
try {
|
|
87
|
-
const out2 = execFileSync(BIN, ['health-check', '--format', 'oneline'], {
|
|
88
|
-
timeout: 2000, stdio: ['pipe', 'pipe', 'pipe']
|
|
89
|
-
}).toString().trim();
|
|
90
|
-
if (out2) process.stdout.write(`\n${out2}`);
|
|
91
|
-
} catch { /* ok */ }
|
|
92
|
-
} catch (e) {
|
|
93
|
-
process.stderr.write(`[code-graph] Auto-index failed: ${e.message || e}\n`);
|
|
94
|
-
}
|
|
28
|
+
if (!manifest.version) {
|
|
29
|
+
install();
|
|
30
|
+
return 'installed';
|
|
31
|
+
}
|
|
32
|
+
if (manifest.version !== currentVersion) {
|
|
33
|
+
update();
|
|
34
|
+
return 'updated';
|
|
95
35
|
}
|
|
36
|
+
return 'noop';
|
|
96
37
|
}
|
|
97
38
|
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
-
}
|
|
39
|
+
function runSessionInit() {
|
|
40
|
+
if (isPluginInactive()) {
|
|
41
|
+
cleanupDisabledStatusline();
|
|
42
|
+
return { inactive: true, lifecycle: 'noop', autoUpdateLaunched: false };
|
|
43
|
+
}
|
|
105
44
|
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const bv = binVersion.split('.').map(Number);
|
|
114
|
-
const pv = pluginVersion.split('.').map(Number);
|
|
115
|
-
const pluginNewer = (pv[0] > bv[0]) ||
|
|
116
|
-
(pv[0] === bv[0] && pv[1] > bv[1]) ||
|
|
117
|
-
(pv[0] === bv[0] && pv[1] === bv[1] && pv[2] > bv[2]);
|
|
118
|
-
if (pluginNewer) {
|
|
119
|
-
process.stderr.write(`[code-graph] Binary v${binVersion} < plugin v${pluginVersion}, updating...\n`);
|
|
120
|
-
let binarySynced = false;
|
|
121
|
-
try {
|
|
122
|
-
execFileSync('npm', ['install', '-g', `@sdsrs/code-graph@${pluginVersion}`], {
|
|
123
|
-
timeout: 30000, stdio: 'pipe'
|
|
124
|
-
});
|
|
125
|
-
// Clear cached binary path so next lookup finds the new binary
|
|
126
|
-
try { fs.unlinkSync(path.join(os.homedir(), '.cache', 'code-graph', 'binary-path')); } catch {}
|
|
127
|
-
process.stderr.write(`[code-graph] Binary updated to v${pluginVersion}\n`);
|
|
128
|
-
binarySynced = true;
|
|
129
|
-
} catch {
|
|
130
|
-
process.stderr.write(
|
|
131
|
-
`[code-graph] Auto-update failed. Run: npm install -g @sdsrs/code-graph@${pluginVersion}\n`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
if (binarySynced) {
|
|
135
|
-
// MCP server is still running old binary — prompt user to reconnect
|
|
136
|
-
process.stdout.write(
|
|
137
|
-
`\n\u26A0\uFE0F [code-graph] Binary updated v${binVersion} \u2192 v${pluginVersion}. ` +
|
|
138
|
-
`Run /mcp to reconnect MCP server with new version.\n`
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
} catch { /* version check failed — not critical */ }
|
|
144
|
-
}
|
|
45
|
+
const conflict = checkScopeConflict();
|
|
46
|
+
if (conflict) {
|
|
47
|
+
process.stderr.write(
|
|
48
|
+
`[code-graph] Warning: conflicting install detected — ${conflict.existingId} (${conflict.scope || 'unknown'} scope). ` +
|
|
49
|
+
`Use /plugin to remove one to avoid config conflicts.\n`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
145
52
|
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
process.stderr.write(
|
|
150
|
-
`[code-graph] Warning: conflicting install detected — ${conflict.existingId} (${conflict.scope || 'unknown'} scope). ` +
|
|
151
|
-
`Use /plugin to remove one to avoid config conflicts.\n`
|
|
152
|
-
);
|
|
53
|
+
const lifecycle = syncLifecycleConfig();
|
|
54
|
+
const autoUpdateLaunched = launchBackgroundAutoUpdate();
|
|
55
|
+
return { inactive: false, lifecycle, autoUpdateLaunched };
|
|
153
56
|
}
|
|
154
57
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
58
|
+
module.exports = {
|
|
59
|
+
launchBackgroundAutoUpdate,
|
|
60
|
+
syncLifecycleConfig,
|
|
61
|
+
runSessionInit,
|
|
62
|
+
};
|
|
158
63
|
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
} else if (manifest.version !== currentVersion) {
|
|
162
|
-
update();
|
|
64
|
+
if (require.main === module) {
|
|
65
|
+
runSessionInit();
|
|
163
66
|
}
|
|
164
|
-
|
|
165
|
-
// --- 4. Auto-update (throttled, non-blocking) ---
|
|
166
|
-
(async () => {
|
|
167
|
-
const result = await checkForUpdate();
|
|
168
|
-
if (result && result.updated) {
|
|
169
|
-
process.stderr.write(`[code-graph] Updated: v${result.from} \u2192 v${result.to}\n`);
|
|
170
|
-
process.stdout.write(
|
|
171
|
-
`\n\uD83D\uDD04 [code-graph] Auto-updated v${result.from} \u2192 v${result.to}. ` +
|
|
172
|
-
`Run /mcp to use the new version.\n`
|
|
173
|
-
);
|
|
174
|
-
} else if (result && result.updateAvailable) {
|
|
175
|
-
process.stderr.write(
|
|
176
|
-
`[code-graph] Update available: v${result.from} \u2192 v${result.to}. ` +
|
|
177
|
-
`Run: npx @sdsrs/code-graph@latest\n`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
})();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const test = require('node:test');
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
|
|
5
|
+
const { launchBackgroundAutoUpdate, syncLifecycleConfig } = require('./session-init');
|
|
6
|
+
|
|
7
|
+
test('syncLifecycleConfig is exported as a callable helper', () => {
|
|
8
|
+
assert.equal(typeof syncLifecycleConfig, 'function');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('launchBackgroundAutoUpdate spawns detached silent updater', () => {
|
|
12
|
+
const calls = [];
|
|
13
|
+
|
|
14
|
+
const ok = launchBackgroundAutoUpdate((command, args, options) => {
|
|
15
|
+
const record = { command, args, options, unrefCalled: false };
|
|
16
|
+
calls.push(record);
|
|
17
|
+
return {
|
|
18
|
+
unref() {
|
|
19
|
+
record.unrefCalled = true;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}, { HOME: '/tmp/fake-home' });
|
|
23
|
+
|
|
24
|
+
assert.equal(ok, true);
|
|
25
|
+
assert.equal(calls.length, 1);
|
|
26
|
+
assert.equal(calls[0].command, process.execPath);
|
|
27
|
+
assert.match(calls[0].args[0], /auto-update\.js$/);
|
|
28
|
+
assert.equal(calls[0].args[1], 'check');
|
|
29
|
+
assert.equal(calls[0].args[2], '--silent');
|
|
30
|
+
assert.equal(calls[0].options.detached, true);
|
|
31
|
+
assert.equal(calls[0].options.stdio, 'ignore');
|
|
32
|
+
assert.equal(calls[0].options.env.CODE_GRAPH_AUTO_UPDATE_SILENT, '1');
|
|
33
|
+
assert.equal(calls[0].unrefCalled, true);
|
|
34
|
+
});
|
|
35
|
+
|
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
const { execFileSync } = require('child_process');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const { readRegistry } = require('./lifecycle');
|
|
10
|
+
const { cleanupDisabledStatusline, readRegistry } = require('./lifecycle');
|
|
11
11
|
|
|
12
12
|
const SEPARATOR = ' \x1b[2m|\x1b[0m ';
|
|
13
13
|
|
|
14
|
+
const disabledCleanup = cleanupDisabledStatusline();
|
|
15
|
+
if (disabledCleanup.cleaned) process.exit(0);
|
|
16
|
+
|
|
14
17
|
// Collect stdin (Claude Code pipes JSON context)
|
|
15
18
|
let stdinData = '';
|
|
16
19
|
let ran = false;
|
|
@@ -4,6 +4,10 @@ const { execFileSync } = require('child_process');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { findBinary } = require('./find-binary');
|
|
7
|
+
const { cleanupDisabledStatusline } = require('./lifecycle');
|
|
8
|
+
|
|
9
|
+
const disabledCleanup = cleanupDisabledStatusline();
|
|
10
|
+
if (disabledCleanup.cleaned) process.exit(0);
|
|
7
11
|
|
|
8
12
|
// Only show status in projects that have a code-graph index.
|
|
9
13
|
// The statusLine config is global, so we must exit silently for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.28",
|
|
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": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"node": ">=16"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@sdsrs/code-graph-linux-x64": "0.5.
|
|
37
|
-
"@sdsrs/code-graph-linux-arm64": "0.5.
|
|
38
|
-
"@sdsrs/code-graph-darwin-x64": "0.5.
|
|
39
|
-
"@sdsrs/code-graph-darwin-arm64": "0.5.
|
|
40
|
-
"@sdsrs/code-graph-win32-x64": "0.5.
|
|
36
|
+
"@sdsrs/code-graph-linux-x64": "0.5.28",
|
|
37
|
+
"@sdsrs/code-graph-linux-arm64": "0.5.28",
|
|
38
|
+
"@sdsrs/code-graph-darwin-x64": "0.5.28",
|
|
39
|
+
"@sdsrs/code-graph-darwin-arm64": "0.5.28",
|
|
40
|
+
"@sdsrs/code-graph-win32-x64": "0.5.28"
|
|
41
41
|
}
|
|
42
42
|
}
|