mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-08 07:37:48 +02:00
fix(commands): probe for auto-update.js in auto-update ECC_ROOT resolver (#2462)
The auto-update command's inline ECC_ROOT resolver delegates to
resolveEccRoot() with the default probe (scripts/lib/utils.js). A
hooks-runtime-only install copies scripts/lib/ into ~/.claude, so the
partial install satisfies the probe and shadows the full plugin root
under ~/.claude/plugins/marketplaces/. The command then fails with
MODULE_NOT_FOUND because ~/.claude/scripts/auto-update.js does not
exist.
Pass {probe: scripts/auto-update.js} so the resolver only accepts a
root that actually contains the script the command runs. Applied to
the command doc and its ja-JP/zh-CN translations, with regression
tests for both the resolver behavior and the embedded snippets.
This commit is contained in:
@@ -11,7 +11,7 @@ Update ECC from its upstream repo and regenerate the current context's managed i
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Preview the update without mutating anything
|
# Preview the update without mutating anything
|
||||||
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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();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({probe:p.join('scripts','auto-update.js')})}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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
|
||||||
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
||||||
|
|
||||||
# Update only Cursor-managed files in the current project
|
# Update only Cursor-managed files in the current project
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ECCをアップストリームリポジトリから更新し、元のインス
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 何も変更せずに更新をプレビュー
|
# 何も変更せずに更新をプレビュー
|
||||||
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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();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({probe:p.join('scripts','auto-update.js')})}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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
|
||||||
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
||||||
|
|
||||||
# 現在のプロジェクトのCursor管理ファイルのみ更新
|
# 現在のプロジェクトのCursor管理ファイルのみ更新
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ disable-model-invocation: true
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Preview the update without mutating anything
|
# Preview the update without mutating anything
|
||||||
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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();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({probe:p.join('scripts','auto-update.js')})}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<s.length;i++){r=L(p.join(d,'plugins',s[i]));if(r)return r}try{var g=['ecc','everything-claude-code'];for(var j=0;j<g.length;j++){var c=p.join(d,'plugins','cache',g[j]);var O=f.readdirSync(c);for(var k=0;k<O.length;k++){var q=p.join(c,O[k]);var V=f.readdirSync(q);for(var m=0;m<V.length;m++){r=L(p.join(q,V[m]));if(r)return r}}}}catch(_){}return d})();console.log(r)")}"
|
||||||
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
||||||
|
|
||||||
# Update only Cursor-managed files in the current project
|
# Update only Cursor-managed files in the current project
|
||||||
|
|||||||
@@ -50,6 +50,29 @@ test('instinct-status command uses shared inline resolver (no stale legacy fallb
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auto-update command probes for the script it runs, not just scripts/lib', () => {
|
||||||
|
// A partial install can carry full resolver evidence (script tree plus
|
||||||
|
// sentinel ECC skill, per #2544/#2577) yet still lack scripts/auto-update.js.
|
||||||
|
// The command's inline resolver must probe for the script it actually
|
||||||
|
// executes so such roots don't shadow the complete plugin root.
|
||||||
|
const autoUpdateDocs = [
|
||||||
|
path.join(__dirname, '..', '..', 'commands', 'auto-update.md'),
|
||||||
|
path.join(__dirname, '..', '..', 'docs', 'ja-JP', 'commands', 'auto-update.md'),
|
||||||
|
path.join(__dirname, '..', '..', 'docs', 'zh-CN', 'commands', 'auto-update.md'),
|
||||||
|
];
|
||||||
|
for (const docPath of autoUpdateDocs) {
|
||||||
|
const doc = fs.readFileSync(docPath, 'utf8');
|
||||||
|
assert.strictEqual(
|
||||||
|
(doc.match(/scripts','lib','resolve-ecc-root/g) || []).length, 1,
|
||||||
|
`${docPath} should embed the shared inline resolver`
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
doc.includes("resolveEccRoot({probe:p.join('scripts','auto-update.js')})"),
|
||||||
|
`${docPath} should probe for scripts/auto-update.js`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('resolveEccRoot module covers current and legacy marketplace plugin roots', () => {
|
test('resolveEccRoot module covers current and legacy marketplace plugin roots', () => {
|
||||||
const { resolveEccRoot } = require('../../scripts/lib/resolve-ecc-root');
|
const { resolveEccRoot } = require('../../scripts/lib/resolve-ecc-root');
|
||||||
assert.ok(typeof resolveEccRoot === 'function');
|
assert.ok(typeof resolveEccRoot === 'function');
|
||||||
|
|||||||
@@ -363,6 +363,37 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('custom probe skips a qualifying root that lacks the probed script (auto-update)', () => {
|
||||||
|
// The surviving failure shape after #2544/#2577: a partial install can
|
||||||
|
// carry full resolver evidence (script tree + sentinel ECC skill) yet
|
||||||
|
// still lack the top-level script that auto-update will execute. The
|
||||||
|
// default probe rightly accepts such a root; a caller probing for the
|
||||||
|
// script it runs must skip it and reach the complete plugin root.
|
||||||
|
const homeDir = createTempDir();
|
||||||
|
try {
|
||||||
|
const claudeDir = setupStandardInstall(homeDir);
|
||||||
|
const marketplaceRoot = setupLegacyPluginInstall(homeDir, ['marketplaces', 'ecc']);
|
||||||
|
fs.writeFileSync(path.join(marketplaceRoot, 'scripts', 'auto-update.js'), '// stub');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
resolveEccRoot({ envRoot: '', homeDir }),
|
||||||
|
claudeDir,
|
||||||
|
'default probe accepts a root with full resolver evidence'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
resolveEccRoot({
|
||||||
|
envRoot: '',
|
||||||
|
homeDir,
|
||||||
|
probe: path.join('scripts', 'auto-update.js'),
|
||||||
|
}),
|
||||||
|
marketplaceRoot,
|
||||||
|
'auto-update probe must skip roots that lack the script it will execute'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ─── INLINE_RESOLVE ───
|
// ─── INLINE_RESOLVE ───
|
||||||
|
|
||||||
if (test('INLINE_RESOLVE is a non-empty string', () => {
|
if (test('INLINE_RESOLVE is a non-empty string', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user