From 4250d23385e8c40557025736779a91433ffa9ed2 Mon Sep 17 00:00:00 2001 From: affaan Date: Wed, 1 Jul 2026 00:43:49 +0000 Subject: [PATCH] fix(resolve-ecc-root): restore full env-unset discovery in inline resolver Address Greptile review on #2410: when CLAUDE_PLUGIN_ROOT is unset the delegating inline could only load the resolver module from ~/.claude, returning ~/.claude without ever reaching the plugin/cache search. Restore the old inline's discovery breadth (exact plugin roots + versioned cache) Windows-safely (no spread, nested arrays, or escaped quotes), then delegate the authoritative decision to resolveEccRoot(). Add regression tests for plugin-subdir and versioned-cache bootstrap with env unset. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .opencode/commands/instinct-status.md | 2 +- commands/auto-update.md | 2 +- commands/instinct-status.md | 2 +- commands/sessions.md | 12 +++--- commands/skill-health.md | 6 +-- docs/ja-JP/commands/auto-update.md | 2 +- docs/ja-JP/commands/instinct-status.md | 2 +- docs/ja-JP/commands/skill-health.md | 6 +-- docs/tr/commands/instinct-status.md | 2 +- docs/tr/commands/sessions.md | 16 ++++---- docs/zh-CN/commands/auto-update.md | 2 +- docs/zh-CN/commands/instinct-status.md | 2 +- docs/zh-CN/commands/sessions.md | 20 ++++----- docs/zh-CN/commands/skill-health.md | 6 +-- hooks/hooks.json | 56 +++++++++++++------------- scripts/lib/resolve-ecc-root.js | 18 ++++----- tests/lib/command-plugin-root.test.js | 4 +- tests/lib/resolve-ecc-root.test.js | 41 +++++++++++++++++++ 18 files changed, 121 insertions(+), 80 deletions(-) diff --git a/.opencode/commands/instinct-status.md b/.opencode/commands/instinct-status.md index 0fa72788c..85aa1b53c 100644 --- a/.opencode/commands/instinct-status.md +++ b/.opencode/commands/instinct-status.md @@ -16,7 +16,7 @@ fallback), then run the instinct CLI. This avoids reading a stale legacy active under `~/.claude/plugins/cache/...` (#2037). ```bash -ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;var b=(e&&e.trim())?e.trim():p.join(o.homedir(),'.claude');try{return require(p.join(b,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(x){return b}})();console.log(r)")}" +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i 0) { ```bash node -e " -const aa = require((function(){var p=require('path'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;var b=(e&&e.trim())?e.trim():p.join(o.homedir(),'.claude');try{return require(p.join(b,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(x){return b}})()+'/scripts/lib/session-aliases'); +const aa = require((function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i; * const sm = require(_r + '/scripts/lib/session-manager'); */ -const INLINE_RESOLVE = `(function(){var p=require('path'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;var b=(e&&e.trim())?e.trim():p.join(o.homedir(),'.claude');try{return require(p.join(b,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(x){return b}})()`; +const INLINE_RESOLVE = `(function(){var p=require('path'),f=require('fs'),o=require('os');var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var d=p.join(o.homedir(),'.claude');function L(x){try{return require(p.join(x,'scripts','lib','resolve-ecc-root')).resolveEccRoot()}catch(_){return null}}var r=L(d);if(r)return r;var s=['ecc','ecc@ecc','marketplaces/ecc','everything-claude-code','everything-claude-code@everything-claude-code','marketplaces/everything-claude-code'];for(var i=0;i { }); test('skill-health command uses shared inline resolver in all shell snippets', () => { - assert.strictEqual((skillHealthDoc.match(/var r=/g) || []).length, 3); + assert.strictEqual((skillHealthDoc.match(/var r=\(function/g) || []).length, 3); assert.strictEqual((skillHealthDoc.match(/scripts','lib','resolve-ecc-root/g) || []).length, 3); }); test('instinct-status command uses shared inline resolver (no stale legacy fallback) (#2037)', () => { - assert.strictEqual((instinctStatusDoc.match(/var r=/g) || []).length, 1); + assert.strictEqual((instinctStatusDoc.match(/var r=\(function/g) || []).length, 1); assert.strictEqual((instinctStatusDoc.match(/scripts','lib','resolve-ecc-root/g) || []).length, 1); // The pre-fix template hard-coded the legacy path as a fallback when // CLAUDE_PLUGIN_ROOT was unset. Asserting its absence prevents regression. diff --git a/tests/lib/resolve-ecc-root.test.js b/tests/lib/resolve-ecc-root.test.js index 63970538b..765fd01f0 100644 --- a/tests/lib/resolve-ecc-root.test.js +++ b/tests/lib/resolve-ecc-root.test.js @@ -345,6 +345,47 @@ module.exports = { resolveEccRoot() { assert.strictEqual(process.env.HOME, ${JSO } })) passed++; else failed++; + if (test('INLINE_RESOLVE bootstraps module from an exact plugin root when env unset', () => { + const homeDir = createTempDir(); + try { + const resolverDir = path.join(homeDir, '.claude', 'plugins', 'ecc', 'scripts', 'lib'); + fs.mkdirSync(resolverDir, { recursive: true }); + fs.writeFileSync(path.join(resolverDir, 'resolve-ecc-root.js'), `module.exports = { resolveEccRoot() { return 'plugin-root'; } };`); + const { execFileSync } = require('child_process'); + const result = execFileSync('node', [ + '-e', `console.log(${INLINE_RESOLVE})`, + ], { + env: { PATH: process.env.PATH, HOME: homeDir, USERPROFILE: homeDir }, + encoding: 'utf8', + }).trim(); + assert.strictEqual(result, 'plugin-root'); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + + if (test('INLINE_RESOLVE bootstraps module from the versioned plugin cache when env unset', () => { + const homeDir = createTempDir(); + try { + const resolverDir = path.join( + homeDir, '.claude', 'plugins', 'cache', 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION, + 'scripts', 'lib' + ); + fs.mkdirSync(resolverDir, { recursive: true }); + fs.writeFileSync(path.join(resolverDir, 'resolve-ecc-root.js'), `module.exports = { resolveEccRoot() { return 'cache-root'; } };`); + const { execFileSync } = require('child_process'); + const result = execFileSync('node', [ + '-e', `console.log(${INLINE_RESOLVE})`, + ], { + env: { PATH: process.env.PATH, HOME: homeDir, USERPROFILE: homeDir }, + encoding: 'utf8', + }).trim(); + assert.strictEqual(result, 'cache-root'); + } 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 {