3b26457165
- **抬哪一档成为硬门禁**:破坏性条数取**目标提交里候选那一段** ⇒ `0.x` 有破坏性必须抬**次版本**(或主版本), `≥1.0.0` 有破坏性必须抬**主版本**;只抬修订/抬次不够都阻断。无破坏性时抬哪档不限, 但候选号必须严格大于上一正式版与当前开发号(号只增不减)。 - **`1.0.0` 首见的前置**:《稳定契约承诺》必须写实(接口 / 数据模型 / 配置 / CLI 输出与退出码, 不留 `待填`/`TODO`/`TBD`)并在 `## [1.0.0]` 记录里链接它;`1.x → 2.0.0` 不重复要求。 - 同步:正本 §6 五次修正(⑭⑮)、07 发版判据表、"抬哪一档"对照表、templates/release-preflight.py、 templates/README.md、04 §M(V-14~V-17)。 验证:templates/release-preflight.py --self-test 32 分支全过;试点仓夹具 91/0、make verify 15/15。
templates/ —— 可直接落仓的骨架
每个文件都替换掉附件原稿的 GitLab 口径;改法见
../04-模板评估与修正.md。 ⚠️ 复制到仓库后不要就地重写:ci_gate.py/ci_report.py请从已验证的仓库逐字节复制 (Weknora/knowledge-governance/.gitea/scripts/或/srv/aiball/ci/attach/)。
| 文件 | 落仓位置 | 说明 |
|---|---|---|
gitea-build.yml |
.gitea/workflows/build.yml |
提交触发的校验+编译+预览+上报。只做 CI:里面没有任何部署作业(CD 由项目另写独立 workflow,见正本 §8) |
gitea-prod-deploy.yml |
.gitea/workflows/deploy-production.yml |
生产发布唯一入口,只 workflow_dispatch,不构建。⚠️ 未实现的步骤故意 exit 1:占位不许返回成功 |
gitignore |
.gitignore |
去掉原稿自相矛盾的 !src/secrets/ 例外 |
commit-msg |
~/.githooks/commit-msg |
本机提交闸门;规则必须与 ci_gate.py 一致 |
AGENTS.md 骨架 |
AGENTS.md |
见下(agent 指令文件有写入保护,故内联在此) |
| —— 随仓携带 | .gitea/scripts/ci_gate.py.gitea/scripts/ci_report.py |
逐字节复制已验证版本,不要重写 |
落仓后的自检(反向自检,规范 §3.2-3)
# 1) 故意违规的提交必须被闸门拦住,且 build 不跑
git commit -F - <<'EOF'
update stuff
EOF
# 判据:本地 hook 报 ❌;push 后 CI 的 gate 红、build 未执行
# ⚠️ 口径别说过头:CI 的 gate 是**推完再跑**的,它拦不住那次推送本身。
# 要拦推送只有两条路:本机 hook,或开分支保护(禁直推)。gate 的价值是
# "把违规提交标红、挡住编译与预览",不是"没进 main"。
# 2) 合规提交必须过(正例对照:证明不是"永远红")
git commit -F - <<'EOF'
ci(deploy): 接入 Gitea Actions 生产发布 workflow
生产发布改成独立 workflow(只 workflow_dispatch),避免塞进 push 流水线;
concurrency 明确 cancel-in-progress: false —— 取消不是回滚。
验证:手动发起一次,confirm 不匹配时 guard 作业必须红。
EOF
# 3) 产物地址每个都要 200
curl -m 5 -o /dev/null -w '%{http_code}\n' http://192.168.50.134:8899/healthz
curl -m 5 -o /dev/null -w '%{http_code}\n' http://100.92.49.78:8899/healthz
上报脚本的非秘密配置(写在 workflow 里,便于审查)
| 变量 | 作用 | 缺失时 |
|---|---|---|
CI_PUBLIC_BASE_URL |
通知里 CI 运行 / 提交 / 变更 三类链接的基址(对外域名,如 https://gitea.example.com) |
上报脚本明确回退 GITHUB_SERVER_URL(内网地址),并把来源写进上报的 link_base_source |
- 不覆盖
GITHUB_SERVER_URL:检出、gate、内部调用继续用它;只影响通知里的三类链接。 - 非法值(非 http(s)、带 userinfo、带空白)⇒ 明确回退并在 CI 日志里写明原因,不生成垃圾链接。
- 消息里"查看"的报告入口就是 Gitea 运行页(不附 bridge 自己的
/r/<id>内网页); 只有拿不到运行链接时才退回 bridge 报告页。 - bridge 侧
ci_base_url是链接的可信来源(字符串或数组): 公网域名与(回退时的)内网地址都应登记,否则会被当成"非可信链接"丢掉。
AGENTS.md 骨架(复制到仓库根,按本工程改写 TODO)
# AGENTS.md — 给在 <PROJECT> 里干活的 AI 代理
> 这是**操作手册**,不是给用户看的介绍。改代码前先读「硬约束」与「已知陷阱」。
>
> **系统级规范(本机所有 Gitea 仓库都适用)**:仓库 `zheromiku/eng-standards-main`(本机工作副本 `~/eng-standards/`)
> —— `01-规范-Git与CI-CD(Gitea版).md`(正本)+ `02-流程说明与Runbook.md`。
> 做 git / 提交 / CI / 发布 / 回滚 / 密钥 这些事之前先读它;本文件只写**本工程特有**的东西。
## 0. 这个工程是什么
<PROJECT>(一句话说清做什么、给谁用、部署在哪)。
## 1. 硬约束(违反其中任何一条,这次改动就不合格)
1. TODO:不许改变对外可观测行为(接口、报文、退出码、配置语义)
2. TODO:本工程最硬的那条证据是什么(对拍 / 契约 / 字节一致 / 快照)
3. **同一含义的代码只准有一份**(禁止同义复制)
4. **注释写"为什么"**,不写"是什么"
5. 提交按"一件事一个提交",conventional commits,正文用中文,标题 ≤72 字符
6. **生产使用测试验收过的同一份构建产物**(同 sha / 同镜像 ID)—— 不许在生产机上另行 build
## 2. 本地验证闭环(提交前的最低要求)
```bash
make verify # 本仓唯一门禁入口(本地与 CI 同一条命令);跳过项会显式警告「不算全绿」
```
- 门禁**串行**、失败即停;**汇总按必需清单逐项核对**:失败即停后剩下的记 `not_run`,
而 `not_run` / `skipped` 都不算通过(别把「没跑到」当成「没问题」)。
- 只有「跑产物」的套件(端到端 / 契约 / 产物自检)才必须排在构建之后;
单测(`go test` / `pytest` / `vitest`)各自跑源码,不需要先构建。
- 改到部署脚本或产物形状时,必须另外跑一次真实的装配/启动自检。
## 3. CI 闭环
```bash
giteactl runs <ns>/<repo> # 最近运行
giteactl logs <ns>/<repo> <runId> # 作业日志
python3 ~/.hermes/scripts/gitea_api.py GET "/api/v1/repos/<ns>/<repo>/actions/tasks?limit=5"
```
- 失败时**先读日志再改代码**;追到第一处 `❌`。
- `status=pending` 且 `status_count=0` **不是**失败(那是"没有配置任何状态检查")。
- run 一直 `queued` ⇒ 先查**本仓库有没有自己的 Runner**,别怀疑工作流。
- 合并读回:`GET /pulls/{n}` → `state=closed` + `merged=true` + `merge_commit_sha` 非空。
⚠️ 合并接口成功时可能返回**空 body** → **永不重试**,改读回。
## 4. 已知陷阱(索引)
全文在 `docs/agents/traps.md`。**只追加、不重排、不复用旧号**;编号会被代码注释与测试引用。
⚠️ 新增陷阱要改两处(traps.md 加全文 + 这张表加一行)。
| # | 一句话 | 全文 |
| --- | ------ | ---- |
| 1 | TODO | [读](docs/agents/traps.md#trap-1) |
## 5. 提交与推送
- **默认直推 `main`**(原稿口径,正本 §1.1);**需要评审时**才开短命分支 + PR(正本 §1.2,那是**建议**、不是门禁)。
- conventional commits,正文中文,**说清"为什么"**;一件事一个提交。
- 提交信息不要用反引号 / `$()`(会触发命令替换):用 `git commit -F -` + heredoc。
- 推之前跑完第 2 节;推送后用第 3 节确认流水线,失败就**先读 trace 再改代码**。
- 生产发布**不走 push**:它是独立的 `deploy-production` workflow,只由人手动发起(见规范 §5.2)。
## 6. 版本与发版(**CI 不代劳,必须人批准**)
- **开工前先读系统级规范的正本 §6**(`~/eng-standards/01-规范-Git与CI-CD(Gitea版).md`)
与 `07-发版操作说明(tag与Release).md`。
- 版本号的**唯一权威来源**:本仓 `CHANGELOG.md` 顶部那条已发布版本(正本 §6.1)。
禁止再放 `VERSION` 文件、禁止写进源码常量、禁止在 CI 里用 `git describe`(检出取不到 tag)。
- **完成一个功能**时,把**使用者可见**的变化写进 `CHANGELOG.md` 的 `## [Unreleased]`
(四类小节 `Added` / `Changed` / `Fixed` / `Security`,分类可省略、别留空栏目;
**不要求每个提交升版本**)。过程记录放 `docs/archive/`,未合并事项放 `docs/BACKLOG.md`。
- **构建身份**:`python3 scripts/version.py --identity` —— **开发号自动递增**:
`<主>.<次>.<自 v<正式版本> 以来的提交数>`(如 `0.1.12`;无基线/取不到 tag 时 `dev+g<短SHA>`),
**不写进 CHANGELOG**、数字只增;**不许只显示上一正式版本号、不许自称正式版**。
正式发布时请把**次版本加一**(`0.1.x` → `0.2.0`),预检会挡住"候选 <= 当前开发号"。
**抬档也是硬门禁**:候选发布记录里有破坏性变化 ⇒ `0.x` 必须抬**次版本**、`≥1.0.0` 必须抬**主版本**
(只抬修订 ⇒ 阻断);**发 `1.0.0` 前**必须在 `docs/RELEASE_RUNBOOK.md` 里把《稳定契约承诺》
(接口 / 数据模型 / 配置 / CLI 输出与退出码)逐项写实、去掉占位,并在发布记录里链接它。
⚠️ 浅克隆里拒绝算号(退回 `dev+g<短SHA>`),检出步骤在有 tag 时会 `--unshallow` 补全历史。
- ⚠️ **正式构建要核证据**:`--identity --release` 必须能核到 **完整 40 位 SHA + 指向该提交的注释 tag
`v<版本号>` + 该提交里 CHANGELOG 顶部版本一致**;缺一条 ⇒ **拒绝标为正式版**(rc=2)。
自查:`python3 scripts/version.py --self-test`(正例 + 无 tag / 短 SHA / tag 不符 / 轻量 tag 负例)。
- **准备发版**时,先给用户这五样,批准后才动:
1. **版本建议**(`python3 scripts/release_preflight.py` 给的候选值;
**还没有正式发布过时版本号必须由用户显式指定**,预检会把它列成阻塞项);
2. **变更说明**(四类小节;破坏性变化写在 `### Changed` 里、以 `**破坏性**` 开头并链接发布文档);
3. **准确提交的完整 40 位 SHA**;
4. 那次提交的 **CI 成功证据**(status context + run 链接)—— 不是"本地跑过了";
5. **未验证项与遗留风险**。
- ⛔ **未获用户明确批准,不创建、不推送正式 tag,不发布 Gitea Release。**
CI **不**自动升版本、**不**打 tag、**不**发布。
- 正式发版用**注释 tag**(`v<版本号>`)指向验收过的提交;Gitea Release 关联该 tag,
说明**来自 CHANGELOG**,不另写一份。
- 自查命令:`python3 scripts/check_changelog.py --tags --require-remote`(结构 + tag 一致性,**真查远端**);
`python3 scripts/release_preflight.py`(**只读**预检:`准备建议` + `发布就绪判定`;
**不给"就绪"就是不能发**;**自动发布路径已禁用** —— `--execute` 会被明确拒绝(rc=2,零写操作),
输出里也不推荐自动路径)。
- ⚠️ **就绪判据**(缺一条都不算):候选版本**已写进目标提交**(工作区里写了没提交不算)、
完整 SHA 指向准确提交、记录检查通过、**必需 context 集合全部 success**
(集合在 `.gitea/required-contexts.txt`,缺失/为空 ⇒ 没有策略 ⇒ 阻断;失败/缺项/pending/查询异常都阻断)、
远端 tag 已核对未占用、**Release 查询只有 HTTP 404 才算不存在**(403/5xx/超时算异常 ⇒ 阻断)。
`.gitea/required-contexts.txt` **故意留空**:请按本仓真实的 status context 逐行填好,否则不能判就绪。
- ⚠️ **开发版本没打 tag 是正常的**:日常 CI 不许因此变红。