@sdsrs/code-graph
Advanced tools
| 'use strict'; | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| // Resolve Claude Code's config directory. Honors CLAUDE_CONFIG_DIR — when set | ||
| // (commonly used to keep multiple accounts isolated, e.g. personal vs work), | ||
| // Claude Code reads `settings.json`, `plugins/`, `projects/`, etc. from there | ||
| // instead of `~/.claude/`. Our plugin must follow the same override so its | ||
| // hook registrations, statusline, adoption files, and cache cleanup land in | ||
| // the directory Claude Code is actually using. | ||
| // | ||
| // Read fresh on every call so per-process env mutation (tests, child procs | ||
| // spawned with a different env) takes effect immediately. Unlike | ||
| // CLAUDE_PLUGIN_ROOT (which leaks across plugins — see | ||
| // feedback_plugin_env_isolation.md), CLAUDE_CONFIG_DIR is user-set and | ||
| // process-wide, so reading it is safe. | ||
| function claudeHome() { | ||
| return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'); | ||
| } | ||
| module.exports = { claudeHome }; |
| 'use strict'; | ||
| const test = require('node:test'); | ||
| const assert = require('node:assert'); | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| const { claudeHome } = require('./claude-config'); | ||
| test('claudeHome defaults to ~/.claude when CLAUDE_CONFIG_DIR unset', () => { | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| delete process.env.CLAUDE_CONFIG_DIR; | ||
| try { | ||
| assert.strictEqual(claudeHome(), path.join(os.homedir(), '.claude')); | ||
| } finally { | ||
| if (prev !== undefined) process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); | ||
| test('claudeHome honors CLAUDE_CONFIG_DIR when set', () => { | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| process.env.CLAUDE_CONFIG_DIR = '/tmp/work-claude'; | ||
| try { | ||
| assert.strictEqual(claudeHome(), '/tmp/work-claude'); | ||
| } finally { | ||
| if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; | ||
| else process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); | ||
| test('claudeHome re-reads env on every call (not cached)', () => { | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| delete process.env.CLAUDE_CONFIG_DIR; | ||
| try { | ||
| const before = claudeHome(); | ||
| process.env.CLAUDE_CONFIG_DIR = '/tmp/account-A'; | ||
| const during = claudeHome(); | ||
| delete process.env.CLAUDE_CONFIG_DIR; | ||
| const after = claudeHome(); | ||
| assert.strictEqual(before, path.join(os.homedir(), '.claude')); | ||
| assert.strictEqual(during, '/tmp/account-A'); | ||
| assert.strictEqual(after, path.join(os.homedir(), '.claude')); | ||
| } finally { | ||
| if (prev !== undefined) process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); | ||
| test('claudeHome ignores empty CLAUDE_CONFIG_DIR (falls back to ~/.claude)', () => { | ||
| // Empty string is falsy in JS — sanity-check the `||` fallback path so an | ||
| // accidentally `CLAUDE_CONFIG_DIR=` (unset-style) shell line does not strand | ||
| // us writing to the literal repository root `/`. | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| process.env.CLAUDE_CONFIG_DIR = ''; | ||
| try { | ||
| assert.strictEqual(claudeHome(), path.join(os.homedir(), '.claude')); | ||
| } finally { | ||
| if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; | ||
| else process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); |
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "0.30.0", | ||
| "version": "0.31.0", | ||
| "keywords": [ | ||
@@ -10,0 +10,0 @@ "code-graph", |
@@ -272,5 +272,10 @@ #!/usr/bin/env node | ||
| // `/home/sds/.claude/x` → `-home-sds--claude-x` (double-dash from `/.`) | ||
| // | ||
| // `home` is the OS home dir (default `os.homedir()`). When `CLAUDE_CONFIG_DIR` | ||
| // is set it overrides `home/.claude`, so multi-account users (personal vs work) | ||
| // land in the directory Claude Code itself is using for `projects/`. | ||
| function memoryDir(cwd = process.cwd(), home = os.homedir()) { | ||
| const slug = cwd.replace(/[^a-zA-Z0-9-]/g, '-'); | ||
| return path.join(home, '.claude', 'projects', slug, 'memory'); | ||
| const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(home, '.claude'); | ||
| return path.join(claudeDir, 'projects', slug, 'memory'); | ||
| } | ||
@@ -425,5 +430,13 @@ | ||
| // (见 feedback_plugin_env_isolation.md)。 | ||
| // 默认匹配 `.claude/plugins/` 路径;CLAUDE_CONFIG_DIR 自定义目录时 | ||
| // 走 startsWith(CLAUDE_CONFIG_DIR/plugins/) 兜底。 | ||
| function isPluginModeInstall(scriptPath = __dirname) { | ||
| const sep = path.sep; | ||
| return scriptPath.includes(`${sep}.claude${sep}plugins${sep}`); | ||
| if (scriptPath.includes(`${sep}.claude${sep}plugins${sep}`)) return true; | ||
| const envDir = process.env.CLAUDE_CONFIG_DIR; | ||
| if (envDir) { | ||
| const marker = path.join(envDir, 'plugins') + sep; | ||
| if (scriptPath.startsWith(marker)) return true; | ||
| } | ||
| return false; | ||
| } | ||
@@ -430,0 +443,0 @@ |
@@ -281,2 +281,40 @@ 'use strict'; | ||
| test('memoryDir honors CLAUDE_CONFIG_DIR override (multi-account isolation)', () => { | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| process.env.CLAUDE_CONFIG_DIR = '/home/alice/work-claude'; | ||
| try { | ||
| // home arg is irrelevant when env var is set — projects live under the | ||
| // configured claude dir, not home/.claude. | ||
| assert.strictEqual( | ||
| memoryDir('/home/alice/proj', '/home/alice'), | ||
| '/home/alice/work-claude/projects/-home-alice-proj/memory' | ||
| ); | ||
| } finally { | ||
| if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; | ||
| else process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); | ||
| test('isPluginModeInstall recognizes CLAUDE_CONFIG_DIR/plugins/... paths', () => { | ||
| const prev = process.env.CLAUDE_CONFIG_DIR; | ||
| process.env.CLAUDE_CONFIG_DIR = '/home/alice/work-claude'; | ||
| try { | ||
| const pluginPath = '/home/alice/work-claude/plugins/cache/code-graph-mcp@0.31.0/scripts'; | ||
| assert.strictEqual(isPluginModeInstall(pluginPath), true); | ||
| // Legacy ~/.claude/plugins/ path still works even with env var set. | ||
| assert.strictEqual( | ||
| isPluginModeInstall('/home/user/.claude/plugins/cache/code-graph-mcp/scripts'), | ||
| true | ||
| ); | ||
| // Unrelated path under same prefix is still rejected. | ||
| assert.strictEqual( | ||
| isPluginModeInstall('/home/alice/work-claude/projects/foo/memory'), | ||
| false | ||
| ); | ||
| } finally { | ||
| if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; | ||
| else process.env.CLAUDE_CONFIG_DIR = prev; | ||
| } | ||
| }); | ||
| test('maybeAutoAdopt skips when CODE_GRAPH_NO_AUTO_ADOPT=1', () => { | ||
@@ -283,0 +321,0 @@ const sb = makeSandbox(); |
@@ -8,3 +8,3 @@ #!/usr/bin/env node | ||
| const os = require('os'); | ||
| const { CACHE_DIR, PLUGIN_ID, MARKETPLACE_NAME, readManifest, readJson, writeJsonAtomic } = require('./lifecycle'); | ||
| const { CACHE_DIR, PLUGIN_ID, MARKETPLACE_NAME, readManifest, readJson, writeJsonAtomic, installedPluginsPath, pluginsCacheDir } = require('./lifecycle'); | ||
| const { clearCache: clearBinaryCache } = require('./find-binary'); | ||
@@ -276,3 +276,3 @@ const { readBinaryVersion, isDevMode } = require('./version-utils'); | ||
| const pluginDst = path.join( | ||
| os.homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_NAME, 'code-graph-mcp', latest.version | ||
| pluginsCacheDir(), MARKETPLACE_NAME, 'code-graph-mcp', latest.version | ||
| ); | ||
@@ -287,3 +287,3 @@ | ||
| // Update installed_plugins.json to point to new version | ||
| const installedPath = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json'); | ||
| const installedPath = installedPluginsPath(); | ||
| try { | ||
@@ -290,0 +290,0 @@ const installed = readJson(installedPath); |
@@ -11,2 +11,3 @@ #!/usr/bin/env node | ||
| removeHooksFromSettings, isOurHookEntry, writeJsonAtomic, | ||
| settingsPath, | ||
| } = require('./lifecycle'); | ||
@@ -194,4 +195,3 @@ const { findBinary, clearCache: clearBinaryCache } = require('./find-binary'); | ||
| try { | ||
| const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json'); | ||
| const settings = readJson(SETTINGS_PATH) || {}; | ||
| const settings = readJson(settingsPath()) || {}; | ||
| const legacyCount = countLegacyHookEntries(settings); | ||
@@ -382,6 +382,6 @@ if (legacyCount === 0) { | ||
| console.log('\n Removing legacy code-graph hooks from settings.json...'); | ||
| const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json'); | ||
| const settings = readJson(SETTINGS_PATH) || {}; | ||
| const settingsFile = settingsPath(); | ||
| const settings = readJson(settingsFile) || {}; | ||
| if (removeHooksFromSettings(settings)) { | ||
| writeJsonAtomic(SETTINGS_PATH, settings); | ||
| writeJsonAtomic(settingsFile, settings); | ||
| console.log(' \u2705 settings.json cleaned — restart Claude Code to apply'); | ||
@@ -388,0 +388,0 @@ fixed++; |
@@ -98,1 +98,47 @@ 'use strict'; | ||
| test('lifecycle install writes to CLAUDE_CONFIG_DIR instead of ~/.claude when set', (t) => { | ||
| // Multi-account isolation: a user with CLAUDE_CONFIG_DIR=~/work-claude | ||
| // expects all plugin config (settings.json, installed_plugins.json, | ||
| // statusline-providers backup) to land under that directory, not the | ||
| // default ~/.claude. Default path must remain untouched. | ||
| const homeDir = mkHome(t); | ||
| const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-graph-cfgdir-')); | ||
| t.after(() => fs.rmSync(configDir, { recursive: true, force: true })); | ||
| const cfgSettings = path.join(configDir, 'settings.json'); | ||
| const cfgInstalled = path.join(configDir, 'plugins', 'installed_plugins.json'); | ||
| const cfgBackup = path.join(configDir, 'statusline-providers.json'); | ||
| const defaultSettings = path.join(homeDir, '.claude', 'settings.json'); | ||
| writeJson(cfgSettings, { | ||
| statusLine: { type: 'command', command: 'echo prior-work-status' }, | ||
| enabledPlugins: { 'code-graph-mcp@code-graph-mcp': true }, | ||
| }); | ||
| writeJson(cfgInstalled, { | ||
| plugins: { | ||
| 'code-graph-mcp@code-graph-mcp': [{ | ||
| installPath: pluginRoot, | ||
| version: currentVersion, | ||
| scope: 'user', | ||
| }], | ||
| }, | ||
| }); | ||
| // Run install with CLAUDE_CONFIG_DIR set; HOME points elsewhere. | ||
| const env = { ...process.env, HOME: homeDir, CLAUDE_CONFIG_DIR: configDir }; | ||
| delete env.CLAUDE_PLUGIN_ROOT; | ||
| execFileSync(process.execPath, [lifecycleCli, 'install'], { | ||
| cwd: repoRoot, env, stdio: ['pipe', 'pipe', 'pipe'], | ||
| }); | ||
| // Config landed in the override dir... | ||
| const settings = readJson(cfgSettings); | ||
| assert.match(settings.statusLine.command, /statusline-composite\.js/); | ||
| assert.equal(fs.existsSync(cfgBackup), true, | ||
| 'statusline-providers backup should land in CLAUDE_CONFIG_DIR'); | ||
| // ...and default ~/.claude was never touched. | ||
| assert.equal(fs.existsSync(defaultSettings), false, | ||
| 'default ~/.claude/settings.json must not be written when override is set'); | ||
| }); | ||
@@ -6,2 +6,3 @@ #!/usr/bin/env node | ||
| const os = require('os'); | ||
| const { claudeHome } = require('./claude-config'); | ||
@@ -20,9 +21,14 @@ const PLUGIN_ID = 'code-graph-mcp@code-graph-mcp'; | ||
| const MANIFEST_FILE = path.join(CACHE_DIR, 'install-manifest.json'); | ||
| const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json'); | ||
| const INSTALLED_PLUGINS_PATH = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json'); | ||
| const REGISTRY_FILE = path.join(CACHE_DIR, 'statusline-registry.json'); | ||
| // Lazy resolvers — Claude Code's config dir can be overridden by CLAUDE_CONFIG_DIR | ||
| // (multi-account isolation). Re-read every call so test subprocesses with a | ||
| // different env see the right path. | ||
| function settingsPath() { return path.join(claudeHome(), 'settings.json'); } | ||
| function installedPluginsPath() { return path.join(claudeHome(), 'plugins', 'installed_plugins.json'); } | ||
| // Durable mirror outside ~/.cache/ — survives cache cleanup. Captures the | ||
| // `_previous` snapshot (pre-install statusline) and any third-party providers | ||
| // (GSD, etc.). readRegistry() self-heals from this file when primary is missing. | ||
| const PROVIDERS_BACKUP_FILE = path.join(os.homedir(), '.claude', 'statusline-providers.json'); | ||
| function providersBackupFile() { return path.join(claudeHome(), 'statusline-providers.json'); } | ||
| function pluginsCacheDir() { return path.join(claudeHome(), 'plugins', 'cache'); } | ||
@@ -70,3 +76,3 @@ // --- Helpers --- | ||
| function hasInstalledPluginRecord() { | ||
| const installed = readJson(INSTALLED_PLUGINS_PATH); | ||
| const installed = readJson(installedPluginsPath()); | ||
| return !!(installed && installed.plugins && Array.isArray(installed.plugins[PLUGIN_ID]) && installed.plugins[PLUGIN_ID].length > 0); | ||
@@ -89,3 +95,3 @@ } | ||
| // Durable backup in ~/.claude/ retains `_previous` + third-party providers. | ||
| const backup = readJson(PROVIDERS_BACKUP_FILE); | ||
| const backup = readJson(providersBackupFile()); | ||
| if (backup && Array.isArray(backup) && backup.length > 0) { | ||
@@ -101,3 +107,3 @@ try { writeJsonAtomic(REGISTRY_FILE, backup); } catch { /* ok */ } | ||
| try { fs.unlinkSync(REGISTRY_FILE); } catch { /* ok */ } | ||
| try { fs.unlinkSync(PROVIDERS_BACKUP_FILE); } catch { /* ok */ } | ||
| try { fs.unlinkSync(providersBackupFile()); } catch { /* ok */ } | ||
| return; | ||
@@ -108,3 +114,3 @@ } | ||
| // or third-party provider entries. | ||
| try { writeJsonAtomic(PROVIDERS_BACKUP_FILE, registry); } catch { /* ok */ } | ||
| try { writeJsonAtomic(providersBackupFile(), registry); } catch { /* ok */ } | ||
| } | ||
@@ -135,7 +141,7 @@ | ||
| function isPluginExplicitlyDisabled(settings = readJson(SETTINGS_PATH) || {}) { | ||
| function isPluginExplicitlyDisabled(settings = readJson(settingsPath()) || {}) { | ||
| return hasOwn(settings.enabledPlugins, PLUGIN_ID) && settings.enabledPlugins[PLUGIN_ID] === false; | ||
| } | ||
| function isPluginInactive(settings = readJson(SETTINGS_PATH) || {}) { | ||
| function isPluginInactive(settings = readJson(settingsPath()) || {}) { | ||
| if (isPluginExplicitlyDisabled(settings)) return true; | ||
@@ -147,3 +153,3 @@ | ||
| const installed = readJson(INSTALLED_PLUGINS_PATH); | ||
| const installed = readJson(installedPluginsPath()); | ||
| if (!installed || !installed.plugins) return false; | ||
@@ -176,3 +182,3 @@ return !hasInstalledPluginRecord(); | ||
| function cleanupDisabledStatusline() { | ||
| const settings = readJson(SETTINGS_PATH); | ||
| const settings = readJson(settingsPath()); | ||
| if (!settings || !isPluginInactive(settings)) { | ||
@@ -185,3 +191,3 @@ return { cleaned: false, settingsChanged: false }; | ||
| if (settingsChanged) { | ||
| writeJsonAtomic(SETTINGS_PATH, settings); | ||
| writeJsonAtomic(settingsPath(), settings); | ||
| } | ||
@@ -195,3 +201,3 @@ | ||
| function checkScopeConflict() { | ||
| const installed = readJson(INSTALLED_PLUGINS_PATH); | ||
| const installed = readJson(installedPluginsPath()); | ||
| if (!installed || !installed.plugins) return null; | ||
@@ -221,6 +227,6 @@ for (const [key, entries] of Object.entries(installed.plugins)) { | ||
| // Clean old ID from installed_plugins.json | ||
| const installed = readJson(INSTALLED_PLUGINS_PATH); | ||
| const installed = readJson(installedPluginsPath()); | ||
| if (installed && installed.plugins && oldId in installed.plugins) { | ||
| delete installed.plugins[oldId]; | ||
| writeJsonAtomic(INSTALLED_PLUGINS_PATH, installed); | ||
| writeJsonAtomic(installedPluginsPath(), installed); | ||
| } | ||
@@ -240,6 +246,7 @@ } | ||
| // Clean old cache paths | ||
| const cacheRoot = pluginsCacheDir(); | ||
| const oldCacheDirs = [ | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', 'sdsrss', 'code-graph'), | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', 'sdsrss-code-graph', 'code-graph'), | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', 'sdsrss-code-graph'), | ||
| path.join(cacheRoot, 'sdsrss', 'code-graph'), | ||
| path.join(cacheRoot, 'sdsrss-code-graph', 'code-graph'), | ||
| path.join(cacheRoot, 'sdsrss-code-graph'), | ||
| ]; | ||
@@ -300,3 +307,3 @@ for (const dir of oldCacheDirs) { | ||
| const manifest = readManifest(); | ||
| const settings = readJson(SETTINGS_PATH) || {}; | ||
| const settings = readJson(settingsPath()) || {}; | ||
| let settingsChanged = false; | ||
@@ -346,3 +353,3 @@ | ||
| if (settingsChanged) { | ||
| writeJsonAtomic(SETTINGS_PATH, settings); | ||
| writeJsonAtomic(settingsPath(), settings); | ||
| } | ||
@@ -362,3 +369,3 @@ | ||
| function uninstall() { | ||
| const settings = readJson(SETTINGS_PATH); | ||
| const settings = readJson(settingsPath()); | ||
| let settingsChanged = false; | ||
@@ -389,3 +396,3 @@ | ||
| if (settingsChanged) { | ||
| writeJsonAtomic(SETTINGS_PATH, settings); | ||
| writeJsonAtomic(settingsPath(), settings); | ||
| } | ||
@@ -395,3 +402,3 @@ } | ||
| // 5. Remove all known IDs from installed_plugins.json | ||
| const installedPlugins = readJson(INSTALLED_PLUGINS_PATH); | ||
| const installedPlugins = readJson(installedPluginsPath()); | ||
| if (installedPlugins && installedPlugins.plugins) { | ||
@@ -405,3 +412,3 @@ let ipChanged = false; | ||
| } | ||
| if (ipChanged) writeJsonAtomic(INSTALLED_PLUGINS_PATH, installedPlugins); | ||
| if (ipChanged) writeJsonAtomic(installedPluginsPath(), installedPlugins); | ||
| } | ||
@@ -413,6 +420,7 @@ | ||
| // 7. Remove plugin files from cache (all known paths, including parent dirs) | ||
| const cacheRoot = pluginsCacheDir(); | ||
| const pluginCacheDirs = [ | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_NAME), | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', 'sdsrss-code-graph'), | ||
| path.join(os.homedir(), '.claude', 'plugins', 'cache', 'sdsrss', 'code-graph'), | ||
| path.join(cacheRoot, MARKETPLACE_NAME), | ||
| path.join(cacheRoot, 'sdsrss-code-graph'), | ||
| path.join(cacheRoot, 'sdsrss', 'code-graph'), | ||
| ]; | ||
@@ -432,3 +440,3 @@ for (const dir of pluginCacheDirs) { | ||
| const oldVersion = manifest.version; | ||
| const settings = readJson(SETTINGS_PATH) || {}; | ||
| const settings = readJson(settingsPath()) || {}; | ||
| let settingsChanged = false; | ||
@@ -462,3 +470,3 @@ | ||
| if (settingsChanged) { | ||
| writeJsonAtomic(SETTINGS_PATH, settings); | ||
| writeJsonAtomic(settingsPath(), settings); | ||
| } | ||
@@ -488,3 +496,3 @@ | ||
| function cleanupOldCacheVersions(keep = 3) { | ||
| const cacheParent = path.join(os.homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_NAME); | ||
| const cacheParent = path.join(pluginsCacheDir(), MARKETPLACE_NAME); | ||
| try { | ||
@@ -524,3 +532,3 @@ // List all subdirectories under the marketplace cache | ||
| function healthCheck() { | ||
| const settings = readJson(SETTINGS_PATH) || {}; | ||
| const settings = readJson(settingsPath()) || {}; | ||
| const issues = []; | ||
@@ -581,3 +589,3 @@ | ||
| PLUGIN_ID, OLD_PLUGIN_IDS, MARKETPLACE_NAME, CACHE_DIR, REGISTRY_FILE, | ||
| PROVIDERS_BACKUP_FILE, | ||
| settingsPath, installedPluginsPath, providersBackupFile, pluginsCacheDir, | ||
| }; | ||
@@ -584,0 +592,0 @@ |
@@ -5,3 +5,2 @@ #!/usr/bin/env node | ||
| const path = require('path'); | ||
| const os = require('os'); | ||
| const fs = require('fs'); | ||
@@ -11,2 +10,3 @@ const { | ||
| cleanupDisabledStatusline, isPluginInactive, readJson, CACHE_DIR, | ||
| settingsPath, | ||
| } = require('./lifecycle'); | ||
@@ -63,3 +63,3 @@ const { readBinaryVersion, isDevMode, getNewestMtime } = require('./version-utils'); | ||
| // install() is idempotent — isOurComposite guard prevents duplicate work. | ||
| const settings = readJson(path.join(os.homedir(), '.claude', 'settings.json')) || {}; | ||
| const settings = readJson(settingsPath()) || {}; | ||
| if (!settings.statusLine || !settings.statusLine.command || | ||
@@ -66,0 +66,0 @@ !settings.statusLine.command.includes('statusline-composite')) { |
+6
-6
| { | ||
| "name": "@sdsrs/code-graph", | ||
| "version": "0.30.0", | ||
| "version": "0.31.0", | ||
| "description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing", | ||
@@ -38,8 +38,8 @@ "license": "MIT", | ||
| "optionalDependencies": { | ||
| "@sdsrs/code-graph-linux-x64": "0.30.0", | ||
| "@sdsrs/code-graph-linux-arm64": "0.30.0", | ||
| "@sdsrs/code-graph-darwin-x64": "0.30.0", | ||
| "@sdsrs/code-graph-darwin-arm64": "0.30.0", | ||
| "@sdsrs/code-graph-win32-x64": "0.30.0" | ||
| "@sdsrs/code-graph-linux-x64": "0.31.0", | ||
| "@sdsrs/code-graph-linux-arm64": "0.31.0", | ||
| "@sdsrs/code-graph-darwin-x64": "0.31.0", | ||
| "@sdsrs/code-graph-darwin-arm64": "0.31.0", | ||
| "@sdsrs/code-graph-win32-x64": "0.31.0" | ||
| } | ||
| } |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
342950
2.16%43
4.88%6942
2.5%108
35%