Files
meta/tools/ste100-lint.sh
T
jiantw83 7c963b73cf feat(lint): 機檢改讀共用簡體字表、加亂碼類別、掃描擴及程式碼檔
What
- 簡體字表改成優先讀 jsc-hooks 的 `hooks/simplified.txt`,讀不到才退回內建備援字表。
- 新增「亂碼」類別:U+FFFD 替代字元、雙重編碼的 `�`,以及 Latin-1 雙重編碼特徵。
- 掃描範圍加入程式碼檔(sh、js、ts、py、cs、java、go、rb、php、sql、yml、yaml、toml)。
- 檔頭註解補上分流理由,並把本檔自己加進跳過清單。

Why
- 字表寫死在本檔,和 hook 端的守門字表各自維護,兩邊遲早不一致;真實來源只該有一份。
- 內建備援不能拿掉:jsc-hooks 不一定裝在這台機器上,單獨 clone plugins/meta 或 CI 只取
  一個 repo 都會發生。缺基礎設施就讓簡體字檢查靜靜失效,會把「沒命中」變成假通過。
- 新規則要求所有非程式碼輸出無亂碼、無簡體字,程式碼註解也算,所以檢查得看得到程式碼檔。

How
- `simplified_file()` 的搜尋路徑比照 `jsc-hooks/hooks/lib.sh` 的 `jsc_gitea_sh()`:先環境變數
  `JSC_SIMPLIFIED_FILE`,再並排存取庫版面,最後已安裝的 plugin 快取版面取版本排序最後一份。
- 字表格式一行一個字,`#` 開頭與空行忽略,串成 grep -E 交替式。
- 分流:`.md` 與 `.json` 跑全部六項;程式碼檔只跑簡體字與亂碼。中國用語、半形標點、AI 套話、
  並列斜線這四項在程式碼裡會被英文標點、路徑、URL、正規表示式大量誤報,留著只會讓人忽略輸出。
- 亂碼樣式一律用十六進位跳脫寫,本檔才不會存進真的亂碼字元自己打自己。

Who
- 影響跑 `tools/ste100-lint.sh` 的人與 `jsc-meta:skill-check`、`jsc-meta:ste100-sync` 兩支技能。
2026-08-27 09:43:20 +08:00

123 lines
7.0 KiB
Bash
Executable File
Raw 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 sh
# ste100-lint.sh — 檢查檔案是否違反 STE100 擬人台灣感規則(可機檢的部分)。
# 用法: ste100-lint.sh <file|dir> [...]
# 檢查項目:
# 1. 中國用語(references/ste100.md 的替換表左欄)
# 2. 中文句內的半形標點(, ; ! ? 緊鄰中日韓字元)
# 3. 常見 AI 套話(總的來說、綜上所述、希望這對你有幫助 等)
# 4. 簡體字(只收沒有繁體正當用法的字,避免誤報)
# 5. 中文並列項用斜線(半形 / 或全形 / 夾在中日韓字元之間),應改頓號「、」
# 6. 亂碼(U+FFFD 替代字元、Latin-1 雙重編碼殘留)
# 輸出: {檔案}:{行號}:{類別}:{命中內容};全部通過 exit 0,有命中 exit 1。
# 掃描範圍與分流:
# 文件檔(.md、.json)跑全部六項。
# 程式碼檔(.sh .js .ts .py .cs .java .go .rb .php .sql .yml .yaml .toml)只跑簡體字與亂碼。
# 理由: 程式碼註解也要繁中無亂碼,但中國用語、半形標點、AI 套話、並列斜線這四項在程式碼裡
# 誤報率太高——英文標點、檔案路徑、URL、正規表示式到處都是半形逗號與斜線,識別字也常撞到
# 替換表左欄。只留簡體字與亂碼,命中就幾乎都是真的。
# 限制: 程式碼圍欄內的內容可能誤報,人工複核。
# references/ste100.md 與本檔本身跳過——規則文件與規則實作裡的詞是被討論,不是被使用。
# 並列斜線會誤報兩類:含中文的路徑或分支名範例(例 feat/報表/P2),
# 以及英文項目並列(準則允許)。命中後先判斷是不是真的並列項。
set -u
TERMS='默認|支持某|兼容|信息|數據庫|服務器|軟件|硬件|網絡|質量|卸載|反饋|視頻|屏幕|鼠標|打印|立馬|靠譜|賦能|閉環|抓手|復盤'
CLICHES='總的來說|綜上所述|希望這對你有幫助|好問題|在當今|瞬息萬變|標誌著|奠定了基礎|體現了|不僅僅是|讓我們一起'
# 亂碼特徵,全部用十六進位跳脫寫,本檔才不會存進真的亂碼字元自己打自己:
# \x{fffd} 替代字元,轉檔失敗留下的痕跡
# \x{ef}\x{bf}\x{bd} U+FFFD 本身又被雙重編碼一次(顯示成 �)
# [\x{c2}-\x{f4}][\x{80}-\x{bf}]
# 雙重編碼特徵:UTF-8 位元組被當 Latin-1 解讀再存回去,原本的前導位元組變成
# U+00C2–U+00F4 的拉丁字母(Ã、â、ä),後面緊跟著本來的續接位元組 U+0080–U+00BF。
# 兩者相連在正常文字裡幾乎不會出現,所以誤報極低;只寫 Ã 或 â 開頭會漏掉中日韓
# 字元最常見的 中 這一類。
GARBLED='\x{fffd}|\x{ef}\x{bf}\x{bd}|[\x{c2}-\x{f4}][\x{80}-\x{bf}]'
# 簡體字表的真實來源是 jsc-hooks 的 hooks/simplified.txt(ste100-guard.sh 共用同一份)。
# 讀不到就退回下面的內建備援字表。備援不能拿掉: jsc-hooks 不一定裝在這台機器上(單獨 clone
# plugins/meta、CI 只取一個 repo 都會發生),缺了基礎設施就讓簡體字檢查靜靜失效,會把「沒命中」
# 變成假通過,比不檢查更危險。
# 刻意排除繁體也在用的字(后、台、干、只、里、面、制、志),只留簡化後才出現的字形。
SIMPLIFIED_FALLBACK='应|为|这|说|发|国|过|对|开|关|问|题|东|车|马|鸟|龙|飞|见|无|产|业|务|书|写|学|习|报|纸|认|识|证|际|网|络|计|划|实|现|给|条|约|级|组|织|变|换|电|脑|两|双|单|构|价|钱|众|议|论|传|统|么|儿|们|从|来|时|间|长|门|闻|声|员|图|团|转|输|达|运|进|远|连|边|还|经|该|营|规|则|范|围|参|数|类|结|态|设|备|辑|译|码|库|档'
# 找共用字表。搜尋路徑比照 jsc-hooks/hooks/lib.sh 的 jsc_gitea_sh():
# 先環境變數,再開發用的並排存取庫版面,最後已安裝的 plugin 快取版面。
simplified_file() {
if [ -n "${JSC_SIMPLIFIED_FILE:-}" ] && [ -f "$JSC_SIMPLIFIED_FILE" ]; then
printf '%s\n' "$JSC_SIMPLIFIED_FILE"; return 0
fi
_root="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
# 開發用的並排存取庫版面:{workspace}/meta 旁邊就是 {workspace}/hooks
for _c in "$_root/../hooks/hooks/simplified.txt" "$_root/../jsc-hooks/hooks/simplified.txt"; do
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
done
# 已安裝版面:每個 plugin 各有版本目錄,取排序最後的一份(通常即最新版)
_c=$(ls "$_root"/../../jsc-hooks/*/hooks/simplified.txt \
"$_root"/../../hooks/*/hooks/simplified.txt \
"$HOME"/.claude/plugins/cache/*/jsc-hooks/*/hooks/simplified.txt 2>/dev/null \
| sort | tail -n1)
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
return 1
}
SIMPLIFIED=''
SIMPLIFIED_SRC=$(simplified_file || true)
if [ -n "$SIMPLIFIED_SRC" ]; then
# 一行一個字,# 開頭與空行忽略,行內空白去掉,再串成 grep -E 的交替式
SIMPLIFIED=$(sed 's/#.*//; s/[[:space:]]//g' "$SIMPLIFIED_SRC" \
| grep -v '^$' | tr '\n' '|' | sed 's/|$//')
fi
[ -n "$SIMPLIFIED" ] || SIMPLIFIED=$SIMPLIFIED_FALLBACK
hit=0
# 不帶參數會空跑並回 0,看起來像「全部通過」。這種假通過比不檢查更危險,所以擋掉。
if [ "$#" -eq 0 ]; then
echo '用法: ste100-lint.sh <file|dir> [...]' >&2
echo '沒有給檢查對象。空跑會回 0,看起來像通過,所以這裡直接視為錯誤。' >&2
exit 2
fi
skip() { # $1=路徑;規則文件與規則實作本身不掃
case "$1" in
*/references/ste100.md|references/ste100.md) return 0 ;;
*/tools/ste100-lint.sh|tools/ste100-lint.sh) return 0 ;;
*) return 1 ;;
esac
}
kind() { # $1=路徑;輸出 doc(跑全部六項)或 code(只跑簡體字與亂碼),其餘不掃
case "$1" in
*.md|*.json) printf 'doc' ;;
*.sh|*.js|*.ts|*.py|*.cs|*.java|*.go|*.rb|*.php|*.sql|*.yml|*.yaml|*.toml) printf 'code' ;;
*) printf '' ;;
esac
}
files() {
for p in "$@"; do
if [ -d "$p" ]; then
find "$p" \( \
-name '*.md' -o -name '*.json' \
-o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.py' \
-o -name '*.cs' -o -name '*.java' -o -name '*.go' -o -name '*.rb' \
-o -name '*.php' -o -name '*.sql' -o -name '*.yml' -o -name '*.yaml' \
-o -name '*.toml' \) -not -path '*/.git/*'
else
echo "$p"
fi
done
}
check() { # $1=檔案 $2=類別 $3=grep 模式 $4=grep 旗標
out=$(grep -n"$4" "$3" "$1" 2>/dev/null | sed "s|^|$1:|;s|:\([0-9]*\):|:\1:$2:|")
if [ -n "$out" ]; then printf '%s\n' "$out"; hit=1; fi
}
for f in $(files "$@"); do
skip "$f" && continue
k=$(kind "$f")
[ -n "$k" ] || continue
if [ "$k" = doc ]; then
check "$f" "中國用語" "$TERMS" E
check "$f" "半形標點" '[\x{4e00}-\x{9fff}][,;!?]|[,;][\x{4e00}-\x{9fff}]' P
check "$f" "AI 套話" "$CLICHES" E
check "$f" "並列斜線" '[\x{4e00}-\x{9fff}][ ]?[//][ ]?[\x{4e00}-\x{9fff}]' P
fi
check "$f" "簡體字" "$SIMPLIFIED" E
check "$f" "亂碼" "$GARBLED" P
done
exit $hit