What:把 sync-domains.sh 檔頭註解的簡體字「脏」改成正體「髒」;ste100-lint.sh 的 skip() 新增三個檔案:jsc-hooks 的 simplified.txt、ste100-guard.sh、lang-guard.sh。 Why:「脏」是既有的簡體字,擴充字表後才驗出來,正好證明檢查有效。另外三個檔案 裡的簡體字是被列舉、被討論的對象,不是被使用;掃它們會每次都命中卻永遠改不掉, 久了就會有人乾脆把整個檢查關掉。 How:skip() 沿用既有的 case 比對,同時吃絕對路徑與純檔名兩種寫法,並在註解裡 寫明為什麼要跳過,避免後人誤以為是漏掉。 Who:STE100 語言規則的機檢工具。
128 lines
7.3 KiB
Bash
Executable File
128 lines
7.3 KiB
Bash
Executable File
#!/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 ;;
|
||
*/hooks/simplified.txt|simplified.txt) return 0 ;;
|
||
*/hooks/ste100-guard.sh|ste100-guard.sh) return 0 ;;
|
||
*/hooks/lang-guard.sh|lang-guard.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
|