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>
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
# 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/` |
|
||||
Reference in New Issue
Block a user