Files
superpowers-zh/scripts/audit.sh
AI不止语 725977b422 feat: 加 Qoder 适配(第 18 款工具)— skills + always_on bootstrap rule
closes #26, closes #34

## 改动

- bin/superpowers-zh.js: TARGETS 加 Qoder (`.qoder/skills`, detect `.qoder`),
  TOOL_ALIASES 加 `qoder`,新增 `generateQoderBootstrap()` 生成
  `.qoder/rules/superpowers-zh.md`(`trigger: always_on` + `alwaysApply: true`)
- scripts/audit.sh: TOOLS 加 qoder,17→18
- 3 个 plugin manifest(.claude-plugin/{plugin,marketplace}.json、.cursor-plugin/plugin.json)
  description 同步到 18 款
- README、CLAUDE.md、package.json 文案对齐 18
- docs/README.qoder.md: 新增完整安装/使用/卸载指南,附 schema 说明

## 设计要点

Qoder Rules 的 frontmatter schema(`trigger: always_on` / `model_decision` /
`manual`)官方 docs.qoder.com/zh/user-guide/rules 未公开,来自 GitHub 上
4 份真实社区样本(python-office、termiClaude、QoderTest、TelegramFileServer)
的交叉验证。

按 Trae 模式生成 always_on bootstrap 而非 Cursor/Windsurf 的"裸 skill"模式,
是为了确保核心规则(先 brainstorming / 先 TDD / 先验证)在每个会话都自动加载,
不依赖模型对 description 文本的隐式匹配。

## 验证

- 端到端:--tool qoder 装、自动检测、二次幂等、卸载(20 skills + 1 bootstrap
  全部清掉,0 残留)
- bootstrap 文件 skill 列表(20)与实际 skills/ 目录数(20)一致
- 全量 audit: 96 PASS / 0 FAIL(18 款工具完整 install + 幂等 + uninstall 跑通)
2026-05-21 00:56:18 +08:00

255 lines
8.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 质量审计脚本 —— 跑 4 类检查防漂移
#
# 1. 静态校验JSON parse / SKILL.md frontmatter / symlink / hook 可执行性
# 2. Installer 功能18 款工具装 / 卸载 / 幂等
# 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 remoteCI 上需要 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 功能测试18 款工具)"
#==============================================================================
declare -a TOOLS=(claude cursor codex kiro deerflow trae antigravity vscode openclaw windsurf gemini aider opencode qwen hermes claw copilot qoder)
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