test(audit): 新增工具计数一致性检查;修 bash 3.2 下 CJK 报错信息乱码

审核 v1.7.2 时发现三个问题,一并修掉。

1) 工具计数散落十几处,没有任何检查兜着(audit 新增 Category 5,+24 项)

加一款工具要同步简繁 README(各 6 处)、package.json、CLAUDE.md、
site/build.mjs(三语言 15 处)、3 份 plugin manifest —— 这次全靠人工 grep 才
没漏。现在改为从 installer 的 TARGETS 推导期望值并逐处断言。

口径写进注释:TARGETS 是「安装目标」共 21 个;Copilot CLI 与 CC 共用
.claude/skills(别名 copilot 映射过去)不占独立条目,但文案里作为独立产品
单独计数,所以「文案数 = TARGETS 数 + 1 = 22」。

除逐处计数外还加了两条结构性断言:
- README 工具表的实际表格行数必须等于宣称数(防漏加/多加行)
- Category 2 实际回归测试的工具数必须等于宣称数(宣称了就必须测)

已验证这个检查真的会拦:把标题改成 23 款、删掉一行工具表,两种漂移都被抓到。

2) bash 3.2 下 CJK 报错信息乱码(真 bug,只在检查失败时才暴露)

macOS 自带 bash 3.2.57 解析标识符时不识别多字节字符,`$file「` 会把「的首字节
(0xE3) 吞进变量名,导致变量展开为空且字符被截断 —— 报错信息变成
「计数不一致: ����= ��应为 22」。写检查的人看不到,因为只有失败时才打印。

复现:bash -c 'f=README.md; echo "文件: $f「标签」结束"' -> 文件: ��标签」结束

修法是给紧跟 CJK 标点的变量加花括号。全仓扫描 `$var` 紧跟非 ASCII 字节的模式,
命中 3 处:audit.sh 2 处(本次新增)、verify-release.sh 1 处(上个提交带进来的,
因为检测项一直全通过所以从没暴露)。修后报错信息可读:
  计数不一致: README.md「工具表标题」= 23,应为 22
  检测 .clinerules/ -> 得到「Cline」,期望「错误期望值」

3) .gitignore 漏了新工具的安装产物

.trae/、.qoder/ 等都在忽略列表里,但 .cline/、.clinerules/、.kilocode/ 没加 ——
在本仓库内跑安装器自测会留一堆未跟踪文件。已补上并验证 git status 干净。

验证:audit.sh 154 pass / 0 fail(原 130,+24 为 Category 5)
      verify-release.sh 82 pass / 0 fail
This commit is contained in:
AI不止语
2026-08-07 21:54:48 +08:00
parent c6b741447b
commit 1321cc65fb
3 changed files with 80 additions and 1 deletions

3
.gitignore vendored
View File

@@ -10,6 +10,9 @@ triage/
site/dist/ site/dist/
# 安装器生成的 skills 副本(源在 skills/ 目录) # 安装器生成的 skills 副本(源在 skills/ 目录)
.cline/
.clinerules/
.kilocode/
.codex/skills/ .codex/skills/
.gemini/skills/ .gemini/skills/
.hermes/skills/ .hermes/skills/

View File

@@ -225,6 +225,82 @@ fi
popd >/dev/null popd >/dev/null
rm -rf "$TMP" rm -rf "$TMP"
#==============================================================================
hdr "Category 5: 工具计数一致性"
#==============================================================================
# 文案里宣称的工具数必须与 installer 实际支持的数量一致。
#
# 口径说明installer 的 TARGETS 是「安装目标」Copilot CLI 与 Claude Code 共用
# .claude/skills别名 copilot -> Claude Code在 TARGETS 里不占独立条目,但文案
# 里作为独立产品单独计数 —— 所以「文案工具数 = TARGETS 条目数 + 1」。
#
# 加新工具时最容易漏改文案:计数散落在简繁 README、package.json、CLAUDE.md、
# site/build.mjs、3 份 plugin manifest 十几处。这一类检查专门堵这个。
TARGET_COUNT=$(sed -n '/^const TARGETS = \[/,/^\];/p' "$INSTALLER" | grep -cE "^ \{ name: '")
EXPECTED_TOOLS=$((TARGET_COUNT + 1))
echo " installer TARGETS = $TARGET_COUNT 个安装目标 -> 文案应宣称 $EXPECTED_TOOLS"
# check_count <文件> <正则> <说明>:正则匹配到的所有数字都必须等于 EXPECTED_TOOLS
check_count() {
local file="$1" re="$2" label="$3" got n
if [ ! -f "$file" ]; then bad "计数检查: $file 不存在"; return; fi
got=$(grep -oE "$re" "$file" 2>/dev/null | grep -oE '[0-9]+' | sort -u)
if [ -z "$got" ]; then
bad "计数检查: ${file} 里没匹配到「${label}」—— 文案改动过?正则需同步更新"
return
fi
for n in $got; do
if [ "$n" != "$EXPECTED_TOOLS" ]; then
bad "计数不一致: ${file}${label}」= ${n},应为 ${EXPECTED_TOOLS}"
return
fi
done
ok
}
# ── 简体 README ──
check_count README.md '等 \*\*[0-9]+ 款 AI 编程工具\*\*' '首屏标语'
check_count README.md '\*\*[0-9]+ 款\*\*:上述' '对比表支持工具'
check_count README.md '\*\*\+\*\* [0-9]+ 款工具一键适配' '一句话总结'
check_count README.md '### 🤖 支持 [0-9]+ 款主流' '工具表标题'
check_count README.md '等 [0-9]+ 款工具真正会干活' '页脚标语'
check_count README.md 'across [0-9]+ AI coding tools' '英文简介'
# ── 繁體 README ──
check_count README.zh-Hant.md '等 \*\*[0-9]+ 款 AI 編程工具\*\*' '首屏標語'
check_count README.zh-Hant.md '\*\*[0-9]+ 款\*\*:上述' '對比表支援工具'
check_count README.zh-Hant.md '\*\*\+\*\* [0-9]+ 款工具一鍵適配' '一句話總結'
check_count README.zh-Hant.md '### 🤖 支援 [0-9]+ 款主流' '工具表標題'
check_count README.zh-Hant.md '等 [0-9]+ 款工具真正會幹活' '頁腳標語'
check_count README.zh-Hant.md 'across [0-9]+ AI coding tools' '英文簡介'
# ── 元数据与 manifest ──
check_count package.json '等 [0-9]+ 款工具' 'npm description'
check_count CLAUDE.md '支持 [0-9]+ 款 IDE/CLI' '贡献者指南'
check_count .claude-plugin/plugin.json '等 [0-9]+ 款工具' 'plugin manifest'
check_count .claude-plugin/marketplace.json '等 [0-9]+ 款工具' 'marketplace manifest'
check_count .cursor-plugin/plugin.json '等 [0-9]+ 款工具' 'cursor manifest'
# ── 官网(三语言) ──
check_count site/build.mjs '[0-9]+ 款 AI 编程工具装上' '官网简体标语'
check_count site/build.mjs '[0-9]+ 款 AI 編程工具裝上' '官网繁體標語'
check_count site/build.mjs '共 [0-9]+ 款:' '官网 FAQ 枚举'
check_count site/build.mjs 'into [0-9]+ AI coding tools' '官网英文标语'
check_count site/build.mjs '^ \{ q: .Which AI coding tools are supported.*[0-9]+ tools:' '官网英文 FAQ'
# ── 结构性检查README 工具表的实际行数 ──
rows=$(awk '/^### 🤖 支持 [0-9]+ 款主流/,/^> 运行 `npx/' README.md | grep -cE '^\| \[')
if [ "$rows" = "$EXPECTED_TOOLS" ]; then ok; else
bad "README 工具表有 $rows 行,应为 $EXPECTED_TOOLS 行(漏加/多加了表格行)"
fi
# ── 自检Category 2 测的工具数应等于宣称数(宣称了就必须测) ──
tools_tested=$(grep -oE '^declare -a TOOLS=\(.*\)' "$0" | sed -E 's/^declare -a TOOLS=\(//; s/\)$//' | wc -w | tr -d ' ')
if [ "$tools_tested" = "$EXPECTED_TOOLS" ]; then ok; else
bad "Category 2 只测了 $tools_tested 款,但文案宣称 $EXPECTED_TOOLS 款(宣称的工具必须都有回归测试)"
fi
#============================================================================== #==============================================================================
echo "" echo ""
echo "==========================================" echo "=========================================="

View File

@@ -90,7 +90,7 @@ for entry in "${DETECT[@]}"; do
T=$(mktemp -d); cd "$T"; mkdir -p "$marker" T=$(mktemp -d); cd "$T"; mkdir -p "$marker"
got=$(node "$INS" 2>&1 | grep -oE '✅ [A-Za-z][A-Za-z ]*(\[|:)' | sed 's/✅ //; s/ *[:[]$//' | sort -u | tr '\n' ',' | sed 's/,$//') got=$(node "$INS" 2>&1 | grep -oE '✅ [A-Za-z][A-Za-z ]*(\[|:)' | sed 's/✅ //; s/ *[:[]$//' | sort -u | tr '\n' ',' | sed 's/,$//')
if [ "$got" != "$want" ]; then if [ "$got" != "$want" ]; then
bad "检测 $marker/ -> 得到「$got」,期望「$want" bad "检测 ${marker}/ -> 得到「${got}」,期望「${want}"
else else
ok ok
fi fi