mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-03 07:23:56 +08:00
两款都是 VS Code 扩展,加载的是「rules」而非懒加载的 skills —— rules 会 并入每一轮 system prompt。所以照搬「把 20 个 SKILL.md 复制进规则目录」会 让每轮对话背着几万字常驻开销(Cline 官方也提示规则超约 300 行后遵守度下降)。 改用仓库既有的 Trae 模式:规则目录里只放一份小索引(核心规则 + 20 个 skill 的名称/触发条件表),skills 本体放各自的 skills/ 目录由 agent 按需读取。 Cline(#112) - skills -> .cline/skills/,索引 -> .clinerules/superpowers-zh.md - 不写 YAML frontmatter:Cline 目前只支持 paths 一个条件字段, 无 frontmatter 即始终生效,正是所需 - 索引保持在 .clinerules/ 根层单文件 —— 子目录是否递归扫描官方未说明 Kilo Code(#88) - skills -> .kilocode/skills/,索引 -> .kilocode/rules/superpowers-zh.md - 走 .kilocode/rules/ 而非 v7 推荐的 .kilo/rules/ + kilo.jsonc:后者需要把 路径登记进用户的 kilo.jsonc,那是带注释的 JSONC,安装器安全合并容易改坏 用户配置。官方明确 .kilocode/rules/ 向后兼容且零配置生效,故选它。 docs 里写了想迁到 v7 方式该改哪一行,以及万一没生效怎么反馈。 接入点(全部为新增,未改动任何既有工具的条目) - TARGETS 加 2 条,检测标记 .clinerules / [.kilocode,.kilo,kilo.jsonc] 均唯一 - installForTarget 分发、TOOL_ALIASES(cline/kilocode/kilo/kilo-code) - BOOTSTRAP_DELETE 加两份索引路径,--uninstall 能清干净 - --global 明确拒绝并指向 docs:Cline 全局 rules 路径随 OS 变 (~/Documents/Cline/Rules,Linux/WSL 还有 ~/Cline/Rules 回退), Kilo 全局需改 kilo.jsonc,都不是通用 --global 能可靠命中的 - audit.sh TOOLS 数组加 cline kilocode,纳入 install/幂等/卸载回归 - 新增 docs/README.cline.md、docs/README.kilocode.md - 计数 20 -> 22:简繁 README、package.json、CLAUDE.md、3 份 plugin manifest (RELEASE-NOTES 里的历史计数是过往版本记录,不动) 实测 - 两款均通过 装 / 二次装幂等 / 卸载无残留 - 检测隔离:.clinerules 只触发 Cline、.kilocode|.kilo|kilo.jsonc 只触发 Kilo Code;.claude/.cursor/.trae/.qoder/.codebuddy 检测结果与改动前一致; .claude + .clinerules 共存时两款并行安装正常 - audit.sh: 130 pass / 0 fail(2 项 warn 为既有上游漂移,见 #19) 路径依据:docs.cline.bot/customization/cline-rules、kilo.ai/docs/customize/custom-rules 两款合并为一个提交:共用同一套「rules 型工具」机制与同一批计数改动, 拆开会产生 21 款的中间状态,audit 无法在中间提交上自洽通过。
255 lines
8.4 KiB
Bash
Executable File
255 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 质量审计脚本 —— 跑 4 类检查防漂移
|
||
#
|
||
# 1. 静态校验:JSON parse / SKILL.md frontmatter / symlink / hook 可执行性
|
||
# 2. Installer 功能:22 款工具装 / 卸载 / 幂等
|
||
# 3. 上游对齐:hooks 3 文件 + brainstorm scripts 3 文件 + 14 翻译 skill 结构层级
|
||
# 4. 交叉引用:README → docs/ 链接 + skill 间引用 + bootstrap 注入路径
|
||
#
|
||
# 用法:
|
||
# bash scripts/audit.sh # 跑全部,FAIL > 0 时 exit 1
|
||
# bash scripts/audit.sh --quick # 跳过 installer 功能测试
|
||
# bash scripts/audit.sh --no-upstream # 跳过上游对齐(CI 没 upstream remote 时)
|
||
#
|
||
# CI 默认在 PR + push to main 跑,发现漂移立刻拦下。
|
||
|
||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
cd "$ROOT"
|
||
|
||
QUICK=0
|
||
NO_UPSTREAM=0
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--quick) QUICK=1 ;;
|
||
--no-upstream) NO_UPSTREAM=1 ;;
|
||
esac
|
||
done
|
||
|
||
PASS=0; FAIL=0; WARN=0
|
||
declare -a FAILURES=()
|
||
declare -a WARNINGS=()
|
||
INSTALLER="$ROOT/bin/superpowers-zh.js"
|
||
|
||
ok() { PASS=$((PASS+1)); }
|
||
bad() { FAIL=$((FAIL+1)); FAILURES+=("$1"); echo " ❌ $1"; }
|
||
warn() { WARN=$((WARN+1)); WARNINGS+=("$1"); echo " ⚠️ $1"; }
|
||
hdr() { echo ""; echo "=== $1 ==="; }
|
||
|
||
# 确保有 upstream remote(CI 上需要 fetch)
|
||
ensure_upstream() {
|
||
if [ "$NO_UPSTREAM" = "1" ]; then return 1; fi
|
||
if ! git ls-remote --exit-code upstream HEAD >/dev/null 2>&1; then
|
||
if git remote get-url upstream >/dev/null 2>&1; then
|
||
git fetch upstream main --depth=50 --quiet 2>/dev/null || return 1
|
||
else
|
||
git remote add upstream https://github.com/obra/superpowers.git 2>/dev/null
|
||
git fetch upstream main --depth=50 --quiet 2>/dev/null || return 1
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
#==============================================================================
|
||
hdr "Category 1: 静态校验"
|
||
#==============================================================================
|
||
|
||
# 1a. JSON parse
|
||
while IFS= read -r f; do
|
||
if node -e "JSON.parse(require('fs').readFileSync('$f','utf8'))" 2>/dev/null; then
|
||
ok
|
||
else
|
||
bad "JSON parse failure: $f"
|
||
fi
|
||
done < <(find . -name "*.json" \
|
||
-not -path "./node_modules/*" \
|
||
-not -path "./.git/*" \
|
||
-not -path "./tests/*/node_modules/*")
|
||
|
||
# 1b. SKILL.md frontmatter 完整性
|
||
for f in skills/*/SKILL.md; do
|
||
if ! head -1 "$f" | grep -q '^---$'; then
|
||
bad "No frontmatter: $f"
|
||
continue
|
||
fi
|
||
fm=$(sed -n '/^---$/,/^---$/p' "$f" | head -20)
|
||
for field in name description; do
|
||
if ! echo "$fm" | grep -q "^${field}:"; then
|
||
bad "Missing frontmatter field '$field': $f"
|
||
fi
|
||
done
|
||
ok
|
||
done
|
||
|
||
# 1c. Symlink 解析
|
||
while IFS= read -r l; do
|
||
if [ -e "$l" ]; then ok; else bad "Broken symlink: $l"; fi
|
||
done < <(find . -type l -not -path "./node_modules/*" -not -path "./.git/*")
|
||
|
||
# 1d. Hook 脚本可执行权限
|
||
for f in hooks/session-start hooks/run-hook.cmd; do
|
||
if [ -x "$f" ]; then ok; else bad "Not executable: $f"; fi
|
||
done
|
||
|
||
#==============================================================================
|
||
if [ "$QUICK" != "1" ]; then
|
||
hdr "Category 2: Installer 功能测试(22 款工具)"
|
||
#==============================================================================
|
||
|
||
declare -a TOOLS=(claude cursor codex kiro deerflow trae antigravity vscode openclaw windsurf gemini aider opencode qwen hermes claw copilot qoder codebuddy codearts cline kilocode)
|
||
|
||
for tool in "${TOOLS[@]}"; do
|
||
TMP=$(mktemp -d)
|
||
pushd "$TMP" >/dev/null
|
||
|
||
if ! node "$INSTALLER" --tool "$tool" >/dev/null 2>&1; then
|
||
bad "Installer: $tool 安装失败"
|
||
popd >/dev/null
|
||
rm -rf "$TMP"
|
||
continue
|
||
fi
|
||
|
||
# 幂等:再装一遍不应炸
|
||
if ! node "$INSTALLER" --tool "$tool" >/dev/null 2>&1; then
|
||
bad "Installer: $tool 二次安装失败(幂等性破坏)"
|
||
popd >/dev/null
|
||
rm -rf "$TMP"
|
||
continue
|
||
fi
|
||
|
||
if ! node "$INSTALLER" --uninstall >/dev/null 2>&1; then
|
||
bad "Installer: $tool 卸载失败"
|
||
else
|
||
ok
|
||
fi
|
||
|
||
popd >/dev/null
|
||
rm -rf "$TMP"
|
||
done
|
||
|
||
else
|
||
echo ""
|
||
echo "[--quick 跳过 installer 功能测试]"
|
||
fi
|
||
|
||
#==============================================================================
|
||
hdr "Category 3: 上游对齐"
|
||
#==============================================================================
|
||
|
||
if ! ensure_upstream; then
|
||
warn "无法访问 upstream,跳过对齐检查(CI 上请确保有网络)"
|
||
else
|
||
# 3a. Hooks 3 文件 + cursor manifest
|
||
for f in hooks/session-start hooks/hooks.json hooks/run-hook.cmd hooks/hooks-cursor.json; do
|
||
d=$(diff <(git show upstream/main:$f 2>/dev/null) "$f" 2>/dev/null | wc -l | tr -d ' ')
|
||
if [ "$d" = "0" ]; then ok; else bad "Hooks 漂移: $f ($d 行)"; fi
|
||
done
|
||
|
||
# 3b. Brainstorm scripts 3 文件
|
||
for f in skills/brainstorming/scripts/server.cjs \
|
||
skills/brainstorming/scripts/start-server.sh \
|
||
skills/brainstorming/scripts/stop-server.sh; do
|
||
d=$(diff <(git show upstream/main:$f 2>/dev/null) "$f" 2>/dev/null | wc -l | tr -d ' ')
|
||
if [ "$d" = "0" ]; then ok; else bad "Brainstorm script 漂移: $(basename $f) ($d 行)"; fi
|
||
done
|
||
|
||
# 3c. 14 翻译 skill 结构层级(H1-H4 标题数)
|
||
declare -a SKILLS=(brainstorming dispatching-parallel-agents executing-plans \
|
||
finishing-a-development-branch receiving-code-review requesting-code-review \
|
||
subagent-driven-development systematic-debugging test-driven-development \
|
||
using-git-worktrees using-superpowers verification-before-completion \
|
||
writing-plans writing-skills)
|
||
|
||
for s in "${SKILLS[@]}"; do
|
||
up=$(git show upstream/main:skills/$s/SKILL.md 2>/dev/null | grep -cE '^#{1,4} ' || echo 0)
|
||
our=$(grep -cE '^#{1,4} ' "skills/$s/SKILL.md" 2>/dev/null || echo 0)
|
||
diff=$((up - our))
|
||
abs=${diff#-}
|
||
# 允许 3 个 header 差异(翻译造成的合并/拆分小幅波动)
|
||
if [ "$abs" -le "3" ]; then
|
||
ok
|
||
else
|
||
warn "Skill 结构漂移: ${s} (上游 H=${up}, 我们 H=${our}) -- 可能 v5.1.0 没跟,或主动扩写"
|
||
fi
|
||
done
|
||
|
||
# 3d. requesting-code-review/code-reviewer.md 结构(v5.1.0 self-contained)
|
||
up=$(git show upstream/main:skills/requesting-code-review/code-reviewer.md 2>/dev/null | grep -cE '^#{1,3} ' || echo 0)
|
||
our=$(grep -cE '^#{1,3} ' skills/requesting-code-review/code-reviewer.md)
|
||
diff=$((up - our))
|
||
abs=${diff#-}
|
||
if [ "$abs" -le "2" ]; then
|
||
ok
|
||
else
|
||
bad "code-reviewer.md 结构漂移 (上游 v5.1.0 self-contained, H=${up}; 我们 H=${our})"
|
||
fi
|
||
fi
|
||
|
||
#==============================================================================
|
||
hdr "Category 4: 交叉引用完整性"
|
||
#==============================================================================
|
||
|
||
# 4a. README → docs/ 链接
|
||
BROKEN=0
|
||
while IFS= read -r link; do
|
||
link=${link#(}; link=${link%)}
|
||
if [ -f "$link" ]; then ok; else
|
||
bad "README 链接断: $link"
|
||
BROKEN=$((BROKEN+1))
|
||
fi
|
||
done < <(grep -oE '\(docs/README\.[a-z-]+\.md\)' README.md)
|
||
|
||
# 4b. Skill 间引用(superpowers:xxx)
|
||
while IFS= read -r line; do
|
||
skill_file=$(echo "$line" | cut -d: -f1)
|
||
refs=$(echo "$line" | grep -oE '\bsuperpowers:[a-z-]+\b' | sort -u)
|
||
for ref in $refs; do
|
||
name=${ref#superpowers:}
|
||
if [ -d "skills/$name" ]; then ok; else
|
||
src=$(basename $(dirname "$skill_file"))
|
||
bad "Skill 引用断: $src 引用了不存在的 skills/$name"
|
||
fi
|
||
done
|
||
done < <(grep -rln 'superpowers:' skills/*/SKILL.md 2>/dev/null | \
|
||
xargs -I{} grep -H 'superpowers:' {} 2>/dev/null)
|
||
|
||
# 4c. 装完后 .claude/skills/using-superpowers/SKILL.md 路径必须存在(hook 依赖)
|
||
TMP=$(mktemp -d)
|
||
pushd "$TMP" >/dev/null
|
||
if node "$INSTALLER" --tool claude >/dev/null 2>&1; then
|
||
if [ -f "$TMP/.claude/skills/using-superpowers/SKILL.md" ]; then
|
||
ok
|
||
else
|
||
bad "装完后 .claude/skills/using-superpowers/SKILL.md 不存在(hook 会找不到)"
|
||
fi
|
||
fi
|
||
popd >/dev/null
|
||
rm -rf "$TMP"
|
||
|
||
#==============================================================================
|
||
echo ""
|
||
echo "=========================================="
|
||
echo "📊 审计结果"
|
||
echo "=========================================="
|
||
echo "✅ PASS: $PASS"
|
||
echo "⚠️ WARN: $WARN"
|
||
echo "❌ FAIL: $FAIL"
|
||
|
||
if [ "$WARN" -gt 0 ]; then
|
||
echo ""
|
||
echo "Warnings(不阻塞):"
|
||
for w in "${WARNINGS[@]}"; do echo " ⚠️ $w"; done
|
||
fi
|
||
|
||
if [ "$FAIL" -gt 0 ]; then
|
||
echo ""
|
||
echo "Failures(必须修):"
|
||
for f in "${FAILURES[@]}"; do echo " ❌ $f"; done
|
||
echo ""
|
||
echo "❌ Audit 失败:$FAIL 个 P0 问题。看 README 「质量审计」段了解每项含义。"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "✅ Audit 通过"
|
||
exit 0
|