# Ratchet Script Template 泛化的 ratchet 脚本模板,基于 new-ai-proj `check-architecture.sh` 提取。 ## 使用方法 1. 复制下方脚本到 `scripts/check-{NAME}.sh` 2. 替换所有 `{PLACEHOLDER}` 为项目特定值 3. 运行 `chmod +x scripts/check-{NAME}.sh` 4. 运行 `./scripts/check-{NAME}.sh baseline` 记录首次基线 5. 提交脚本和基线文件 ## 脚本模板 ```bash #!/usr/bin/env bash # # {DESCRIPTION} Ratchet — ensures new code doesn't increase {WHAT} violations. # # Usage: # ./scripts/check-{NAME}.sh check # CI / pre-push: fail if violations increased # ./scripts/check-{NAME}.sh baseline # Record current counts (commit the result) # ./scripts/check-{NAME}.sh report # Pretty-print current vs baseline # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" BASELINE_FILE="$SCRIPT_DIR/.{NAME}-baseline.json" TARGET_DIR="$SCRIPT_DIR/../{TARGET_DIRECTORY}" # ── rule definitions ──────────────────────────────────────────────── # Add/remove rules as needed. Each rule has: # - A name (used as JSON key) # - A type: "ratchet" (baseline can decrease) or "wall" (must be 0) # - A count function RULES=({RULE_NAMES}) # e.g. ("rule_one" "rule_two" "rule_three") TYPES=({RULE_TYPES}) # e.g. ("ratchet" "ratchet" "wall") # ── counting functions ────────────────────────────────────────────── count_rule() { local rule="$1" case "$rule" in {RULE_NAME_1}) # Example: count files in {DIR} that import {PACKAGE} grep -rl '{PATTERN_1}' "$TARGET_DIR/{SUBDIR_1}/" 2>/dev/null | wc -l | tr -d ' ' ;; {RULE_NAME_2}) grep -rl '{PATTERN_2}' "$TARGET_DIR/{SUBDIR_2}/" 2>/dev/null | wc -l | tr -d ' ' ;; # Add more rules... *) echo "0" ;; esac } # ── helpers ───────────────────────────────────────────────────────── current_counts() { echo "{" local first=true for rule in "${RULES[@]}"; do local count count=$(count_rule "$rule") $first || echo "," printf ' "%s": %s' "$rule" "${count:-0}" first=false done echo "" echo "}" } json_val() { local key="$1" file="$2" grep "\"${key}\"" "$file" | head -1 | sed 's/[^0-9]//g' } json_val_str() { local key="$1" json="$2" echo "$json" | grep "\"${key}\"" | head -1 | sed 's/[^0-9]//g' } # ── commands ──────────────────────────────────────────────────────── cmd_baseline() { echo "Recording {NAME} baseline..." current_counts > "$BASELINE_FILE" echo "Baseline written to $BASELINE_FILE" cat "$BASELINE_FILE" } cmd_report() { if [[ ! -f "$BASELINE_FILE" ]]; then echo "No baseline found. Run: $0 baseline" exit 1 fi local current current=$(current_counts) printf "\n%-35s %10s %10s %8s\n" "Rule" "Baseline" "Current" "Status" printf "%-35s %10s %10s %8s\n" "---" "---" "---" "---" for i in "${!RULES[@]}"; do local rule="${RULES[$i]}" local type="${TYPES[$i]}" local base cur status base=$(json_val "$rule" "$BASELINE_FILE") cur=$(json_val_str "$rule" "$current") if [[ "$type" == "wall" ]]; then [[ "$cur" -eq 0 ]] && status="PASS" || status="FAIL" else [[ "$cur" -le "$base" ]] && status="PASS" || status="FAIL" fi printf "%-35s %10d %10d %8s\n" "$rule" "$base" "$cur" "$status" done echo "" } cmd_check() { if [[ ! -f "$BASELINE_FILE" ]]; then echo "ERROR: No baseline found. Run: $0 baseline" exit 1 fi local current current=$(current_counts) local failed=0 for i in "${!RULES[@]}"; do local rule="${RULES[$i]}" local type="${TYPES[$i]}" local base cur base=$(json_val "$rule" "$BASELINE_FILE") cur=$(json_val_str "$rule" "$current") if [[ "$type" == "wall" ]]; then if [[ "$cur" -ne 0 ]]; then echo "FAIL [hard wall] $rule: expected 0, got $cur" failed=1 fi else if [[ "$cur" -gt "$base" ]]; then echo "FAIL [ratchet] $rule: baseline=$base, current=$cur (+$((cur - base)))" failed=1 fi fi done if [[ "$failed" -eq 1 ]]; then echo "" echo "{NAME} check FAILED. New violations detected." echo "If this is intentional, update the baseline: $0 baseline" exit 1 fi echo "{NAME} check PASSED." } # ── main ──────────────────────────────────────────────────────────── case "${1:-check}" in baseline) cmd_baseline ;; report) cmd_report ;; check) cmd_check ;; *) echo "Usage: $0 {check|baseline|report}" exit 1 ;; esac ``` ## Placeholder 说明 | Placeholder | 含义 | 示例 | |-------------|------|------| | `{NAME}` | 脚本短名 | `architecture`, `import-rules` | | `{DESCRIPTION}` | 一句话描述 | `Architecture`, `Import Boundary` | | `{TARGET_DIRECTORY}` | 扫描目标目录(相对于 scripts/) | `backend`, `frontend/src` | | `{RULE_NAMES}` | Bash 数组,规则名列表 | `"handler_db" "route_db"` | | `{RULE_TYPES}` | Bash 数组,规则类型列表 | `"ratchet" "wall"` | | `{PATTERN_N}` | grep 匹配模式 | `"my-pkg/database"` | | `{SUBDIR_N}` | 目标子目录 | `handlers/`, `routes/` |