@sdsrs/code-graph 0.94.0 → 0.95.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.
Files changed (30) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/claude-plugin/scripts/auto-update.js +69 -9
  3. package/package.json +8 -7
  4. package/claude-plugin/scripts/adopt.test.js +0 -679
  5. package/claude-plugin/scripts/auto-update.test.js +0 -515
  6. package/claude-plugin/scripts/cg-answer.test.js +0 -309
  7. package/claude-plugin/scripts/claude-config.test.js +0 -58
  8. package/claude-plugin/scripts/covering-tests.test.js +0 -78
  9. package/claude-plugin/scripts/doctor.test.js +0 -215
  10. package/claude-plugin/scripts/find-binary.test.js +0 -246
  11. package/claude-plugin/scripts/hook-fire.test.js +0 -117
  12. package/claude-plugin/scripts/hooks.test.js +0 -230
  13. package/claude-plugin/scripts/incremental-index.test.js +0 -102
  14. package/claude-plugin/scripts/lifecycle.e2e.test.js +0 -179
  15. package/claude-plugin/scripts/lifecycle.test.js +0 -786
  16. package/claude-plugin/scripts/mcp-launcher.test.js +0 -162
  17. package/claude-plugin/scripts/mcp-stub.test.js +0 -207
  18. package/claude-plugin/scripts/post-grep-inject.test.js +0 -531
  19. package/claude-plugin/scripts/pr-impact-comment.test.js +0 -110
  20. package/claude-plugin/scripts/pre-edit-guide.test.js +0 -218
  21. package/claude-plugin/scripts/pre-grep-guide.test.js +0 -1682
  22. package/claude-plugin/scripts/pre-read-guide.test.js +0 -363
  23. package/claude-plugin/scripts/project-detect.test.js +0 -95
  24. package/claude-plugin/scripts/recommendation-log.test.js +0 -79
  25. package/claude-plugin/scripts/session-init.test.js +0 -479
  26. package/claude-plugin/scripts/statusline-composite.test.js +0 -65
  27. package/claude-plugin/scripts/statusline.test.js +0 -235
  28. package/claude-plugin/scripts/tmp-dir.test.js +0 -50
  29. package/claude-plugin/scripts/user-prompt-context.test.js +0 -743
  30. package/claude-plugin/scripts/version-utils.test.js +0 -141
@@ -1,102 +0,0 @@
1
- 'use strict';
2
- const test = require('node:test');
3
- const assert = require('node:assert/strict');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const os = require('os');
7
- const { spawnSync } = require('child_process');
8
-
9
- const { findBinary } = require('./find-binary');
10
- const { shouldRun } = require('./incremental-index');
11
-
12
- // ── shouldRun gate (v0.21 opt-in flip) ──────────────────
13
-
14
- test('shouldRun: default (no env) is OFF', () => {
15
- // v0.21: hook-driven incremental-index is opt-in. Rely on
16
- // ensure_file_indexed query-time freshness for MCP workflows.
17
- assert.equal(shouldRun({}), false);
18
- });
19
-
20
- test('shouldRun: CODE_GRAPH_HOOK_INDEX=on enables hook (opt-in)', () => {
21
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'on' }), true);
22
- });
23
-
24
- test('shouldRun: CODE_GRAPH_HOOK_INDEX=1 enables hook (truthy alias)', () => {
25
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: '1' }), true);
26
- });
27
-
28
- test('shouldRun: CODE_GRAPH_HOOK_INDEX=true enables hook (truthy alias)', () => {
29
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'true' }), true);
30
- });
31
-
32
- test('shouldRun: CODE_GRAPH_HOOK_INDEX=off keeps hook off (explicit)', () => {
33
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'off' }), false);
34
- });
35
-
36
- test('shouldRun: CODE_GRAPH_HOOK_INDEX with empty string is OFF', () => {
37
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: '' }), false);
38
- });
39
-
40
- test('shouldRun: CODE_GRAPH_HOOK_INDEX is case-insensitive', () => {
41
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'ON' }), true);
42
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'On' }), true);
43
- assert.equal(shouldRun({ CODE_GRAPH_HOOK_INDEX: 'TRUE' }), true);
44
- });
45
-
46
- test('hook script default-env spawn does not invoke the binary (default OFF)', () => {
47
- // End-to-end check: with the v0.21 opt-in flip, a default-env spawn of
48
- // incremental-index.js exits 0 immediately without touching the binary.
49
- const script = path.join(__dirname, 'incremental-index.js');
50
- const cleanEnv = { ...process.env };
51
- delete cleanEnv.CODE_GRAPH_HOOK_INDEX;
52
- const t0 = Date.now();
53
- const proc = spawnSync(process.execPath, [script], {
54
- env: cleanEnv,
55
- encoding: 'utf8',
56
- timeout: 2000,
57
- stdio: ['pipe', 'pipe', 'pipe'],
58
- });
59
- // Should be much faster than the 80ms+ cold-start of running the binary.
60
- // 500ms is generous — actual is ~30-50ms node startup.
61
- assert.equal(proc.status, 0, `expected exit 0, got ${proc.status}; stderr: ${proc.stderr}`);
62
- assert.equal(proc.stdout, '', 'stdout must be empty when default-OFF');
63
- assert.ok(Date.now() - t0 < 500, 'default-OFF must short-circuit fast (< 500ms)');
64
- });
65
-
66
- test('incremental-index bails silently when cwd is not a git repo', (t) => {
67
- const bin = findBinary();
68
- if (!bin) {
69
- // Binary not built — skip rather than fail; matches session-init.test.js convention.
70
- return;
71
- }
72
- const tmpRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-no-git-')));
73
- t.after(() => fs.rmSync(tmpRoot, { recursive: true, force: true }));
74
-
75
- const result = spawnSync(bin, ['incremental-index', '--quiet'], {
76
- cwd: tmpRoot,
77
- timeout: 8000,
78
- stdio: ['pipe', 'pipe', 'pipe'],
79
- });
80
- assert.equal(result.status, 0, `expected exit 0, got ${result.status}; stderr: ${result.stderr}`);
81
- assert.equal(
82
- fs.existsSync(path.join(tmpRoot, '.code-graph')),
83
- false,
84
- '.code-graph must not be created outside a git repo',
85
- );
86
- });
87
-
88
- test('incremental-index runs inside a minimal git repo without creating stray state', (t) => {
89
- const bin = findBinary();
90
- if (!bin) return;
91
- const tmpRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-git-')));
92
- t.after(() => fs.rmSync(tmpRoot, { recursive: true, force: true }));
93
-
94
- fs.mkdirSync(path.join(tmpRoot, '.git'));
95
- const result = spawnSync(bin, ['incremental-index', '--quiet'], {
96
- cwd: tmpRoot,
97
- timeout: 8000,
98
- stdio: ['pipe', 'pipe', 'pipe'],
99
- });
100
- assert.equal(result.status, 0, `expected exit 0, got ${result.status}; stderr: ${result.stderr}`);
101
- // Index may or may not materialize for an empty repo; the contract is that the guard does NOT block this case.
102
- });
@@ -1,179 +0,0 @@
1
- 'use strict';
2
- const test = require('node:test');
3
- const assert = require('node:assert/strict');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
7
- const { execFileSync } = require('child_process');
8
-
9
- const repoRoot = path.resolve(__dirname, '..', '..');
10
- const pluginRoot = path.resolve(__dirname, '..');
11
- const lifecycleCli = path.join(__dirname, 'lifecycle.js');
12
- const compositeCli = path.join(__dirname, 'statusline-composite.js');
13
- const currentVersion = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).version;
14
-
15
- function mkHome(t) {
16
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-e2e-'));
17
- t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
18
- return dir;
19
- }
20
-
21
- function writeJson(filePath, value) {
22
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
23
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n');
24
- }
25
-
26
- function readJson(filePath) {
27
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
28
- }
29
-
30
- function runScript(homeDir, scriptPath, args = [], options = {}) {
31
- const env = { ...process.env, HOME: homeDir };
32
- // Do NOT set CLAUDE_PLUGIN_ROOT — lifecycle.js derives PLUGIN_ROOT from __dirname
33
- // to avoid env var leakage from other plugins in shared hook execution context.
34
- delete env.CLAUDE_PLUGIN_ROOT;
35
- return execFileSync(process.execPath, [scriptPath, ...args], {
36
- cwd: options.cwd || repoRoot,
37
- env,
38
- input: options.input,
39
- stdio: ['pipe', 'pipe', 'pipe'],
40
- }).toString();
41
- }
42
-
43
- test('lifecycle CLI handles install, disable self-heal, re-enable, and uninstall', (t) => {
44
- const homeDir = mkHome(t);
45
- const settingsPath = path.join(homeDir, '.claude', 'settings.json');
46
- const installedPath = path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json');
47
- const registryPath = path.join(homeDir, '.cache', 'code-graph', 'statusline-registry.json');
48
- const manifestPath = path.join(homeDir, '.cache', 'code-graph', 'install-manifest.json');
49
- const cacheDir = path.join(homeDir, '.cache', 'code-graph');
50
-
51
- writeJson(settingsPath, {
52
- statusLine: { type: 'command', command: 'echo previous-status' },
53
- enabledPlugins: { 'code-graph-mcp@code-graph-mcp': true },
54
- });
55
- writeJson(installedPath, {
56
- plugins: {
57
- 'code-graph-mcp@code-graph-mcp': [{
58
- installPath: pluginRoot,
59
- version: currentVersion,
60
- scope: 'user',
61
- }],
62
- },
63
- });
64
-
65
- runScript(homeDir, lifecycleCli, ['install']);
66
- let settings = readJson(settingsPath);
67
- let registry = readJson(registryPath);
68
- let manifest = readJson(manifestPath);
69
- assert.match(settings.statusLine.command, /statusline-composite\.js/);
70
- assert.equal(registry[0].id, '_previous');
71
- assert.equal(registry[1].id, 'code-graph');
72
- assert.equal(manifest.version, currentVersion);
73
-
74
- settings.enabledPlugins['code-graph-mcp@code-graph-mcp'] = false;
75
- writeJson(settingsPath, settings);
76
- runScript(homeDir, compositeCli, [], { input: '{}' });
77
- settings = readJson(settingsPath);
78
- assert.equal(settings.statusLine.command, 'echo previous-status');
79
- assert.equal(fs.existsSync(registryPath), false);
80
-
81
- settings.enabledPlugins['code-graph-mcp@code-graph-mcp'] = true;
82
- writeJson(settingsPath, settings);
83
- runScript(homeDir, lifecycleCli, ['install']);
84
- settings = readJson(settingsPath);
85
- registry = readJson(registryPath);
86
- assert.match(settings.statusLine.command, /statusline-composite\.js/);
87
- assert.equal(registry.length, 2);
88
-
89
- runScript(homeDir, lifecycleCli, ['uninstall']);
90
- settings = readJson(settingsPath);
91
- const installed = readJson(installedPath);
92
- assert.equal(settings.statusLine.command, 'echo previous-status');
93
- assert.deepEqual(settings.enabledPlugins, {});
94
- assert.deepEqual(installed.plugins, {});
95
- assert.equal(fs.existsSync(cacheDir), false);
96
- });
97
-
98
- test('lifecycle install writes to CLAUDE_CONFIG_DIR instead of ~/.claude when set', (t) => {
99
- // Multi-account isolation: a user with CLAUDE_CONFIG_DIR=~/work-claude
100
- // expects all plugin config (settings.json, installed_plugins.json,
101
- // statusline-providers backup) to land under that directory, not the
102
- // default ~/.claude. Default path must remain untouched.
103
- const homeDir = mkHome(t);
104
- const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-cfgdir-'));
105
- t.after(() => fs.rmSync(configDir, { recursive: true, force: true }));
106
-
107
- const cfgSettings = path.join(configDir, 'settings.json');
108
- const cfgInstalled = path.join(configDir, 'plugins', 'installed_plugins.json');
109
- const cfgBackup = path.join(configDir, 'statusline-providers.json');
110
- const defaultSettings = path.join(homeDir, '.claude', 'settings.json');
111
-
112
- writeJson(cfgSettings, {
113
- statusLine: { type: 'command', command: 'echo prior-work-status' },
114
- enabledPlugins: { 'code-graph-mcp@code-graph-mcp': true },
115
- });
116
- writeJson(cfgInstalled, {
117
- plugins: {
118
- 'code-graph-mcp@code-graph-mcp': [{
119
- installPath: pluginRoot,
120
- version: currentVersion,
121
- scope: 'user',
122
- }],
123
- },
124
- });
125
-
126
- // Run install with CLAUDE_CONFIG_DIR set; HOME points elsewhere.
127
- const env = { ...process.env, HOME: homeDir, CLAUDE_CONFIG_DIR: configDir };
128
- delete env.CLAUDE_PLUGIN_ROOT;
129
- execFileSync(process.execPath, [lifecycleCli, 'install'], {
130
- cwd: repoRoot, env, stdio: ['pipe', 'pipe', 'pipe'],
131
- });
132
-
133
- // Config landed in the override dir...
134
- const settings = readJson(cfgSettings);
135
- assert.match(settings.statusLine.command, /statusline-composite\.js/);
136
- assert.equal(fs.existsSync(cfgBackup), true,
137
- 'statusline-providers backup should land in CLAUDE_CONFIG_DIR');
138
-
139
- // ...and default ~/.claude was never touched.
140
- assert.equal(fs.existsSync(defaultSettings), false,
141
- 'default ~/.claude/settings.json must not be written when override is set');
142
- });
143
-
144
- test('composite expands a leading ~ in a _previous command instead of dropping it (issue #24)', (t) => {
145
- // A user whose prior statusline used a leading ~ (valid in settings.json, which
146
- // Claude Code runs through a shell). install() captures it verbatim as _previous.
147
- // The composite runs providers via execFileSync (no shell), so without tilde
148
- // expansion the command throws ENOENT and is silently swallowed — the user's
149
- // original statusline vanishes.
150
- const homeDir = mkHome(t);
151
- const prevScript = path.join(homeDir, '.claude', 'utils', 'statusline.sh');
152
- fs.mkdirSync(path.dirname(prevScript), { recursive: true });
153
- fs.writeFileSync(prevScript, '#!/bin/sh\necho "PREV-STATUSLINE-OK"\n');
154
- fs.chmodSync(prevScript, 0o755);
155
-
156
- const registryPath = path.join(homeDir, '.cache', 'code-graph', 'statusline-registry.json');
157
- writeJson(registryPath, [
158
- { id: '_previous', command: '~/.claude/utils/statusline.sh', needsStdin: true },
159
- ]);
160
-
161
- const out = runScript(homeDir, compositeCli, [], { input: '{}' });
162
- assert.match(out, /PREV-STATUSLINE-OK/,
163
- 'a _previous command using a leading ~ must be tilde-expanded, not silently dropped');
164
- });
165
-
166
- test('expandTilde mirrors shell tilde expansion (only a leading ~ / ~/)', () => {
167
- const composite = require('./statusline-composite');
168
- const home = os.homedir();
169
- assert.equal(composite.expandTilde('~'), home);
170
- assert.equal(composite.expandTilde('~/.claude/utils/statusline.sh'),
171
- path.join(home, '.claude', 'utils', 'statusline.sh'));
172
- assert.equal(composite.expandTilde('/abs/path/script.sh'), '/abs/path/script.sh');
173
- assert.equal(composite.expandTilde('node'), 'node');
174
- assert.equal(composite.expandTilde('~user/script.sh'), '~user/script.sh',
175
- 'other-user home dirs are not resolved');
176
- assert.equal(composite.expandTilde('a~/b'), 'a~/b',
177
- 'only a leading ~ expands, not a mid-string ~');
178
- });
179
-