#!/bin/sh # .githooks/pre-commit — TVU mockup handoff conformance gate(零依赖 · F62-b) # # 目的:消费仓库 commit 含 标记的 docs/handoffs/*.md 时,强制它 # 引用一份存在、通过(exitCode 全 0)、且新鲜(24h 内)的 conformance report JSON。 # 逻辑全在同目录 audit-mockup-handoff-evidence.mjs,唯一硬依赖是 node。 # # 启用(两变体都用这一条,git 原生支持、不需要 husky/npm): # git config core.hooksPath .githooks # # 变体 A(已装 husky):husky 占用 .husky/ 作为 hooksPath,与本文件二选一。 # 推荐把下面这段追加到 .husky/pre-commit(而不是切换 hooksPath),见 .githooks/README.md。 # # ⛔ 变体 C(目标仓的 .githooks/pre-commit 这个位置**已被别的闸占用**) # —— 2026-09-11 从 [[INFRA-F62]] entry 搬来;entry 与那份 spec 此前都没有这条。 # ⛔ **别照本目录原样整份拷过去** —— 那会把对方已有的 hook 整份覆盖掉,且不留痕迹。 # 实证出处:`TVU Pack` 仓自 2026-09-09 起 `core.hooksPath=.githooks`,而它的 # `.githooks/pre-commit` 是一份活着的**支柱④ upstream-gate** artifact validator # (只扫 staged docs/specs/upstream-gate.*.md,与 mockup conformance 无关)。 # ⇒ 两条出路:① 扩对方那份 pre-commit,多调一个 validator;② 先把本模板改成可共存形态。 # ⚠️ 顺带一条最容易读错的:**「那个仓已经有 githooks」不等于「所以可以顺手把本闸也装上去」** # —— owner 的「不装」裁定仍然有效,两条重开条件(出现非 owner 的 claim 生产者 / §8.4 失效) # 都没发生。真源 = docs/superpowers/specs/2026-08-31-infra-f62-delayed-verification-design.md §10 非目标 1。 # # 可选环境变量: # TVU_HANDOFF_GATE_STRICT=1 → node 缺失时判失败(默认 fail-open:只告警不拦) set -e # 解析脚本所在目录(core.hooksPath 调用时 CWD 是 repo 根;husky 调用时也一样)。 # 用 `pwd -P`(物理路径,解 symlink)——否则 macOS /var→/private/var 这类 symlink 会让本路径 # 与 mjs 里的 import.meta.url(realpath) 不一致,CLI 守卫失配、脚本被当模块空跑而假通过。 HOOK_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) CHECK="$HOOK_DIR/audit-mockup-handoff-evidence.mjs" # 只看本次 staged 的(新增/复制/修改)handoff markdown STAGED_HANDOFFS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^docs/handoffs/.*\.md$' || true) if [ -z "$STAGED_HANDOFFS" ]; then exit 0 fi echo "▶ pre-commit: mockup handoff conformance evidence" # node 缺失降级:写设计 / 无 node 环境的变体 B 不应被 brick,但要大声告警 if ! command -v node >/dev/null 2>&1; then echo "⚠️ 未找到 node —— 无法机校 handoff conformance evidence。" echo " handoff 的 mockup 一致性本次【未经机器验证】,只能靠 owner design-walkthrough 人工兜底。" echo " 装 node 后本闸自动生效:https://nodejs.org (node 是本闸唯一硬依赖)" if [ "${TVU_HANDOFF_GATE_STRICT:-0}" = "1" ]; then echo "❌ TVU_HANDOFF_GATE_STRICT=1:node 缺失按失败处理,阻断提交。" exit 1 fi echo " (fail-open:不阻断本次提交。设 TVU_HANDOFF_GATE_STRICT=1 可改为阻断。)" exit 0 fi if [ ! -f "$CHECK" ]; then echo "⚠️ 缺 $CHECK —— 重新 scaffold 或从 TVU 模板复制 .githooks/。本次不阻断。" exit 0 fi # shellcheck disable=SC2086 # 需按空白分词成多个文件参数 node "$CHECK" $STAGED_HANDOFFS || exit 1 echo " ✓ handoff conformance evidence OK" # 第二条判据(INFRA-F123):M37 / M42 / M-LIFECYCLE-CRUD 三条规则各自指定的产物段落 # 是否真的落进了 handoff。三条规则的正文里已经写了产出什么,本闸只做存在性检查。 SECTIONS_CHECK="$HOOK_DIR/audit-handoff-deliverable-sections.mjs" if [ -f "$SECTIONS_CHECK" ]; then echo "▶ pre-commit: handoff 强制段落存在性 (M37 / M42 / CRUD)" # shellcheck disable=SC2086 node "$SECTIONS_CHECK" $STAGED_HANDOFFS || exit 1 else echo "⚠️ 缺 $SECTIONS_CHECK —— 重新 scaffold 或从 TVU 模板复制 .githooks/。本次不阻断。" fi exit 0