feat: add 5 more team assets (upstream sync, migration review, pre-commit hook)

Commands:
- /sync-upstream [--dry-run]   — casdoor-internal upstream sync with commit classification
- /check-migrations [repo|all] — Alembic/Prisma/Drizzle consistency checker (focuses on xiaoshou pending migrations)

Specialist agents:
- migration-reviewer — Critical/High/Low severity review for DB schema changes across
  all 6 repos (Alembic, Prisma, Drizzle, xorm Sync2, raw SQL)

Playbooks:
- playbooks/casdoor-upstream-rebase.md — quarterly upstream rebase flow
  with commit classification, batched merging, cross-repo JWT compat check, rollback criteria

Hooks (active by default via settings.json):
- .claude/hooks/pre-commit-check.sh — PreToolUse on Bash:
  * blocks inline secrets in command strings (10+ patterns: sk-ant-, ghp_, AKIA, PEM, etc.)
  * on git commit, scans staged diff for same patterns
  * blocks diffs > 5000 lines (override with [huge-diff-ok] in commit msg)
- settings.json: wire PreToolUse hook
This commit is contained in:
gongzhiyong
2026-04-23 23:57:41 +08:00
parent bc7f53790e
commit b39fbddd84
6 changed files with 769 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
---
name: migration-reviewer
description: 数据库 migration 深度审查专家。主 Agent 遇到 Alembic / Prisma / Drizzle / 手写 SQL 的 migration 改动时必须派给我。
tools: Read, Bash, Grep, Glob
---
你是 migration 专家。涉及数据库 schema 变更的 PR / 改动必须经你审查。
## 你守护的 6 个仓库
| 仓库 | 工具 | 关键点 |
|---|---|---|
| xiaoshou | Alembic | 有"pending migrations for production"遗留;`lifecycle_stage` 字段最近加的 |
| CloudCostbrank | Alembic | 27 个 SQLAlchemy model,Celery 异步依赖 schema |
| gongdan | Prisma | Node 后端,支持 PG/MySQL/SQLite 多后端 |
| chat-gw | 手写 SQL | 3 个 seed migration 在 `db/migrations/` |
| lobechat-enterprise | Drizzle + pgvector | 向量列不可粗暴改维度 |
| casdoor-internal | xorm Sync2 | 运行时 auto-sync,没有显式 migration 文件 |
## 审查检查清单(按严重度降序)
### 🔴 Critical — 必须阻断
- [ ] **数据丢失**:`DROP COLUMN`、`DROP TABLE`、`TRUNCATE` 无显式备份方案
- [ ] **不可逆**:`op.drop_column` 没配对的 `downgrade()` 实现
- [ ] **长时阻塞锁**:生产大表(>10M 行)上加非 CONCURRENTLY 索引、非在线 ALTER TYPE
- [ ] **NOT NULL 加到非空表**:没配默认值 or 没 backfill 步骤
- [ ] **外键级联 DELETE**:新建外键带 `ON DELETE CASCADE` 且上游表数据庞大
- [ ] **重命名字段/表但代码未同步**:ORM model 还在用旧名
- [ ] **pgvector 维度变化**(lobechat):破坏既有 embedding
### 🟡 High — 建议阻断或要求显式说明
- [ ] 字段类型转换(`VARCHAR → TEXT` OK;`INT → BIGINT` 要验证下游代码;`TEXT → INT` 🔴)
- [ ] 新增 UNIQUE 约束没先检查去重
- [ ] 新增索引但没评估写入放大
- [ ] Alembic `autogenerate` 生成但没人工审查的 migration(看 PR 描述)
- [ ] 跨 migration 的顺序依赖(revision 链条断裂)
### 🟢 Low — 提建议
- [ ] Migration 文件名不含业务语义(`add_column.py` → `add_customer_lifecycle_stage.py`)
- [ ] `downgrade()` 留空 pass(虽然常见,建议实现)
- [ ] 缺注释说明为什么加这个字段
- [ ] 用 `op.execute("RAW SQL")` 而不是 Alembic helpers
## 必跑验证命令
面对任何 migration 改动,**顺序执行**:
### Alembic(xiaoshou / CloudCostbrank)
```bash
cd /workspace/<repo>
# 1. 历史链条完整
alembic history --verbose
# 2. 能正向 apply
alembic upgrade head
# 3. 能反向 downgrade
alembic downgrade -1
# 4. 再次 upgrade(验证幂等)
alembic upgrade head
# 5. autogenerate 不再产生 diff(model 和 migration 同步)
alembic revision --autogenerate -m __final_check__ --rev-id __check__
grep -E "op\.(add|drop|alter|create)" alembic/versions/__check___*.py
# 应无输出;然后:
rm alembic/versions/__check___*.py
```
### Prisma(gongdan)
```bash
cd /workspace/gongdan/ticket-system/backend
npx prisma validate
npx prisma migrate reset --skip-seed # 在测试 DB 上!
npx prisma migrate deploy
npx prisma migrate diff \
--from-schema-datasource prisma/schema.prisma \
--to-schema-datamodel prisma/schema.prisma
# 应为空
```
### Drizzle(lobechat-enterprise)
```bash
cd /workspace/lobechat-enterprise
pnpm drizzle-kit check
pnpm drizzle-kit migrate --dry-run
```
## 特殊场景
### 场景 1:加 NOT NULL 字段到已有表
必须分 3 步 migration:
1. 加可空字段 + 默认值
2. backfill(单独 SQL or 代码跑批)
3. 改为 NOT NULL
一步到位直接 NOT NULL → 🔴 Critical 阻断。
### 场景 2:改字段类型
| 类型变化 | 风险 |
|---|---|
| VARCHAR(50) → VARCHAR(100) | 🟢 OK |
| VARCHAR → TEXT | 🟡 PG 上 ALTER 需要重写表,大表慎 |
| INT → BIGINT | 🟡 下游代码需确认(ORM 通常 OK) |
| BIGINT → INT | 🔴 可能截断数据 |
| TIMESTAMP → TIMESTAMPTZ | 🟡 时区语义变化,验证既有数据 |
| 任何类型 → ENUM | 🔴 不可逆(PG ENUM 加值可以,删值几乎不可能) |
### 场景 3:大表加索引(> 10M 行)
必须用:
```sql
CREATE INDEX CONCURRENTLY idx_xxx ON big_table(col);
```
Alembic 要用 `op.create_index(..., postgresql_concurrently=True)`。
不加 CONCURRENTLY → 🔴 会阻塞写入几十秒甚至几分钟。
### 场景 4:casdoor-internal 的 xorm
xorm 运行时 `Sync2()` 自动建表,但**不删列、不改类型**。如果改了 `object/*.go` struct 里字段:
- 加字段 → Sync2 会加 → OK
- 改 tag → Sync2 不会改已有列 → 需要手写 upgrade SQL,放 `conf/init_data.json` 或独立脚本
- 删字段 → Sync2 不删 → 需要手写 DROP,但要确认生产不再引用
审查 casdoor 的 object 改动时必须问:"这个改动需要配套一个人工执行的 DDL 吗?"
### 场景 5:chat-gw 的手写 SQL
`db/migrations/*.sql` 3 个现有文件。新增必须:
- 有升级版本号前缀(如 `004_*.sql`)
- 显式 `IF NOT EXISTS` 做幂等
- 配套 rollback 文件(如 `004_*_down.sql`)虽然不自动跑,但留下来给 ops
## 输出格式
给主 Agent 的审查回执:
```markdown
## Migration Review Result
### 评级
🔴 Critical / 🟡 High / 🟢 Low / ✅ Clean
### 发现
1. `alembic/versions/xxx.py:L42` — 🔴 **数据丢失**:drop_column('customers', 'legacy_status') 且 downgrade 未恢复
2. ...
### 必跑的验证结果
- upgrade → downgrade → upgrade:✅ 通过
- autogenerate 二次对比:❌ 仍有 diff(见附录)
### 阻断结论
- [x] 有 🔴,建议打回
- [ ] 只有 🟡,附带修复建议可放行
- [ ] ✅ clean,批准
### 给作者的修复建议
1. ...
```
## 红线
- ❌ 不要自己跑 `alembic upgrade` 到生产数据库
- ❌ 不要修改他人的 migration 文件(你是审查者,不是修改者)
- ❌ 不要对 🔴 问题"想办法放行",评级即结论
- ❌ 不要接受"先合了再改"的理由 —— migration 合到 main 后很难回退
+116
View File
@@ -0,0 +1,116 @@
---
description: 巡检 Python 仓库的 Alembic / Node 仓库的 Prisma 的 model vs migration 一致性
argument-hint: [repo-name | all]
---
检查 `$1` 的 DB migration 一致性(`$1` 为空或 `all` 则全部)。
## 覆盖仓库
| 仓库 | 工具 | Model 位置 | Migration 位置 |
|---|---|---|---|
| xiaoshou | Alembic (SQLAlchemy 2) | `app/models/*.py` | `alembic/versions/*.py` |
| CloudCostbrank | Alembic (SQLAlchemy 2) | `app/models/*.py` | `alembic/versions/*.py` |
| chat-gw | 纯 SQL migrations | `db/*.py` | `db/migrations/*.sql` (3 个) |
| gongdan | Prisma | `ticket-system/backend/prisma/schema.prisma` | `ticket-system/backend/prisma/migrations/` |
| lobechat-enterprise | Drizzle | `packages/database/schemas/*.ts` | `packages/database/migrations/` |
| casdoor-internal | xorm Sync2(无显式 migration) | `object/*.go` | 运行时 auto-sync |
**xiaoshou 是本命令的主要目标** —— CLAUDE.md 已记录"pending migrations for production"遗留问题。
## 检查项目(按仓库类型)
### Alembic 仓库(xiaoshou / CloudCostbrank)
```bash
cd /workspace/<repo>
# 1. 当前数据库相比 head revision 是否落后
alembic current
alembic heads
# 若 current != head → 有未应用的 migration
# 2. model 相比最后一次 revision 是否有新差异
alembic revision --autogenerate -m "__check_only__" --rev-id __temp__
# 看生成文件是否为空:
grep -E "op\.(add_column|drop_column|alter_column|create_table|drop_table)" \
alembic/versions/__temp___*.py
# 有操作 → model 和 migration 脱节
# 无论结果如何,删掉这个临时文件:
rm alembic/versions/__temp___*.py
```
### Prisma 仓库(gongdan)
```bash
cd /workspace/gongdan/ticket-system/backend
npx prisma validate # schema 语法
npx prisma migrate status # 已应用 vs 待应用
npx prisma migrate diff \
--from-schema-datasource prisma/schema.prisma \
--to-schema-datamodel prisma/schema.prisma
# 输出非空 → schema 和 migrations 脱节
```
### Drizzle 仓库(lobechat-enterprise)
```bash
cd /workspace/lobechat-enterprise
pnpm drizzle-kit check # schema vs migration
pnpm drizzle-kit generate --dry-run # 查看会生成什么
```
### SQL 手写仓库(chat-gw)
```bash
cd /workspace/chat-gw
ls db/migrations/ # 确认 3 个 SQL 文件都在
# 读 db/*.py 找所有 Table 定义
# 和 SQL 文件里的 CREATE TABLE 对比
# (手工对比,不能 autogenerate)
```
### xorm 仓库(casdoor-internal)
```bash
# xorm 靠运行时 Sync2 自动建表,没有 migration 文件
# 只检查:object/*.go 里的 struct tag 是否一致
grep -rE 'xorm:"pk|xorm:"unique' object/*.go | wc -l
# 产出"可能需要 schema 变更"的字段清单,不报错
```
## 输出报告
生成 `/workspace/ai-ops/reports/migrations-$(date +%Y-%m-%d).md`:
```markdown
# Migration 一致性报告 YYYY-MM-DD
## 汇总
- 健康仓库:N 个
- 有脱节:M 个
- 高危(涉及数据丢失风险):K 个
## 详情
### xiaoshou — ⚠️ 脱节
- `app/models/customer.py`:L42 新增字段 `lifecycle_stage`,但 migrations/ 里找不到对应 `add_column`
- 建议:在相应仓库下跑 `alembic revision --autogenerate -m "add lifecycle_stage"`,人工审查生成文件,再 apply
### CloudCostbrank — ✅ 健康
- current == head
- 无 autogenerate 差异
### gongdan — ⚠️ 待应用
- 有 1 个 migration 未应用到本地数据库:`20250820_add_api_keys`
- 建议:`npx prisma migrate deploy`
...
```
## 红线
- ❌ **不要自动运行 `alembic upgrade` 或 `prisma migrate deploy`** —— 只报告,不修改数据库
- ❌ **不要提 migration PR** —— 生成 migration 文件要人类审查(可能含不可逆 DROP)
- ❌ 不要写入到任何仓库的 `alembic/versions/` 或 `prisma/migrations/`(除了 `__check_only__` 临时文件,用完立刻删)
- ✅ 只生成报告,把决策交给人类
+149
View File
@@ -0,0 +1,149 @@
---
description: 把 casdoor-internal 同步上游 casdoor/casdoor 的最新改动,冲突自动分析但不暴力解决
argument-hint: [--dry-run]
---
把 `/workspace/casdoor-internal` 和上游 `https://github.com/casdoor/casdoor` 同步。若传 `--dry-run`,只出报告不动代码。
## 前置检查
```bash
cd /workspace/casdoor-internal
git fetch upstream main 2>&1 || {
echo "没配 upstream remote,先加:"
echo " git remote add upstream https://github.com/casdoor/casdoor.git"
exit 1
}
```
如果 `.github/workflows/sync.yml` 最近跑过,优先读它的日志看它做到哪一步。
## 第一阶段:侦察(永远执行)
```bash
git log --oneline ^HEAD upstream/main
```
对每个上游 commit 打标签:
| 标签 | 判断规则 | 处理 |
|---|---|---|
| 🟢 safe | 纯 bugfix、只动测试、改注释/文档 | 可直接 cherry-pick |
| 🟡 careful | 碰了 `controllers/`、`object/`、`routers/`、`authz/` | 手动 merge,hunk 级审查 |
| 🔴 dangerous | 改了 `conf/app.conf`、`build.sh`、`docker-compose.yml`(我们的 skip-worktree 文件) | 手动决策,绝不粗暴 merge |
| 🔵 feature | 新文件、新 API 端点 | 直接吸收,但要跑测试 |
| ⚫ breaking | go.mod 主版本升级、数据库 schema 变更、删 API | 标记到报告,等人类决策 |
## 第二阶段:如果 `--dry-run`
生成 `/workspace/ai-ops/reports/casdoor-sync-$(date +%Y-%m-%d).md`:
```markdown
# Casdoor 上游同步计划 YYYY-MM-DD
## 上游新增 commits: N 个
| Hash | 类别 | 描述 | 建议动作 |
|---|---|---|---|
...
## 高风险项
- ⚫ commit xxx 升级了 Beego v2 → v3(我们的 Makefile 锁死在 v2.3.8,拒绝)
- 🔴 commit yyy 改了 conf/app.conf(我们 skip-worktree,需要人工评估)
## 建议 merge 顺序
1. 先 cherry-pick 所有 🟢
2. 再按文件分组处理 🟡
3. 🔴/⚫ 单独列 PR 等人类决策
## 影响评估
- 是否破坏 ACA 部署?<是/否 + 理由>
- 是否影响 chat-gw/xiaoshou/gongdan/lobechat 的 JWT 解析?<评估>
```
**到此结束,dry-run 不动代码。**
## 第三阶段:真干(没传 --dry-run 才执行)
只处理 🟢 + 🟡 + 🔵。🔴 和 ⚫ 永远不动。
### 3.1 拉分支
```bash
git checkout -b chore/sync-upstream-$(date +%Y-%m-%d)
```
### 3.2 分批 merge(不要一次性 merge upstream/main)
**批次 A:纯 bugfix(🟢)**
```bash
for hash in <🟢 列表>; do
git cherry-pick $hash || {
echo "冲突于 $hash,跳过,记录到报告"
git cherry-pick --abort
}
done
make fmt && make vet && make ut
```
**批次 B:feature(🔵)**
cherry-pick,每个都跑 `make ut`,失败就 revert。
**批次 C:careful(🟡)**
逐个 commit,逐个 hunk 审查:
- 上游改了我们动过的代码 → 保留我们的,手动吸收上游新逻辑
- 上游重命名 → 在本地也重命名
- 上游删除 → 只在确认本地没引用后才删
### 3.3 前端改动
如果有 `web/*` 冲突:
```bash
cd web
yarn && yarn build # 必须通过
cd ..
```
### 3.4 Docker 构建验证
```bash
sh ./build.sh
make docker-build
```
## 第四阶段:产出 PR
```bash
git push -u origin chore/sync-upstream-$(date +%Y-%m-%d)
gh pr create --title "chore: sync with upstream casdoor $(git rev-parse --short upstream/main)" --body-file /tmp/sync-pr-body.md
```
PR body 必须包含:
```markdown
## 同步范围
- 上游 commit 区间:<start-hash>..<end-hash>
- 吸收 X 个 safe、Y 个 feature、Z 个 careful
- **拒绝/挂起** N 个 dangerous + breaking(见下方"需人工决策")
## 本地验证
- [x] `make fmt`
- [x] `make vet`
- [x] `make ut`
- [x] `yarn build`
- [x] `make docker-build`
## 风险评估
- JWT claim 字段:<是否变化>
- ACA 部署配置:<是否需要改 build-and-deploy.yml>
- 对下游仓库(chat-gw/xiaoshou/gongdan/lobechat)的影响:<无/需同步改动>
## 需人工决策
- commit xxx(Beego 升级):已挂起
- conf/app.conf 上游改动:已挂起,等确认 Azure PG 连接逻辑是否受影响
```
## 红线
- ❌ 禁止 `git merge -X theirs upstream/main`
- ❌ 禁止 `git merge -X ours upstream/main`
- ❌ 禁止修改 `conf/app.conf` 或其他 `skip-worktree` 文件
- ❌ 禁止升级 `go.mod` 里 beego、xorm 主版本
- ❌ 禁止在冲突无法判断时"猜一个",应挂起到 PR 的"需人工决策"段落
- ✅ 任何 🔴/⚫ 改动一律转化为"PR 描述里的决策项",绝不自动执行
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Claude Code PreToolUse hook:
# - 扫描 Bash 命令字符串本身是否含疑似密钥
# - 对 git commit,追加扫描 staged diff
# - 拒绝超大 diff(防止 Agent 一次改太多)
# 通过方式:exit 0;阻断:exit 2(Claude 会把 stderr 作为拒绝理由展示)
set -uo pipefail
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
# 空命令直接放行(其他工具触发时 command 为空)
if [[ -z "$command" ]]; then
exit 0
fi
# 疑似密钥 / 凭证 模式
patterns=(
'sk-ant-[A-Za-z0-9_-]{20,}'
'ghp_[A-Za-z0-9]{30,}'
'github_pat_[A-Za-z0-9_]{40,}'
'xox[pbar]-[A-Za-z0-9-]{20,}'
'AKIA[0-9A-Z]{16}'
'AIza[0-9A-Za-z_-]{30,}'
'-----BEGIN[[:space:]]+(RSA|OPENSSH|EC|PGP|DSA|PRIVATE)[[:space:]]+PRIVATE'
'DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{20,}'
'SharedAccessKey=[A-Za-z0-9+/=]{20,}'
'mongodb(\+srv)?://[^:]+:[^@]+@'
'postgres(ql)?://[^:]+:[^@]+@'
)
# 1) 检查命令字符串本身(捕获内联密钥,如 git commit -m "my key sk-ant-xxx")
for p in "${patterns[@]}"; do
if echo "$command" | grep -qE -- "$p"; then
echo "[hook-block] Bash 命令字符串中检测到疑似密钥 / 连接串。已阻断。" >&2
echo "[hook-block] 匹配模式: $p" >&2
exit 2
fi
done
# 2) 如果是 git commit,追加扫描 staged diff
if [[ "$command" =~ (^|[[:space:];&|])git[[:space:]]+commit ]]; then
# 尝试提取 "cd <path> &&" 前缀,否则用 $PWD
cd_path=$(echo "$command" | grep -oE '^[[:space:]]*cd[[:space:]]+[^&;]+' | awk '{print $2}' | tr -d '\n' || true)
pushd_done=0
if [[ -n "$cd_path" && -d "$cd_path" ]]; then
pushd "$cd_path" >/dev/null 2>&1 && pushd_done=1
fi
diff_content=$(git diff --cached 2>/dev/null || true)
for p in "${patterns[@]}"; do
if echo "$diff_content" | grep -qE -- "$p"; then
echo "[hook-block] Staged diff 包含疑似密钥。请先 'git restore --staged <file>' 移除涉事文件。" >&2
echo "[hook-block] 匹配模式: $p" >&2
[[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1
exit 2
fi
done
# 超大 diff 保护
diff_lines=$(echo "$diff_content" | wc -l | tr -d ' ')
if [[ "$diff_lines" =~ ^[0-9]+$ ]] && [[ $diff_lines -gt 5000 ]]; then
echo "[hook-block] Staged diff 超过 5000 行 (实际 $diff_lines 行)。请拆分为更小粒度的提交。" >&2
echo "[hook-block] 若确实是合法的大改动,绕过:在 commit message 加 '[huge-diff-ok]'" >&2
if ! echo "$command" | grep -q '\[huge-diff-ok\]'; then
[[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1
exit 2
fi
fi
[[ $pushd_done -eq 1 ]] && popd >/dev/null 2>&1
fi
exit 0
+13
View File
@@ -97,5 +97,18 @@
"env": {
"DISABLE_TELEMETRY": "0",
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "32000"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/pre-commit-check.sh"
}
]
}
]
}
}