153 lines
10 KiB
Markdown
153 lines
10 KiB
Markdown
# 接入既有工程:让现在的工程启用这套规范
|
||
|
||
> 一句话:接入 = **前置体检 → 加指针 → 开保护 → 反向自检**,一条命令走完。
|
||
> 为什么要有"前置体检"这一关,见 §2 —— 跳过它会把 PR **永久锁死**。
|
||
>
|
||
> ⚠️ **工具不替人合并、也不替人审批**(2026-10 改):它建完 PR 就把状态如实打给你,然后停下。
|
||
> 批准之后才跑 `--merge-pr <n> --approve-sha <完整40位SHA>`,而它会**先核对**:
|
||
> ① 获批的就是头提交那个**完整 SHA**(短 sha 不算批准依据);② **明确策略要求的**每一个 status context
|
||
> 都是 `success`(失败 / 取消 / 从未运行 / 状态拿不到 / 等超时 / 头提交变了 —— 任一情形都拒绝);
|
||
> ③ PR 的目标分支就是默认分支、状态是 `open`、head 就是本仓(跨仓/合错分支都不做)。
|
||
> `--self-test`(11 分支)+ `--self-test-chain`(假 Gitea 跑真实 `merge_pr()`,13 分支)逐条证明。
|
||
>
|
||
> **"要求哪些检查"只能来自明确策略**:`--require-context "build / build (push)"`,
|
||
> 或仓库里的 `.gitea/required-contexts.txt`(一行一个)。两个都没有 / 文件是空的 ⇒ **拒绝合并** ——
|
||
> **不接受"体检时观察到哪些状态"来代替**:`combined state` 只报"报过状态的都成功",
|
||
> 一个无关检查成功就能骗过它,于是没跑过必需检查的 PR 会被合进去。
|
||
|
||
## 1. 一条命令
|
||
|
||
```bash
|
||
# ① 只体检 + 打印计划(**不动任何东西**)—— 先看,再决定
|
||
python3 ~/eng-standards/scripts/adopt-existing.py <owner/repo>
|
||
|
||
# ② 计划无误再执行
|
||
python3 ~/eng-standards/scripts/adopt-existing.py <owner/repo> --apply
|
||
|
||
# 需要"谁都不能直推"时才开分支保护 —— **默认不开**(正本 §1.3:原稿口径是直推 main)
|
||
python3 ~/eng-standards/scripts/adopt-existing.py <owner/repo> --apply --with-protection
|
||
```
|
||
|
||
工具只依赖 Python 标准库 + git;凭据运行时从 `~/.git-credentials` 读,**永不打印**。
|
||
**幂等**:重复跑不会重复建 PR、不会改 `main`。
|
||
|
||
它做的事:
|
||
|
||
| 步 | 动作 | 怎么落地 |
|
||
|---|---|---|
|
||
| 1 | 体检 | 读默认分支、工作流触发方式、runner(仓库级/组织级)、最新提交的 status context、已有保护 |
|
||
| 2 | 加指针 | 新起分支 → 写 `AGENTS.md` → 提交(过本机闸门)→ **PR** → 有界等 CI → **停下**(工具不替你合并) |
|
||
| 3 | 开保护(**仅 `--with-protection` 时**) | `POST/PATCH /branch_protections`:禁直推 + 必须 PR;**状态检查按第 1 步判定**。**默认不做** —— 原稿口径是直推 `main`(正本 §1.3) |
|
||
| 4 | 给自检命令 | 打印"直推必须被拒"的验证命令(**这步工具不代做,必须你跑**) |
|
||
|
||
⚠️ `main` 一旦被保护,**所有改动都得走 PR** —— 包括工具自己加 `AGENTS.md` 这一步。
|
||
|
||
## 2. 前置体检为什么是必须的(本环境最容易踩的坑)
|
||
|
||
"必须状态检查通过"开错了,症状是:**PR 打开后永远等一个不会出现的检查,永远合不了**。
|
||
判据只有一条:**该仓的工作流,在 PR 的头提交上会不会产生 status?**
|
||
|
||
| 工作流触发 | PR 头提交有没有状态 | 能否开强制状态检查 |
|
||
|---|---|---|
|
||
| `on: push: branches: "**"`(推**任意分支**都跑) | 有 —— 分支推送产生 `xxx (push)` | ✅ **可以** |
|
||
| `on: push: branches: [main]`,无 `pull_request` | **没有** —— PR 完全不触发 | ⛔ 开了就锁死 |
|
||
| `on: push: branches: [main]` + `pull_request` | 有,但 context 是 `xxx (pull_request)` | ⛔ 与 `(push)` 不匹配;要开得按 `(pull_request)` 人工确认 |
|
||
|
||
工具按这张表自动判定,**判定不通过就只开"禁直推 + 必须 PR",不碰状态检查**。
|
||
|
||
实测证据(本机 7 个仓):
|
||
|
||
| 仓 | 工作流触发 | 判定 |
|
||
|---|---|---|
|
||
| `Weknora/knowledge-governance` | `push: "**"` | ✅ 可开 —— context `build / build (push)` |
|
||
| `Weknora/dobotai-platform` | `push: "**"` | ✅ 可开 —— 同上 |
|
||
| `aiball/software-pc` | `push: [main]`,无 `pull_request` | ⛔ 不可开 |
|
||
| `aiball/protocol-docs` | `push: [main]` + `pull_request` | ⛔ 上下文不匹配 |
|
||
| `Weknora/knowledge-adapter-service` | 默认分支上**没有**工作流 | ⛔ 无 CI 可强制 |
|
||
| `Weknora/Enterprise-Knowledge-Base` | 本仓无工作流(现有 status 来自仓外) | ⛔ 不可开 |
|
||
| `zheromiku/eng-standards-main` | 无工作流 | ⛔ 不可开 |
|
||
|
||
**要做的是先补 CI,再开检查** —— 不是先开检查(那会锁死)。所以接入顺序是:
|
||
**工作流能覆盖任意分支 → 再开强制状态检查**。工作流只覆盖 `main` 的仓(aiball 两个),
|
||
第一步是把它改成 `on: push: branches: "**"` 或补一个 `pull_request` 触发。
|
||
|
||
## 3. 试点记录:`Weknora/knowledge-governance`(已跑通)
|
||
|
||
> ⚠️ **口径已更新**:下表里的"分支保护"作法**后来按原稿口径关掉了**(正本 §1.3:默认不开、直推 `main`)。
|
||
> 记录保留有两个用处:① 万一将来真要开保护,它证明"PR + 强制状态检查"是**能跑通**的;
|
||
> ② §2 那条体检判据是真的有用。其余部分(加 `AGENTS.md` 指针)不受影响。
|
||
|
||
执行前该仓状态:无 `AGENTS.md`、无分支保护;`build.yml` 是 `push: "**"`;仓库级 runner `runner-weknora-kg-01`。
|
||
|
||
| 步 | 实测结果 |
|
||
|---|---|
|
||
| 分支 + 提交 | `docs/agents-pointer`,提交通过本机提交闸门(`~/.githooks/commit-msg`) |
|
||
| 推送触发 CI | 头提交 `d025fc9adfe2` → context `build / build (push)` → `combined=success` |
|
||
| PR | **PR #1**,`mergeable=True`(**关键结论:强制状态检查认 PR** —— 因为推分支就产生了 `(push)` 状态) |
|
||
| 合并 | 当时由工具**自动**合并:HTTP 200(**空 body,属正常**,不重试)→ 读回 `state=closed` `merged=true` `merge_commit_sha=32b0ec58a612`。**该行为已改**:现在工具不自动合并(见上) |
|
||
| 结果 | `AGENTS.md` 在 `main`;分支已自动删除 |
|
||
| 反向自检 | 直推 main 被服务端拒绝:`Not allowed to push to protected branch main` / `pre-receive hook declined` |
|
||
| 幂等 | 再跑一次 `--apply`:`AGENTS.md` 跳过、保护值不变、**main 仍是 `32b0ec58a612`** |
|
||
|
||
## 4. 不想用工具?手工四步
|
||
|
||
```bash
|
||
# ① 体检(判据同 §2)
|
||
python3 ~/.hermes/scripts/gitea_api.py GET "/api/v1/repos/<ns>/<repo>/actions/tasks?limit=1"
|
||
git clone <repo> /tmp/t && cd /tmp/t
|
||
python3 -c "import re;print([l for l in open('.gitea/workflows/build.yml') if re.match(r'^\s*(on|push|pull_request|branches):',l)])"
|
||
docker ps --format '{{.Names}}' | grep runner # 本仓有没有 runner
|
||
|
||
# ② 加指针:走分支 + PR,不要直推 main
|
||
git switch -c docs/agents-pointer
|
||
# AGENTS.md 内容取自 ~/eng-standards/templates/README.md 里的 ```markdown 骨架块,
|
||
# 把 <PROJECT> / <ns>/<repo> 替换掉,并如实写下该仓的 CI / runner / 保护现状
|
||
git commit -F - <<'EOF'
|
||
docs(agents): 加 AGENTS.md 操作手册与规范指针
|
||
|
||
指向系统级规范(zheromiku/eng-standards-main)。
|
||
EOF
|
||
git push -u origin HEAD # 然后网页开 PR → 等 CI → 合并
|
||
|
||
# ③ 开保护:网页 Settings → Branches;或 API(字段见 swagger CreateBranchProtectionOption)
|
||
# enable_push=false、enable_force_push=false、required_approvals=0、
|
||
# block_on_outdated_branch=true、dismiss_stale_approvals=true、
|
||
# enable_status_check=<按 §2 判定>、status_check_contexts=<匹配的 context>
|
||
|
||
# ④ 反向自检(必须)
|
||
cd /tmp/t && git commit --allow-empty -m 'chore(probe): 不该进 main' && git push origin main
|
||
# 判据:Not allowed to push to protected branch
|
||
```
|
||
|
||
## 5. 接入后的验收判据(缺一即不算接入)
|
||
|
||
1. `AGENTS.md`(或 `.hermes.md`)在默认分支上,且含指向 `zheromiku/eng-standards-main` 的路径。
|
||
2. **(只在开了保护时)** `GET /repos/{r}/branch_protections` 非空、`enable_push=false`,且直推默认分支**被服务端拒绝**。
|
||
⚠️ 默认口径是**直推 `main`**,所以正常情况下这里应当是"没有保护" —— **别把"没保护"当成缺陷**。
|
||
3. 推一个故意违规的提交 → 本机 hook 红、CI 的 `gate` 红。
|
||
⚠️ 注意口径:CI 的 `gate` 是**推完再跑**的,它拦不住那次推送本身(正本 §2 / §1.3)。
|
||
"直推也拦得住"是**错的**说法:要拦推送只有两条路 —— 本机 hook,或开分支保护(禁直推)。
|
||
gate 的价值是"把违规提交标红并挡住编译/部署",不是"阻止它进 main"。
|
||
4. **(只在开了保护时)** PR 能正常合并 —— 证明强制状态检查没有把 PR 锁死。
|
||
5. 会话里问"这个仓提交信息什么规范",代理能**不靠你重复**答出来(说明指针真的被注入了)。
|
||
|
||
## 6. 会遇到的拒绝与处理
|
||
|
||
| 现象 | 原因 | 处理 |
|
||
|---|---|---|
|
||
| 推 `main` 被拒 | 已开保护 —— 这是**预期行为** | 走分支 + PR |
|
||
| PR 一直显示等待状态检查 | 该仓工作流没覆盖 PR(§2 表后两行) | 先改工作流触发,或临时关掉 `enable_status_check` |
|
||
| PR 报 `mergeable=false` 但没有冲突 | 强制状态检查未满足 / 分支过期 | 看 `GET /pulls/{n}` 的 `mergeable` 与 head 上的 status |
|
||
| 合并接口返回空 body | **Gitea 合并成功的正常返回** | **永不重试**,改 `GET /pulls/{n}` 读回 `merged=true` |
|
||
| run 一直 `queued` | 该仓没有归属自己的 runner | `bash /srv/aiball/runner/deploy_repo_runner.sh <owner/repo> <name>` |
|
||
| 工具说"没有凭据" | `~/.git-credentials` 里主机是 URL 编码的 | 工具已按解码后比对;手工查时别按字面比 |
|
||
|
||
## 7. 扩到其余仓的顺序建议
|
||
|
||
先补 CI 再开检查,别反过来:
|
||
|
||
1. `Weknora/knowledge-adapter-service` —— 先把 `build.yml` 从 `ci/setup` 合到 `main`(现在 main 上零校验)
|
||
2. `aiball/software-pc`、`aiball/protocol-docs` —— 先把工作流触发改成覆盖 PR,再开强制检查
|
||
3. `Weknora/dobotai-platform` —— 工作流已覆盖任意分支,可直接 `--apply`
|
||
4. `Weknora/Enterprise-Knowledge-Base` —— 需先接 CI + runner(分支也待收敛)
|
||
5. `zheromiku/eng-standards-main` —— 等守门脚本做完,再给自己开检查 |