Skip to content

Commit bcf817f

Browse files
committed
chore(ci): 加 audit 脚本 + workflow 自动检测上游漂移
防回归基建:把本轮全量质量审计的 4 类检查打包成可重复运行的脚本, 每次 PR 自动跑,发现漂移立刻拦下。 scripts/audit.sh: 1. 静态校验:JSON parse、SKILL.md frontmatter、symlink、hook 可执行性 2. Installer 功能:17 款工具装 / 重装(幂等)/ 卸载全跑一遍 3. 上游对齐:hooks 4 文件 + brainstorm scripts 3 文件 + 14 翻译 skill 结构层级(H1-H4 标题数)+ code-reviewer.md self-contained 版结构 4. 交叉引用:README → docs/ 链接、skill 间 superpowers:xxx 引用、 装完后 .claude/skills/using-superpowers/SKILL.md 路径解析 支持 --quick(跳过 installer)和 --no-upstream(跳过对齐)两个开关。 .github/workflows/audit.yml: - 触发:PR + push to main + 手动 dispatch - 步骤:checkout(fetch-depth 0)+ setup node 20 + 加 upstream remote + fetch upstream main 浅克隆 + 跑 audit.sh - FAIL > 0 → 整个 workflow 失败,PR 被卡 设计原则: - 这次"4 个 P0 缺陷漂"事件(hooks-cursor + brainstorm + code-reviewer 引用 + code-reviewer.md 整合)如果当时有这个 audit 在 CI 跑,PR 阶段就会被拦下 - 用 H 数对齐而不是行数 diff 来判断结构漂移(避免翻译造成的假阳性) - WARN(不阻塞)vs FAIL(阻塞)分级:主动扩写算 WARN, 结构性落后上游算 FAIL 本地烟测: - main 分支跑:92 PASS / 2 WARN / 8 FAIL(捕获到所有已知漂移) - chore/sync-upstream-p0-drift(PR #30)跑:97 PASS / 2 WARN / 0 FAIL(PR 修复后全绿) 后续:PR #28 + #30 merge 后,main 上 FAIL 应归零(WARN 只剩 executing-plans 主动扩写一项)。
1 parent 132b658 commit bcf817f

2 files changed

Lines changed: 283 additions & 0 deletions

File tree

.github/workflows/audit.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Audit
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
jobs:
11+
audit:
12+
name: 上游对齐 + 交叉引用 + Installer 功能 全量审计
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: '20'
22+
23+
- name: Add upstream remote
24+
run: |
25+
git remote add upstream https://github.com/obra/superpowers.git
26+
git fetch upstream main --depth=50
27+
28+
- name: Run audit
29+
run: bash scripts/audit.sh

scripts/audit.sh

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#!/usr/bin/env bash
2+
# 质量审计脚本 —— 跑 4 类检查防漂移
3+
#
4+
# 1. 静态校验:JSON parse / SKILL.md frontmatter / symlink / hook 可执行性
5+
# 2. Installer 功能:17 款工具装 / 卸载 / 幂等
6+
# 3. 上游对齐:hooks 3 文件 + brainstorm scripts 3 文件 + 14 翻译 skill 结构层级
7+
# 4. 交叉引用:README → docs/ 链接 + skill 间引用 + bootstrap 注入路径
8+
#
9+
# 用法:
10+
# bash scripts/audit.sh # 跑全部,FAIL > 0 时 exit 1
11+
# bash scripts/audit.sh --quick # 跳过 installer 功能测试
12+
# bash scripts/audit.sh --no-upstream # 跳过上游对齐(CI 没 upstream remote 时)
13+
#
14+
# CI 默认在 PR + push to main 跑,发现漂移立刻拦下。
15+
16+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17+
cd "$ROOT"
18+
19+
QUICK=0
20+
NO_UPSTREAM=0
21+
for arg in "$@"; do
22+
case "$arg" in
23+
--quick) QUICK=1 ;;
24+
--no-upstream) NO_UPSTREAM=1 ;;
25+
esac
26+
done
27+
28+
PASS=0; FAIL=0; WARN=0
29+
declare -a FAILURES=()
30+
declare -a WARNINGS=()
31+
INSTALLER="$ROOT/bin/superpowers-zh.js"
32+
33+
ok() { PASS=$((PASS+1)); }
34+
bad() { FAIL=$((FAIL+1)); FAILURES+=("$1"); echo "$1"; }
35+
warn() { WARN=$((WARN+1)); WARNINGS+=("$1"); echo " ⚠️ $1"; }
36+
hdr() { echo ""; echo "=== $1 ==="; }
37+
38+
# 确保有 upstream remote(CI 上需要 fetch)
39+
ensure_upstream() {
40+
if [ "$NO_UPSTREAM" = "1" ]; then return 1; fi
41+
if ! git ls-remote --exit-code upstream HEAD >/dev/null 2>&1; then
42+
if git remote get-url upstream >/dev/null 2>&1; then
43+
git fetch upstream main --depth=50 --quiet 2>/dev/null || return 1
44+
else
45+
git remote add upstream https://github.com/obra/superpowers.git 2>/dev/null
46+
git fetch upstream main --depth=50 --quiet 2>/dev/null || return 1
47+
fi
48+
fi
49+
return 0
50+
}
51+
52+
#==============================================================================
53+
hdr "Category 1: 静态校验"
54+
#==============================================================================
55+
56+
# 1a. JSON parse
57+
while IFS= read -r f; do
58+
if node -e "JSON.parse(require('fs').readFileSync('$f','utf8'))" 2>/dev/null; then
59+
ok
60+
else
61+
bad "JSON parse failure: $f"
62+
fi
63+
done < <(find . -name "*.json" \
64+
-not -path "./node_modules/*" \
65+
-not -path "./.git/*" \
66+
-not -path "./tests/*/node_modules/*")
67+
68+
# 1b. SKILL.md frontmatter 完整性
69+
for f in skills/*/SKILL.md; do
70+
if ! head -1 "$f" | grep -q '^---$'; then
71+
bad "No frontmatter: $f"
72+
continue
73+
fi
74+
fm=$(sed -n '/^---$/,/^---$/p' "$f" | head -20)
75+
for field in name description; do
76+
if ! echo "$fm" | grep -q "^${field}:"; then
77+
bad "Missing frontmatter field '$field': $f"
78+
fi
79+
done
80+
ok
81+
done
82+
83+
# 1c. Symlink 解析
84+
while IFS= read -r l; do
85+
if [ -e "$l" ]; then ok; else bad "Broken symlink: $l"; fi
86+
done < <(find . -type l -not -path "./node_modules/*" -not -path "./.git/*")
87+
88+
# 1d. Hook 脚本可执行权限
89+
for f in hooks/session-start hooks/run-hook.cmd; do
90+
if [ -x "$f" ]; then ok; else bad "Not executable: $f"; fi
91+
done
92+
93+
#==============================================================================
94+
if [ "$QUICK" != "1" ]; then
95+
hdr "Category 2: Installer 功能测试(17 款工具)"
96+
#==============================================================================
97+
98+
declare -a TOOLS=(claude cursor codex kiro deerflow trae antigravity vscode openclaw windsurf gemini aider opencode qwen hermes claw copilot)
99+
100+
for tool in "${TOOLS[@]}"; do
101+
TMP=$(mktemp -d)
102+
pushd "$TMP" >/dev/null
103+
104+
if ! node "$INSTALLER" --tool "$tool" >/dev/null 2>&1; then
105+
bad "Installer: $tool 安装失败"
106+
popd >/dev/null
107+
rm -rf "$TMP"
108+
continue
109+
fi
110+
111+
# 幂等:再装一遍不应炸
112+
if ! node "$INSTALLER" --tool "$tool" >/dev/null 2>&1; then
113+
bad "Installer: $tool 二次安装失败(幂等性破坏)"
114+
popd >/dev/null
115+
rm -rf "$TMP"
116+
continue
117+
fi
118+
119+
if ! node "$INSTALLER" --uninstall >/dev/null 2>&1; then
120+
bad "Installer: $tool 卸载失败"
121+
else
122+
ok
123+
fi
124+
125+
popd >/dev/null
126+
rm -rf "$TMP"
127+
done
128+
129+
else
130+
echo ""
131+
echo "[--quick 跳过 installer 功能测试]"
132+
fi
133+
134+
#==============================================================================
135+
hdr "Category 3: 上游对齐"
136+
#==============================================================================
137+
138+
if ! ensure_upstream; then
139+
warn "无法访问 upstream,跳过对齐检查(CI 上请确保有网络)"
140+
else
141+
# 3a. Hooks 3 文件 + cursor manifest
142+
for f in hooks/session-start hooks/hooks.json hooks/run-hook.cmd hooks/hooks-cursor.json; do
143+
d=$(diff <(git show upstream/main:$f 2>/dev/null) "$f" 2>/dev/null | wc -l | tr -d ' ')
144+
if [ "$d" = "0" ]; then ok; else bad "Hooks 漂移: $f ($d 行)"; fi
145+
done
146+
147+
# 3b. Brainstorm scripts 3 文件
148+
for f in skills/brainstorming/scripts/server.cjs \
149+
skills/brainstorming/scripts/start-server.sh \
150+
skills/brainstorming/scripts/stop-server.sh; do
151+
d=$(diff <(git show upstream/main:$f 2>/dev/null) "$f" 2>/dev/null | wc -l | tr -d ' ')
152+
if [ "$d" = "0" ]; then ok; else bad "Brainstorm script 漂移: $(basename $f) ($d 行)"; fi
153+
done
154+
155+
# 3c. 14 翻译 skill 结构层级(H1-H4 标题数)
156+
declare -a SKILLS=(brainstorming dispatching-parallel-agents executing-plans \
157+
finishing-a-development-branch receiving-code-review requesting-code-review \
158+
subagent-driven-development systematic-debugging test-driven-development \
159+
using-git-worktrees using-superpowers verification-before-completion \
160+
writing-plans writing-skills)
161+
162+
for s in "${SKILLS[@]}"; do
163+
up=$(git show upstream/main:skills/$s/SKILL.md 2>/dev/null | grep -cE '^#{1,4} ' || echo 0)
164+
our=$(grep -cE '^#{1,4} ' "skills/$s/SKILL.md" 2>/dev/null || echo 0)
165+
diff=$((up - our))
166+
abs=${diff#-}
167+
# 允许 3 个 header 差异(翻译造成的合并/拆分小幅波动)
168+
if [ "$abs" -le "3" ]; then
169+
ok
170+
else
171+
warn "Skill 结构漂移: ${s} (上游 H=${up}, 我们 H=${our}) -- 可能 v5.1.0 没跟,或主动扩写"
172+
fi
173+
done
174+
175+
# 3d. requesting-code-review/code-reviewer.md 结构(v5.1.0 self-contained)
176+
up=$(git show upstream/main:skills/requesting-code-review/code-reviewer.md 2>/dev/null | grep -cE '^#{1,3} ' || echo 0)
177+
our=$(grep -cE '^#{1,3} ' skills/requesting-code-review/code-reviewer.md)
178+
diff=$((up - our))
179+
abs=${diff#-}
180+
if [ "$abs" -le "2" ]; then
181+
ok
182+
else
183+
bad "code-reviewer.md 结构漂移 (上游 v5.1.0 self-contained, H=${up}; 我们 H=${our})"
184+
fi
185+
fi
186+
187+
#==============================================================================
188+
hdr "Category 4: 交叉引用完整性"
189+
#==============================================================================
190+
191+
# 4a. README → docs/ 链接
192+
BROKEN=0
193+
while IFS= read -r link; do
194+
link=${link#(}; link=${link%)}
195+
if [ -f "$link" ]; then ok; else
196+
bad "README 链接断: $link"
197+
BROKEN=$((BROKEN+1))
198+
fi
199+
done < <(grep -oE '\(docs/README\.[a-z-]+\.md\)' README.md)
200+
201+
# 4b. Skill 间引用(superpowers:xxx)
202+
while IFS= read -r line; do
203+
skill_file=$(echo "$line" | cut -d: -f1)
204+
refs=$(echo "$line" | grep -oE '\bsuperpowers:[a-z-]+\b' | sort -u)
205+
for ref in $refs; do
206+
name=${ref#superpowers:}
207+
if [ -d "skills/$name" ]; then ok; else
208+
src=$(basename $(dirname "$skill_file"))
209+
bad "Skill 引用断: $src 引用了不存在的 skills/$name"
210+
fi
211+
done
212+
done < <(grep -rln 'superpowers:' skills/*/SKILL.md 2>/dev/null | \
213+
xargs -I{} grep -H 'superpowers:' {} 2>/dev/null)
214+
215+
# 4c. 装完后 .claude/skills/using-superpowers/SKILL.md 路径必须存在(hook 依赖)
216+
TMP=$(mktemp -d)
217+
pushd "$TMP" >/dev/null
218+
if node "$INSTALLER" --tool claude >/dev/null 2>&1; then
219+
if [ -f "$TMP/.claude/skills/using-superpowers/SKILL.md" ]; then
220+
ok
221+
else
222+
bad "装完后 .claude/skills/using-superpowers/SKILL.md 不存在(hook 会找不到)"
223+
fi
224+
fi
225+
popd >/dev/null
226+
rm -rf "$TMP"
227+
228+
#==============================================================================
229+
echo ""
230+
echo "=========================================="
231+
echo "📊 审计结果"
232+
echo "=========================================="
233+
echo "✅ PASS: $PASS"
234+
echo "⚠️ WARN: $WARN"
235+
echo "❌ FAIL: $FAIL"
236+
237+
if [ "$WARN" -gt 0 ]; then
238+
echo ""
239+
echo "Warnings(不阻塞):"
240+
for w in "${WARNINGS[@]}"; do echo " ⚠️ $w"; done
241+
fi
242+
243+
if [ "$FAIL" -gt 0 ]; then
244+
echo ""
245+
echo "Failures(必须修):"
246+
for f in "${FAILURES[@]}"; do echo "$f"; done
247+
echo ""
248+
echo "❌ Audit 失败:$FAIL 个 P0 问题。看 README 「质量审计」段了解每项含义。"
249+
exit 1
250+
fi
251+
252+
echo ""
253+
echo "✅ Audit 通过"
254+
exit 0

0 commit comments

Comments
 (0)