Harden dev-flow: pre-authorization marker, alternatives log, check script
- Record rejected answers in decision.md at the moment they are rejected - Allow explicit pre-authorized Build via a pre-authorized marker line - Land scripts/check-dev-flow.sh and derive devflow/index.md from archives - Add dev-flow process artifacts (decisions, prd) for open changes
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# dev-flow 收尾检查 + index 派生
|
||||
# 检查模式: check-dev-flow.sh <slug> → 对 openspec/changes/<slug>/ 执行三项收尾检查
|
||||
# 索引模式: check-dev-flow.sh --index → 扫描 openspec/changes/archive/ 与 openspec/archive/ 生成 devflow/index.md
|
||||
# 全量模式: check-dev-flow.sh --all → 对所有活跃 change 执行检查
|
||||
#
|
||||
# 三项收尾检查:
|
||||
# 1. decision.md 存在(豁免需显式: decision.md 或提交信息中写明)
|
||||
# 2. 含 ## Alternatives considered 或显式 <!-- alternatives-not-recorded -->
|
||||
# 3. ## Verification 无"已确认 X"类不可重跑结论
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CHANGES_ROOT="$PROJECT_ROOT/openspec/changes"
|
||||
|
||||
fail() { echo "✗ $1" >&2; exit 1; }
|
||||
|
||||
check_change() {
|
||||
local slug="$1"
|
||||
local dir="$CHANGES_ROOT/$slug"
|
||||
local decision="$dir/decision.md"
|
||||
local errors=0
|
||||
|
||||
[ -d "$dir" ] || fail "change 目录不存在: openspec/changes/$slug"
|
||||
|
||||
echo "== dev-flow 收尾检查: $slug =="
|
||||
|
||||
# 1. decision.md 存在
|
||||
if [ ! -f "$decision" ]; then
|
||||
echo "✗ [1] 缺少 openspec/changes/$slug/decision.md (纯机械改动可豁免,但豁免必须显式写在提交信息里)"
|
||||
errors=$((errors+1))
|
||||
else
|
||||
echo "✓ [1] decision.md 存在"
|
||||
|
||||
# 2. Alternatives 强制
|
||||
if grep -q '^## Alternatives considered' "$decision" || grep -q '<!-- alternatives-not-recorded -->' "$decision"; then
|
||||
echo "✓ [2] 备选已记录或显式声明未记录"
|
||||
else
|
||||
echo "✗ [2] decision.md 缺少 ## Alternatives considered,也没有 <!-- alternatives-not-recorded --> 显式豁免"
|
||||
errors=$((errors+1))
|
||||
fi
|
||||
|
||||
# 3. Verification 无不可重跑结论
|
||||
local vtext
|
||||
vtext="$(awk '/^## Verification/{flag=1;next} /^## /{flag=0} flag' "$decision")"
|
||||
if echo "$vtext" | grep -qE '已确认|已验证[^,。;(]|测试通过$'; then
|
||||
echo "✗ [3] ## Verification 含不可重跑的结论(如\"已确认 X 存在\")——改为可重跑的命令或明确的人工步骤"
|
||||
errors=$((errors+1))
|
||||
elif [ -z "$vtext" ]; then
|
||||
echo "✗ [3] 缺少 ## Verification 小节"
|
||||
errors=$((errors+1))
|
||||
else
|
||||
echo "✓ [3] Verification 通过"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$errors" -gt 0 ]; then
|
||||
echo "== $slug: $errors 项未通过 =="
|
||||
return 1
|
||||
fi
|
||||
echo "== $slug: 全部通过 =="
|
||||
return 0
|
||||
}
|
||||
|
||||
gen_index() {
|
||||
local out="$PROJECT_ROOT/devflow/index.md"
|
||||
echo "# devflow 索引" > "$out"
|
||||
echo "" >> "$out"
|
||||
echo "> 本文件由 scripts/check-dev-flow.sh --index 派生生成,**不要手写**。" >> "$out"
|
||||
echo "> 存储层是 openspec/archive/ 下的归档目录;此处只是浏览视图。" >> "$out"
|
||||
echo "" >> "$out"
|
||||
|
||||
local found=0
|
||||
local rows=()
|
||||
local arch_dir
|
||||
# 两个历史归档位置都扫:openspec/changes/archive/ 与 openspec/archive/
|
||||
for arch_dir in "$CHANGES_ROOT/archive" "$PROJECT_ROOT/openspec/archive"; do
|
||||
[ -d "$arch_dir" ] || continue
|
||||
local d
|
||||
while IFS= read -r d; do
|
||||
d="${d%/}"
|
||||
[ -d "$d" ] || continue
|
||||
local name; name="$(basename "$d")"
|
||||
local title; title=""
|
||||
local date; date=""
|
||||
local rel; rel="${d#"$PROJECT_ROOT"/}"
|
||||
# 标题取 decision.md 首行,否则 proposal.md 首个 # 标题
|
||||
if [ -f "$d/decision.md" ]; then
|
||||
title="$(head -1 "$d/decision.md" | sed 's/^# *//; s/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
|
||||
elif [ -f "$d/proposal.md" ]; then
|
||||
title="$(grep -m1 '^# ' "$d/proposal.md" | sed 's/^# *//' || true)"
|
||||
fi
|
||||
# 日期取目录名前缀 YYYY-MM-DD
|
||||
if [[ "$name" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
|
||||
date="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
rows+=("| ${date:-—} | ${title:-$name} | ${rel} |")
|
||||
found=$((found+1))
|
||||
done < <(find "$arch_dir" -mindepth 1 -maxdepth 1 -type d | sort)
|
||||
done
|
||||
|
||||
# 写表格(带表头)
|
||||
{
|
||||
echo "| 日期 | 标题 | 归档位置 |"
|
||||
echo "|---|---|---|"
|
||||
if [ ${#rows[@]} -gt 0 ]; then
|
||||
printf '%s\n' "${rows[@]}"
|
||||
fi
|
||||
} >> "$out"
|
||||
|
||||
echo "[index] 生成 $found 个归档条目 → devflow/index.md"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--index)
|
||||
gen_index
|
||||
;;
|
||||
--all)
|
||||
rc=0
|
||||
for d in "$CHANGES_ROOT"/*/; do
|
||||
d="${d%/}"
|
||||
[ -d "$d" ] || continue
|
||||
name="$(basename "$d")"
|
||||
[ "$name" = "archive" ] && continue
|
||||
check_change "$name" || rc=1
|
||||
done
|
||||
exit $rc
|
||||
;;
|
||||
"")
|
||||
echo "用法: check-dev-flow.sh <slug> | --all | --index" >&2
|
||||
echo " <slug> 对单个活跃 change 执行三项收尾检查" >&2
|
||||
echo " --all 检查所有活跃 change" >&2
|
||||
echo " --index 扫描归档生成 devflow/index.md" >&2
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
check_change "$1"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user