scripts/ai_guard/pre_commit_hook.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
# git pre-commit hook: IDE 无关的后备保护。
# 在每次 git commit 前运行关键门禁检查。
# 安装: bash scripts/ai_guard/install-hooks.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
export PYTHONPATH=.
# Shared protected-files list (also used by commit-msg hook).
. "$ROOT/scripts/ai_guard/protected_files.sh"
echo "== AI Guard: pre-commit 检查 =="
# 1. 检查是否修改了受保护文件 (warn-only here; commit-msg hook enforces)
PROTECTED_HITS=()
for pf in "${PROTECTED_FILES[@]}"; do
if git diff --cached --name-only | grep -q "^${pf}$"; then
PROTECTED_HITS+=("$pf")
fi
done
if [ "${#PROTECTED_HITS[@]}" -gt 0 ]; then
echo "⚠️ 受保护文件被修改(commit-msg hook 将检查 [MANUAL-OVERRIDE]):"
for pf in "${PROTECTED_HITS[@]}"; do
echo " - $pf"
done
echo ""
fi
# 2. 检查层依赖(快速版)
if git diff --cached --name-only | grep -qE "^butler/.*\.py$"; then
echo "→ 运行层依赖检查..."
if ! python -m pytest tests/test_eng15_layer_dependency_matrix.py -q --tb=line --no-header 2>/dev/null; then
echo "❌ 层依赖检查失败!"
echo " 运行完整检查: bash scripts/butler-layer-import-gate.sh"
exit 1
fi
echo "✅ 层依赖检查通过"
# G1: lazy import 预算检查(仅改 butler/*.py 时)
echo "→ 运行 lazy import 预算检查..."
if ! bash scripts/p3i-lazy-import-report.sh 2>/dev/null; then
echo "❌ Lazy import 预算超限(>1910)!"
echo " AI 可能添加了模块级 import 导致循环依赖。"
echo " 请将新增的 from butler.* 改为函数内 lazy import。"
echo " 完整报告: bash scripts/p3i-lazy-import-report.sh"
exit 1
fi
echo "✅ Lazy import 预算检查通过"
# G6: 文件大小守卫
echo "→ 运行文件大小守卫..."
if ! python3 scripts/ai_guard/file_size_check.py --staged; then
echo "❌ 文件大小守卫失败!"
echo " 项目约定 .py 文件 >800 行应拆分。"
echo " 如确需大文件,请在 commit message 中包含 [MANUAL-OVERRIDE] 标记。"
# 文件大小是警告,不阻止(exit 0)— 由 file_size_check.py 控制
fi
fi
# G3: Secret scanning(所有文件改动时都检查;no override — secrets must not be committed)
echo "→ 运行 secret 扫描..."
# 注意:while 循环的退出码 = 最后一条 body 命令的退出码。set -euo pipefail 下,
# git check-ignore 对"未被忽略的文件"返回 1,会使该 pipeline 返回非零。
# 用 if/continue 归一化 + 末尾 || true 兜底,杜绝其把非零退出码带出命令替换。
SECRET_HITS=$(git diff --cached --name-only | while read f; do
[ -f "$f" ] || continue
# 跳过 .gitignore 中的文件
if git check-ignore "$f" >/dev/null 2>&1; then
continue
fi
# 扫描常见密钥模式
if grep -nE '(sk-[a-zA-Z0-9]{32,}|AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36}|eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+|-----BEGIN (RSA |EC )?PRIVATE KEY-----)' "$f" 2>/dev/null; then
echo "$f"
fi
done || true)
if [ -n "$SECRET_HITS" ]; then
echo "❌ 检测到可能的密钥/凭证:"
echo "$SECRET_HITS"
echo ""
echo "提交被阻止。如确需提交(如测试 fixture),请使用 --no-verify 跳过所有 hooks。"
exit 1
fi
echo "✅ Secret 扫描通过"
# 3. Butler v5 迁移 schema 警告
if git diff --cached --name-only | grep -qE "^butler-v5/packages/persistence/src/migrations/"; then
echo "⚠️ WARNING: v5 persistence migration 被修改"
echo " 请运行: cd butler-v5 && pnpm exec vitest run packages/persistence/src/runtime-schema.test.ts"
fi
# 4. 检查契约文件是否被修改(警告)
if git diff --cached --name-only | grep -qE "^butler/contracts/"; then
echo "⚠️ WARNING: 契约层文件被修改"
echo " 请确保已运行契约测试: python -m pytest tests/contracts/ -v"
echo " 并确认所有 Port 实现已同步更新。"
fi
# 5. 检查 shim 文件是否被修改(警告)
SHIM_FILES=$(git diff --cached --name-only | grep -E "\.py$" || true | while read f; do
if [ -f "$f" ] && head -5 "$f" 2>/dev/null | grep -q "Deprecated:"; then
echo "$f"
fi
done)
if [ -n "$SHIM_FILES" ]; then
echo "⚠️ WARNING: 以下 shim 文件被修改:"
echo "$SHIM_FILES" | while read f; do echo " - $f"; done
echo " shim 文件是向后兼容层,修改不会影响实际实现。"
echo " 如需修改功能,请修改对应的包目录下的实际实现文件。"
fi
echo "== AI Guard: pre-commit 检查通过 =="
exit 0