Files
John Qiu b9c808cce0 feat(req-test-gate): 集成 Harness Engineering 工程约束方法论
将项目级的 Ratchet/约定检测方法论融入 req-test-gate 技能,
通过 /req 流程三个节点自动触发(dev 环境检测、cr 约定建议、test Gate 0B),
无需手动记忆执行。

新增文档:harness-engineering.md、ratchet-pattern.md、convention-flow.md、
project-bootstrap.md 及 4 个模板(ratchet/convention 脚本、GATES.md、pre-commit)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 11:34:42 +10:30

5.5 KiB
Raw Permalink Blame History

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. 提交脚本和基线文件

脚本模板

#!/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/