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

10 KiB
Raw Permalink Blame History

接入既有工程:让现在的工程启用这套规范

一句话:接入 = 前置体检 → 加指针 → 开保护 → 反向自检,一条命令走完。 为什么要有"前置体检"这一关,见 §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. 一条命令

# ① 只体检 + 打印计划(**不动任何东西**)—— 先看,再决定
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. 不想用工具?手工四步

# ① 体检(判据同 §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 —— 等守门脚本做完,再给自己开检查