chore(repo): 移植 PR-First 提交流程门禁(脚本 + Gitea PR 模板)
对齐 novavis AGENTS.md §27,逐项按本仓实测改写,不照抄不存在的命令: - scripts/check-pr-checklist.sh:三种模式(模板结构 / 单 PR 文件 / --pr-dir 扫描) 与退出码 0/1/2 保持同源;模板根改为按脚本自身位置解析并保留 AGENT_PROJECT_DIR 覆写;修上游 --pr-dir 的参数解析缺陷(for arg in "$@" 内 shift 不消费值, 目录会被再当成 PR 文件),改 while+shift - .gitea/PULL_REQUEST_TEMPLATE.md:30 项 checklist 全部换成本仓真实门禁命令 (type-check / lint / test:unit / test:coverage 阈值 / Playwright / visual / check:contrast / check:headings / lighthouse / test:security:headers),并补 CMS 字段-需重跑 seed 声明、:3000 旧预览不作数、暗黑 token、动效 180–280ms、 品牌红触达、数字 basis 口径等本仓专有约束 - 文档同源:docs/development/quality-gates.md 新增「提交与 PR 流程」权威副本 (AGENTS.md 被 .gitignore 排除,不可依赖)、CLAUDE.md 补 Submission Flow 节 门禁自检:脚本 bash -n 通过;11 个用例覆盖三模式 + 缺节/缺文件/子项不足/非 PR 文件/未知选項/--quiet 等负路径,均带正控制以防「匹配零的假绿」。
This commit is contained in:
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-pr-checklist.sh — PR 提交门禁检查(对齐 AGENTS.md §5 质量门禁 / §6 文档同步清单)
|
||||
#
|
||||
# 检查 PR 描述文件中 checklist 子项的勾选完成情况,以及 PR 模板的结构完整性。
|
||||
# 规则:
|
||||
# ① PR 模板必须包含「全链路检查」「测试分层检查」「质量门禁」三节
|
||||
# ② PR 描述文件中所有 checklist 子项(- [ ])必须已勾选(不适用项勾选并注明 N/A 理由)
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/check-pr-checklist.sh # 检查模板结构
|
||||
# bash scripts/check-pr-checklist.sh <pr-description-file> # 检查 PR 描述文件
|
||||
# bash scripts/check-pr-checklist.sh --pr-dir <目录> # 扫描目录下所有 PR 文件
|
||||
# bash scripts/check-pr-checklist.sh --quiet # 仅违规时输出
|
||||
# bash scripts/check-pr-checklist.sh --help
|
||||
#
|
||||
# 退出码: 0=全部通过 1=发现违规 2=模板不存在/格式错误
|
||||
set -euo pipefail
|
||||
|
||||
# 说明:AGENT_PROJECT_DIR 供测试与多工作树场景覆写模板根目录;默认取脚本所在仓库根,
|
||||
# 因此从任意子目录调用都能定位到 .gitea/PULL_REQUEST_TEMPLATE.md。
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TEMPLATE_FILE="${AGENT_PROJECT_DIR:-$REPO_ROOT}/.gitea/PULL_REQUEST_TEMPLATE.md"
|
||||
|
||||
QUIET=0
|
||||
PR_FILE=""
|
||||
SCAN_DIR=""
|
||||
|
||||
# 采用 while+shift 而非 for:for arg in "$@" 里 shift 不会跳过已消费的值,
|
||||
# `--pr-dir <目录>` 会把 <目录> 再当成一次位置参数误判为 PR 文件。
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--help | -h)
|
||||
echo "check-pr-checklist.sh — PR 提交门禁检查"
|
||||
echo ""
|
||||
echo "用法:"
|
||||
echo " bash scripts/check-pr-checklist.sh # 检查模板结构"
|
||||
echo " bash scripts/check-pr-checklist.sh <pr-file> # 检查 PR 描述文件"
|
||||
echo " bash scripts/check-pr-checklist.sh --pr-dir <目录> # 扫描目录下所有 PR 文件"
|
||||
echo " bash scripts/check-pr-checklist.sh --quiet # 仅违规时输出"
|
||||
echo ""
|
||||
echo "检查项:"
|
||||
echo " - PR 模板结构完整性(三节必填 + checklist 子项 ≥ 20)"
|
||||
echo " - PR 描述文件中所有 checklist 子项均已勾选"
|
||||
exit 0
|
||||
;;
|
||||
--quiet | -q) QUIET=1 ;;
|
||||
--pr-dir)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "❌ --pr-dir 需要一个目录参数" >&2
|
||||
exit 2
|
||||
fi
|
||||
SCAN_DIR="$2"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "❌ 未知选项: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
if [ -n "$PR_FILE" ]; then
|
||||
echo "❌ 只接受一个 PR 描述文件,多余参数: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
PR_FILE="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
violations=()
|
||||
|
||||
# ── 读取 PR 描述正文:仅在文件确实以 YAML frontmatter 开头时才剥离 ──
|
||||
# 若无 frontmatter 也无条件剥离,awk 的 fm 永远 <2 → 正文为空 → 三个标准节全部
|
||||
# 「未发现」→ 任何普通描述都被误判为不是 PR 描述文件。故按首行判别并加空正文兜底。
|
||||
read_pr_body() {
|
||||
local file="$1" body=""
|
||||
if [ "$(head -1 "$file" 2>/dev/null)" = "---" ]; then
|
||||
body=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$file" 2>/dev/null || true)
|
||||
fi
|
||||
[ -n "$body" ] || body=$(cat "$file")
|
||||
printf '%s\n' "$body"
|
||||
}
|
||||
|
||||
report_and_exit() {
|
||||
local ok_msg="$1"
|
||||
if [ "${#violations[@]}" -gt 0 ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo "❌ ${2}:"
|
||||
printf '%s\n' "${violations[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
[ "$QUIET" -eq 0 ] && echo "✅ ${ok_msg}"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── 模式 1: 扫描目录 ──
|
||||
if [ -n "$SCAN_DIR" ]; then
|
||||
if [ ! -d "$SCAN_DIR" ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo "⚠ PR 目录不存在: ${SCAN_DIR}(跳过)" >&2
|
||||
exit 0
|
||||
fi
|
||||
while IFS= read -r f; do
|
||||
[ -f "$f" ] || continue
|
||||
body=$(read_pr_body "$f")
|
||||
unchecked=$(printf '%s\n' "$body" | grep -c '^- \[ \]' || true)
|
||||
if [ "$unchecked" -gt 0 ]; then
|
||||
violations+=("$(basename "$f"): 存在 ${unchecked} 个未勾选子项——所有 checklist 子项须勾选后才可提交 PR")
|
||||
fi
|
||||
done < <(find "$SCAN_DIR" -name '*.md' -type f | sort)
|
||||
report_and_exit "PR 描述文件门禁通过:所有文件子项均已勾选" "PR 描述文件门禁未通过"
|
||||
fi
|
||||
|
||||
# ── 模式 2: 检查单个 PR 描述文件 ──
|
||||
if [ -n "$PR_FILE" ]; then
|
||||
if [ ! -f "$PR_FILE" ]; then
|
||||
echo "❌ PR 描述文件不存在: $PR_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
body=$(read_pr_body "$PR_FILE")
|
||||
unchecked=$(printf '%s\n' "$body" | grep -c '^- \[ \]' || true)
|
||||
|
||||
has_section1=$(printf '%s\n' "$body" | grep -c '全链路检查' || true)
|
||||
has_section2=$(printf '%s\n' "$body" | grep -c '测试分层检查' || true)
|
||||
has_section3=$(printf '%s\n' "$body" | grep -c '质量门禁' || true)
|
||||
|
||||
if [ "$has_section1" -eq 0 ] && [ "$has_section2" -eq 0 ] && [ "$has_section3" -eq 0 ]; then
|
||||
violations+=("$(basename "$PR_FILE"): 未发现 PR 模板标准节(全链路检查/测试分层检查/质量门禁)——可能不是 PR 描述文件,或模板结构被修改")
|
||||
fi
|
||||
|
||||
if [ "$unchecked" -gt 0 ]; then
|
||||
violations+=("$(basename "$PR_FILE"): 存在 ${unchecked} 个未勾选子项——所有 checklist 子项须勾选后才可提交 PR")
|
||||
fi
|
||||
|
||||
report_and_exit "PR 描述文件门禁通过:所有子项均已勾选" "PR 描述文件门禁未通过"
|
||||
fi
|
||||
|
||||
# ── 模式 3: 检查 PR 模板结构完整性(默认模式)──
|
||||
if [ ! -f "$TEMPLATE_FILE" ]; then
|
||||
echo "❌ PR 模板文件不存在: $TEMPLATE_FILE" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
required_sections=("全链路检查" "测试分层检查" "质量门禁")
|
||||
missing_sections=()
|
||||
|
||||
for section in "${required_sections[@]}"; do
|
||||
if ! grep -q "^##.*${section}" "$TEMPLATE_FILE"; then
|
||||
missing_sections+=("${section}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing_sections[@]}" -gt 0 ]; then
|
||||
sections_str=$(IFS=, ; echo "${missing_sections[*]}")
|
||||
violations+=("PR 模板缺失必要节: ${sections_str}(PR 模板须包含全链路检查/测试分层检查/质量门禁三节)")
|
||||
fi
|
||||
|
||||
checklist_count=$(grep -c '^- \[ \]' "$TEMPLATE_FILE" || true)
|
||||
if [ "$checklist_count" -lt 20 ]; then
|
||||
violations+=("PR 模板中 checklist 子项数量异常(仅 ${checklist_count} 项,预期 ≥ 20 项)——模板可能被意外修改")
|
||||
fi
|
||||
|
||||
report_and_exit "PR 模板结构门禁通过:${checklist_count} 项子项,三节完整" "PR 模板结构门禁未通过"
|
||||
Reference in New Issue
Block a user