Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CodeLinkOps 工程规范

这份文档对 org 下全部仓库生效,人和 AI 都照它执行。

三份文件各管一件事,职责不许重叠 —— 这是为了让 agent 一进仓库就知道去哪找什么:

文件 回答的问题 谁维护
README.md 这是什么、怎么跑起来 每个仓库自己
DESIGN.md 要做成什么样、为什么这么设计 每个仓库自己
AGENTS.md 在这个仓库里怎么干活 本文档 + 仓库补充

AGENTS.md(与本仓库的 AGENTS.md 同源)是给 agent 读的那一份。接法见 §7


0. 四条原则

后面几十条规则多半是这四条在不同地方的实例。遇到没被覆盖的新场景,照原则推导比找类比可靠。

  1. 规则必须可机器校验,否则它只是一个愿望。 每条规则都要说清楚它靠什么落地:平台强制、CI 检查,还是纯自觉。标着「靠自觉」的规则一定会被违反,这不是道德问题,是概率问题 —— 所以要么给它一个检查,要么承认它是建议而不是规则。本文档里每一节末尾都标了「怎么落地」。
  2. AI 写代码,人负责判断。 分工不是「AI 替人干活」,是「AI 出实现,人决定要不要」。点下 Merge 的人为这次改动负责,不是写它的模型 —— 「AI 写的」不能当免责声明用,否则代码审查会立刻退化成走过场。
  3. 能被统计的东西必须先被结构化。 进度、成本、事故都要能自动汇总,所以 issue 必须挂里程碑、PR 必须引用 issue、提交必须有署名。只存在于群聊和脑子里的东西,AIOps 看不见 —— 那不是「统计不准」,是「压根不存在」,而两者在报表上长得一模一样。
  4. 例外要有明确入口,否则规则会被整体绕过。 一条在紧急情况下做不到的规则,会在第一次紧急情况时被丢掉,而且之后不会被捡回来。所以有 §9 逃生门:允许破例,但破例本身要留痕。

1. 新建项目

顺序是固定的:先设计,再拆里程碑,最后写代码。

1.1 建仓库

  • 命名:全小写、连字符分隔(order-service,不是 OrderService / order_service)。
  • 可见性:默认私有。要公开需要在 issue 里说明理由并由 owner 确认。
  • 默认分支:dev(见 §2)。
  • 建完立刻放三个文件:README.mdDESIGN.mdAGENTS.md(模板见 templates/)。

1.2 先出 DESIGN.md

没有 DESIGN.md 不许开始写代码。 模板:templates/DESIGN.md

理由不是流程洁癖:AI 是照着仓库里的文档决定做法的(它会 read 你的 DESIGN.md)。没有设计文档时,AI 不会停下来问,它会自己编一个设计然后照着做 —— 而那个设计只存在于某一次会话的上下文里,下一次会话会编一个不一样的。表现出来就是「同一个项目里两个模块风格完全不同」,而没有任何地方记着为什么。

DESIGN.md 至少要有:要解决什么问题、不做什么(边界)、技术选型与理由、数据模型、对外接口、以及功能点清单(下一步直接变成 issue)。

1.3 把功能点拆成里程碑 + issue

DESIGN.md 里的功能点清单要逐条变成 GitHub issue,并挂到里程碑上。规则见 §6

这一步由 AI 做、人审。 让 AI 读 DESIGN.md 生成 issue 列表,人过一遍再批量建 —— 人手写二十条 issue 会写到一半开始偷懒合并,而合并过的粒度会让进度统计失真(§6.3)。

1.4 CI 第一天就要有

第一个 PR 就该有 CI,不要「先写功能,回头补测试」。回头补的时间不存在。

最低要求:构建通过 + lint 零警告 + 测试通过。加上 §3 的署名检查。

怎么落地:靠自觉 + review。建仓库时照 templates/ 抄一遍就都齐了。


2. 分支与合入

2.1 三种分支

分支 是什么 谁能写
prod 生产。这个分支上的每一个 commit 都应该是能上线的 只接受来自 dev 的 PR,以及 hotfix PR
dev 集成分支,默认分支。所有工作分支合到这里 只接受 PR
feat/* fix/* chore/* docs/* 工作分支 随便 push,这是你的地盘

工作分支命名:<类型>/<issue号>-<短横线描述>,例如 feat/128-milestone-rollup。带上 issue 号是为了让 AIOps 把分支、PR、issue、里程碑串成一条线 —— 少了它,这条链在第一环就断了。

2.2 合入方式

  • 一律走 PR,不许直接 push 到 dev / prod
  • 合并策略统一 Squash mergedev 上一个 PR 一个 commit,历史读得下去;工作分支里那些「fix typo」「再试一次」不该进主干。
  • 禁止 force push 到 dev / prod,任何情况。
  • 回滚用 git revert 开 PR,不要 force push、不要改历史。 回滚是一次改动,它也该被记录、被审查 —— 改历史等于让「那次事故到底做了什么」查不出来。

2.3 从 dev 到 prod

devprod 是一次发布,走 PR(我们叫它 promotion PR)。PR 正文要写清这次带了哪些改动、影响哪些里程碑、怎么回滚。

合进 prod 之后打 tag,由 tag 驱动上线 —— 见 §5

hotfix 例外:可以从 prodfix/*,PR 直接回 prod,但必须在合并后同一天回合到 dev。忘了这一步的后果是下次发布把 hotfix 覆盖掉 —— 而那个 bug 会带着「我们明明修过」的记忆重新出现。

2.4 PR 的要求

  • 标题用 Conventional Commits:feat: 支持里程碑跨仓库汇总fix(auth): 会话过期后不再 500
  • 正文必须写 Closes #<issue号>。跨仓库用 owner/repo#123
    • 注意:GitHub 的 Closes 只在同仓库内自动闭环,跨仓库引用它会显示成链接但合并时不会关掉那条 issue。跨仓库的闭环要靠人或 AIOps 平台回写。
  • 一个 PR 对应一个 issue。改着改着顺手修了别的 —— 另开一个 PR。

2.5 本仓库自己不遵守这一节

.github 仓库的默认分支是 main,没有 dev / prod。这是有意的,不是漏改:

  • GitHub 只从默认分支读取组织级模板,多一层 devprod 只会让改一条 issue 模板要开两个 PR。
  • 各仓库调用可复用 workflow 时钉的是 @main(见 templates/workflows/)。

这里没有「生产」可言 —— 它就是规范本身。

怎么落地:分支写保护与必需检查在仓库设置里配(proddev 都要)。PR 标题与 Closes 可以加 CI 检查。


3. 谁能改代码

代码由 AI 改、由 AI 提交。人不直接写代码、不直接提交。

人做的是:提需求、审设计、review PR、点 Merge、以及为结果负责。

3.1 边界在哪

「代码」指仓库里的一切:源码、配置、CI yaml、README、注释。没有例外档 —— 一旦开了「文档可以手改」的口子,它会在两周内变成「小改动可以手改」。

顺带一提,最容易破例的地方是 GitHub 网页上直接编辑 README。那太方便了,而它正是要禁的。

3.2 怎么认出是 AI 提交的

每个 commit message 里必须有 agent 署名 trailer。Claude Code 默认就会加:

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

其他工具用通用形式:

Agent: codex/gpt-5

CI 会检查 PR 里每一个非 merge commit 都带了其中之一(templates/workflows/agent-authorship.yml)。

说清楚这个检查的能力边界:它挡不住有意为之的人。 手写一行 Co-Authored-By: 谁都会。它拦的是顺手:网页上改一行、本地图省事直接改 —— 那些不会带 trailer,会当场变红。真正的把关在 review:人看 diff 时会看出来哪段不像 AI 写的。

3.3 为什么要这样

不是因为 AI 写得比人好。是因为:

  • 上下文会沉淀。 AI 每次改动都读 AGENTS.mdDESIGN.md;人凭记忆改,而记忆不进仓库。
  • 改动会解释自己。 AI 的 commit message 和 PR 正文成本近乎为零,所以它会认真写;人赶时间时写的是「fix」。
  • AIOps 才有东西可统计。 谁改的、花了多少 token、对应哪个 issue —— 人手动改的那部分在报表上是个空洞。

怎么落地:署名检查是 CI(能拦住顺手)。「人不写代码」本身靠自觉 + review。


4. 技术选型

不要每个项目重新吵一遍。 下面是默认值,偏离默认值要在 DESIGN.md 里写明理由。

默认 说明
后端 Rust 优先级最高。新服务默认 Rust
Web 前端 React Vue 也允许,但见下
移动 / 桌面 App Flutter 严格。不要 React Native、不要各写一套原生
脚本 / 一次性工具 shell 或 Rust Python 仅限数据/ML
运行形态 Kubernetes §5
镜像仓库 私有 GHCR 同上

4.1 后端为什么是 Rust 优先

单一语言意味着:一套工具链、一套 CI、一套依赖审计、agent 的上下文可以跨项目复用。混着写的代价不在写的时候,在半年后没人记得某个服务是用什么起的。

可以不用 Rust 的情况(写进 DESIGN.md):生态里没有可用的库(典型是 ML / 数据科学 → Python);或者要嵌进一个已有的非 Rust 运行时。「Rust 写这个太慢了」不是理由 —— 现在写代码的是 AI。

4.2 React 还是 Vue

默认 React。 「React 或 Vue 都行」听起来是灵活,实际是让每个项目重新决定一次,然后 org 里出现两套互不通用的组件、两套 CI、两份 agent 上下文。

选 Vue 需要一个具体理由(已有可复用的 Vue 资产、或对接的第三方只给 Vue 组件),写进 DESIGN.md。「团队更熟」不算理由 —— 写代码的是 AI。

4.3 版本必须钉死

每个仓库都要有:

  • Rust:rust-toolchain.toml
  • Node:package.jsonpackageManager 字段 + .nvmrc,包管理器统一 pnpm
  • Flutter:.fvmrc 或在 README 里写死 SDK 版本

lockfile 必须提交。 不钉版本的后果是「我这儿能跑」,而 AI 在一个和你不同的版本上改代码,产出的东西你验证不了。

怎么落地:靠 review + DESIGN.md 的选型章节。


5. 部署

一句话概括整节:打 tag 触发的是「构建 + 开 PR」,不是「部署」。集群里跑什么,由 Git 里写着什么决定。

5.1 运行形态一律 Kubernetes

不接受把生产跑成「一台机器上的 docker compose」或「systemd 服务」。

理由不是时髦:AIOps 只在 k8s 上看得见东西。 集群登记之后,它能读 pod、日志、事件、helm release、同步状态;跑在别处的服务,出事时 AI 一行日志都拿不到。那不是「少一个功能」,是那个服务在运维视角里根本不存在 —— 而它照样会在半夜挂掉。

  • 集群必须先在 AIOps 里登记(人登记地址与凭据,AI 只说集群名)。
  • 每个环境一条独立登记,dev 和 prod 是两条,不是一条加参数。同名的「重启 api」在两个环境上是两件完全不同的事。
  • 本地开发用 docker compose 完全没问题,这条只管生产与预发。

5.2 镜像一律私有 GHCR

ghcr.io/codelinkops/<仓库名>

  • 必须打 org.opencontainers.image.source 标签指向仓库。少了它,GHCR 上的包和仓库关联不上 —— 包的权限继承、以及「这个镜像是哪份代码构建的」两件事同时断掉。
  • 集群侧需要一个 imagePullSecret 才拉得动私有包。这是一次性配置,但忘了配的表现是 ImagePullBackOff,而它看起来像镜像不存在 —— 会让人去查 tag 而不是查凭据。
  • tag 不可变:推上去的 tag 永不重推,也不要用 latest k8s 认的是 tag,重推同一个 tag 之后「集群里跑的到底是哪份代码」就查不出来了;更糟的是 rollout 不会被触发(digest 变了但 spec 没变),于是新起的 pod 是新版、老 pod 还是旧版 —— 这种半新半旧的状态最难查。
  • 每次构建打两个 tag:语义化版本v1.2.3,给人看)和 commit shasha-abc1234,唯一能反查代码的锚点)。
  • Dockerfile:多阶段构建、不要用 root 跑、暴露一个健康检查端点。

5.3 打 tag 之后会发生什么

prod 分支上打 v1.2.3,然后:

  1. CI 构建镜像,推 GHCR,tag 为 v1.2.3sha-<短sha>
  2. CI 开一个 PR,把部署清单里的 image tag 改成 v1.2.3 —— 只改那一行;
  3. 人审、合并;
  4. 同步器把集群拉到位。

为什么 tag 不直接部署到集群:

  • 那样集群状态的来源就不是 Git 了。下一次同步会把它改回去,留下一段谁也说不清的历史。
  • 合并那一下才是决策点。 tag 只说明「这个版本构建好了」,不等于「决定上线」。两件事挤在一个动作里,就没有地方可以喊停了。
  • 走 Git 的话,「现在线上跑的是什么」的答案在仓库里,而且带着是谁批的;直接部署的话,只能去问集群。

dev 环境可以自动。 dev 分支合并 → 构建 → 自动更新 dev 的清单,不用人点。它风险低而且要求快。prod 永远要人点一下。

5.4 部署清单

  • 与代码同仓库的 deploy/,或独立的 GitOps 仓库。两种都行,但一个项目只能选一种,在 DESIGN.md 里写明。
  • 每个环境一个目录deploy/dev/deploy/prod/),不要一份清单加一堆条件判断 —— 那种写法里「prod 到底生效了哪几条」谁也看不出来。
  • image tag 在 YAML 里必须加引号。 tag: "1.0" 是字符串,tag: 1.0 是浮点数,会被解析成 1,然后拉一个不存在的镜像。
  • 副本数由 HPA 管的话,清单里就不要写 replicas 写了会和 HPA 互相覆盖,表现是副本数反复横跳,而两边的配置单独看都是对的。

5.5 回滚

git revert 那个改 tag 的 PR,然后合并。 就这一条路。

不要用 helm rollbackargocd rollbackkubectl rollout undo —— 它们都是绕过 Git 直接改集群,同步器随后会把它改回去。表现出来是「回滚过了,但过一会儿又变回故障版本」,而且没有任何地方记着中间发生过什么。

嫌 revert PR 慢的话,正确做法是给它降低审批门槛,不是绕过 Git。

5.6 什么算「部署完成」

CI 绿了不算。PR 合了也不算。 同步是异步的。

判据是同步器报 Synced + Healthy。在那之前一律说「已触发」,不要说「已上线」—— 说成完成态会让人(和 AI)据此继续往下走,而那时集群可能还没动。

5.7 不要手改集群

生产集群上不许 kubectl apply / kubectl edit / helm upgrade。理由同 5.5:制造漂移,然后被同步器改回去,中间那段时间的行为没人解释得了。

只读的排查命令(logs / describe / events / get)随便用,那些不改变任何东西。

真的必须手动介入 —— 走 §9 逃生门

5.8 清理镜像之前先查在用的

「保留最近 20 个」这类定时规则假设没被保留的就没人用,而 GitOps 下集群引用的往往正是某个具体的旧 tag。删掉它,下次 pod 重建就是 ImagePullBackOff —— 而那多半发生在半夜扩容或节点漂移的时候。

顺序:仓库里有什么 → 集群在用什么 → 差集才可以删。

查在用的必须算上 initContainers 漏了它,一个只在 init 阶段用到的镜像会被判成没人用而删掉,然后下次 pod 起不来。

5.9 密钥不进 Git

GitOps 的前提是清单进 Git,而 Secret 不能跟着进。用 sealed-secrets / external-secrets,或者在集群侧手工建。详见 §8

怎么落地

  • CI 能拦住的:镜像 tag 格式、org.opencontainers.image.source 标签、tag 不可重推(GHCR 可配不可变 tag)、清单里 image tag 带引号。
  • 拦不住的:手改集群。那要靠集群 RBAC(人不发生产集群的写权限)。光写在文档里等于没写。

6. 里程碑与进度

这一节是 AIOps 能不能自动汇总进度的唯一输入。写得马虎,报表就是假的 —— 而假报表比没有报表糟,因为它看起来是对的。

进度的算法很简单:GitHub 里程碑下 closed issue 数 / 总 issue 数。所有规则都是从这个算法倒推出来的。

6.1 每个 issue 必须挂里程碑

没挂里程碑的 issue 在进度里不存在。 不是「算成未完成」,是根本不参与计算 —— 做完十件事而进度纹丝不动,人会以为统计坏了。

6.2 每个里程碑必须有 due date,且带时区

不写截止日期的里程碑排不出优先级,日报里也没法说「还剩几天」。日期一律按 UTC+8 理解,DESIGN.md 里也这么写 —— 服务器多跑在 UTC 上,「9 点」在两边差 8 小时,而这种错不会报错,只会让报告在某天出现在一个奇怪的时间。

6.3 粒度

一个 issue = 一个能在一次 AI 会话里做完的功能点。

这条最容易被忽略,后果最直接:进度按 issue 条数算,不按工作量。把十个功能点塞进一个 issue,那个里程碑就会长期停在 0%,然后某天突然跳到 100%。中间那段时间它看起来毫无进展,而实际上一直在动。

反过来也别拆得太碎(「改一个变量名」不配一个 issue),那会让进度虚高。

6.4 别把「暂时不做」的 issue 挂在里程碑上

一条永远不会被关掉的 issue 会把这个里程碑永久压在 90%。挪到下一个里程碑,或者标 blocked 后从里程碑里摘出去。

6.5 里程碑命名

v<版本> <一句话目标>,例如 v0.2 多仓库支持

跨仓库同名里程碑很常见(需求仓库排「v2 上线」、源码仓库也叫「v2」),汇总时会出现两行几乎一样的东西。带上一句话目标能分开。

6.6 标签是路由的输入,不是装饰

AIOps 按 issue 的标签把任务派给人。标签打错 = 派错人,或者根本没人接。org 统一标签:

前缀 取值
type/ feat fix chore docs
area/ backend web app infra docs
优先级 p0 p1 p2
状态 blocked needs-design break-glass

每个 issue 至少一个 type/ 和一个 area/

怎么落地

  • type/area/ 标签由 issue 模板自动带上(.github/ISSUE_TEMPLATE/),空白 issue 入口已关掉。
  • 里程碑设不了 —— GitHub 的 issue form 没有这个字段。所以由 issue-hygiene 兜底:没挂里程碑的 issue 会被打上 needs-triage 并留一条说明,挂上之后自动摘掉。
  • 粒度和「别把不做的挂在里程碑上」靠 review,没有检查。

7. 仓库里必须有的文件

README.md          这是什么、怎么本地跑起来
DESIGN.md          要做成什么、为什么(先于代码存在)
AGENTS.md          agent 在这个仓库怎么干活
CLAUDE.md          一行 @AGENTS.md,见下
.env.example       所有环境变量,值用占位符
.org-spec.json     记录本仓库同步到了 org 规范的哪一版,见 §7.1
Dockerfile         多阶段、非 root、带健康检查端点
deploy/            按环境分目录的部署清单(或在 DESIGN.md 里指明用独立 GitOps 仓库)
rust-toolchain.toml / .nvmrc / .fvmrc
.github/workflows/ci.yml

7.1 AGENTS.md 与 CLAUDE.md 怎么接

AGENTS.md 是跨工具的事实标准(Codex、Cursor 等都读它);Claude Code 读 CLAUDE.md不要写两份,会分叉,而分叉的那份多半是漏了新规则的那份。

做法:AGENTS.md 放全部内容,CLAUDE.md 只有一行导入:

@AGENTS.md

AGENTS.md 的开头引用 org 规范:

# <项目名> — Agent 工作指南

本项目遵循 CodeLinkOps 工程规范:https://github.com/CodeLinkOps/.github/blob/main/AGENTS.md
下面只写**本仓库特有**的部分。

注意:agent 不一定能顺着链接读到内容(取决于它有没有网络和登录态)。所以 org 规范的关键条款要抄一份进各仓库的 AGENTS.md,别只留链接。本仓库的 AGENTS.md 就是为抄写准备的 —— 它是精简版,抄进去不会太长。

「抄一份」有个固有代价:这边改了,抄的那份不会跟着变。 而漂移了没有任何征兆 —— agent 会继续按旧规则干活,理直气壮。补部署规则那次就真出过:某个仓库的 DESIGN.md 里写着「不做 Kubernetes」,与新规范直接冲突,而 AI 照着它做完全不会觉得有问题。

所以有 spec-drift:每天比对本仓库的 AGENTS.mdtemplates/DESIGN.md,落后了就在那个仓库开一条 issue(附 compare 链接和该写进 .org-spec.json 的内容),跟上之后自动关闭。

故意不阻塞 PR —— 规范漂移不是紧急问题,让所有 PR 变红会逼人去无视它。

.org-spec.json 记录已同步到哪一版:

{
  "source": "CodeLinkOps/.github@main",
  "synced_commit": "<规范仓库的 commit sha>",
  "files": {
    "AGENTS.md": "<sha256>",
    "templates/DESIGN.md": "<sha256>"
  }
}

新仓库不用手写 —— 没有这个文件时第一次检查会把完整内容贴在 issue 里,复制粘贴即可。

7.2 AGENTS.md 里写什么

抄 org 规范的关键条款,加上从代码里看不出来的东西:为什么这么写、踩过什么坑、改动时容易碰坏什么。

不要写代码结构说明(那些 agent 自己能看出来,而且会过时)。写「这个字段看起来能删,但删了会……」这种。

怎么落地spec-drift 每天检查各仓库有没有跟上本仓库的改动,落后了开 issue、跟上了自动关 —— 它能发现漂移,但不阻塞 PR,实际同步仍然靠人/AI 做。「必备文件齐不齐」目前没有检查,靠 review。


8. 安全与密钥

  • 任何密钥都不许进仓库,包括测试用的、包括注释掉的、包括部署清单里的。
  • 所有环境变量在 .env.example 里列出来,值用占位符。新增一个环境变量必须同时改 .env.example —— 漏了的后果是下一个人(或下一次 AI 会话)起不来,而报错通常离原因很远。
  • 真实密钥放 GitHub Secrets、集群 Secret 或部署环境;本地放 .env.env 必须在 .gitignore 里。
  • GitHub 的 secret scanning 对私有仓库要付费,所以 CI 里跑一个免费的替代(gitleaks)。
  • 万一提交了密钥:先吊销,再清历史。顺序反了等于给攻击者留了一个窗口,而且清历史比吊销慢得多。
    • 另外要清就得删仓库重建或让 GitHub 回收 —— force push 之后那个提交仍然能按 SHA 访问,公开仓库上尤其要当真。

怎么落地gitleaks 是 CI 检查,能拦住。其余靠 review。


9. 逃生门

生产在烧、AI 用不了、或者其他任何时候,允许人直接改代码、直接改集群。规则不是用来在事故里坚守的。

但破例要留痕,三步:

  1. 分支或 PR 打 break-glass 标签(署名检查见到这个标签会放行)。
  2. PR 正文写清楚:为什么必须绕过、当时的情况、手动改了集群的哪些东西。
  3. 事后补一条 issue,说明这次破例暴露了什么问题(AI 为什么用不了?规范哪里不合理?)。

手改过集群的话还有第 4 步:把手改的内容补回 Git,否则下一次同步会把它抹掉 —— 那时故障会以「明明修好了又坏了」的形式回来。

第 3 步是这一整节存在的理由。 只有 1、2 的话,逃生门会慢慢变成正门 —— 而没人会注意到那个转变,因为每一次单独看都是合理的。

About

CodeLinkOps 工程规范 · agent 约束 · 组织级模板

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors