Files
eng-standards-main/06-接入既有工程.md

153 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 接入既有工程:让现在的工程启用这套规范
> 一句话:接入 = **前置体检 → 加指针 → 开保护 → 反向自检**,一条命令走完。
> 为什么要有"前置体检"这一关,见 §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` —— 等守门脚本做完,再给自己开检查