Make development gates preserve work and verifiable evidence

Why: cleanup, component synchronization, receipt recording and credential handling could contradict the documented development boundaries.

Evidence: SELF 75 isolated governance tests and source checks are captured in .task/feature-governance-reliability.md; combination and CI verification follow.

Impact: hardens local task and CI tooling, aligns policy and component documentation, and adds continuous regression tests without changing product behavior.

Refs: .task/feature-governance-reliability.md; policy/governance.md; policy/platform-admission.md
This commit is contained in:
2026-10-05 12:36:02 +00:00
parent 46a1770798
commit 8af945a1d5
24 changed files with 2297 additions and 228 deletions
+8 -36
View File
@@ -1,38 +1,10 @@
#!/usr/bin/env bash
# zcode PreToolUse 写路径守卫(Edit/Write/ApplyPatch)。
# 单一权威声明:本脚本是 Development-Guidelines standards/tool-adaptation.md
# 要求 A1/A3 在 zcode 客户端的机制实例;语义以库与项目入口为准,本文件不复制规则正文。
# 退出码:0 放行;2 阻断(PreToolUse 的 deny);其余非零为错误。
# PreToolUse 写路径守卫。解析/依赖错误均拒绝,不能把未知请求当作安全请求。
set -uo pipefail
input=$(cat)
file=$(printf '%s' "$input" | python3 -c '
import json, sys
try:
d = json.load(sys.stdin)
except Exception:
sys.exit(0)
ti = d.get("tool_input") if isinstance(d.get("tool_input"), dict) else {}
for src in (ti, d):
for k in ("file_path", "path", "filename"):
v = src.get(k)
if isinstance(v, str) and v:
print(v)
sys.exit(0)
' 2>/dev/null)
[ -n "$file" ] || exit 0
repo=$(git -C "$(dirname "$file")" rev-parse --show-toplevel 2>/dev/null) || exit 0
branch=$(git -C "$repo" rev-parse --abbrev-ref HEAD 2>/dev/null) || exit 0
deny() { echo "write-guard: 拒绝——${1}(目标 ${file},仓库 ${repo})" >&2; exit 2; }
case "$branch" in
main|master)
deny "集成分支 ${branch} 不得直接写入;先 make task-start NAME=feature|fix/<slug>(A1/A3)" ;;
HEAD)
deny "仓库处于 detached HEAD(常为他人现场或未建分支的子仓);先显式建任务分支(A1)" ;;
esac
echo "$branch" | grep -qE '^(feature|fix|hotfix|release)/[A-Za-z0-9._-]+$' \
|| deny "分支名 ${branch} 不符 feature|fix|hotfix|release/<slug>(A3)"
exit 0
if ! command -v python3 >/dev/null 2>&1; then
echo 'write-guard: 拒绝——缺少 python3,无法检查写入目标' >&2
exit 2
fi
python3 "$(dirname "$0")/write_guard.py"
result=$?
if [ "$result" -ne 0 ]; then exit 2; fi
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""Check every write target; a local branch guard, not an OS sandbox."""
import json
import os
from pathlib import Path
import re
import subprocess
import sys
def deny(reason):
raise ValueError(reason)
def path_value(value):
if not isinstance(value, str) or not value.strip() or any(c in value for c in "\x00\r\n"):
deny("写入路径缺失或无法解析")
return value
def patch_paths(patch):
if not isinstance(patch, str):
deny("ApplyPatch 缺少 patch_text")
lines = patch.strip().splitlines()
if len(lines) < 3 or lines[0] != "*** Begin Patch" or lines[-1] != "*** End Patch":
deny("无法解析 ApplyPatch 边界")
paths = []
operation = None
moved = False
for line in lines[1:-1]:
match = re.fullmatch(r"\*\*\* (Add File|Update File|Delete File): (.+)", line)
if match:
operation = match[1]
moved = False
paths.append(path_value(match[2]))
elif line.startswith("*** Move to: ") and operation == "Update File" and not moved:
paths.append(path_value(line[len("*** Move to: "):]))
moved = True
elif line == "*** End of File" and operation == "Update File":
continue
elif line.startswith("***") or operation is None:
deny("ApplyPatch 包含未知路径指令")
elif not (line.startswith(("+", "-", " ", "@@")) or line == ""):
deny("ApplyPatch 包含无法解析的内容")
if not paths:
deny("ApplyPatch 没有可检查的路径")
return paths
def check_path(raw, cwd):
target = Path(raw)
if not target.is_absolute():
target = cwd / target
# A tool can follow a symlink OR replace/delete its directory entry. Check
# both locations, including a dangling final symlink on a protected branch.
location = target.parent.resolve() / target.name
check_location(location, entry_only=location.is_symlink())
resolved = target.resolve()
if resolved != location:
check_location(resolved)
def check_location(target, entry_only=False):
ancestor = target if target.is_dir() and not entry_only else target.parent
while not ancestor.exists():
ancestor = ancestor.parent
if not ancestor.is_dir():
deny(f"目标祖先不是目录:{ancestor}")
# GIT_DIR/GIT_WORK_TREE must not redirect inspection to another checkout.
env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
env["LC_ALL"] = "C"
result = subprocess.run(["git", "-C", str(ancestor), "rev-parse", "--show-toplevel"],
env=env, capture_output=True, text=True)
if result.returncode:
if any((p / ".git").exists() or (p / ".git").is_symlink()
for p in (ancestor, *ancestor.parents)):
deny(f"无法读取目标仓库状态:{target}")
if "not a git repository" not in result.stderr:
deny(f"无法确认目标仓库:{target}")
print(f"write-guard: outside-git(未受 Git 分支规则覆盖):{target}", file=sys.stderr)
return
repo = result.stdout.strip()
branch_result = subprocess.run(["git", "-C", repo, "symbolic-ref", "--quiet", "--short", "HEAD"],
env=env, capture_output=True, text=True)
if branch_result.returncode:
deny(f"仓库处于 detached HEAD 或状态不可读:{repo};先建立任务分支(A1)")
branch = branch_result.stdout.strip()
if not re.fullmatch(r"(?:feature|fix|hotfix|release)/[A-Za-z0-9._-]+", branch):
deny(f"分支 {branch} 不允许写入(目标 {target},仓库 {repo});先 make task-start NAME=feature/<slug>(A1/A3)")
def main():
data = json.load(sys.stdin)
if not isinstance(data, dict):
deny("钩子输入必须为对象")
tool = data.get("tool_name")
if tool in {"Read", "Glob", "Grep", "WebFetch", "WebSearch"}:
return
if tool not in {None, "Edit", "Write", "ApplyPatch", "apply_patch"}:
deny("未知写入工具,必须先补充适配")
payload = data.get("tool_input", data)
if isinstance(payload, str) and tool in {"ApplyPatch", "apply_patch"}:
paths = patch_paths(payload)
elif isinstance(payload, dict):
# Installed zcode uses patch_text; aliases use the same structured format.
patches = [payload[k] for k in ("patch_text", "patch", "input") if k in payload]
paths = [path_value(payload[k]) for k in ("file_path", "path", "filename") if k in payload]
for patch in patches:
paths.extend(patch_paths(patch))
else:
deny("无法解析写入工具参数")
if not paths:
deny("写入请求未提供可检查路径")
cwd = Path(path_value(data.get("cwd", os.getcwd()))).resolve()
if not cwd.is_dir():
deny("钩子 cwd 不存在")
for path in paths:
check_path(path, cwd)
if __name__ == "__main__":
try:
main()
except (ValueError, OSError, RuntimeError) as error:
print(f"write-guard: 拒绝——{error}", file=sys.stderr)
sys.exit(2)