diff --git a/.opencode/commands/instinct-status.md b/.opencode/commands/instinct-status.md index 5ca7ce8a..9c864cec 100644 --- a/.opencode/commands/instinct-status.md +++ b/.opencode/commands/instinct-status.md @@ -9,16 +9,15 @@ Show instinct status from continuous-learning-v2: $ARGUMENTS ## Your Task -Run: +Resolve the active ECC plugin root with the same walker `hooks/hooks.json` +uses (env var → standard install → known plugin roots → plugin cache → +fallback), then run the instinct CLI. This avoids reading a stale legacy +`~/.claude/skills/continuous-learning-v2/` install when the plugin is +active under `~/.claude/plugins/cache/...` (#2037). ```bash -python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status -``` - -If `CLAUDE_PLUGIN_ROOT` is unavailable, use: - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{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;for(var s of [['ecc'],['ecc@ecc'],['marketplaces','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplaces','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}" +python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` ## Behavior Notes diff --git a/commands/instinct-status.md b/commands/instinct-status.md index c54f8022..33682ea5 100644 --- a/commands/instinct-status.md +++ b/commands/instinct-status.md @@ -10,16 +10,16 @@ Shows learned instincts for the current project plus global instincts, grouped b ## Implementation -Run the instinct CLI using the plugin root path: +Run the instinct CLI, resolving the active ECC plugin root the same way +`hooks/hooks.json` and the other slash commands (`/sessions`, `/skill-health`) +do — env var → standard install → known plugin roots → plugin cache → fallback. +This avoids the divergence that happens when `CLAUDE_PLUGIN_ROOT` is unset +while a legacy `~/.claude/skills/continuous-learning-v2/` directory still +exists (#2037). ```bash -python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status -``` - -Or if `CLAUDE_PLUGIN_ROOT` is not set (manual installation), use: - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{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;for(var s of [['ecc'],['ecc@ecc'],['marketplaces','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplaces','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}" +python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` ## Usage diff --git a/docs/ja-JP/commands/instinct-status.md b/docs/ja-JP/commands/instinct-status.md index af87a1de..a140a621 100644 --- a/docs/ja-JP/commands/instinct-status.md +++ b/docs/ja-JP/commands/instinct-status.md @@ -10,16 +10,16 @@ command: true ## 実装 -プラグインルートパスを使用してインスティンクトCLIを実行します: +`hooks/hooks.json` および他のスラッシュコマンド(`/sessions`、`/skill-health`) +と同じリゾルバ(環境変数 → 標準インストール → 既知のプラグインルート → +プラグインキャッシュ → フォールバック)でインスティンクトCLIを実行します。 +これにより、`CLAUDE_PLUGIN_ROOT` が未設定で +レガシーの `~/.claude/skills/continuous-learning-v2/` ディレクトリが +残っているときに発生するパスの分岐を回避します (#2037)。 ```bash -python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status -``` - -または、`CLAUDE_PLUGIN_ROOT` が設定されていない場合(手動インストール)の場合は: - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{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;for(var s of [['ecc'],['ecc@ecc'],['marketplaces','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplaces','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}" +python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` ## 使用方法 diff --git a/docs/tr/commands/instinct-status.md b/docs/tr/commands/instinct-status.md index be334acb..a35a4af7 100644 --- a/docs/tr/commands/instinct-status.md +++ b/docs/tr/commands/instinct-status.md @@ -10,16 +10,16 @@ Mevcut proje için öğrenilen içgüdüleri ve global içgüdüleri, domain'e g ## Uygulama -Plugin root path kullanarak instinct CLI'ı çalıştır: +`hooks/hooks.json` ve diğer slash komutlarının (`/sessions`, `/skill-health`) +kullandığı çözümleyiciyle (env var → standart kurulum → bilinen plugin +kökleri → plugin önbelleği → fallback) instinct CLI'ı çalıştır. +Bu, `CLAUDE_PLUGIN_ROOT` ayarlanmamışken eski bir +`~/.claude/skills/continuous-learning-v2/` dizini hâlâ varsa oluşan +yol sapmasını önler (#2037). ```bash -python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status -``` - -Veya `CLAUDE_PLUGIN_ROOT` ayarlanmamışsa (manuel kurulum): - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{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;for(var s of [['ecc'],['ecc@ecc'],['marketplaces','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplaces','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}" +python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` ## Kullanım diff --git a/docs/zh-CN/commands/instinct-status.md b/docs/zh-CN/commands/instinct-status.md index 86da92e3..110cedba 100644 --- a/docs/zh-CN/commands/instinct-status.md +++ b/docs/zh-CN/commands/instinct-status.md @@ -10,16 +10,14 @@ command: true ## 实现 -使用插件根路径运行本能 CLI: +以与 `hooks/hooks.json` 和其他斜杠命令(`/sessions`、`/skill-health`) +相同的解析器运行本能 CLI——环境变量 → 标准安装 → 已知插件根 → 插件缓存 → 回退。 +这样可以避免当 `CLAUDE_PLUGIN_ROOT` 未设置而旧的 +`~/.claude/skills/continuous-learning-v2/` 目录仍然存在时发生的路径分歧 (#2037)。 ```bash -python3 "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/scripts/instinct-cli.py" status -``` - -或者,如果未设置 `CLAUDE_PLUGIN_ROOT`(手动安装),则使用: - -```bash -python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status +ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{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;for(var s of [['ecc'],['ecc@ecc'],['marketplaces','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplaces','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}" +python3 "$ECC_ROOT/skills/continuous-learning-v2/scripts/instinct-cli.py" status ``` ## 用法 diff --git a/tests/lib/command-plugin-root.test.js b/tests/lib/command-plugin-root.test.js new file mode 100644 index 00000000..5960be9d --- /dev/null +++ b/tests/lib/command-plugin-root.test.js @@ -0,0 +1,63 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const { INLINE_RESOLVE } = require('../../scripts/lib/resolve-ecc-root'); + +let passed = 0; +let failed = 0; + +function test(name, fn) { + try { + fn(); + console.log(`PASS ${name}`); + passed += 1; + } catch (error) { + console.error(`FAIL ${name}`); + console.error(error.stack || error.message || String(error)); + failed += 1; + } +} + +const sessionsDoc = fs.readFileSync(path.join(__dirname, '..', '..', 'commands', 'sessions.md'), 'utf8'); +const skillHealthDoc = fs.readFileSync(path.join(__dirname, '..', '..', 'commands', 'skill-health.md'), 'utf8'); +const instinctStatusDoc = fs.readFileSync(path.join(__dirname, '..', '..', 'commands', 'instinct-status.md'), 'utf8'); + +test('sessions command uses shared inline resolver in all node scripts', () => { + assert.strictEqual((sessionsDoc.match(/const _r = /g) || []).length, 6); + assert.strictEqual((sessionsDoc.match(/\['marketplaces','ecc'\]/g) || []).length, 6); + assert.strictEqual((sessionsDoc.match(/\['marketplaces','everything-claude-code'\]/g) || []).length, 6); + assert.strictEqual((sessionsDoc.match(/\['ecc','everything-claude-code'\]/g) || []).length, 6); +}); + +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(/\['marketplaces','ecc'\]/g) || []).length, 3); + assert.strictEqual((skillHealthDoc.match(/\['marketplaces','everything-claude-code'\]/g) || []).length, 3); + assert.strictEqual((skillHealthDoc.match(/\['ecc','everything-claude-code'\]/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(/\['marketplaces','ecc'\]/g) || []).length, 1); + assert.strictEqual((instinctStatusDoc.match(/\['marketplaces','everything-claude-code'\]/g) || []).length, 1); + assert.strictEqual((instinctStatusDoc.match(/\['ecc','everything-claude-code'\]/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. + assert.ok( + !instinctStatusDoc.includes('python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py'), + 'instinct-status should not hard-code the legacy ~/.claude install path as a fallback' + ); +}); + +test('inline resolver covers current and legacy marketplace plugin roots', () => { + assert.ok(INLINE_RESOLVE.includes("'marketplaces','ecc'")); + assert.ok(INLINE_RESOLVE.includes("'marketplaces','everything-claude-code'")); + assert.ok(!INLINE_RESOLVE.includes('\\"'), 'Inline resolver should not require escaped double quotes'); +}); + +console.log(`Passed: ${passed}`); +console.log(`Failed: ${failed}`); + +process.exit(failed > 0 ? 1 : 0);