Why: declarations were passing as capability; hand-transcribed outputs flowed through cards and PR bodies as if they were execution evidence, and stale external facts (e.g. the dead git-panel host) kept being trusted until burned. Evidence: guidelines draft.10 (PR Development-Guidelines#4, merged af199ee) pinned here; policy/facts.md F1-F4 each re-verified on 2026-10-05 before registration (endpoint version, token HTTP 200 via header only, SSH ls-remote, dead-host negative probe); scripts syntax-checked (bash -n); tool self-tests and make test recorded in the task card via the new make evidence. Impact: new AGENTS.md section (SELF/REPLAY/GATE, tool-written evidence, replay-on-review, zero-trust facts, honest-empty); make evidence/facts-verify; precheck gains [hooks]/[facts], task-sync gains stale-evidence-HEAD warning; task-start cards now embed the evidence table; gitlink advances to draft.10. Refs: task card .task/feature-evidence-driven-gates.md; guidelines CHANGELOG 0.1.0-draft.10.
55 lines
2.9 KiB
Bash
Executable File
55 lines
2.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 证据写入工具:执行命令,把(命令、被测 HEAD、退出码、输出 sha256、尾部摘录、时间、来源)
|
||
# 追加到当前分支任务卡的「## 证据(Evidence)」表。规范依据:AGENTS.md「证据与验证门禁」、
|
||
# guidelines standards/verification.md「声明与证据来源」——转写/手抄的输出视为无效证据。
|
||
# 用法:make evidence CMD="<单行命令>" [SRC=SELF|REPLAY|GATE] [TAIL=15]
|
||
# 退出码 = 被执行命令的退出码(失败可见),证据行无论成败都会写入。
|
||
set -uo pipefail
|
||
|
||
CMD="${CMD:-}"; SRC="${SRC:-SELF}"; TAILN="${TAIL:-15}"
|
||
[ -n "$CMD" ] || { echo 'usage: make evidence CMD="<command>" [SRC=SELF|REPLAY|GATE] [TAIL=15]' >&2; exit 2; }
|
||
case "$SRC" in SELF|REPLAY|GATE) ;; *) echo "ERROR: SRC 须为 SELF|REPLAY|GATE(当前:$SRC)" >&2; exit 2 ;; esac
|
||
case "$TAILN" in ''|*[!0-9]*) TAILN=15 ;; esac
|
||
case "$CMD" in *$'\n'*) echo 'ERROR: CMD 须为单行命令' >&2; exit 2 ;; esac
|
||
|
||
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||
[ -n "$branch" ] || { echo "ERROR: 不在 git 仓库内" >&2; exit 1; }
|
||
slug=$(echo "$branch" | tr '/' '-')
|
||
card=".task/${slug}.md"
|
||
if [ ! -f "$card" ]; then
|
||
# 回退:按卡片「分支:」字段匹配(与 task.sh cmd_sync 同款口径)
|
||
for pc in .task/*.md; do
|
||
[ -f "$pc" ] || continue
|
||
if [ "$(sed -n 's/^- 工作区:.*分支:\([A-Za-z0-9._/-]\+\).*/\1/p' "$pc" | head -1)" = "$branch" ]; then
|
||
card=$pc; break
|
||
fi
|
||
done
|
||
fi
|
||
[ -f "$card" ] || { echo "ERROR: 找不到分支 ${branch} 的任务卡(.task/${slug}.md)" >&2; exit 1; }
|
||
|
||
head=$(git rev-parse HEAD)
|
||
short_head=$(echo "$head" | cut -c1-10)
|
||
ts=$(date -u +%FT%TZ)
|
||
|
||
tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT
|
||
bash -c "$CMD" >"$tmp" 2>&1; rc=$?
|
||
digest=$(sha256sum "$tmp" | cut -c1-16)
|
||
|
||
# 脱敏(baseline 硬边界 2):token 风格的 40 位 hex / Authorization 头不打进任务卡。
|
||
sanitize() {
|
||
sed -E -e 's/(token[ =:]+)[0-9a-fA-F]{40}/\1<redacted>/g' \
|
||
-e 's/(Authorization: *token +)[^ ]+/\1<redacted>/Ig' \
|
||
-e 's/(access_token[=:]+)[0-9a-fA-F]{40}/\1<redacted>/Ig'
|
||
}
|
||
|
||
if ! grep -q '^## 证据(Evidence)' "$card"; then
|
||
printf '\n## 证据(Evidence)\n\n<!-- make evidence CMD="…" 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->\n| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |\n|---|---|---|---|---|---|\n' >> "$card"
|
||
fi
|
||
cmd_cell=$(echo "$CMD" | sed -e 's/|/\\|/g' -e 's/`/‘/g')
|
||
printf '| %s | `%s` | %s | %d | %s | %s |\n' "$ts" "$cmd_cell" "$short_head" "$rc" "$SRC" "$digest" >> "$card"
|
||
printf '\n<!-- evidence:run %s -->\n```\n$ %s\n[exit %d | HEAD %s | sha256:%s] 输出尾部 %s 行:\n%s\n```\n' \
|
||
"$ts" "$CMD" "$rc" "$short_head" "$digest" "$TAILN" "$(tail -n "$TAILN" "$tmp" | sanitize)" >> "$card"
|
||
|
||
echo "[evidence] card=$card cmd=\"${CMD}\" exit=$rc src=$SRC head=$short_head sha256=$digest"
|
||
exit "$rc"
|