From 68484da2fc445655165f9755dcde18a7c094b7bc Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 20 Mar 2026 01:38:15 -0700 Subject: [PATCH] fix: auto-detect ECC root from plugin cache when CLAUDE_PLUGIN_ROOT is unset (#547) (#691) When ECC is installed as a Claude Code plugin via the marketplace, scripts live in the plugin cache (~/.claude/plugins/cache/...) but commands fallback to ~/.claude/ which doesn't have the scripts. Add resolve-ecc-root.js with a 3-step fallback chain: 1. CLAUDE_PLUGIN_ROOT env var (existing) 2. Standard install at ~/.claude/ (existing) 3. NEW: auto-scan the plugin cache directory Update sessions.md and skill-health.md commands to use the new inline resolver. Includes 15 tests covering all fallback paths including env var priority, standard install, cache discovery, and the compact INLINE_RESOLVE used in command .md files. --- commands/sessions.md | 20 +-- commands/skill-health.md | 9 +- scripts/lib/resolve-ecc-root.js | 89 +++++++++++ tests/lib/resolve-ecc-root.test.js | 247 +++++++++++++++++++++++++++++ 4 files changed, 352 insertions(+), 13 deletions(-) create mode 100644 scripts/lib/resolve-ecc-root.js create mode 100644 tests/lib/resolve-ecc-root.test.js diff --git a/commands/sessions.md b/commands/sessions.md index 4713b82f..3bfb914d 100644 --- a/commands/sessions.md +++ b/commands/sessions.md @@ -29,8 +29,8 @@ Use `/sessions info` when you need operator-surface context for a swarm: branch, **Script:** ```bash node -e " -const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager'); -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const sm = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-manager'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const path = require('path'); const result = sm.getAllSessions({ limit: 20 }); @@ -70,8 +70,8 @@ Load and display a session's content (by ID or alias). **Script:** ```bash node -e " -const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager'); -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const sm = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-manager'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const id = process.argv[1]; // First try to resolve as alias @@ -143,8 +143,8 @@ Create a memorable alias for a session. **Script:** ```bash node -e " -const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager'); -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const sm = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-manager'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const sessionId = process.argv[1]; const aliasName = process.argv[2]; @@ -183,7 +183,7 @@ Delete an existing alias. **Script:** ```bash node -e " -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const aliasName = process.argv[1]; if (!aliasName) { @@ -212,8 +212,8 @@ Show detailed information about a session. **Script:** ```bash node -e " -const sm = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-manager'); -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const sm = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-manager'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const id = process.argv[1]; const resolved = aa.resolveAlias(id); @@ -262,7 +262,7 @@ Show all session aliases. **Script:** ```bash node -e " -const aa = require((process.env.CLAUDE_PLUGIN_ROOT||require('path').join(require('os').homedir(),'.claude'))+'/scripts/lib/session-aliases'); +const aa = require((()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()+'/scripts/lib/session-aliases'); const aliases = aa.listAliases(); console.log('Session Aliases (' + aliases.length + '):'); diff --git a/commands/skill-health.md b/commands/skill-health.md index b9cb64f5..185c9a62 100644 --- a/commands/skill-health.md +++ b/commands/skill-health.md @@ -13,19 +13,22 @@ Shows a comprehensive health dashboard for all skills in the portfolio with succ Run the skill health CLI in dashboard mode: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/skills-health.js" --dashboard +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(!f.existsSync(p.join(d,q))){try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q))){d=c;break}}}catch(x){}}console.log(d)")}" +node "$ECC_ROOT/scripts/skills-health.js" --dashboard ``` For a specific panel only: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/skills-health.js" --dashboard --panel failures +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(!f.existsSync(p.join(d,q))){try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q))){d=c;break}}}catch(x){}}console.log(d)")}" +node "$ECC_ROOT/scripts/skills-health.js" --dashboard --panel failures ``` For machine-readable output: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/skills-health.js" --dashboard --json +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(!f.existsSync(p.join(d,q))){try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q))){d=c;break}}}catch(x){}}console.log(d)")}" +node "$ECC_ROOT/scripts/skills-health.js" --dashboard --json ``` ## Usage diff --git a/scripts/lib/resolve-ecc-root.js b/scripts/lib/resolve-ecc-root.js new file mode 100644 index 00000000..848bcbf8 --- /dev/null +++ b/scripts/lib/resolve-ecc-root.js @@ -0,0 +1,89 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +/** + * Resolve the ECC source root directory. + * + * Tries, in order: + * 1. CLAUDE_PLUGIN_ROOT env var (set by Claude Code for hooks, or by user) + * 2. Standard install location (~/.claude/) — when scripts exist there + * 3. Plugin cache auto-detection — scans ~/.claude/plugins/cache/everything-claude-code/ + * 4. Fallback to ~/.claude/ (original behaviour) + * + * @param {object} [options] + * @param {string} [options.homeDir] Override home directory (for testing) + * @param {string} [options.envRoot] Override CLAUDE_PLUGIN_ROOT (for testing) + * @param {string} [options.probe] Relative path used to verify a candidate root + * contains ECC scripts. Default: 'scripts/lib/utils.js' + * @returns {string} Resolved ECC root path + */ +function resolveEccRoot(options = {}) { + const envRoot = options.envRoot !== undefined + ? options.envRoot + : (process.env.CLAUDE_PLUGIN_ROOT || ''); + + if (envRoot && envRoot.trim()) { + return envRoot.trim(); + } + + const homeDir = options.homeDir || os.homedir(); + const claudeDir = path.join(homeDir, '.claude'); + const probe = options.probe || path.join('scripts', 'lib', 'utils.js'); + + // Standard install — files are copied directly into ~/.claude/ + if (fs.existsSync(path.join(claudeDir, probe))) { + return claudeDir; + } + + // Plugin cache — Claude Code stores marketplace plugins under + // ~/.claude/plugins/cache//// + try { + const cacheBase = path.join(claudeDir, 'plugins', 'cache', 'everything-claude-code'); + const orgDirs = fs.readdirSync(cacheBase, { withFileTypes: true }); + + for (const orgEntry of orgDirs) { + if (!orgEntry.isDirectory()) continue; + const orgPath = path.join(cacheBase, orgEntry.name); + + let versionDirs; + try { + versionDirs = fs.readdirSync(orgPath, { withFileTypes: true }); + } catch { + continue; + } + + for (const verEntry of versionDirs) { + if (!verEntry.isDirectory()) continue; + const candidate = path.join(orgPath, verEntry.name); + if (fs.existsSync(path.join(candidate, probe))) { + return candidate; + } + } + } + } catch { + // Plugin cache doesn't exist or isn't readable — continue to fallback + } + + return claudeDir; +} + +/** + * Compact inline version for embedding in command .md code blocks. + * + * This is the minified form of resolveEccRoot() suitable for use in + * node -e "..." scripts where require() is not available before the + * root is known. + * + * Usage in commands: + * const _r = ; + * const sm = require(_r + '/scripts/lib/session-manager'); + */ +const INLINE_RESOLVE = `(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b))for(var v of f.readdirSync(p.join(b,o))){var c=p.join(b,o,v);if(f.existsSync(p.join(c,q)))return c}}catch(x){}return d})()`; + +module.exports = { + resolveEccRoot, + INLINE_RESOLVE, +}; diff --git a/tests/lib/resolve-ecc-root.test.js b/tests/lib/resolve-ecc-root.test.js new file mode 100644 index 00000000..f153c215 --- /dev/null +++ b/tests/lib/resolve-ecc-root.test.js @@ -0,0 +1,247 @@ +/** + * Tests for scripts/lib/resolve-ecc-root.js + * + * Covers the ECC root resolution fallback chain: + * 1. CLAUDE_PLUGIN_ROOT env var + * 2. Standard install (~/.claude/) + * 3. Plugin cache auto-detection + * 4. Fallback to ~/.claude/ + */ + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const { resolveEccRoot, INLINE_RESOLVE } = require('../../scripts/lib/resolve-ecc-root'); + +function test(name, fn) { + try { + fn(); + console.log(` \u2713 ${name}`); + return true; + } catch (error) { + console.log(` \u2717 ${name}`); + console.log(` Error: ${error.message}`); + return false; + } +} + +function createTempDir() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-root-test-')); +} + +function setupStandardInstall(homeDir) { + const claudeDir = path.join(homeDir, '.claude'); + const scriptDir = path.join(claudeDir, 'scripts', 'lib'); + fs.mkdirSync(scriptDir, { recursive: true }); + fs.writeFileSync(path.join(scriptDir, 'utils.js'), '// stub'); + return claudeDir; +} + +function setupPluginCache(homeDir, orgName, version) { + const cacheDir = path.join( + homeDir, '.claude', 'plugins', 'cache', + 'everything-claude-code', orgName, version + ); + const scriptDir = path.join(cacheDir, 'scripts', 'lib'); + fs.mkdirSync(scriptDir, { recursive: true }); + fs.writeFileSync(path.join(scriptDir, 'utils.js'), '// stub'); + return cacheDir; +} + +function runTests() { + console.log('\n=== Testing resolve-ecc-root.js ===\n'); + + let passed = 0; + let failed = 0; + + // ─── Env Var Priority ─── + + if (test('returns CLAUDE_PLUGIN_ROOT when set', () => { + const result = resolveEccRoot({ envRoot: '/custom/plugin/root' }); + assert.strictEqual(result, '/custom/plugin/root'); + })) passed++; else failed++; + + if (test('trims whitespace from CLAUDE_PLUGIN_ROOT', () => { + const result = resolveEccRoot({ envRoot: ' /trimmed/root ' }); + assert.strictEqual(result, '/trimmed/root'); + })) passed++; else failed++; + + if (test('skips empty CLAUDE_PLUGIN_ROOT', () => { + const homeDir = createTempDir(); + try { + setupStandardInstall(homeDir); + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('skips whitespace-only CLAUDE_PLUGIN_ROOT', () => { + const homeDir = createTempDir(); + try { + setupStandardInstall(homeDir); + const result = resolveEccRoot({ envRoot: ' ', homeDir }); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + // ─── Standard Install ─── + + if (test('finds standard install at ~/.claude/', () => { + const homeDir = createTempDir(); + try { + setupStandardInstall(homeDir); + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + // ─── Plugin Cache Auto-Detection ─── + + if (test('discovers plugin root from cache directory', () => { + const homeDir = createTempDir(); + try { + const expected = setupPluginCache(homeDir, 'everything-claude-code', '1.8.0'); + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, expected); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('prefers standard install over plugin cache', () => { + const homeDir = createTempDir(); + try { + const claudeDir = setupStandardInstall(homeDir); + setupPluginCache(homeDir, 'everything-claude-code', '1.8.0'); + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, claudeDir, + 'Standard install should take precedence over plugin cache'); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('handles multiple versions in plugin cache', () => { + const homeDir = createTempDir(); + try { + setupPluginCache(homeDir, 'everything-claude-code', '1.7.0'); + const expected = setupPluginCache(homeDir, 'everything-claude-code', '1.8.0'); + const result = resolveEccRoot({ envRoot: '', homeDir }); + // Should find one of them (either is valid) + assert.ok( + result === expected || + result === path.join(homeDir, '.claude', 'plugins', 'cache', 'everything-claude-code', 'everything-claude-code', '1.7.0'), + 'Should resolve to a valid plugin cache directory' + ); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + // ─── Fallback ─── + + if (test('falls back to ~/.claude/ when nothing is found', () => { + const homeDir = createTempDir(); + try { + // Create ~/.claude but don't put scripts there + fs.mkdirSync(path.join(homeDir, '.claude'), { recursive: true }); + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('falls back gracefully when ~/.claude/ does not exist', () => { + const homeDir = createTempDir(); + try { + const result = resolveEccRoot({ envRoot: '', homeDir }); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + // ─── Custom Probe ─── + + if (test('supports custom probe path', () => { + const homeDir = createTempDir(); + try { + const claudeDir = path.join(homeDir, '.claude'); + fs.mkdirSync(path.join(claudeDir, 'custom'), { recursive: true }); + fs.writeFileSync(path.join(claudeDir, 'custom', 'marker.js'), '// probe'); + const result = resolveEccRoot({ + envRoot: '', + homeDir, + probe: path.join('custom', 'marker.js'), + }); + assert.strictEqual(result, claudeDir); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + // ─── INLINE_RESOLVE ─── + + if (test('INLINE_RESOLVE is a non-empty string', () => { + assert.ok(typeof INLINE_RESOLVE === 'string'); + assert.ok(INLINE_RESOLVE.length > 50, 'Should be a substantial inline expression'); + })) passed++; else failed++; + + if (test('INLINE_RESOLVE returns CLAUDE_PLUGIN_ROOT when set', () => { + const { execFileSync } = require('child_process'); + const result = execFileSync('node', [ + '-e', `console.log(${INLINE_RESOLVE})`, + ], { + env: { ...process.env, CLAUDE_PLUGIN_ROOT: '/inline/test/root' }, + encoding: 'utf8', + }).trim(); + assert.strictEqual(result, '/inline/test/root'); + })) passed++; else failed++; + + if (test('INLINE_RESOLVE discovers plugin cache when env var is unset', () => { + const homeDir = createTempDir(); + try { + const expected = setupPluginCache(homeDir, 'everything-claude-code', '1.9.0'); + const { execFileSync } = require('child_process'); + const result = execFileSync('node', [ + '-e', `console.log(${INLINE_RESOLVE})`, + ], { + env: { PATH: process.env.PATH, HOME: homeDir }, + encoding: 'utf8', + }).trim(); + assert.strictEqual(result, expected); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('INLINE_RESOLVE falls back to ~/.claude/ when nothing found', () => { + const homeDir = createTempDir(); + try { + const { execFileSync } = require('child_process'); + const result = execFileSync('node', [ + '-e', `console.log(${INLINE_RESOLVE})`, + ], { + env: { PATH: process.env.PATH, HOME: homeDir }, + encoding: 'utf8', + }).trim(); + assert.strictEqual(result, path.join(homeDir, '.claude')); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); + process.exit(failed > 0 ? 1 : 0); +} + +runTests();