开发机迁移
This commit is contained in:
26
.claude/hooks/post_edit_db_doc_sync.py
Normal file
26
.claude/hooks/post_edit_db_doc_sync.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PostToolUse hook: 编辑 db/ 下 SQL 文件后提醒同步 docs/database/"""
|
||||
import json, re, sys
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
fp = (data.get("tool_input") or {}).get("file_path", "")
|
||||
if not fp:
|
||||
sys.exit(0)
|
||||
|
||||
rel = re.sub(r"^.*?NeoZQYY[/\\]", "", fp.replace("\\", "/"))
|
||||
|
||||
# 匹配 db/ 下的 SQL 文件(schemas、migrations、脚本等)
|
||||
if re.search(r"^db/.*\.sql$", rel):
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": (
|
||||
f"[db-doc-sync] 已编辑数据库文件: {rel} — "
|
||||
"根据 Schema 变更规则,完成后须同步更新 docs/database/(变更说明、兼容性、回滚策略、验证 SQL)"
|
||||
)
|
||||
}
|
||||
}))
|
||||
37
.claude/hooks/post_edit_rls_dual_schema.py
Normal file
37
.claude/hooks/post_edit_rls_dual_schema.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PostToolUse hook: 编辑含 CREATE VIEW 的 SQL 文件时提醒双 Schema 规则"""
|
||||
import json, re, sys
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
fp = (data.get("tool_input") or {}).get("file_path", "")
|
||||
if not fp:
|
||||
sys.exit(0)
|
||||
|
||||
rel = re.sub(r"^.*?NeoZQYY[/\\]", "", fp.replace("\\", "/"))
|
||||
|
||||
# 仅检查 db/ 下的 SQL 文件
|
||||
if not re.search(r"^db/.*\.sql$", rel):
|
||||
sys.exit(0)
|
||||
|
||||
# 读取文件内容检查是否包含 CREATE VIEW
|
||||
try:
|
||||
with open(fp, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
# 检测 CREATE [OR REPLACE] VIEW 语句
|
||||
if re.search(r"CREATE\s+(OR\s+REPLACE\s+)?VIEW\s+(dws|dwd|core)\.", content, re.IGNORECASE):
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": (
|
||||
f"[rls-dual-schema] 检测到 {rel} 中包含 dws/dwd/core schema 的 VIEW 定义 — "
|
||||
"根据 RLS 视图双 Schema 规则,必须同时在原 schema 和 app schema 创建对应视图,否则后端查询会失败。"
|
||||
)
|
||||
}
|
||||
}))
|
||||
24
.claude/hooks/pre_demo_protect.py
Normal file
24
.claude/hooks/pre_demo_protect.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PreToolUse hook: 保护 demo-miniprogram 目录不被删除或移入 _DEL/"""
|
||||
import json, re, sys
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
tool = data.get("tool_name", "")
|
||||
tool_input = data.get("tool_input") or {}
|
||||
|
||||
DEMO_DIR = "demo-miniprogram"
|
||||
|
||||
if tool == "Bash":
|
||||
cmd = tool_input.get("command", "")
|
||||
# 检查命令是否在对 demo-miniprogram 执行删除/移动操作
|
||||
if DEMO_DIR in cmd and re.search(r"\b(rm|rmdir|del|move|mv)\b", cmd, re.IGNORECASE):
|
||||
# 允许 mv 到非 _DEL 目录(如正常重命名),但阻止移入 _DEL
|
||||
if re.search(r"\brm\b|\brmdir\b|\bdel\b", cmd, re.IGNORECASE) or "_DEL" in cmd:
|
||||
print(json.dumps({
|
||||
"decision": "block",
|
||||
"reason": f"[demo-protect] apps/{DEMO_DIR}/ 禁止删除或移入 _DEL/。该目录是 UI 样式标杆校对基准。"
|
||||
}))
|
||||
28
.claude/hooks/pre_read_archived_block.py
Normal file
28
.claude/hooks/pre_read_archived_block.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PreToolUse hook: 阻止读取 _archived/ 目录下的文件"""
|
||||
import json, re, sys
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
tool = data.get("tool_name", "")
|
||||
tool_input = data.get("tool_input") or {}
|
||||
|
||||
# 从不同工具中提取路径
|
||||
path = ""
|
||||
if tool in ("Read", "Edit", "Write"):
|
||||
path = tool_input.get("file_path", "")
|
||||
elif tool == "Glob":
|
||||
path = tool_input.get("path", "")
|
||||
|
||||
path = path.replace("\\", "/")
|
||||
|
||||
if re.search(r"/_archived/|/_archived$|^_archived/", path) or re.search(
|
||||
r"[/\\]_archived[/\\]", tool_input.get("file_path", "")
|
||||
):
|
||||
print(json.dumps({
|
||||
"decision": "block",
|
||||
"reason": "[archived-block] _archived/ 目录内容已废弃,禁止读取或参考。请使用当前版本的文件。"
|
||||
}))
|
||||
@@ -22,8 +22,5 @@ except Exception:
|
||||
high_risk = result.get("high_risk_files", [])
|
||||
if result.get("audit_required", False) and len(high_risk) > 0:
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "Stop",
|
||||
"additionalContext": f"[audit-check] 当前有 {len(high_risk)} 个高风险文件变更未审计。建议执行 /audit。"
|
||||
}
|
||||
"systemMessage": f"[audit-check] 当前有 {len(high_risk)} 个高风险文件变更未审计。建议执行 /audit。"
|
||||
}))
|
||||
|
||||
81
.claude/hooks/stop_verify_check.py
Normal file
81
.claude/hooks/stop_verify_check.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stop hook: 检查是否有逻辑改动未验证(未跑测试)、DDL 变更未建迁移"""
|
||||
import json, re, subprocess, sys, os
|
||||
|
||||
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
||||
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["git", "diff", "--name-only"],
|
||||
capture_output=True, text=True, timeout=10, cwd=project_dir,
|
||||
)
|
||||
staged = subprocess.run(
|
||||
["git", "diff", "--name-only", "--cached"],
|
||||
capture_output=True, text=True, timeout=10, cwd=project_dir,
|
||||
)
|
||||
changed = set((r.stdout + "\n" + staged.stdout).strip().splitlines())
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
if not changed:
|
||||
sys.exit(0)
|
||||
|
||||
warnings = []
|
||||
|
||||
# --- 1. 逻辑改动未验证检查 ---
|
||||
LOGIC_PATTERNS = [
|
||||
r"^apps/etl/connectors/feiqiu/(tasks|loaders|scd|orchestration|config|database|models|quality)/",
|
||||
r"^apps/backend/app/(routers|services|auth|schemas)/",
|
||||
r"^packages/shared/",
|
||||
]
|
||||
|
||||
logic_files = [
|
||||
f for f in changed
|
||||
if any(re.search(p, f) for p in LOGIC_PATTERNS)
|
||||
]
|
||||
|
||||
if logic_files:
|
||||
# 检查是否有测试结果文件被修改(间接判断是否跑了测试)
|
||||
# 更可靠的方式:检查变更中是否包含测试文件
|
||||
test_files = [f for f in changed if re.search(r"tests?/", f)]
|
||||
if not test_files:
|
||||
count = len(logic_files)
|
||||
warnings.append(
|
||||
f"本次会话修改了 {count} 个逻辑文件但未发现测试文件变更,"
|
||||
"建议运行相关测试验证(单元/集成/lint)"
|
||||
)
|
||||
|
||||
# --- 2. DDL 变更未建迁移检查 ---
|
||||
schema_files = [f for f in changed if re.search(r"^db/[^/]+/schemas/.*\.sql$", f)]
|
||||
migration_files = [f for f in changed if re.search(r"^db/[^/]+/migrations/.*\.sql$", f)]
|
||||
|
||||
if schema_files and not migration_files:
|
||||
# 也检查 untracked 的迁移文件
|
||||
try:
|
||||
untracked = subprocess.run(
|
||||
["git", "ls-files", "--others", "--exclude-standard", "db/"],
|
||||
capture_output=True, text=True, timeout=10, cwd=project_dir,
|
||||
)
|
||||
new_migrations = [
|
||||
f for f in untracked.stdout.strip().splitlines()
|
||||
if re.search(r"^db/[^/]+/migrations/.*\.sql$", f)
|
||||
]
|
||||
except Exception:
|
||||
new_migrations = []
|
||||
|
||||
if not new_migrations:
|
||||
dbs = set()
|
||||
for f in schema_files:
|
||||
m = re.match(r"^db/([^/]+)/", f)
|
||||
if m:
|
||||
dbs.add(m.group(1))
|
||||
db_list = ", ".join(sorted(dbs))
|
||||
warnings.append(
|
||||
f"检测到 {db_list} 的 schemas/ DDL 已变更但无对应迁移脚本,"
|
||||
"建议在 db/*/migrations/ 创建增量迁移文件"
|
||||
)
|
||||
|
||||
# --- 输出 ---
|
||||
if warnings:
|
||||
msg = "[verify-check] " + " | ".join(warnings)
|
||||
print(json.dumps({"systemMessage": msg}))
|
||||
@@ -32,7 +32,10 @@
|
||||
"Bash(cp -r tmp _DEL/tmp_backup)",
|
||||
"Bash(*)",
|
||||
"Bash(touch tmp/.gitkeep)",
|
||||
"Bash(ls -la c:/NeoZQYY/docs/audit/session_logs/_session_index*.json)"
|
||||
"Bash(ls -la c:/NeoZQYY/docs/audit/session_logs/_session_index*.json)",
|
||||
"mcp__pg-etl-test__execute_sql",
|
||||
"mcp__pg-app-test__execute_sql",
|
||||
"mcp__pg-app-test__list_schemas"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"C:\\Users\\Administrator\\.claude",
|
||||
@@ -52,6 +55,28 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Read|Edit|Write|Glob",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_read_archived_block.py\"",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Bash|Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre_demo_protect.py\"",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
@@ -62,6 +87,26 @@
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_db_doc_sync.py\"",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/post_edit_rls_dual_schema.py\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
@@ -73,6 +118,15 @@
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop_verify_check.py\"",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user