What:ERROR_CONTENTS 改由 wiki-repo CONTENTS 解析,ERROR_{HASH} 仍走
wiki-repo ERROR,兩者是兩個不同的存取庫。目錄列的連結改用 wiki-url 的絕對網址。
註解掃描的頁面編號樣式補上 40 碼與 H 加 7 碼兩種形狀。
Why:目錄列的網址原本在異常頁寫入之前就取,而頁名的雜湊帶時間戳、每次都是全新頁,
那時查一定是 404,又被吞掉,所以那一格一直都是空的。註解掃描原本只收 8 碼純十六進位,
舊演算法有十三個首碼會改寫成 H 開頭,等於對絕大多數舊頁編號漏偵測。
How:降級語意分兩層——異常頁的存取庫解不出來就整支安靜降級,只有目錄頁解不出來就
只寫異常頁、跳過索引,兩種都維持 exit 0。「只有 exit 4 才准建新頁、7 與 8 一律中止」
那段原樣保留,那是防止把金鑰失效讀成頁面不存在、拿範本蓋掉整頁既有列。
Who:jsc-hooks
160 lines
9.5 KiB
Bash
Executable File
160 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# comment-scope.sh — 程式碼註解不得夾帶文件相關資訊與暫時性流程資訊(hook > prompt 的強制層)。
|
||
# 規則正文的唯一來源:jsc-review 的 references/comment-scope.md。本腳本只實作可用樣式判定的項目;
|
||
# 專案代號、客戶名稱這類無法用樣式判定的,交給 jsc-review:code-review 第 2 組人工審查。
|
||
#
|
||
# 用法:
|
||
# comment-scope.sh prompt 注入規則摘要(UserPromptSubmit 或規則檔取文字用)
|
||
# comment-scope.sh 掃描剛寫入的單一檔案(PostToolUse)
|
||
# comment-scope.sh sweep [dir] 掃描整個工作區這次改過的所有檔案(沒有 post-tool hook 的 CLI 用)
|
||
#
|
||
# 為什麼要有 sweep:只有 claude 接得到 PostToolUse,逐檔精準掃得到。codex 只有每輪結束的
|
||
# notify、kiro 只有 userPromptSubmit、copilot 與 antigravity 只有包裝別名,這四個都拿不到
|
||
# 「剛剛寫了哪個檔」,只能改成掃整個工作區的 git diff。時機晚一點,涵蓋範圍一樣。
|
||
#
|
||
# 輸入相容:
|
||
# Claude: PostToolUse 的 stdin JSON,取 tool_input.file_path。
|
||
# 其他 CLI: 環境變數 JSC_CHANGED_FILE。
|
||
# 兩者都取不到就安靜降級(exit 0)。
|
||
#
|
||
# 掃描範圍:檔案在 git 工作區內就只掃 `git diff HEAD` 的新增行,不翻舊帳;
|
||
# 不在 git 內或檔案尚未追蹤才整檔掃描。sweep 一律只看 git diff。
|
||
#
|
||
# 結束碼:0=沒命中或資料不足;2=命中,訊息走 stderr 交回模型自行修正(不擋寫入,檔案已經寫好了)。
|
||
# 逃生門:JSC_COMMENT_SCOPE=off。
|
||
set -u
|
||
|
||
. "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/lib.sh" 2>/dev/null || true
|
||
|
||
[ "${JSC_COMMENT_SCOPE:-on}" = "off" ] && exit 0
|
||
|
||
if [ "${1:-}" = "prompt" ]; then
|
||
echo "[jsc] 程式碼註解只寫「為什麼這樣寫」,不寫「這件事記在哪份文件」。禁止寫入:議題與 PR 編號、變更單編號、wiki 頁編號與網址、工作包編號、TDD 待辦編號、使用者故事與驗收條件與測試案例編號、規格章節與稽核項編號、commit hash 與分支名、版本號與 Sprint 與里程碑、人名與認領者與 @ 提及、工時估算、專案代號與客戶名稱、產生來源署名、外部文件連結、審查流程痕跡。"
|
||
echo "[jsc] 註解可以寫:日期與時間戳、需求變更歷程、RFC 與 ISO 標準編號、CVE 編號、第三方套件 issue 連結、授權標頭與 SPDX 標記、@deprecated 與 @since 等語言原生標記。命中禁止項就把編號指向的內容搬進註解,再刪掉編號。規則正文見 jsc-review 的 references/comment-scope.md。"
|
||
exit 0
|
||
fi
|
||
|
||
hit() { # $1=樣式 $2=說明;命中就把說明與最多三行證據印到 stdout
|
||
m=$(printf '%s\n' "$cleaned" | grep -nE "$1" | head -n 3)
|
||
[ -n "$m" ] || return 0
|
||
printf ' %s\n' "$2"
|
||
printf '%s\n' "$m" | sed 's/^/ /'
|
||
}
|
||
|
||
scan_file() { # $1=檔案路徑;命中就把報告印到 stdout 並回傳 1,沒命中回傳 0
|
||
f=$1
|
||
[ -f "$f" ] || return 0
|
||
|
||
# 非程式碼檔不受本規則限制:markdown、純文字、資料檔沒有「程式碼註解」。
|
||
case "$f" in
|
||
*.md|*.markdown|*.txt|*.rst|*.json|*.csv|*.tsv|*.svg|*.lock|*.log|*COMMIT_EDITMSG) return 0 ;;
|
||
esac
|
||
# 二進位檔跳過。只認 NUL 位元組——拿「非可列印字元」當判準會把所有含中文的檔案誤判成二進位。
|
||
raw=$(head -c 1024 "$f" 2>/dev/null | wc -c)
|
||
txt=$(head -c 1024 "$f" 2>/dev/null | LC_ALL=C tr -d '\000' | wc -c)
|
||
[ "$raw" = "$txt" ] || return 0
|
||
|
||
d=$(dirname -- "$f")
|
||
if git -C "$d" rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
|
||
git -C "$d" ls-files --error-unmatch -- "$f" >/dev/null 2>&1; then
|
||
lines=$(git -C "$d" diff HEAD -- "$f" 2>/dev/null | sed -n 's/^+[^+]/&/p' | cut -c2-)
|
||
[ -n "$lines" ] || return 0
|
||
else
|
||
lines=$(cat "$f" 2>/dev/null)
|
||
fi
|
||
|
||
# 只留註解行:行首註解符號,或行中出現 // 與 # 的行尾註解。
|
||
comments=$(printf '%s\n' "$lines" | grep -E '^[[:space:]]*(//|#|--|\*|/\*|<!--|;|%)|[[:space:]](//|#)[[:space:]]' || true)
|
||
[ -n "$comments" ] || return 0
|
||
|
||
# 白名單先剪掉,再比對禁止樣式。剪掉而不是整行放行——同一行可能一半合規、一半違規。
|
||
cleaned=$(printf '%s\n' "$comments" | sed -E \
|
||
-e 's#SPDX-License-Identifier:[^[:space:]]*##g' \
|
||
-e 's#CVE-[0-9]{4}-[0-9]+##g' \
|
||
-e 's#(RFC|ISO|IEEE|ANSI|ECMA|UTF|SHA|MD|AES|RSA|HMAC|PBKDF|TLS|SSL|HTTP|BIG|EUC|JIS|GB|RS|IPV|X)-?[0-9]+(-[0-9]+)?##g' \
|
||
-e 's#@(deprecated|since|param|returns?|throws|type|typedef|example|see|link|inheritdoc|override|nullable|internal)##g' \
|
||
-e 's#https?://(github|gitlab|bitbucket)\.com/[^[:space:]]*##g' \
|
||
-e 's#https?://[^[:space:]]*[{<][^[:space:]]*##g' \
|
||
-e 's#[0-9]{4}[-/][0-9]{1,2}[-/][0-9]{1,2}##g')
|
||
|
||
out=$(
|
||
hit '(^|[^[:alnum:]_/])#[0-9]+' '議題編號(#123)'
|
||
hit '(^|[^[:alnum:]_])![0-9]+' 'PR、MR 編號(!45)'
|
||
hit '[A-Z]{2,6}-[0-9]{1,6}' '工作包、故事、驗收、測試案例、變更單、議題編號(前綴加流水號)'
|
||
# 頁名樣式:十五種頁型,尾段是 CONTENTS(目錄頁)、40 碼大寫十六進位(內容頁),
|
||
# 或尚未遷移的舊頁編號。舊頁那兩條要留著,不然舊頁編號會漏偵測。
|
||
# 舊頁為什麼有 H 開頭這一條:舊的短碼演算法只要首碼落在 0-9ABC 就改寫成 H 加原前 7 碼,
|
||
# 十六個十六進位首碼有十三個會命中,所以既有舊頁名大多是 H 開頭,只收 [0-9A-F]{8} 會漏掉。
|
||
# 這條式子在別處另有兩份各自獨立的定義,稽核時才比對一致。刻意不共用函式:hook 要能
|
||
# 自足執行,執行期相依別的 plugin 路徑,那條路徑一缺,整支 hook 就掃不動了。
|
||
hit '(QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR|CONTENTS)_(CONTENTS|[0-9A-F]{8}|H[0-9A-F]{7}|[0-9A-F]{40})' 'jsc wiki 頁面編號'
|
||
hit '(todo|TODO|待辦)[[:space:]]*#?[0-9]+' 'TDD 待辦編號'
|
||
hit '([Ss]print|里程碑|[Mm]ilestone)[[:space:]]*[0-9]+' 'Sprint、里程碑編號'
|
||
hit '(^|[^[:alnum:].])v[0-9]+\.[0-9]+|版本[[:space:]]*v?[0-9]+\.[0-9]+' '版本號'
|
||
hit '(commit|提交|hash|SHA)[[:space:]:]*[0-9a-f]{7,40}' 'commit hash'
|
||
hit '(branch|分支)[[:space:]:]*[a-z]+/[a-z0-9-]+' '分支名稱'
|
||
hit '(^|[[:space:]])@[a-zA-Z][a-zA-Z0-9_.-]{2,}' '人名、認領者、@ 提及(含 @author)'
|
||
hit 'https?://[^[:space:]]*(wiki|confluence|atlassian|notion\.so|docs\.google|sharepoint)' 'wiki 或外部文件連結'
|
||
hit 'https?://[^[:space:]]*/(issues|pulls)/[0-9]+' '內部議題、PR 連結'
|
||
hit '([Gg]enerated (with|by)|Co-Authored-By|本檔(案)?由|AI (產生|生成|撰寫))' '產生來源署名'
|
||
hit '預估[[:space:]]*[0-9]+[[:space:]]*(小時|分鐘|人日|人天|天)|[0-9]+[[:space:]]*(人日|人天|工時)' '工時估算'
|
||
hit '(規格書|需求書|準則|規範|清單|guidelines)[^。]{0,8}第[[:space:]]*[0-9]+|(規格書|需求書|SRS)[^。]{0,6}[0-9]+(\.[0-9]+)+' '規格文件章節、稽核檢查項編號'
|
||
hit '([Cc]ode[[:space:]]+[Rr]eview|[Rr]eview[[:space:]]*(round|finding|findings|feedback|fix|status)|審查(流程|輪次|狀態))' '審查流程字樣'
|
||
hit '((審查|[Rr]eview|留言|修正)[^。]{0,12}(第[[:space:]]*[0-9一二三四五六七八九十]+[[:space:]]*輪|追加|後續)|第[[:space:]]*[0-9一二三四五六七八九十]+[[:space:]]*輪[^。]{0,12}(審查|[Rr]eview|留言|修正|追加|後續|檢查)|[Rr]ound[[:space:]]*#?[0-9]+)' '審查輪次描述'
|
||
hit '([Ff]inding|問題|缺陷)[[:space:]]*#?[0-9]+' '問題、發現、缺陷編號'
|
||
hit '([Hh]ermes|H[0-9]{4}|[Cc]ode[ -]?[Rr]eview[[:space:]]*[Bb]ot)' '審查者代稱或工具名'
|
||
hit '(真缺陷|BLOCKING|已解決|未解決)' '審查狀態標籤'
|
||
)
|
||
|
||
[ -n "$out" ] || return 0
|
||
printf '%s\n' "$f"
|
||
printf '%s\n' "$out"
|
||
return 1
|
||
}
|
||
|
||
advice() {
|
||
printf ' 修法:把編號指向的內容搬進註解,然後刪掉編號。搬不動就代表那件事不該用註解表達。\n'
|
||
printf ' 規則正文與白名單見 jsc-review 的 references/comment-scope.md。誤判時用 JSC_COMMENT_SCOPE=off 關閉。\n'
|
||
}
|
||
|
||
if [ "${1:-}" = "sweep" ]; then
|
||
target=${2:-.}
|
||
[ -d "$target" ] || exit 0
|
||
root=$(git -C "$target" rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||
[ -n "$root" ] || exit 0
|
||
changed=$(git -C "$root" diff --name-only HEAD 2>/dev/null)
|
||
[ -n "$changed" ] || exit 0
|
||
|
||
# 報告累積在暫存檔:迴圈跑在管線的子行程裡,變數帶不回來。
|
||
tmp=${TMPDIR:-/tmp}/jsc-comment-scope.$$
|
||
: > "$tmp" 2>/dev/null || exit 0
|
||
printf '%s\n' "$changed" | while IFS= read -r rel; do
|
||
[ -n "$rel" ] || continue
|
||
scan_file "$root/$rel" >> "$tmp" 2>/dev/null
|
||
done
|
||
if [ -s "$tmp" ]; then
|
||
{
|
||
printf '[jsc] 工作區有註解夾帶文件相關資訊或審查流程痕跡,請就地修正:\n'
|
||
sed 's/^/ /' "$tmp"
|
||
advice
|
||
} >&2
|
||
rm -f "$tmp"
|
||
exit 2
|
||
fi
|
||
rm -f "$tmp"
|
||
exit 0
|
||
fi
|
||
|
||
read_stdin 2>/dev/null || STDIN_JSON=""
|
||
file=$(json_str file_path 2>/dev/null || true)
|
||
[ -n "$file" ] || file="${JSC_CHANGED_FILE:-}"
|
||
[ -n "$file" ] || exit 0
|
||
|
||
report=$(scan_file "$file") && exit 0
|
||
{
|
||
printf '[jsc] 程式碼註解夾帶了文件相關資訊或審查流程痕跡,請就地修正:\n'
|
||
printf '%s\n' "$report"
|
||
advice
|
||
} >&2
|
||
exit 2
|