fix(installer): CC npx 路径补齐 hooks 注册(v1.4.0 关键修复)

v1.3.0 及之前,`npx superpowers-zh` 装 CC 目标只复制 skills/ + 改
CLAUDE.md,完全没装 hooks。结果用户社区高频反馈:

- skills 静静躺在 .claude/skills/ 但 AI 从不主动调用
- using-superpowers 指令也不进流程
- /skills 命令读不到,挪到用户级也不自动调用

根因:上游 hooks/hooks.json 的 ${CLAUDE_PLUGIN_ROOT} 只有走 CC plugin
marketplace 时才会注入,npx 路径下根本不会展开。而 SessionStart hook
是 superpowers 自动触发链路的核心——CLAUDE.md 文本是被动的,模型可能
完全忽略。

修复(bin/superpowers-zh.js):

- 新增 installClaudeCodeHooks() — 复制 hooks/ 到 .claude/hooks/,并把
  SessionStart hook 注册到 .claude/settings.json,用绝对路径替代
  ${CLAUDE_PLUGIN_ROOT}(npx 路径下后者不展开)
- 幂等:重复跑不会重复注册(按 hooks 目录路径去重)
- generateClaudeCodeBootstrap() 改为先装 hooks 再写 CLAUDE.md
- 新增 uninstallClaudeCodeHooks() — --uninstall 精确摘 settings.json
  entry + 删 .claude/hooks/ 下我们装过的脚本,目录空就连目录删

- 新增 printMarketplaceHintIfNeeded() — 装 CC / Codex CLI / OpenCode /
  VS Code 这 4 款"有官方 plugin marketplace 的工具"时,主动打印
  marketplace 命令作为 high-fidelity 推荐路径。
  npx 仍可用但是 low-fidelity(不走 plugin 注册)。

README 新增"📌 先选对路径:marketplace vs npx"表格,诚实标注 17 款
工具里这 4 款的 npx 路径是 low-fidelity。其余 13 款(Cursor/Trae/Kiro/
Gemini/Hermes 等只看本地配置文件的工具)npx 是 canonical 安装路径。

RELEASE-NOTES.zh.md 新增 v1.4.0 段记录修复内容。

本地烟测:
- mktemp 测试目录 → npx --tool claude →  skills + hooks + settings.json
  + CLAUDE.md 都到位
- 第二次跑 →  幂等,settings.json 仍只有 1 个 SessionStart entry
- --uninstall →  清干净(skills + hooks + settings.json + CLAUDE.md 段)

向后兼容:v1.3.0 已装的用户重新跑 npx superpowers-zh 即可补齐 hooks。

不影响上游 plugin marketplace 安装路径(仍按上游正常工作)。
This commit is contained in:
AI不止语
2026-05-12 00:59:25 +08:00
parent 132b658491
commit ef039d37ef
3 changed files with 211 additions and 1 deletions

View File

@@ -137,13 +137,28 @@ AI在开始实现之前我需要了解几个关键问题
## 快速开始
### 方式一npm 安装(推荐)
### 📌 先选对路径marketplace vs npx
> **以下 4 款工具有官方 plugin marketplace——走 marketplace 才是 high-fidelity**(正式注册为 plugin、`/skills` 命令可见、hooks 走标准 `${CLAUDE_PLUGIN_ROOT}` 路径)。`npx superpowers-zh` 对它们是 low-fidelity 路径——v1.4.0 起已补齐 hooks 注册和 `.claude/settings.json` 写入,能让 SessionStart hook 自动触发 `using-superpowers` 注入,但仍是"项目级 loose plugin"。
| 工具 | High-fidelity 路径(**推荐** | npx low-fidelity 路径 |
|---|---|---|
| **Claude Code** | `/plugin marketplace add jnMetaCode/superpowers-zh`<br>`/plugin install superpowers-zh@superpowers-zh` | `npx superpowers-zh`v1.4.0+ 已补 hooks |
| **Codex CLI** | 在 CLI 里输入 `/plugins`,搜 `superpowers` | `npx superpowers-zh --tool codex` |
| **OpenCode** | 见 [上游 OpenCode 安装](https://github.com/obra/superpowers#opencode) | `npx superpowers-zh --tool opencode` |
| **VS Code (Copilot)** | 见 [docs/README.vscode.md](docs/README.vscode.md) | `npx superpowers-zh`(仅复制 skills |
**其余 13 款工具**Cursor / Trae / Kiro / Gemini CLI / Hermes Agent / Aider / Antigravity / Windsurf / Qwen Code / Claw Code / OpenClaw / DeerFlow / Hermes—— 它们本来就只看本地配置文件rules / steering / instructions**`npx superpowers-zh` 是它们的 canonical 安装路径**,没有"更高保真"可言。
### 方式一npm 安装(多工具自适应)
```bash
cd /your/project
npx superpowers-zh
```
自动检测当前项目使用的工具,把 20 个 skills 装到对应位置 + 写工具专属的 bootstrap 文件。**Claude Code v1.4.0+ 还会同时复制 hooks/ 并注册 `.claude/settings.json`** 让 SessionStart hook 自动注入 `using-superpowers`
> ⚠️ **不要在主目录(`~`)下跑**。v1.2.1 起会拒绝并提示,老版本会把 skills 和 `CLAUDE.md` 等 bootstrap 文件写到你的 home 目录,污染所有项目。如已误装见下文「卸载 / 误装清理」。
### 方式二手动安装low-fidelity仅作备选

View File

@@ -6,6 +6,46 @@
---
## v1.4.0 (2026-05-12)
### 🔴 关键修复Claude Code npx 安装现在会装 hooks + 注册 SessionStart
**背景:** v1.3.0 及之前,`npx superpowers-zh` 装 Claude Code 目标**只**复制 `skills/``.claude/skills/` 并改 `CLAUDE.md`**完全没装 hooks**。结果:
- `hooks/hooks.json` 用的 `${CLAUDE_PLUGIN_ROOT}` 只有走 Claude Code plugin marketplace 时才会注入npx 路径下根本不会展开
- 没有 SessionStart hook → 不会把 `using-superpowers` 元 skill 注入到对话开头
- 仅靠 `CLAUDE.md` 文本(被动)告诉模型 "有 skills",模型可能完全忽略
**用户体感:** skills 静静躺在 `.claude/skills/` 但 AI 从不主动调用,连 `using-superpowers` 指令也不进流程。
**修复(`bin/superpowers-zh.js`**
- 新增 `installClaudeCodeHooks(projectDir)` —— 复制 `hooks/``.claude/hooks/`,并把 SessionStart hook 注册到 `.claude/settings.json`**用绝对路径**替代 `${CLAUDE_PLUGIN_ROOT}`
- 幂等:重复跑 `npx superpowers-zh` 不会重复注册(按 hooks 目录路径去重)
- `--uninstall` 跟随:新增 `uninstallClaudeCodeHooks(projectDir)`,精确摘掉 settings.json 里的 entry + 删除 `.claude/hooks/` 下我们装过的脚本
**新增 marketplace 提示:** 安装时若识别到 Claude Code / Codex CLI / OpenCode / VS Code 这 4 款"有官方 plugin marketplace 的工具",自动打印 marketplace 命令作为 high-fidelity 推荐路径npx 仍可用但是 low-fidelity
### 📚 README 透明度章节
加 "📌 先选对路径marketplace vs npx" 表格,**诚实标注 17 款工具里有 4 款的 npx 路径是 low-fidelity**
- Claude Code / Codex CLI / OpenCode / VS Code → marketplace 是 high-fidelitynpx 是 fallback
- 其余 13 款Cursor / Trae / Kiro / Gemini CLI / Hermes Agent / Aider / Antigravity / Windsurf / Qwen Code / Claw Code / OpenClaw / DeerFlow → npx 是 canonical它们本来就只看本地配置文件
### 影响范围
- ✅ 修复用户社区高频反馈:"npx 装完 skill 不会自动触发"、"using-superpowers 指令不走流程"
- ✅ 不影响上游 marketplace 安装路径(仍按上游正常工作)
- ✅ 向后兼容v1.3.0 已装的用户重新跑 `npx superpowers-zh` 即可补齐 hooks无需手动清理
### Refs
- 用户反馈 issue: TBD社区微信群报告
- 相关 issue: #19 tracking 上游同步worktree 部分见 PR #28
---
## v1.3.0 (2026-05-10)
### 跟上游对齐 (v5.1.0)

View File

@@ -39,6 +39,7 @@ function copyDirSync(src, dest) {
const __dirname = dirname(fileURLToPath(import.meta.url));
const PKG = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
const SKILLS_SRC = resolve(__dirname, '..', 'skills');
const HOOKS_SRC = resolve(__dirname, '..', 'hooks');
const PROJECT_DIR = process.cwd();
// 历史遗留 agent 文件名 — 用于 --uninstall 清理已装用户机器上的残留。
@@ -314,7 +315,126 @@ ${skillList}
}
}
// Claude Code 专用:把 hooks/ 复制到 .claude/hooks/,并把 SessionStart hook
// 注册到 .claude/settings.json用绝对路径替代 ${CLAUDE_PLUGIN_ROOT})。
// 没有这一步CLAUDE.md 是被动文本——模型可能忽略——而 hook 注入是 superpowers
// 自动触发链路的核心。详见 README "Claude Code 用户必读" 段。
function installClaudeCodeHooks(projectDir) {
if (!existsSync(HOOKS_SRC)) {
console.warn(` ⚠️ Claude Code: 源目录 hooks/ 不存在,跳过 hook 注册`);
return false;
}
const hooksDest = resolve(projectDir, '.claude', 'hooks');
copyDirSync(HOOKS_SRC, hooksDest);
console.log(` ✅ Claude Code: hooks -> ${hooksDest}`);
// 写/合并 .claude/settings.json
const settingsPath = resolve(projectDir, '.claude', 'settings.json');
let settings = {};
if (existsSync(settingsPath)) {
try {
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
} catch (e) {
console.warn(` ⚠️ .claude/settings.json 解析失败,跳过 hook 注册: ${e.message}`);
return false;
}
}
if (!settings.hooks) settings.hooks = {};
if (!Array.isArray(settings.hooks.SessionStart)) settings.hooks.SessionStart = [];
const cmdPath = resolve(hooksDest, 'run-hook.cmd');
const ourCommand = `"${cmdPath}" session-start`;
// 幂等:若已存在指向我们 hooksDest 的 entry不重复注册
const alreadyExists = settings.hooks.SessionStart.some(entry =>
Array.isArray(entry.hooks) && entry.hooks.some(h =>
h && typeof h.command === 'string' && h.command.includes(hooksDest)
)
);
if (!alreadyExists) {
settings.hooks.SessionStart.push({
matcher: 'startup|clear|compact',
hooks: [
{ type: 'command', command: ourCommand, async: false }
]
});
}
mkdirSync(resolve(projectDir, '.claude'), { recursive: true });
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
console.log(` ✅ Claude Code: SessionStart hook 注册 -> ${settingsPath}`);
return true;
}
function uninstallClaudeCodeHooks(projectDir) {
const hooksDest = resolve(projectDir, '.claude', 'hooks');
const settingsPath = resolve(projectDir, '.claude', 'settings.json');
let removed = 0;
// 1. 从 settings.json 摘掉指向 hooksDest 的 entry
if (existsSync(settingsPath)) {
try {
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
if (settings.hooks && Array.isArray(settings.hooks.SessionStart)) {
const before = settings.hooks.SessionStart.length;
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(entry => {
if (!Array.isArray(entry.hooks)) return true;
return !entry.hooks.some(h =>
h && typeof h.command === 'string' && h.command.includes(hooksDest)
);
});
const dropped = before - settings.hooks.SessionStart.length;
if (dropped > 0) {
if (settings.hooks.SessionStart.length === 0) delete settings.hooks.SessionStart;
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
if (Object.keys(settings).length === 0) {
rmSync(settingsPath);
console.log(` ✅ Claude Code: 删除空 settings.json -> ${settingsPath}`);
} else {
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
console.log(` ✅ Claude Code: 从 settings.json 摘掉 ${dropped} 个 hook entry`);
}
removed += dropped;
}
}
} catch (e) {
console.warn(` ⚠️ .claude/settings.json 解析失败,跳过 hook 清理: ${e.message}`);
}
}
// 2. 删 .claude/hooks/ 下我们装过的文件,目录空了就连目录一起删
if (existsSync(hooksDest) && existsSync(HOOKS_SRC)) {
const ourFiles = readdirSync(HOOKS_SRC, { withFileTypes: true })
.filter(e => e.isFile())
.map(e => e.name);
let fileRemoved = 0;
for (const f of ourFiles) {
const fp = resolve(hooksDest, f);
if (existsSync(fp)) {
try { rmSync(fp); fileRemoved++; } catch {}
}
}
if (fileRemoved > 0) {
console.log(` ✅ Claude Code: 移除 ${fileRemoved} 个 hook 脚本 -> ${hooksDest}`);
removed += fileRemoved;
}
try {
const left = readdirSync(hooksDest).filter(n => n !== '.DS_Store');
if (left.length === 0) rmSync(hooksDest, { recursive: true, force: true });
} catch {}
}
return removed;
}
function generateClaudeCodeBootstrap(projectDir) {
// 关键修复 (v1.4.0):先装 hooks——CLAUDE.md 文本是被动的,只有 SessionStart hook
// 才能保证每次会话注入 using-superpowers让 skill 真正能被自动触发。
installClaudeCodeHooks(projectDir);
const skillEntries = scanSkillEntries(SKILLS_SRC);
const skillList = skillEntries.map(s => `- **${s.name}**: ${s.desc}`).join('\n');
@@ -458,6 +578,33 @@ function installForTarget(target) {
}
}
// 对走 npx 路径装 Claude Code/Codex CLI/OpenCode/VS Code 的用户提示官方 plugin
// marketplace 才是 high-fidelity 路径。npx 装这些工具是 low-fidelityv1.4.0 起
// Claude Code 已补 hooks 注册,但 /skills 命令仍可能识别不到,因为没走 plugin 注册)。
const LOW_FIDELITY_TARGETS = ['Claude Code', 'Codex CLI', 'OpenCode', 'VS Code'];
function printMarketplaceHintIfNeeded(installedTargets) {
const lowFidelity = installedTargets.filter(t => LOW_FIDELITY_TARGETS.includes(t));
if (lowFidelity.length === 0) return;
console.log(` 📌 提示:以下工具有官方 plugin marketplace走那条路径体验更完整\n`);
if (lowFidelity.includes('Claude Code')) {
console.log(` Claude Code:`);
console.log(` /plugin marketplace add jnMetaCode/superpowers-zh`);
console.log(` /plugin install superpowers-zh@superpowers-zh`);
}
if (lowFidelity.includes('Codex CLI')) {
console.log(` Codex CLI: 在 CLI 内输入 /plugins搜索 superpowers`);
}
if (lowFidelity.includes('OpenCode')) {
console.log(` OpenCode: 见上游官方安装说明 https://github.com/obra/superpowers#opencode`);
}
if (lowFidelity.includes('VS Code')) {
console.log(` VS Code (Copilot): 见 docs/README.vscode.md`);
}
console.log(`\n npx 安装是 low-fidelity 路径,胜在跨工具一致;上面这些工具的 plugin marketplace 路径才是 high-fidelity。\n`);
}
function isHomeDir(p) {
const home = homedir();
if (!home) return false;
@@ -596,6 +743,9 @@ function uninstall() {
}
}
// v1.4.0+ 装的 Claude Code SessionStart hook 与 .claude/hooks/ 脚本
uninstallClaudeCodeHooks(PROJECT_DIR);
// 清理 .claude/agents 下旧版本装过的 legacy agentv1.2.x 及之前会装 code-reviewer.md
// v1.3.0 起跟随上游 v5.1.0 移除)。即使 agents/ 源目录已删,已装用户跑 --uninstall 仍应能清干净。
const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
@@ -680,11 +830,13 @@ function install(forceToolName, force) {
}
installForTarget(target);
console.log('\n 安装完成!重启你的 AI 编程工具即可生效。\n');
printMarketplaceHintIfNeeded([target.name]);
return;
}
// 自动检测
let installed = 0;
const installedTargets = [];
for (const target of TARGETS) {
const detects = Array.isArray(target.detect) ? target.detect : [target.detect];
@@ -692,6 +844,7 @@ function install(forceToolName, force) {
if (found) {
installForTarget(target);
installed++;
installedTargets.push(target.name);
}
}
@@ -708,9 +861,11 @@ function install(forceToolName, force) {
console.log(` ✅ 默认安装: ${countDirs(dest)} 个 skills -> ${dest}`);
generateClaudeCodeBootstrap(PROJECT_DIR);
installedTargets.push('Claude Code');
}
console.log('\n 安装完成!重启你的 AI 编程工具即可生效。\n');
printMarketplaceHintIfNeeded(installedTargets);
} catch (err) {
console.error(` ❌ 安装失败:${err.message}`);
process.exit(1);