mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-03 07:23:56 +08:00
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:
17
README.md
17
README.md
@@ -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,仅作备选)
|
||||
|
||||
@@ -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-fidelity,npx 是 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)
|
||||
|
||||
@@ -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-fidelity(v1.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 agent(v1.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);
|
||||
|
||||
Reference in New Issue
Block a user