219 lines
6.1 KiB
Bash
219 lines
6.1 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# AI Guard — 质量门禁脚本 (PHP Hyperf + Vue 3 双栈)
|
|
# =============================================================================
|
|
# 用法:
|
|
# bash scripts/ai-guard.sh # 运行所有检查
|
|
# bash scripts/ai-guard.sh --pre # 预提交检查
|
|
# bash scripts/ai-guard.sh --post # 后执行检查
|
|
# bash scripts/ai-guard.sh --quick # 快速检查 (lint + types)
|
|
# =============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
LOG_DIR=".ai-logs/guard"
|
|
mkdir -p "$LOG_DIR"
|
|
LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).log"
|
|
|
|
HAS_NODE=false
|
|
HAS_PHP=false
|
|
|
|
# Detect project stacks
|
|
[ -f "package.json" ] && HAS_NODE=true
|
|
[ -f "composer.json" ] && HAS_PHP=true
|
|
|
|
# Also check subdirectories
|
|
[ -f "Case-Database-Frontend-user/package.json" ] && HAS_NODE=true
|
|
[ -f "Case-Database-Frontend-admin/package.json" ] && HAS_NODE=true
|
|
[ -f "Case-Database-Backend/composer.json" ] && HAS_PHP=true
|
|
|
|
log() {
|
|
echo "[$(date +%H:%M:%S)] $*" >> "$LOG_FILE"
|
|
}
|
|
|
|
check() {
|
|
local name="$1"
|
|
local cmd="$2"
|
|
local required="${3:-true}"
|
|
|
|
printf " %-30s" "$name"
|
|
|
|
if eval "$cmd" > /dev/null 2>&1; then
|
|
echo -e "${GREEN}✅ PASS${NC}"
|
|
PASS=$((PASS + 1))
|
|
log "PASS: $name"
|
|
else
|
|
if [ "$required" = "true" ]; then
|
|
echo -e "${RED}❌ FAIL${NC}"
|
|
FAIL=$((FAIL + 1))
|
|
log "FAIL: $name"
|
|
else
|
|
echo -e "${YELLOW}⚠️ WARN${NC}"
|
|
WARN=$((WARN + 1))
|
|
log "WARN: $name"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ----- 敏感信息扫描 -----
|
|
check_secrets() {
|
|
local name="Secret Scan"
|
|
printf " %-30s" "$name"
|
|
|
|
local patterns=(
|
|
'AKIA[0-9A-Z]{16}'
|
|
'password\s*=\s*["'\''"][^"'\''"]+'
|
|
'api[_-]?key\s*=\s*["'\''"][^"'\''"]+'
|
|
'BEGIN\s+(RSA\s+)?PRIVATE\s+KEY'
|
|
'ghp_[a-zA-Z0-9]{36}'
|
|
'sk-[a-zA-Z0-9]{48}'
|
|
)
|
|
|
|
local found=false
|
|
for pattern in "${patterns[@]}"; do
|
|
if git diff --cached --diff-filter=d -U0 2>/dev/null | grep -qiE "$pattern"; then
|
|
found=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$found" = "true" ]; then
|
|
echo -e "${RED}❌ FAIL — 检测到疑似密钥/凭证!${NC}"
|
|
FAIL=$((FAIL + 1))
|
|
log "FAIL: $name — secrets detected"
|
|
else
|
|
echo -e "${GREEN}✅ PASS${NC}"
|
|
PASS=$((PASS + 1))
|
|
log "PASS: $name"
|
|
fi
|
|
}
|
|
|
|
# ----- Context Doctor (lite) -----
|
|
check_context_lite() {
|
|
local name="Context Doctor Lite"
|
|
printf " %-30s" "$name"
|
|
|
|
if bash scripts/context-doctor.sh --lite --strict > /dev/null 2>&1; then
|
|
echo -e "${GREEN}✅ PASS${NC}"
|
|
PASS=$((PASS + 1))
|
|
log "PASS: $name"
|
|
else
|
|
echo -e "${RED}❌ FAIL${NC}"
|
|
FAIL=$((FAIL + 1))
|
|
log "FAIL: $name"
|
|
echo -e " ${YELLOW}↳ 运行 'bash scripts/context-doctor.sh --lite --strict' 查看详情${NC}"
|
|
fi
|
|
}
|
|
|
|
# ----- Frontend checks -----
|
|
run_frontend_checks() {
|
|
if [ "$HAS_NODE" = "true" ]; then
|
|
for dir in Case-Database-Frontend-user Case-Database-Frontend-admin; do
|
|
if [ -f "$dir/package.json" ]; then
|
|
echo -e "\n ${BLUE}── Frontend ($dir) ──${NC}"
|
|
check "ESLint ($dir)" "cd $dir && npm run lint --silent" "${1:-true}"
|
|
if [ "${2:-false}" = "true" ]; then
|
|
check "Frontend Tests ($dir)" "cd $dir && npm run test --silent" "false"
|
|
check "Frontend Build ($dir)" "cd $dir && npm run build --silent" "false"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
# ----- Backend checks -----
|
|
run_backend_checks() {
|
|
if [ "$HAS_PHP" = "true" ]; then
|
|
echo -e "\n ${BLUE}── Backend (PHP Hyperf / Swoole) ──${NC}"
|
|
|
|
local BACKEND_DIR="."
|
|
[ -d "Case-Database-Backend" ] && BACKEND_DIR="Case-Database-Backend"
|
|
|
|
check "PHPStan" "cd $BACKEND_DIR && composer analyse --no-progress 2>/dev/null || vendor/bin/phpstan analyse --no-progress" "${1:-true}"
|
|
if [ "${2:-false}" = "true" ]; then
|
|
check "PHPUnit" "cd $BACKEND_DIR && composer test --no-interaction 2>/dev/null || vendor/bin/phpunit" "false"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ----- 主逻辑 -----
|
|
MODE="${1:---post}"
|
|
|
|
echo ""
|
|
echo -e "${BLUE}🛡️ AI Guard — Quality Gateway (Dual Stack)${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
if [ "$HAS_NODE" = "true" ]; then
|
|
echo -e " ${GREEN}✓${NC} Frontend detected (package.json)"
|
|
fi
|
|
if [ "$HAS_PHP" = "true" ]; then
|
|
echo -e " ${GREEN}✓${NC} Backend detected (composer.json)"
|
|
fi
|
|
|
|
log "=== AI Guard Run: mode=$MODE node=$HAS_NODE php=$HAS_PHP ==="
|
|
|
|
case "$MODE" in
|
|
--pre)
|
|
echo -e "\n${BLUE}Mode: Pre-Commit${NC}"
|
|
check_secrets
|
|
check "File count (≤10)" "[ $(git diff --cached --name-only | wc -l) -le 10 ]" "false"
|
|
check_context_lite
|
|
;;
|
|
--post)
|
|
echo -e "\n${BLUE}Mode: Post-Execute${NC}"
|
|
run_frontend_checks "true"
|
|
run_backend_checks "true"
|
|
check_secrets
|
|
;;
|
|
--quick)
|
|
echo -e "\n${BLUE}Mode: Quick Check${NC}"
|
|
run_frontend_checks "true"
|
|
run_backend_checks "true"
|
|
;;
|
|
*)
|
|
echo -e "\n${BLUE}Mode: Full Check${NC}"
|
|
run_frontend_checks "true" "true"
|
|
run_backend_checks "true" "true"
|
|
|
|
if [ "$HAS_NODE" = "true" ]; then
|
|
for dir in Case-Database-Frontend-user Case-Database-Frontend-admin; do
|
|
if [ -f "$dir/package.json" ]; then
|
|
check "npm audit ($dir)" "cd $dir && npm audit --audit-level=high" "false"
|
|
fi
|
|
done
|
|
fi
|
|
if [ "$HAS_PHP" = "true" ]; then
|
|
BD="."
|
|
[ -d "Case-Database-Backend" ] && BD="Case-Database-Backend"
|
|
check "composer audit" "cd $BD && composer audit" "false"
|
|
fi
|
|
|
|
check_secrets
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo -e " ${GREEN}✅ $PASS passed${NC} ${RED}❌ $FAIL failed${NC} ${YELLOW}⚠️ $WARN warnings${NC}"
|
|
echo ""
|
|
|
|
log "Summary: pass=$PASS fail=$FAIL warn=$WARN"
|
|
|
|
if [ "$FAIL" -gt 0 ]; then
|
|
echo -e "${RED}💥 有必须修复的问题!${NC}"
|
|
exit 1
|
|
else
|
|
echo -e "${GREEN}🎉 所有必要检查已通过!${NC}"
|
|
exit 0
|
|
fi
|