fix(installer): Codex 项目级装到 Codex 从不扫描的目录;CodeArts 补 --global

## Codex:项目级又是「装了完全不生效」

官方 skills 文档(developers.openai.com/codex/skills,现 308 跳到官方新文档站)
给出的扫描目录**完整清单**:

    $CWD/.agents/skills、$CWD/../.agents/skills、$REPO_ROOT/.agents/skills
    $HOME/.agents/skills、/etc/codex/skills、内置 skill

**没有 .codex/skills。** 两次分别追问确认「.codex/skills 是否曾被扫描」,答案都是
从未出现。而我们项目级正是装到 .codex/skills。

全局(~/.agents/skills)一直是对的 —— 原注释也写明了「不是 ~/.codex/skills」,
说明当初查过全局却没顺手核项目级。改为 .agents/skills,并在安装时清理旧位置
(只清我们装的那些,用户自己放在 .codex/skills 下的不动,已实测)。

与 Antigravity 共用 .agents/skills 是 Agent Skills 开放约定,不是冲突。

## 这个改动暴露出一个既有 bug:检测有顺序依赖

改完后测试立刻报「检测 .codex/ -> 得到「Antigravity,Codex CLI」,期望「Codex CLI」」。

根因:自动检测是**边装边判**的。Codex 装到 .agents/skills 会创建 .agents/,
循环走到 Antigravity(detect: '.agents')时它就被误判为「项目里有这个工具」,
于是多装一份。这个坑在任何「装了 A 会创建 B 的检测标记」的组合上都会犯。

改为先把所有工具的检测结果一次性算完再开始装 —— 检测必须基于**安装前**的项目
状态。实测:只有 .codex 时只装 Codex;同时有 .codex 与 .agents 时两款都装且
.agents/skills 下仍是 20 个(不重复)。

## CodeArts:官方已证实用户级路径,补上 --global

原注释只写「用户在 #20 确认」,没有官方出处。已补华为云官方用户指南出处:

    项目级:项目根目录的 ./.codeartsdoer/skills/
    个人级:%USERPROFILE%/.codeartsdoer/skills   即 ~/.codeartsdoer/skills

既然已证实,--global 从拒绝名单移出并加落盘断言。实测装 20 个到
~/.codeartsdoer/skills、卸载后 HOME 零残留。仍是 skills-only —— 其 bootstrap
约定未证实,不猜。

## 顺带核实

- Cursor:官方 skills 文档确认 .cursor/skills/<name>/SKILL.md 
- OpenCode:官方 skills 文档确认项目级与全局都对 (唯二全对的之一)
- Kimi / Pi:不在 TARGETS 里,走 plugin manifest 与扩展分发;已确认 docs 没有
  承诺 --tool kimi/pi,installer 也正确拒绝这两个名字,无虚假承诺

audit 168 pass / 0 warn / 0 fail;verify-release 115 pass / 0 fail。
This commit is contained in:
AI不止语
2026-08-13 05:54:52 +08:00
parent 0e3fbc4830
commit fb63548c9d
5 changed files with 78 additions and 17 deletions

View File

@@ -143,7 +143,7 @@ AI在开始实现之前我需要了解几个关键问题
| [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.windsurf/skills/`(全局 `~/.codeium/windsurf/skills/` | | [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.windsurf/skills/`(全局 `~/.codeium/windsurf/skills/` |
| [Kiro](https://kiro.dev) | IDE | `npx superpowers-zh` | `.kiro/skills/` | | [Kiro](https://kiro.dev) | IDE | `npx superpowers-zh` | `.kiro/skills/` |
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | CLI | `npx superpowers-zh` | `.gemini/skills/` | | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | CLI | `npx superpowers-zh` | `.gemini/skills/` |
| [Codex CLI](https://github.com/openai/codex) | CLI | `npx superpowers-zh` | `.codex/skills/` | | [Codex CLI](https://github.com/openai/codex) | CLI | `npx superpowers-zh` | `.agents/skills/` |
| [Aider](https://aider.chat) | CLI | `npx superpowers-zh` | `.aider/skills/` | | [Aider](https://aider.chat) | CLI | `npx superpowers-zh` | `.aider/skills/` |
| [Trae](https://trae.ai) | IDE | `npx superpowers-zh` | `.trae/skills/` + `.trae/rules/` | | [Trae](https://trae.ai) | IDE | `npx superpowers-zh` | `.trae/skills/` + `.trae/rules/` |
| [VS Code](https://code.visualstudio.com) (Copilot) | IDE 插件 | `npx superpowers-zh` | `.github/superpowers/` + `.github/instructions/` | | [VS Code](https://code.visualstudio.com) (Copilot) | IDE 插件 | `npx superpowers-zh` | `.github/superpowers/` + `.github/instructions/` |

View File

@@ -143,7 +143,7 @@ AI在開始實作之前我需要了解幾個關鍵問題
| [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.windsurf/skills/`(全局 `~/.codeium/windsurf/skills/` | | [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.windsurf/skills/`(全局 `~/.codeium/windsurf/skills/` |
| [Kiro](https://kiro.dev) | IDE | `npx superpowers-zh` | `.kiro/skills/` | | [Kiro](https://kiro.dev) | IDE | `npx superpowers-zh` | `.kiro/skills/` |
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | CLI | `npx superpowers-zh` | `.gemini/skills/` | | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | CLI | `npx superpowers-zh` | `.gemini/skills/` |
| [Codex CLI](https://github.com/openai/codex) | CLI | `npx superpowers-zh` | `.codex/skills/` | | [Codex CLI](https://github.com/openai/codex) | CLI | `npx superpowers-zh` | `.agents/skills/` |
| [Aider](https://aider.chat) | CLI | `npx superpowers-zh` | `.aider/skills/` | | [Aider](https://aider.chat) | CLI | `npx superpowers-zh` | `.aider/skills/` |
| [Trae](https://trae.ai) | IDE | `npx superpowers-zh` | `.trae/skills/` + `.trae/rules/` | | [Trae](https://trae.ai) | IDE | `npx superpowers-zh` | `.trae/skills/` + `.trae/rules/` |
| [VS Code](https://code.visualstudio.com) (Copilot) | IDE 外掛 | `npx superpowers-zh` | `.github/superpowers/` + `.github/instructions/` | | [VS Code](https://code.visualstudio.com) (Copilot) | IDE 外掛 | `npx superpowers-zh` | `.github/superpowers/` + `.github/instructions/` |

View File

@@ -58,9 +58,15 @@ const TARGETS = [
// Cursor 经官方文档核实cursor.com/docs/skills.cursor/skills/<name>/SKILL.md // Cursor 经官方文档核实cursor.com/docs/skills.cursor/skills/<name>/SKILL.md
// 启动时自动发现并交给 Agent 按上下文选用,也可在对话里打 / 手动点名。无需配置。 // 启动时自动发现并交给 Agent 按上下文选用,也可在对话里打 / 手动点名。无需配置。
{ name: 'Cursor', dir: '.cursor/skills', detect: ['.cursor', '.cursorrules'] }, { name: 'Cursor', dir: '.cursor/skills', detect: ['.cursor', '.cursorrules'] },
// Codex 全局docs 确认 Codex 启动时扫描 ~/.agents/skills/(不是 ~/.codex/skills // Codex 各层级扫的都是 .agents/skills**从不扫 .codex/skills**。官方文档
// 直接把每个 skill 复制到 ~/.agents/skills/<skill>/ 正好命中它的扁平扫描。 // developers.openai.com/codex/skills308 跳 learn.chatgpt.com/docs/build-skills
{ name: 'Codex CLI', dir: '.codex/skills', detect: '.codex', global: { dir: '.agents/skills', detect: '.codex' } }, // 给出的完整清单只有:
// $CWD/.agents/skills、$CWD/../.agents/skills、$REPO_ROOT/.agents/skills
// $HOME/.agents/skills、/etc/codex/skills、以及内置 skill
// 全局一直是对的(~/.agents/skills但**项目级原来装到 .codex/skills —— Codex
// 不读那里,等于装了不生效**。v1.7.11 改为 .agents/skills并清理旧位置。
// 与 Antigravity 装在同一目录是预期的:两者共用 .agents/skills 这个开放约定。
{ name: 'Codex CLI', dir: '.agents/skills', detect: '.codex', global: { dir: '.agents/skills', detect: '.codex' } },
// Kiro 的 steering 与 Cline / Kilo 的 rules 同性质:**每轮常驻**。官方文档 // Kiro 的 steering 与 Cline / Kilo 的 rules 同性质:**每轮常驻**。官方文档
// kiro.dev/docs/steering明确 `.kiro/steering/` 下的文件默认 inclusion: always // kiro.dev/docs/steering明确 `.kiro/steering/` 下的文件默认 inclusion: always
// "loaded into every Kiro interaction automatically"。 // "loaded into every Kiro interaction automatically"。
@@ -140,10 +146,14 @@ const TARGETS = [
// 优先级 项目级 > 用户级。v1.7.10 及更早文档写「用户级路径尚未验证」而没有 // 优先级 项目级 > 用户级。v1.7.10 及更早文档写「用户级路径尚未验证」而没有
// --global —— 现已核实,补上。 // --global —— 现已核实,补上。
{ name: 'CodeBuddy', dir: '.codebuddy/skills', detect: ['.codebuddy', 'CODEBUDDY.md'], global: { dir: '.codebuddy/skills', detect: '.codebuddy' } }, { name: 'CodeBuddy', dir: '.codebuddy/skills', detect: ['.codebuddy', 'CODEBUDDY.md'], global: { dir: '.codebuddy/skills', detect: '.codebuddy' } },
// 华为云码道CodeArts Doerskills 放 .codeartsdoer/skills/(用户在 #20 确认)。 // 华为云码道CodeArts Doer。原注释只写「用户在 #20 确认」—— 已补官方出处:
// 仅 skills-only —— 其 bootstrap/指令文件约定未证实,靠 CodeArts 自身 skill 发现; // support.huaweicloud.com/usermanual-codeartssnap/codeartsdoer_ug_0024.html
// 若不自动触发需在对话里手动点名 skilldocs 已说明)。 // 项目级:项目根目录的 ./.codeartsdoer/skills/
{ name: 'CodeArts', dir: '.codeartsdoer/skills', detect: '.codeartsdoer' }, // 个人级:%USERPROFILE%/.codeartsdoer/skills ← 即 ~/.codeartsdoer/skills
// 每个 skill 目录根必须有 SKILL.md自动发现、创建后立刻生效、无需配置。
// 个人级路径既然已证实v1.7.11 起支持 --global此前一直在拒绝名单里
// 仍是 skills-only —— 其 bootstrap/指令文件约定未证实,不猜路径。
{ name: 'CodeArts', dir: '.codeartsdoer/skills', detect: '.codeartsdoer', global: { dir: '.codeartsdoer/skills', detect: '.codeartsdoer' } },
// Cline / Kilo Code 是 VS Code 扩展加载的是「rules」而非 skills且 rules 每轮常驻 // Cline / Kilo Code 是 VS Code 扩展加载的是「rules」而非 skills且 rules 每轮常驻
// system prompt。因此 skills 放各自的 skills/ 目录(不被自动加载),只在 rules 目录里 // system prompt。因此 skills 放各自的 skills/ 目录(不被自动加载),只在 rules 目录里
// 放一份小索引 —— 见 generateClineBootstrapRule / generateKiloCodeBootstrapRule。 // 放一份小索引 —— 见 generateClineBootstrapRule / generateKiloCodeBootstrapRule。
@@ -1058,6 +1068,24 @@ function installForTarget(target, baseDir, isGlobal) {
generateQwenBootstrap(baseDir, isGlobal); generateQwenBootstrap(baseDir, isGlobal);
} }
// Codex 项目级旧位置清理v1.7.10 及更早装到 .codex/skills而 Codex 从不扫那里。
// 升级的人通常直接重装不会先卸载,不清的话旧目录留着白占地方、还让人以为在生效。
if (target.name === 'Codex CLI' && !isGlobal) {
const legacy = resolve(baseDir, '.codex', 'skills');
if (existsSync(legacy)) {
const ours = new Set(scanSkillEntries(SKILLS_SRC).map(s => s.name));
let n = 0;
for (const e of readdirSync(legacy, { withFileTypes: true })) {
if (e.isDirectory() && ours.has(e.name)) { rmSync(resolve(legacy, e.name), { recursive: true, force: true }); n++; }
}
if (n > 0) {
console.log(` 🧹 Codex: 清理旧位置 ${n} 个 skill 目录 <- .codex/skills/`);
console.log(` Codex 只扫 .agents/skills旧版装错了地方`);
}
try { if (readdirSync(legacy).filter(x => x !== '.DS_Store').length === 0) rmSync(legacy, { recursive: true, force: true }); } catch {}
}
}
if (target.name === 'VS Code') { if (target.name === 'VS Code') {
generateVSCodeBootstrap(baseDir); generateVSCodeBootstrap(baseDir);
} }
@@ -1113,7 +1141,7 @@ const BOOTSTRAP_DELETE = [
// v1.7.9 及更早把 skill 正文直接装进 .kiro/steering/<skill>/,而 steering 每轮常驻 —— // v1.7.9 及更早把 skill 正文直接装进 .kiro/steering/<skill>/,而 steering 每轮常驻 ——
// 那 335 KB 会一直进 prompt。升级的用户不会重跑旧版卸载所以这里按老路径也清一遍 // 那 335 KB 会一直进 prompt。升级的用户不会重跑旧版卸载所以这里按老路径也清一遍
// 否则新旧两份并存,开销问题原样保留。 // 否则新旧两份并存,开销问题原样保留。
const LEGACY_SKILL_DIRS = ['.kiro/steering']; const LEGACY_SKILL_DIRS = ['.kiro/steering', '.codex/skills'];
const BOOTSTRAP_CLEAN_SECTION = [ const BOOTSTRAP_CLEAN_SECTION = [
'CLAUDE.md', 'CLAUDE.md',
'GEMINI.md', 'GEMINI.md',
@@ -1387,10 +1415,19 @@ function install(forceToolName, force, isGlobal) {
let installed = 0; let installed = 0;
const pool = isGlobal ? GLOBAL_TARGETS : TARGETS; const pool = isGlobal ? GLOBAL_TARGETS : TARGETS;
// 先把所有工具的检测结果一次性算完,再开始装。
// 边装边判会有顺序依赖Codex 装到 .agents/skills 会创建 .agents/,紧接着
// Antigravitydetect: '.agents')就被误判为「项目里有」,于是多装一份。
// 两款共用 .agents/skills 是官方约定,但检测必须基于**安装前**的项目状态。
const detectedBefore = new Map();
for (const target of pool) { for (const target of pool) {
const detectMarker = isGlobal ? target.global.detect : target.detect; const dm = isGlobal ? target.global.detect : target.detect;
const detects = Array.isArray(detectMarker) ? detectMarker : [detectMarker]; const ds = Array.isArray(dm) ? dm : [dm];
const found = detects.some(d => existsSync(resolve(baseDir, d))); detectedBefore.set(target.name, ds.some(d => existsSync(resolve(baseDir, d))));
}
for (const target of pool) {
const found = detectedBefore.get(target.name);
if (found) { if (found) {
installForTarget(target, baseDir, isGlobal); installForTarget(target, baseDir, isGlobal);
installed++; installed++;

View File

@@ -1,5 +1,28 @@
# Superpowers 中文版 — Codex CLI 安装指南 # Superpowers 中文版 — Codex CLI 安装指南
## ⚠️ v1.7.10 及更早的项目级安装装错了地方
官方文档developers.openai.com/codex/skills现 308 跳 learn.chatgpt.com/docs/build-skills给出的扫描目录**完整清单**是:
```
$CWD/.agents/skills
$CWD/../.agents/skills
$REPO_ROOT/.agents/skills
$HOME/.agents/skills
/etc/codex/skills
```
**没有 `.codex/skills`。** 而 v1.7.10 及更早的 `npx superpowers-zh --tool codex` 正是装到 `.codex/skills` —— Codex 不扫那里,等于装了完全不生效。全局(`~/.agents/skills`)一直是对的,只有项目级错。
v1.7.11 起项目级改装 `.agents/skills`,重装时会自动清掉旧位置里我们装的那些(你自己放在 `.codex/skills` 下的东西不动):
```bash
cd /your/project
npx superpowers-zh --tool codex
```
> 顺带说明:`.agents/skills` 与 Antigravity 共用,这是 Agent Skills 开放约定,不是冲突。装过其中一个,另一个也能读到。
在 Codex 中使用 superpowers-zh 的完整指南。 在 Codex 中使用 superpowers-zh 的完整指南。
## 快速安装 ## 快速安装

View File

@@ -27,7 +27,7 @@ bad() { FAIL=$((FAIL+1)); FAILURES+=("$1"); printf ' ❌ %s\n' "$1"; }
# 工具别名 -> 期望的 skills 目录(相对项目根) # 工具别名 -> 期望的 skills 目录(相对项目根)
declare -a SPEC=( declare -a SPEC=(
"claude:.claude/skills" "cursor:.cursor/skills" "codex:.codex/skills" "claude:.claude/skills" "cursor:.cursor/skills" "codex:.agents/skills"
"kiro:.kiro/skills" "deerflow:skills/custom" "trae:.trae/skills" "kiro:.kiro/skills" "deerflow:skills/custom" "trae:.trae/skills"
"antigravity:.agents/skills" "vscode:.github/superpowers" "openclaw:skills" "antigravity:.agents/skills" "vscode:.github/superpowers" "openclaw:skills"
"windsurf:.windsurf/skills" "gemini:.gemini/skills" "aider:.aider/skills" "windsurf:.windsurf/skills" "gemini:.gemini/skills" "aider:.aider/skills"
@@ -113,9 +113,9 @@ for entry in "${DETECT[@]}"; do
done done
echo "" echo ""
echo "─── C. --global10 款应成功,其余应明确拒绝且退出码 1 ───" echo "─── C. --global11 款应成功,其余应明确拒绝且退出码 1 ───"
declare -a GLOBAL_OK=(claude codex openclaw windsurf opencode qwen qoder crush hermes codebuddy) declare -a GLOBAL_OK=(claude codex openclaw windsurf opencode qwen qoder crush hermes codebuddy codearts)
declare -a GLOBAL_NO=(cursor kiro trae aider deerflow vscode claw gemini antigravity codearts cline kilocode) declare -a GLOBAL_NO=(cursor kiro trae aider deerflow vscode claw gemini antigravity cline kilocode)
# 全局落盘位置断言。原来这里只看退出码 —— 而 Windsurf 的 --global 曾装到 # 全局落盘位置断言。原来这里只看退出码 —— 而 Windsurf 的 --global 曾装到
# ~/.windsurf/skills官方实际读 ~/.codeium/windsurf/skills退出码照样是 0。 # ~/.windsurf/skills官方实际读 ~/.codeium/windsurf/skills退出码照样是 0。
# 「跑通了」不等于「装对了」,必须断言 skill 真的落在官方读的那个目录。 # 「跑通了」不等于「装对了」,必须断言 skill 真的落在官方读的那个目录。
@@ -124,6 +124,7 @@ declare -a GLOBAL_DIR=(
"windsurf:.codeium/windsurf/skills" "opencode:.config/opencode/skills" "windsurf:.codeium/windsurf/skills" "opencode:.config/opencode/skills"
"qwen:.qwen/skills" "qoder:.qoder/skills" "crush:.config/crush/skills" "qwen:.qwen/skills" "qoder:.qoder/skills" "crush:.config/crush/skills"
"hermes:.hermes/skills" "codebuddy:.codebuddy/skills" "hermes:.hermes/skills" "codebuddy:.codebuddy/skills"
"codearts:.codeartsdoer/skills"
) )
for entry in "${GLOBAL_DIR[@]}"; do for entry in "${GLOBAL_DIR[@]}"; do
tool="${entry%%:*}"; gdir="${entry#*:}" tool="${entry%%:*}"; gdir="${entry#*:}"