P0-1: SessionStart Hook — hooks/session-context.sh 从分支名解析 REQ-ID,调 MCP API 查询需求详情注入 system-reminder P0-2: PreToolUse Hook — hooks/pre-tool-confirm.sh 拦截生产推送、force push、docker prod 容器操作、git reset --hard 等 P0-3: Release Draft 闸门设计文档 — docs/design/release-draft-gate.md 完整架构 + 渐进式落地路径(拆 7 个子任务延后) 附最小可用脚本 hooks/release-draft.sh 创建 Gitea draft release P0-4: Memory 隔离规则 — 写入 req-prd / req-design / req-workflow 禁止 auto-memory 污染模板产出物(章节结构、字段定义、文档结构) P0-5: CLAUDE.md 架构检查 + 架构片段库 dev-coding skill 执行前检查架构关键词 新增 templates/claude-md-snippets/ 含 Go+Gin / React+AntD / Vue+Element / MCP+TS / generic 五套骨架 P0-6: /commit 分支保护自动化 — 新 skill dev-commit-plugin 保护分支自动建功能分支 + Conventional Commits + REQ-XXX 自动关联 安装: bash hooks/install.sh 后续: P0-3 完整实现拆 7 个子任务(P0-3.1 ~ P0-3.7) 建议先部署 hooks 跑 1-2 周观察,再推进 Release 机制落地
95 lines
3.6 KiB
Bash
Executable File
95 lines
3.6 KiB
Bash
Executable File
#!/bin/bash
|
||
# pre-tool-confirm.sh
|
||
# PreToolUse Hook: 拦截危险操作,强制原生确认对话框
|
||
#
|
||
# 输入格式(stdin JSON):
|
||
# { "tool_name": "Bash", "tool_input": { "command": "..." } }
|
||
#
|
||
# 输出格式(stdout JSON):
|
||
# { "hookSpecificOutput": { "hookEventName": "PreToolUse",
|
||
# "permissionDecision": "ask",
|
||
# "permissionDecisionReason": "..." } }
|
||
#
|
||
# 安装方式:在 ~/.claude/settings.json 配置
|
||
# hooks.PreToolUse:
|
||
# - matcher: "Bash"
|
||
# hooks:
|
||
# - type: command
|
||
# command: "<path>/hooks/pre-tool-confirm.sh"
|
||
# timeout: 30
|
||
#
|
||
# 参考:devflow-claude confirm-before-commit.sh + ai-proj memory 规则
|
||
# REQ-20260416-0017 P0-2
|
||
|
||
set -e
|
||
|
||
INPUT=$(cat)
|
||
|
||
if ! command -v jq >/dev/null 2>&1; then
|
||
# 没有 jq 就不处理
|
||
exit 0
|
||
fi
|
||
|
||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||
|
||
if [ -z "$COMMAND" ]; then
|
||
exit 0
|
||
fi
|
||
|
||
REASON=""
|
||
|
||
# ============ 1. 生产分支推送 ============
|
||
if echo "$COMMAND" | grep -qE '\bgit\s+push\b.*\b(origin\s+)?(main|master)\b'; then
|
||
REASON="⚠️ 即将推送到 main/master 生产分支。确认已过 PR 评审?"
|
||
|
||
# ============ 2. 强制推送 ============
|
||
elif echo "$COMMAND" | grep -qE '\bgit\s+push\b.*(--force|--force-with-lease|-f\b)'; then
|
||
REASON="⛔ 危险:force push 会覆盖远程历史,可能丢失他人提交。确认继续?"
|
||
|
||
# ============ 3. tea pr merge --base main ============
|
||
elif echo "$COMMAND" | grep -qE '\btea\s+pr\s+merge\b.*--base\s+main\b'; then
|
||
REASON="⚠️ 即将合并 PR 到 main 分支,合并后将触发生产部署。确认 PR 已测试?"
|
||
|
||
# ============ 4. docker rm/stop 生产容器 ============
|
||
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*\b(ai_postgres_prod|ai_backend_prod|ai_frontend_prod|ai_redis_prod)\b'; then
|
||
REASON="⛔ 危险:即将停止/删除生产容器!2026-04-05 曾因此宕机 15 分钟。确认是灾难恢复?"
|
||
|
||
# ============ 5. docker rm/stop 其他 prod 相关 ============
|
||
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*_prod\b'; then
|
||
REASON="⚠️ 即将停止/删除含 _prod 的容器。确认是生产环境?"
|
||
|
||
# ============ 6. git reset --hard / clean -fd ============
|
||
elif echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard\b'; then
|
||
REASON="⚠️ git reset --hard 会丢弃所有未提交改动。确认继续?"
|
||
|
||
elif echo "$COMMAND" | grep -qE '\bgit\s+clean\s+.*-f'; then
|
||
REASON="⚠️ git clean -f 会删除未跟踪文件。确认继续?"
|
||
|
||
# ============ 7. rm -rf / 系统路径 ============
|
||
elif echo "$COMMAND" | grep -qE '\brm\s+.*-[rf]+.*\s+(/|/\*|~|\$HOME)'; then
|
||
REASON="⛔ 危险:rm -rf 指向系统根或家目录。确认继续?"
|
||
|
||
# ============ 8. ssh 生产服务器 + 破坏性命令 ============
|
||
elif echo "$COMMAND" | grep -qE 'ssh\s+\S*prod\S*.*\b(rm|drop|truncate|delete)\b'; then
|
||
REASON="⛔ 危险:在生产服务器执行破坏性命令。确认继续?"
|
||
|
||
# ============ 9. psql/mysql 生产数据库 + DROP/TRUNCATE/DELETE ============
|
||
elif echo "$COMMAND" | grep -qiE '(psql|mysql).*prod.*\b(DROP|TRUNCATE|DELETE FROM)\b'; then
|
||
REASON="⛔ 危险:生产数据库 DROP/TRUNCATE/DELETE。确认已备份?"
|
||
|
||
# ============ 10. MCP advance_delivery_stage force=true ============
|
||
# 这种会走 MCP 而不是 Bash,本 hook 不好拦,留给另一个 matcher 处理
|
||
fi
|
||
|
||
if [ -n "$REASON" ]; then
|
||
jq -n --arg reason "$REASON" '{
|
||
hookSpecificOutput: {
|
||
hookEventName: "PreToolUse",
|
||
permissionDecision: "ask",
|
||
permissionDecisionReason: $reason
|
||
}
|
||
}'
|
||
else
|
||
exit 0
|
||
fi
|