From 43edf34b2c5f431cafa1b58588c2093f56b788a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?AI=E4=B8=8D=E6=AD=A2=E8=AF=AD?= <12096460+jnMetaCode@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:42:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(installer):=20Windsurf=20=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E8=A3=85=E9=94=99=E7=9B=AE=E5=BD=95=EF=BC=88=E8=A3=85=E4=BA=86?= =?UTF-8?q?=E4=B8=8D=E7=94=9F=E6=95=88=EF=BC=89+=20=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E5=A4=B1=E6=95=88=E7=9A=84=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Windsurf --global 一直装在 Windsurf 不读的地方 官方文档(docs.windsurf.com/windsurf/cascade/skills)写明两个路径**不同构**: 项目级:.windsurf/skills// 用户级:~/.codeium/windsurf/skills// ← 在 .codeium 下 我们的 --global 装到 ~/.windsurf/skills —— Windsurf 不读那里。又一个 Hermes 同款 「装了完全不生效」。项目级路径是对的,只有全局错。 Cursor 一并核实:cursor.com/docs/skills 确认 .cursor/skills//SKILL.md 启动时自动发现,无需配置 —— 我们装的是对的。 ## 测试为什么没抓到:只验退出码,不验落盘位置 verify-release 的 C 段对全局白名单只断言「退出码为 0」。装到错目录退出码照样是 0。 **「跑通了」不等于「装对了」。** 新增 GLOBAL_DIR 断言:10 款全局工具逐个断言 skill 真的落在官方读的那个目录,外加一条自检(GLOBAL_DIR 必须覆盖 GLOBAL_OK 全部, 以后加全局工具不能漏断言)。 双向验证:把 Windsurf 全局路径改回旧值,立刻报 「--global windsurf: .codeium/windsurf/skills 里是 0 个 skill,期望 20」。 ## 两个我自己写出来的静默失效 **① `grep -P` 不可移植。** 新加的「变量后紧跟多字节字符」检查第一版用了 grep -P, 在交互 shell 里(ugrep)看着能用,进了脚本跑的是 /usr/bin/grep(BSD grep,不支持 -P),配上 2>/dev/null 就成了永远匹配不到的死检查 —— 正是我这两天一直在修的那类 bug,自己又造了一个。改用 LC_ALL=C + ERE:C locale 下多字节按单字节处理,>0x7F 的字节落在 [^ -~] 之外,BSD/GNU 都支持。 **② 同一个全角括号坑又踩一次。** `期望 $EXPECT_SKILLS(…)` 里全角括号被吞进变量 名,set -u 下脚本半途崩掉。这已经是本仓第二次(第一次是死链检查的 $code)。所以 把它固化成 audit 1e,注释行排除,双向验证过。 ## 一次操作事故(记下来) 用 /tmp 备份做变异测试时,把 verify-release.sh 的未提交改动整段覆盖没了, git diff 才发现。变异测试应该用 git 保护现场,不该用 /tmp 拷贝。 ## 其它 - 外链验活跳过分支补 ok,否则 PASS 总数随网络漂移、发版记录对不上(实测连跑两次 稳定 111) - docs/README.windsurf.md 重写:两个路径不同构、渐进式披露(默认只给模型 name + description,不造成常驻开销)、跨工具发现(也扫 .agents/skills 与 .claude/skills, 装过 Antigravity 或 CC 的不必重复装) audit 168 pass / 0 warn / 0 fail;verify-release 111 pass / 0 fail。 --- README.md | 2 +- README.zh-Hant.md | 2 +- bin/superpowers-zh.js | 10 +++++++++- docs/README.windsurf.md | 26 ++++++++++++++++++++++++-- scripts/audit.sh | 19 +++++++++++++++++++ scripts/verify-release.sh | 27 +++++++++++++++++++++++++-- 6 files changed, 79 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 61e2234..f85671b 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ AI:在开始实现之前,我需要了解几个关键问题: | [Copilot CLI](https://githubnext.com/projects/copilot-cli) | CLI | `npx superpowers-zh --tool copilot` | `.claude/skills/` | | [Hermes Agent](https://github.com/NousResearch/hermes-agent) | CLI | `npx superpowers-zh --global --tool hermes` | `~/.hermes/skills/` | | [Cursor](https://cursor.sh) | IDE | `npx superpowers-zh` | `.cursor/skills/` | -| [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.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/` | | [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/` | diff --git a/README.zh-Hant.md b/README.zh-Hant.md index eaeff7d..4cbc112 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -140,7 +140,7 @@ AI:在開始實作之前,我需要了解幾個關鍵問題: | [Copilot CLI](https://githubnext.com/projects/copilot-cli) | CLI | `npx superpowers-zh --tool copilot` | `.claude/skills/` | | [Hermes Agent](https://github.com/NousResearch/hermes-agent) | CLI | `npx superpowers-zh --global --tool hermes` | `~/.hermes/skills/` | | [Cursor](https://cursor.sh) | IDE | `npx superpowers-zh` | `.cursor/skills/` | -| [Windsurf](https://windsurf.com) | IDE | `npx superpowers-zh` | `.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/` | | [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/` | diff --git a/bin/superpowers-zh.js b/bin/superpowers-zh.js index 6f5fef3..316f12f 100755 --- a/bin/superpowers-zh.js +++ b/bin/superpowers-zh.js @@ -55,6 +55,8 @@ const LEGACY_AGENT_FILENAMES = ['code-reviewer.md']; // 或存在于应用内设置,没有稳定的用户级 skills 加载路径 —— --global 会明确拒绝而非写无效路径。 const TARGETS = [ { name: 'Claude Code', dir: '.claude/skills', detect: '.claude', global: { dir: '.claude/skills', detect: '.claude', boot: '.claude/CLAUDE.md' } }, + // Cursor 经官方文档核实(cursor.com/docs/skills):.cursor/skills//SKILL.md, + // 启动时自动发现并交给 Agent 按上下文选用,也可在对话里打 / 手动点名。无需配置。 { name: 'Cursor', dir: '.cursor/skills', detect: ['.cursor', '.cursorrules'] }, // Codex 全局:docs 确认 Codex 启动时扫描 ~/.agents/skills/(不是 ~/.codex/skills), // 直接把每个 skill 复制到 ~/.agents/skills// 正好命中它的扁平扫描。 @@ -82,7 +84,13 @@ const TARGETS = [ { name: 'Antigravity', dir: '.agents/skills', detect: '.agents' }, { name: 'VS Code', dir: '.github/superpowers', detect: '.github/copilot-instructions.md' }, { name: 'OpenClaw', dir: 'skills', detect: '.openclaw', global: { dir: '.openclaw/skills', detect: '.openclaw' } }, - { name: 'Windsurf', dir: '.windsurf/skills', detect: '.windsurf', global: { dir: '.windsurf/skills', detect: '.windsurf' } }, + // Windsurf 全局路径与项目级**不同构**,这点反直觉:官方文档(docs.windsurf.com + // /windsurf/cascade/skills,现 307 跳 docs.devin.ai/desktop/cascade/skills)写明 + // 项目级:.windsurf/skills// + // 用户级:~/.codeium/windsurf/skills// ← 不是 ~/.windsurf/skills + // v1.7.10 及更早 --global 装到 ~/.windsurf/skills —— Windsurf 不读那里,装了不生效。 + // 它还会扫 .agents/skills 与 ~/.agents/skills;开了读取 CC 配置时也扫 .claude/skills。 + { name: 'Windsurf', dir: '.windsurf/skills', detect: '.windsurf', global: { dir: '.codeium/windsurf/skills', detect: '.codeium' } }, // Gemini 无 global:其全局加载是「扩展目录」~/.gemini/extensions/*/skills/ + gemini-extension.json, // 不是简单复制到 ~/.gemini/skills,通用 --global 覆盖不了。见 docs/README.gemini-cli.md。 { name: 'Gemini CLI', dir: '.gemini/skills', detect: 'GEMINI.md' }, diff --git a/docs/README.windsurf.md b/docs/README.windsurf.md index 2e8ac64..bbb4c4f 100644 --- a/docs/README.windsurf.md +++ b/docs/README.windsurf.md @@ -18,12 +18,34 @@ git clone https://github.com/jnMetaCode/superpowers-zh.git cp -r superpowers-zh/skills /your/project/.windsurf/skills ``` -或全局安装: +或全局安装(注意路径 —— **不是** `~/.windsurf/skills`): ```bash -cp -r superpowers-zh/skills ~/.windsurf/skills +npx superpowers-zh --global --tool windsurf +# 等价于手动:cp -r superpowers-zh/skills/* ~/.codeium/windsurf/skills/ ``` +> 📌 **v1.7.10 及更早的 `--global` 装到了 `~/.windsurf/skills`,Windsurf 不读那里,等于装了不生效。** 这是我们的实现错误,v1.7.11 起修正为官方路径 `~/.codeium/windsurf/skills/`。之前全局装过的请重装,并可手动删掉遗留的 `~/.windsurf/skills`。 + +## 工作原理 + +[Windsurf 官方文档](https://docs.windsurf.com/windsurf/cascade/skills)明确了两个路径,**它们不同构**: + +| 范围 | 路径 | +|---|---| +| 项目级 | `.windsurf/skills//` | +| 用户级(全局) | `~/.codeium/windsurf/skills//` | + +用户级在 `~/.codeium/` 下而不是 `~/.windsurf/` 下 —— 这点反直觉,是我们之前搞错的地方。 + +自动发现,无需配置。Cascade 采用渐进式披露:默认只把 skill 的 name 和 description 交给模型,决定调用时才加载 SKILL.md 全文,所以装 20 个不会造成常驻开销。 + +### 跨工具发现 + +官方还写明 Windsurf 会扫 `.agents/skills/` 与 `~/.agents/skills/`;若开启了读取 Claude Code 配置,`.claude/skills/` 与 `~/.claude/skills/` 也会被扫描。 + +也就是说:**如果你已经为 Antigravity(`.agents/skills`)或 Claude Code 装过,Windsurf 其实已经能读到**,不必重复装 —— 否则会加载两份。 + ## Skill 加载优先级 | 位置 | 优先级 | 说明 | diff --git a/scripts/audit.sh b/scripts/audit.sh index f1b950f..77709c5 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -90,6 +90,25 @@ for f in hooks/session-start hooks/run-hook.cmd; do if [ -x "$f" ]; then ok; else bad "Not executable: $f"; fi done +# 1e. shell 脚本里「变量后紧跟多字节字符」 +# +# bash 解析变量名时会把紧随其后的多字节字符吞进名字里,于是 set -u 下直接报 +# unbound variable 并中断脚本。本仓已踩过两次,两次都是「脚本半途崩掉、看起来 +# 像没跑」。写成花括号界定即可。 +# +# 注意实现:这里不能用 grep -P —— macOS 的 /usr/bin/grep 是 BSD grep,不支持 -P, +# 配上 2>/dev/null 就成了静默失效的检查(本检查第一版正是这么写的,交互 shell 里 +# 用 ugrep 看着能用,进了脚本一个都匹配不到)。用 LC_ALL=C + ERE:C locale 下 +# 多字节字符按单字节处理,>0x7F 的字节自然落在 [^ -~] 之外,BSD/GNU 都支持。 +# 排除注释行,否则本条说明文字自己会被命中。 +while IFS= read -r hit; do + case "${hit#*:*:}" in + \#*|" "*\#*) continue ;; # 注释行(含缩进注释)不算 + esac + bad "变量后紧跟多字节字符,bash 会吞进变量名(用 \${VAR} 界定): $hit" +done < <(LC_ALL=C grep -nE '\$[A-Za-z_][A-Za-z0-9_]*[^ -~]' scripts/*.sh hooks/session-start 2>/dev/null) +ok + #============================================================================== if [ "$QUICK" != "1" ]; then hdr "Category 2: Installer 功能测试(23 款工具)" diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index de5f094..190b2b8 100755 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -116,11 +116,32 @@ echo "" echo "─── C. --global:10 款应成功,其余应明确拒绝且退出码 1 ───" declare -a GLOBAL_OK=(claude codex openclaw windsurf opencode qwen qoder crush hermes codebuddy) declare -a GLOBAL_NO=(cursor kiro trae aider deerflow vscode claw gemini antigravity codearts cline kilocode) -for tool in "${GLOBAL_OK[@]}"; do +# 全局落盘位置断言。原来这里只看退出码 —— 而 Windsurf 的 --global 曾装到 +# ~/.windsurf/skills,官方实际读 ~/.codeium/windsurf/skills,退出码照样是 0。 +# 「跑通了」不等于「装对了」,必须断言 skill 真的落在官方读的那个目录。 +declare -a GLOBAL_DIR=( + "claude:.claude/skills" "codex:.agents/skills" "openclaw:.openclaw/skills" + "windsurf:.codeium/windsurf/skills" "opencode:.config/opencode/skills" + "qwen:.qwen/skills" "qoder:.qoder/skills" "crush:.config/crush/skills" + "hermes:.hermes/skills" "codebuddy:.codebuddy/skills" +) +for entry in "${GLOBAL_DIR[@]}"; do + tool="${entry%%:*}"; gdir="${entry#*:}" H=$(mktemp -d) - if HOME="$H" node "$INS" --global --tool "$tool" >/dev/null 2>&1; then ok; else bad "--global $tool 应成功但失败"; fi + if HOME="$H" node "$INS" --global --tool "$tool" >/dev/null 2>&1; then + n=$(ls -d "$H/$gdir"/*/ 2>/dev/null | wc -l | tr -d ' ') + if [ "$n" = "$EXPECT_SKILLS" ]; then ok; else + bad "--global ${tool}: ${gdir} 里是 ${n} 个 skill,期望 ${EXPECT_SKILLS} —— 装到别处了?" + fi + else + bad "--global ${tool} 应成功但失败" + fi rm -rf "$H" done +# 自检:GLOBAL_DIR 必须覆盖 GLOBAL_OK 全部,新增全局工具时不能漏断言落盘位置 +if [ "${#GLOBAL_DIR[@]}" = "${#GLOBAL_OK[@]}" ]; then ok; else + bad "GLOBAL_DIR(${#GLOBAL_DIR[@]}) 与 GLOBAL_OK(${#GLOBAL_OK[@]}) 数量不一致 —— 有全局工具没断言落盘位置" +fi for tool in "${GLOBAL_NO[@]}"; do H=$(mktemp -d) out=$(HOME="$H" node "$INS" --global --tool "$tool" 2>&1); rc=$? @@ -247,7 +268,9 @@ if curl -sS -o /dev/null --max-time 5 https://github.com 2>/dev/null; then [ "$dead" = "0" ] && ok || true rm -f "$LINKTMP" else + # 跳过时也计一次 ok,否则 PASS 总数会随网络状况漂移,发版记录里对不上 echo " (无网络,跳过外链验活)" + ok fi echo ""