夜雨聆风学习资料网

ARTICLE · 1270570

AI Coding 时代的源码仓库最佳实践

AI Coding 时代的源码仓库最佳实践
AI Coding 把代码生产的成本压低了,但没有压低验证和评审的成本,也没有改变一个更基本的事实:执行者不再提问。人遇到含糊会停下来确认,Agent 会补一个看起来合理的解释,然后把它写成代码,而且语气确定。于是仓库里两件事变得致命——没写下来的意图,会被现存代码的样子自动填补;没被机器检查的约束,会在生成过程中被静默绕过。
这份文档围绕这两件事展开。它不是一份合规清单,而是一组论证:每条实践都说明它防的是什么、为什么选这个载体、缺了它会怎样。实践按"违反的代价"分成三级,因为把所有条目等权罗列,执行者最后往往只做了最容易的那几条。

0. 怎么读这篇文档

0.1 三个级别

"最佳"不等于"全部都要"。最小集是"缺了就会失控"的那部分;在它之上,是按收益与成本分层引入的做法。原因:把所有实践等权罗列,执行者无法取舍,最后往往是最容易的几条被做了,最关键的反而没做。
级别
含义
判断依据
缺失的后果
必须
最小集
违反后难以事后发现,或恢复成本高
系统逐渐失控,补救代价随时间增长
推荐
有效可行
收益明显、成本可控,可分期落地
效率与质量受损,但不至于失控
视规模
达到阈值才做
收益随规模增长,未达阈值时是负债
提前做会拖慢节奏、扩大维护面
判据是违反的代价,不是"它听起来重不重要"。一条规则如果违反了没人发现、发现了也容易改,就不该进最小集。

0.2 必须项速览

以下十三项构成最小集,缺任何一项都会产生结构性问题:
同一产品的前后端放在同一个仓库;
仓库根有 Agent 入口文件;
提供两条稳定命令:启动、验证全部;
接口契约由代码生成,不手写;
至少四道闸门:测试、构建、契约、密钥;
数据库变更只走迁移脚本,且迁移历史单头;
需求文档带状态字段;
architecture.md
带负责人与最后核对日期;
ADR 只记录单向门决策,且只追加;
一次提交一个主题;
依赖锁定文件入库;
配置与密钥分离,密钥不进仓库;
约定文件(入口、规则、配置)中不写绝对路径。

1. 原则

1.1 【必须】四条判据

判据
含义
不满足时会发生什么
单一信源
同一事实只有一处权威表述
两处说法冲突,没人知道该信谁
同仓同提交
一起变的代码在同一个提交里变
出现"两边都在主干但对不上"的窗口
可核验
约束有确定真值,且能被一条命令判定
约束退化为口号,违反时不会报错
可回滚
一次改动能用一个动作撤销
只能靠人工拼凑恢复,回滚比修复更贵
少于这个集合,问题迟早出现;多于这个集合,维护成本会超过收益。
收益:后续每条建议都可以用这四条检验——不满足的,要么补上机制,要么降级为建议,而不是靠语气强调。

1.2 歧义不再被社交吸收

人读"接口要规范",会用自己的判断补全,不确定就问一句。Agent 不问,它会给出一个看起来合理的结果,而且语气确定。
原因:人与人的协作里,含糊由沟通兜底;人与 Agent 的协作里,含糊直接变成代码。收益:把要求写成可判定的断言后,错误从"上线后发现"提前到"提交时被拦住"。

1.3 上下文是稀缺资源

Agent 每次进入仓库,都要用有限的上下文重建对项目的理解。文件越大、约定越分散,重建越慢、越容易漏。
原因:窗口大小是硬约束,不能靠"多读几遍"解决。收益:入口文件短、目录结构可预测、约定按范围分层,能让新会话在几分钟内进入可工作状态。

1.4 产出速度超过评审速度

代码生成的成本在下降,评审的成本没有。差距会变成"未评审的改动在主干上堆积"。
原因:瓶颈从"写"转移到"验",而人工评审的吞吐是固定的。收益:把机械检查交给流水线,人只审设计意图,评审注意力花在最不可替代的地方。

1.5 改动可批量生成,回滚半径必须变小

一次提示词可以改二十个文件。如果这二十个文件跨了三个主题,出问题时你无法只撤掉其中一部分。
原因:生成速度放大了"改动粒度不当"的代价。收益:一次提交一个主题,回滚半径等于一个提交,定位问题靠 git log 就够。

1.6 代码会成为事实上的规格

人读代码会带着怀疑:这里有个捷径,可能不对。Agent 不会——它从现有形态中归纳模式,然后把模式当作规范复制到新代码里,包括其中的偏差。
原因:对生成式执行者来说,"现状"是最强的先验,它不会区分这是设计还是历史遗留。收益:明确写下架构意图后,至少存在一个能与代码冲突的东西;冲突一旦出现,就有机会被发现。反之,漂移不只是可能发生,而是会被下一次生成主动放大。

2. 仓库形态

2.1 【必须】一个产品一个仓库

把同一产品的前端与后端放进同一个仓库,用顶层目录区分。
原因:一次需求经常同时改契约和消费方。两个仓库意味着两个提交、两条流水线、两种合并顺序,必然产生一个窗口:后端已合并而前端未跟进,或反过来。这个窗口里主干是坏的,而且没有单次 revert 能恢复一致——你需要 revert 两个仓库的两次提交并保证顺序。跨仓库的历史二分也无法定位"这两个提交彼此不兼容"。
收益:一致窗口为零;一个提交可以同时包含契约变更、消费方变更和两侧测试;一次评审能看到线缆的两端;出问题时一次 revert 回到一致状态。
边界:同仓不等于自动原子,还缺三件事——契约校验、一次提交覆盖两侧的规则、按路径过滤的流水线。缺了这三样,只是把两个不同步的仓库放进了同一个目录。

2.2 【视规模】分仓的正当理由

只有满足其一才考虑分仓,并且要接受原子性损失:
两侧有真正独立的发布主体(不同产品负责人、不同客户、独立发版节奏);
存在不可协调的权限边界(例如安全策略要求隔离);
单仓体积已经影响日常操作(通常要到数十 GB 级别才成立)。
原因:分仓的唯一正当理由是组织边界,不是技术偏好。用"后端同学不关心前端"当理由,代价由整个团队承担。收益:避免"因为习惯而分仓",也避免为了合并而做无意义的组织调整。

2.3 【必须】同仓之后,用路径过滤而不是拆仓

流水线按路径触发:改后端才跑后端作业,改前端才跑前端作业。
原因:同仓最常见的抱怨是"改一行前端要等全部测试"。这是流水线配置问题,不是仓库形态问题。收益:保留原子性的同时拿回反馈速度。

2.4 【视规模】不要引入构建编排工具

一个后端服务加一个前端应用——也就是两个可独立构建的单元——不需要 Nx、Turborepo、Bazel 这类工具。
原因:这类工具解决的是:几十个包之间的依赖图、任务编排、增量构建、远程缓存、统一工具链。它们的价值随包的数量增长,而不是随代码量增长。两个包没有依赖图可算,这里的"增量"就是"改后端就别跑前端",一条路径过滤就能做到。引入的代价是多一层配置、多一个需要升级的依赖、每个参与者(包括 Agent)都得先理解构建系统。
收益:少一层抽象,少一处会过期的配置,命令是明的而不是推导出来的。
什么时候才划算:前端拆出五到十个共享包(设计系统、工具库、多应用共用代码)时,轻量任务编排开始有意义;包数量到十几个且有真实依赖关系时,带依赖图的工具才有价值;更重的构建系统只在多语言、强可复现构建的大规模场景下才考虑。

3. 目录与命令

3.1 【必须】最小目录骨架

repo/├── AGENTS.md                 Agent 入口:地图、命令、铁律、禁区├── README.md                 # 人的入口:这是什么、怎么跑起来├── Makefile 或 scripts/      # 唯一的命令入口(见 3.2)├── docs/│   ├── architecture.md       # 系统现状:分层、依赖方向、主链路、改动指南│   ├── adr/                  # 架构决策记录,只追加│   ├── specs/                # 需求与能力规格,带状态字段│   ├── changes/              # 进行中的变更计划,合并后移除(见 4.9、9.5)│   ├── domain/               # 领域共同语言:业务共识,手写(见 4.7)│   │   ├── model.md          #   术语表与业务不变量│   │   └── states.md         #   业务状态机与迁移│   ├── generated/            # 生成视图,禁止手改(见 4.7、7.3)│   │   └── erd.md            #   物理数据模型,由数据库 schema 生成│   ├── guides/               # 操作手册:本地起服务、排障、发布│   └── archive/              # 过期文档,显式标注"不作依据"├── contracts/│   ├── openapi.json          # 由后端生成,不手写│   └── events.md             # 异步事件或流式消息的约定├── backend/│   ├── pyproject.toml        # 依赖、测试、检查工具的唯一配置源│   ├── app/│   │   ├── api/              # 路由与请求/响应模型│   │   ├── domain/           # 领域模型与纯逻辑(最该有测试)│   │   ├── services/         # 用例编排│   │   ├── adapters/         # 外部系统、队列、模型调用│   │   └── core/             # 配置、鉴权、错误封装│   ├── migrations/           # 数据库变更的唯一入口│   └── tests/│       ├── unit/  contract/  api/  integration/│       └── conftest.py└── frontend/    ├── package.json    ├── src/    │   ├── api/              # 与 contracts 对齐的请求层    │   ├── pages/  components/  store/  utils/    └── tests/ 或与源码同目录的 *.test.ts
原因:目录本身就是最便宜的一条约束——它让"该动哪一层"可以被看见,而不是靠记忆。收益:Agent 能通过目录名判断改动边界,从而不去改无关代码;评审时也容易发现"改错了层"。
目录树里最容易放错的是生成物:docs/domain/ 只放领域共同语言(术语、业务不变量、业务状态机),全部手写;ER 图放在 docs/generated/,因为它是从 schema 生成的物理数据模型视图,既不是业务共识,也不属于跨组件契约。生成物的目录必须与手写物分开,否则"禁止手改"这条规则没有清晰的适用边界。

3.2 【必须】两条命令原则

仓库必须提供且只提供两个稳定入口——一条启动(make dev),一条验证(make verify)。verify 依次跑完所有本地可跑的检查。
原因:Agent 每次进入仓库都要判断"该怎么验证"。如果答案散落在文档、历史提交和同事记忆里,它就会跳过验证或者猜一条。收益:会话开头不用探索,直接 make verify 拿到确定结论;人和其他工具复用同一个入口,避免"我这里能过"的争议;流水线调用同一个脚本,保证本地与 CI 的判定一致。

3.3 【推荐】按变更原因分层

api/ 因契约变化而变,domain/ 因业务规则变化而变,adapters/ 因外部系统变化而变,services/ 因用例编排变化而变。
原因:按文件类型分层(controllers、models、utils)会把一次需求拆散到各处,且无法回答"这次该动哪一层"。收益:改动的边界可预测;依赖方向可以被检查;测试可以按层定向运行。

3.4 【必须】不要建的东西

每个目录一个 README——空文档比没有更糟:它不会覆盖上级 README 的内容(README 没有继承或"就近生效"的语义),却会让读者和 Agent 认为"这个目录的文档已经补齐了",从而取消掉"该写真正文档"这个动作;
docs/design/
——设计的归宿按生命周期拆分,见 4.5;
根目录堆放临时脚本——它们会变成第二套事实入口;
utils/
里塞跨领域逻辑——它会变成垃圾抽屉,且没有归属。
原因:这些东西的共同问题是没有过期机制,而"看起来已经补齐"比"明显缺失"更难纠正。收益:仓库里每一处存在都能被一句话解释清楚。

4. 文档体系

4.1 【必须】四类文档,四种寿命

类型
回答什么问题
位置
生命周期
能否当依据
约束
必须遵守什么,谁权威
AGENTS.md
、contracts/
低变更,长期有效
是,冲突时以此为准
描述
系统现在是什么样
docs/architecture.md
、docs/domain/、docs/generated/、guides/
随代码更新
是,但滞后时以代码为准
过程
这次打算怎么改
docs/changes/
(仅进行中)
一次任务
只对当前任务有效,合并后移除
决策
为什么这样选
docs/adr/
只追加,不修改
是,用于解释现状
原因:四类文档的"过期速度"差了一个数量级。混放会让读者无法判断手上这份是当前的还是三年前的,而错误的依据比没有依据更危险。收益:看到路径就知道该不该信、该信多久;归档有明确去处,不需要靠删除来"治理"。
领域共同语言是"描述"里的特例:它跨端复用、必须唯一、且只能由业务共识产出,所以单独占一个目录,见 4.7。ER 图不在此列——它由 schema 生成,是物理数据模型的视图,归入 docs/generated/。
"Markdown 是唯一信源"只在文本类文档的范围内成立。项目里的事实各有权威载体:接口形状以代码生成的契约为准,数据库结构以迁移脚本为准,依赖版本以锁文件为准,行为以测试为准。文档写意图、边界与取舍,不复述这些能被机器读出的事实——复述一份就多一处会漂移的副本。单一信源的准确含义是"每个事实只有一处权威",不是"所有事实都写进 Markdown"。

4.2 【必须】需求文档必须带状态

每份需求或规格的开头写三行——状态、负责人、取代者(仅在已被取代时填写)。
最小状态集是三个,其余按需要再加:
状态
含义
读者该怎么用
必填字段
草稿
还在讨论,可能整体推翻
可以引用,但不能作为实现依据
负责人
生效
当前权威版本,实现与测试以它为准
作为依据;发现与代码冲突时,先确认谁过期
负责人
已被取代
曾生效,但已有继任者
只用于追溯;不得据此施工
负责人、superseded_by
可选状态:评审中(草稿与生效之间的卡点,仅在有正式评审流程时需要)、已撤回(需求取消,且没有继任者)、已归档(移入 docs/archive/,并显式标注"不作依据")。可选状态不是必填项——加上它们只在这套流程真的存在时才有意义。
迁移规则:草稿 → 评审中 → 生效;草稿 → 已撤回;生效 → 已被取代(必须填继任者);生效 → 已撤回(需求取消,无继任者)。"已被取代"与"已撤回"的区别是有没有继任者,不是语气强弱。
生效之后不要原地大改。澄清措辞、补例子可以;改变已验收的行为、改写验收标准,应当产生一份新文档,把旧文档标为"已被取代"并填写 superseded_by。原因:实现和测试已经按旧标准写下,原地改写会让"文档说的"和"代码做的"同时存在两套历史,而且没有提交能说明哪一套先发生。小步澄清与行为变更的分界,就看验收标准是否改变。
实现进度不是文档状态。"未开始 / 进行中 / 已验收 / 已上线"变化以天计,属于任务跟踪系统(issue、看板);文档状态变化以月计,回答的是"这份文件还算不算数"。两者混在一个字段里,读者就无法区分"已上线但已过时"和"生效但还没做",状态字段也就失去了筛选作用。
原因:需求文档往往多代并存,且文件名不表达代际关系。没有状态字段时,读者只能按修改时间猜,而"最近改的"不等于"现在生效的"。收益:消除"哪份算数"的猜测;废弃时不必删除,只需改一行状态并指明继任者,历史可追溯;Agent 在入口处就能排除掉不该读的版本。

4.3 【必须】验收标准要能落到检查上

需求里的每条验收标准,要么对应一个自动化测试,要么对应 docs/guides/ 里一段可复现的手工步骤。两者都做不到的条目,明确标为"不验收"。
原因:不可核验的验收标准在实现阶段会被自由解释,最后变成"看起来做了"。收益:需求、实现、验证三者可以对照;需求变更时,能立刻看出哪些测试要跟着改。

4.4 【必须】文本格式,交付物用生成

规格、设计、决策用 Markdown 入库;对外交付的 docx/PDF 由脚本生成。生成物不进入人工评审——人评审的是 Markdown 源和生成脚本。
原因:二进制文件无法 diff、无法行内评论、合并时只能二选一。而"生成物"与"源文件"同时存在时,人们往往会改错那一份。收益:变更可审查、可追溯;交付格式变化时只改脚本,内容不会分叉。
"不参与评审"的准确边界:它指不作为人工评审对象,不是不参与任何验证。流水线仍要验证两件事——能从源重新生成,且生成结果与提交的版本一致;交付前再由人抽查一次排版、分页与图表渲染。分工是:人看内容与脚本(错误源头),机器看可重复性(生成过程),交付时人看成品(渲染问题)。三者缺一,都会出现"源是对的、发出去的是错的"。
例外:若 docx/PDF 本身是唯一法定载体(需要签章、监管报送、对外具有法律效力的文件),它就不是生成物,必须作为正式文件单独评审、单独存档,不能套用这一条。
图:用 Mermaid 写在 Markdown 里,或保留 SVG 源文件。只留导出的 PNG,等于把设计变成了不可编辑的截图。

4.5 【推荐】设计按生命周期拆成三份

设计空间并没有因为 AI 而消失,只是变集中了。表单、列表、CRUD 这些地方,需求加契约已经把可能性压得很窄,直接写代码是合理的;而数据模型、状态机、模块边界、并发语义、迁移路径这些地方,决策空间原封不动,只是数量少——风险全部堆在那里。
所以正确的做法是"少量高密度设计 + 大量无设计直接实现",不是"没有设计"。
设计种类
回答什么
去处
寿命
变更设计
这次怎么做
docs/changes/
,合并后移除
一次任务
结构描述
现在怎么分层
docs/architecture.md
随代码更新
技术决策
为什么这么选
docs/adr/
只追加
收益:设计成本与风险成正比——高密度区得到评审,低密度区不被形式拖慢;变更设计不进入长期文档,结构描述与决策记录各自只维护一次。
为什么不要一个docs/design/:单独的设计目录没有更新触发点,半年后全是过期图,而读者以为那是现状。设计不是一种文档,而是三种寿命不同的东西,混放就无法标注"这份还准不准"。
谁产出与谁批准是两件事:让 AI 写设计没问题,但如果它从规格直接跳到代码,设计决策就被埋进实现,只有写完后才可评审,而那时推翻很贵。高密度区要在代码之前可评审,低密度区代码本身即设计。

4.6 【必须】architecture.md:现状的唯一描述

它回答五件事:系统边界在哪、怎么分层、谁可以依赖谁、请求怎么流、新东西该放哪。
为什么它是必需而不是可选:没有它不等于没有架构模型,只等于模型不可证伪。模型仍然存在——活在代码的隐含模式里、活在"我们一直这么做"里。过期的文档至少提供争论的入口:有人能把它和代码对照,然后说这里不对。而只存在于头脑中的架构,连这句话都没地方说——它不会错,因为它从未被表述;它也不会被修正,同理。
更关键的是 1.6 那条:对 Agent 来说,代码就是事实上的规格。你不写意图,先验就自动取值为"现存代码的样子",瑕疵一并继承。
它的风险是静默过期,而这不是省略的理由。架构很少变,但漂移的来源不是架构变了,而是代码变了、架构没变——文档宣称存在干净的约束,而代码里没有。低变更频率反而让偏离更难被发现,因为没有"更新事件"来触发审查。
三个触发点,把"静默"变成"响铃":
结构变更时强制更新
:新增模块、新增跨层依赖、新增外部集成、改变请求主链路,这四种改动必须同时改文档,否则评审不通过;
检查失败时指向文档
:让依赖规则的报错消息直接写明"违反第 2 节,若为有意变更请更新文档并补 ADR"。文档因此是被调用的,而不是靠自觉维护;
文件头写"最后核对日期 + 负责人"
:日期不是追责,是让读者知道该给予多少信任。
收益:新会话与评审者有一个可被反驳的现状模型;偏离一旦出现,有触发点和报错指向它,而不是被下一次生成当作规范复制。
写法:两页以内,只写已在主干上成立的东西——混入目标态就无法区分已实现与计划。五节即可:上下文与部署拓扑;分层与依赖方向(含由什么检查强制);请求主链路;结构不变量;改动指南(加一个接口、加一个页面、加一个外部集成分别动哪里)。最后一节对执行者价值最高:它把"该动哪一层"从推理变成查表。
一份还是多份:根目录一页写跨端上下文与调用方向;每个可部署单元一份写内部结构。分界是"读者是否需要同时理解两侧"。

4.7 【推荐】领域语言手写,ER 图生成

把术语表、状态机、ER 图统称为"领域模型"并放在同一个目录,是一个常见错误。领域共同语言由业务共识产出,ER 图由 schema 产出;前者生成不出后者,后者也表达不了前者。
产物
回答什么
源是什么
生成还是手写
位置
术语表与不变量(model.md)
一个词在业务上指什么,什么条件必须永远成立
团队共识
手写
docs/domain/
业务状态机(states.md)
业务上允许哪些状态、哪些迁移合法
业务规则
手写,Mermaid 表达
docs/domain/
ER 图(erd.md)
物理上有哪些表、列、外键、索引
数据库 schema
生成
,禁止手改
docs/generated/
为什么必须分开:领域语言回答"订单的'已锁定'是什么意思、哪些状态迁移合法",它不描述表名、列名、外键;从 schema 里读不出这些含义。反过来,ER 图回答"现在库里实际有什么",它不解释业务语义。把两者放在一个目录、一个名字下,后果是有人会以为其中一个能生成另一个——要么把业务术语硬塞进注释冒充领域语言,要么手改生成的图去"补充业务含义"。
为什么 ER 图不放进contracts/:contracts/ 的判据是"跨组件边界 + 被机器消费"(前端按 openapi.json 生成类型、按事件约定解析消息)。ER 图是给人看的现状视图,前端构建不消费它。多个服务共享一个库,属于数据所有权问题,应在 architecture.md 或 ADR 里说明,而不是把 ER 图升级成契约。
它和architecture.md的分工:architecture.md 写分层、边界、依赖方向与"为什么这样设计";ER 图只写"物理结构现在是什么"。数据模型的取舍理由属于前者或 ADR,ER 图不承载意图。
判断是否散落的标准:改一处 schema,需要手工同步几个地方?答案超过一,就说明有视图被当成了源。图是视图,不是源——手绘的 ER 图在第一次加字段时就已经错了,而且错得没人发现。这条标准只适用于 ER 图这类生成视图:领域语言本来就不随加字段而变,它不变是正确的,不是失同步。
原因:手绘图没有任何机制保证与实现同步,而错误的数据结构视图会直接导致错误的查询和错误的迁移。收益:schema 变更必然带动图变更,流水线校验"重新生成后无 diff"(见 7.3);术语表保证前后端用同一套词,而不是各自翻译;两者分开后,各自的更新触发点都很清楚。

4.8 【必须】ADR:只记有替代方案的决策

为什么只追加:ADR 的价值是保留当时的判断现场。允许改写,就无法区分"我们改主意了"和"我们一直这么想"——前者说明约束变了,值得重新审视;后者只是在掩盖摇摆。Git 历史理论上有痕,但读者读的是当前文件,不是 git log。改主意就写新的一条,把旧的状态标为"已被取代"并链接过去。
边界:草稿可以随便改,只有"已接受"之后才只追加;ADR 只记决策,不记实现细节;后果一节必须写清"放弃了什么",否则它退化成宣传稿。
它不是架构元素清单。逐个元素写,得到的是清单,那属于 architecture.md 的职责,而且需要持续维护。ADR 记录的是存在过替代方案、并且选择有代价的点——一个 ADR 常常覆盖多个元素。正常项目几年下来十几到几十条;如果是几百条,说明把"记录"当成了"清单"。
筛子:单向门与双向门。
双向门(可逆、代价低):选哪个 lint 工具、日志库、目录叫 utils 还是 shared——不需要 ADR,改回来就是。
单向门(不可逆或逆转昂贵):数据模型、鉴权模型、部署拓扑、契约风格、供应商锁定——必须有记录。
怎么判断一扇门是不是单向:问四个问题,任一为"是"就按单向门处理。
撤销时会不会丢数据?
删字段、改主键、合并表、改变字段语义(例如金额单位从元改成分)——已经写入的旧数据无法还原,或者还原需要一次专门的数据修复。
撤销是否要同时改外部系统?
已发布的接口、对方已接入的回调、第三方账号体系、客户侧的导出格式——回滚时机不完全由你控制。
能否让新旧两版同时运行?
能双写、能灰度、能按开关回退,多半是双向门;只能整体切换、切换瞬间只有一种版本可用,就是单向门。
回滚需要多久、需要谁?
一个提交就能撤,是双向门;需要停机窗口、数据修复、客户通知和多方排期,就是单向门。
"重要"不是判据。选主数据库听起来很重,但若能双写迁移、能从备份重建,它可能只是一扇比较贵的双向门;反过来,一个不起眼的字段语义变更(时间戳从本地时区改为 UTC)一旦写进历史数据就不可逆,它就是单向门。判据是撤销的成本,不是决策的分量。
门型会随时间和影响范围改变。同一个决策在项目早期可逆(没有数据、没有外部消费者),上线后不可逆;只影响一个模块时可逆,一旦成为跨团队契约就不可逆。所以评估要按"现在的影响范围"做,并把评估时的范围写进 ADR——否则后人无法判断这条记录是否仍然适用。
ADR 至少写四样:当时有哪些备选;为什么选它、放弃了什么;逆转成本高在哪一步;什么信号出现时应该重新评估(数据量、调用方数量、供应商涨价、延迟指标越过某条线)。最后一项让 ADR 从"墓志铭"变成"触发器"。
"改干净了就不用记"这个想法只对了一半。以更换数据库为例:迁移完成、验证通过、没有残留,历史确实可以不写长。但有三件事在最终状态里看不见——触发原因(不记下来,十八个月后会有人提议换回去,而没人说得出当初为什么走)、迁移中的语义损失(类型映射、排序规则、空值语义、时间精度,迁移后完全不可见,却会以偶发缺陷回来)、以及"验证完善"的范围(这本身是个需要被记录的主张,没写就等于默认全都验过)。
ADR 的价值不在保存历史,而在降低下一次同类决策的成本。后来者能区分"改主意"与"一直如此",也能看到迁移中那些在最终状态里消失的语义损失。

4.9 【推荐】计划合并时:先抽取,残值进提交

计划是过程文档,寿命等于一次变更。合并时先做一次抽取:文档里任何一条判断,如果没有被代码、契约、规格、ADR 或测试接住,就内联写进对应载体——取舍与备选进 ADR,需求变化进 specs,结构变化进 architecture.md,不变量进测试。长期文档必须自足,不依赖指向计划的指针;指针只是"想深挖再看这里"的便利项。
抽取完成后,计划不再承载任何唯一价值,于是树里不留它。原则是:树存现状,历史存变更;计划描述的是"这次变更打算怎么发生",天然属于历史。它的残值(判断现场)写进合并提交的 message body:意图、关键取舍、被否的备选、回滚方式、更新了哪些长期文档。提交消息是最可靠的归宿——与变更永久绑定、随 clone 传播、在 log 与 blame 里可见,而且永远不会冒充现状。
若计划太长不适合塞进 message:用 merge commit 保留分支历史(不要 squash),计划文件的增删仍在历史中可查;或用 git notes 给提交挂附件,但 notes 默认不随 fetch/clone 传播、多数工具不显示,只有团队愿意维护传播时才用。
原因:过程文档是意图快照,需求一变即失效且不会自动作废;留在 HEAD 的过期计划会被 Agent 当成现状依据,比没有计划更糟。收益:HEAD 只留现状与长期文档;考古入口收敛到 git log;长期文档自足,不随计划删留而残缺。
提交 message 骨架:
feat(order): 支持订单取消意图:允许未发货订单自助取消,释放库存。取舍:选同步释放而非异步补偿,因为库存服务已有幂等接口;被否:事件驱动(引入最终一致复杂度,收益不抵)。回滚:revert 本提交;迁移 0042 提供 downgrade。文档:architecture.md §3、specs/order.md、ADR-0017 已同步。

5. 把要求变成可核验的

5.1 【必须】三个层次

"可执行优于可阅读"容易被误读成"什么都写成测试"。更准确的三层是:
可运行
——跑一条命令得到通过/失败:测试、类型检查、lint、schema 校验;
可核验
——不一定能跑,但有确定真值,能被脚本或 diff 判定;
可操作
——拿到就能动手,不需要追问:确切路径、确切命令、确切分支条件。
核心动作是:把一个需要解释的形容词,换成一句有确定真值的断言,再配一条能验证它的命令。
收益:一条要求该写成测试、写成脚本检查,还是写成可操作步骤,不再靠感觉;"可执行"也不会被误读为"必须有测试"。

5.2 【推荐】规则的载体有强弱之分

载体
违反时会发生什么
绕过成本
适合承载
类型系统 / 数据模型
编译或校验不通过
无法绕过
结构、必填、枚举、跨端形状
数据库约束
写不进去
必须改迁移
唯一性、引用完整性、状态合法域
代码结构 / 框架机制
编译失败或直接跑不起来
需要重构
依赖方向、模块边界、扩展点
合并前流水线
合不进主干
需要管理员权限
测试、契约、构建、迁移
提交钩子
本地就拦住
可 --no-verify
格式、快速检查
lint 规则
红字或警告
加一行 disable
风格、局部禁止项
文档
什么都不会发生
零成本
意图、取舍、不可检查的边界
推论:规则的强度应该匹配"违反的代价",而不是匹配"它有多重要"。违反代价高且难以事后发现的,放到最上面几层;违反代价低、改回来很容易的,写在文档里就够了。把所有重要的事都塞进流水线,会让每次改动的摩擦大到人们开始绕过它——那本身就是一种失败。
容易被忽略的一个载体是数据库约束:唯一索引、外键、检查约束表达的是"系统中不可能出现的状态",比任何文档都可靠,而且不需要维护。

5.3 后端示例

模糊要求
可核验的表述
验证方式
接口要规范
每个路由必须声明响应模型
扫描 OpenAPI,缺少即失败
数据库改动要可控
schema 只通过迁移脚本变更,且迁移图为单头
空库执行升级 + 检查迁移头唯一
错误返回要统一
所有错误响应使用同一 envelope
契约测试断言错误结构
代码要整洁
静态检查零错误
检查工具,存量设基线

5.4 前端示例

模糊要求
可核验的表述
验证方式
类型要安全
类型检查通过,且禁止隐式 any
类型检查 + lint 规则
接口要对得上
请求/响应类型由 contracts/openapi.json 生成
生成后 diff 为空
核心逻辑要可靠
纯函数(转换、格式化、校验)有边界用例
单元测试
演示数据不能混入
模拟数据只在明确的开发开关下启用
构建时校验环境变量

5.5 【推荐】不可核验的怎么写

"为什么选 A 不选 B"没有真值,只有权衡。这类内容写成 ADR:背景、决策、后果、备选。
原因:强行把它做成测试,会得到编码了偶然行为的脆弱测试,一重构就红,最终被集体忽略。收益:决策有据可查,且不会伪装成必须通过的检查。

5.6 【推荐】生成测试与手写测试的分工

自动生成的测试擅长锁定当前行为(防回退),不擅长表达应有行为(防误解)。它能告诉你"改这里会破坏什么",不能告诉你"这里本来该是什么"。
原因:生成测试的输入是现有实现,它天然把现状当作正确。收益:生成测试铺覆盖面,少量手写测试守住不变量与业务规则。全部依赖生成,得到的只是"当前实现被永久固化",包括其中的错误。

5.7 【推荐】检查循环的前提

以"检查通过"为终止条件的执行循环,是收敛还是震荡,取决于检查的质量。检查太弱,它会学会绕过去——删断言、加跳过、把类型放宽;检查太慢,循环的成本高到没人用;检查有误报,它开始怀疑检查而不是修代码。
原因:循环的终止条件是检查,而不是人的判断,所以检查的质量直接成为产出质量的上界。收益:这类循环要配两样东西才可靠——合并层不可绕过的检查,以及 diff 评审。因为"变绿"和"修好"永远是两件事。

6. 闸门

6.1 【必须】最小四道 + 【推荐】完整七道

闸门不必一次装齐,但要区分哪些可以推迟:最小集是四道,其余三道按风险补。
#
作业
级别
触发路径
命令
失败意味着
1
测试
必须
两侧
测试套件
行为回归
2
构建
必须
两侧
生产构建 / 应用可导入
不可交付
3
契约无漂移
必须
契约与两侧 API 目录
重新生成后 diff 为空
两侧对不上
4
密钥扫描
必须
全仓
扫描
凭据进入历史,不可逆
5
类型检查
推荐
前端(后端可选)
类型检查
契约与用法不一致
6
迁移可执行
推荐
后端
空库升级至最新 + 迁移头唯一
新环境起不来
7
lint
推荐
两侧
检查工具,存量设基线
风格与局部规则失守
原因:这四项覆盖"行为对不对、能不能交付、两侧对不对得上、有没有泄密"。再少的项会留下无法承受的盲区;5 到 7 项的价值随团队规模上升,可以在最小集稳定后再加。收益:主干的可发布性由机器保证,人只需要判断设计是否合理。

6.2 【推荐】lint 用基线,不要一次修完

如果存量已有错误,先记录当前数量为上限,流水线只拦"新增"。
原因:一次性清零成本高、周期长,而在这期间完全没有保护,是最差的组合。收益:闸门当天就能生效;存量随重构自然消化;新代码不再增加负债。

6.3 【必须】没有流水线时的退化方案

至少提供一个 verify 脚本,并在提交前自动执行。
原因:闸门的价值在于"不可绕过"。没有流水线时,本地钩子是次优但有效的替代。收益:在引入流水线之前问题就不会流入主干;脚本本身可直接复用为流水线作业。

6.4 【推荐】分支保护与合并队列

主干应禁止直接推送,合并前必须通过闸门;多人同时合并时启用合并队列,避免"各自通过、合起来坏"。
原因:闸门只在"无法绕过"时才有效;合并队列解决的是并发合并导致的组合回归。收益:主干始终处于可发布状态;不再需要人工盯"谁先合"。

7. 契约与生成物

7.1 【必须】接口契约一处生成,两处消费

后端框架能自动产出接口描述(FastAPI 天然提供),就把它导出为仓库内的契约文件;前端类型由此生成;流水线校验生成结果与提交内容一致。手写接口文档一律不做。
原因:手写文档必然与实现背离,因为没有任何机制强制它们同时更新。而背离的接口文档会让消费方按错误假设开发,错误在集成时才暴露。收益:契约不可能是错的——它与代码同源同提交;前端类型无需人工维护;接口变更会在合并前同时暴露两侧影响。

7.2 【必须】数据库变更只走迁移

schema 变更只通过迁移脚本,且迁移历史必须是一条线(单头)。
"单头"是什么:每个迁移都记录自己的前驱(Alembic 里是 down_revision)。把迁移按前驱关系连成图,没有后继的那个迁移就是头(head)。正常情况只有一个头,所有迁移串成一条线。若两个人从同一个旧迁移各自新增一个迁移再合并,就会出现两个头——两条线都声称自己是最新,部署时只执行其中一条,另一条里的表结构在目标环境根本不存在。
怎么检查:alembic heads 只应列出一个;alembic history 应是一条没有分叉的线。出现多头时,用一个空的合并迁移把它们接起来(alembic merge -m "merge heads" ),而不是删掉其中一个——删掉会让已经执行过它的环境与迁移记录对不上。流水线还应从空库执行一次 alembic upgrade head:单头只证明图是连通的,空库升级才证明整条线能跑通。
原因:手工改表会让本地、测试、生产三套结构悄悄分叉。而多头的迁移历史意味着两个人的变更互不知情,部署顺序决定了结果。收益:任何环境都能从空库重建出一致结构,部署失败可定位到具体迁移;多头在合并前就被拦住,而不是等到发布日才发现。

7.3 【推荐】类型与 ER 同样是生成视图

前端类型从 contracts/openapi.json 生成;ER 图从 schema 生成到 docs/generated/erd.md;二者都加入"重新生成后无 diff"的校验。
这条校验具体做什么:在干净工作区运行生成命令,然后对生成物执行 git diff --exit-code。diff 为空则通过;非空说明提交的生成物与当前源不一致——要么源改了忘了重新生成,要么有人手改了生成物。它不判断"内容是否合理",只判断"是不是由当前源生成的那一份"。
它成立的三个前提:
生成物已入库
——否则没有可比对的对象;
生成是确定性的
——同样的输入必须得到逐字节相同的输出。生成器固定版本、字段与条目稳定排序、不写时间戳、不写绝对路径、固定换行符与 locale。任何一处不确定,diff 就会长期为红,这条检查随即被人为关闭;
生成命令只有一条
,本地与流水线调用同一个。
make gen    生成契约、前端类型、ER 图git diff --exit-code -- contracts/openapi.json frontend/src/api/types.gen.ts docs/generated/erd.md
确定性本身只让差异可见,不阻止修改。完整链条有四段:生成确定 → 生成物入库 → 流水线对 diff 判失败 → 分支保护使这个检查不可绕过。少了最后一段,本地改完生成物可以直接推送;少了第二段,流水线没有基线可比。它防的是"无意的漂移"和"顺手改生成物",不防恶意篡改——有写权限的人总能改源或改生成器,那要靠评审与权限控制。
它抓不到什么:生成器升级导致的整体格式变化(会一次性产生大量 diff,应当作为独立提交);源本身写错(生成物会忠实地跟着错);生成物被路径过滤排除在流水线之外(检查根本没跑)。所以路径过滤规则必须覆盖生成器脚本与源定义,而不只是生成物本身。
原因:凡是"同一事实的第二个表述",只要靠手工同步,就一定会漂移。收益:图与类型永远不是"另一份需要维护的文档";diff 一旦出现,问题在合并前暴露,而不是在消费方集成时才发现。

7.4 【推荐】测试库与生产同引擎

原因:方言差异(类型、约束、函数、并发行为)会把问题藏到上线。收益:迁移和关键查询在上线前就经过真实引擎验证。

8. Agent 入口文件

8.1 【必须】只写五类内容

仓库根放一个入口文件,长度控制在 150 行以内,只写:
仓库地图:三五个目录,各自一句话职责;
命令:启动、验证、以及单层测试怎么跑;
文档权威表:哪份是权威、哪份是参考、哪份已废弃;
铁律:不超过七条,每条必须可核验;
禁区:明确不能做的事(写凭据、改无关代码、把演示当已交付等)。
原因:入口文件是每次会话唯一必读的文件,它越长,单位篇幅的价值越低;而把会过期的信息放进去,会让它整体失信——一旦发现有一条过时,读者就会怀疑全部。收益:新会话能在几分钟内定位到正确位置;约束集中在一处,不会散落在各工具的私有配置里。

8.2 【必须】它是渐进式披露的起点

披露路径是三层:根文件(全局)→ 每个可部署单元一份(命令与铁律不同)→ 代码与文档。冲突时"离被修改文件最近的那份优先",所以模块级文件可以覆盖根文件的局部约定。再往下按功能目录铺通常是浪费——部分工具默认不读嵌套文件,稳妥的上限就是三层。
原因:上下文有限,必须让"必读"和"按需读"分层。收益:常见任务只读根文件即可开工,边缘任务才深入模块文件。

8.3 【必须】文件名优先用 AGENTS.md

它已是跨工具的开放约定,多数编码工具会从仓库根自动读取。有些工具仍以自己的文件名为准(如 CLAUDE.md、.cursor/rules/*.mdc),给它们放一行指针即可。只维护一份内容源,不要复制——复制的那一刻就开始漂移。
原因:多份真相同步没有机制保障。收益:换工具不用重写约定;一处修改处处生效。

8.4 【必须】明确不写什么

完整目录树、依赖清单、最近改了什么、通用编程建议、以及"要写高质量代码"这类无法核验的话。
原因:前四项可以直接读出来或必然过期,最后一项不改变任何行为。收益:文件保持短小,每一条都真的被使用。

9. 日常协作

前八章解决"仓库长什么样",这一章解决"每天怎么用它"。漂移往往发生在日常操作里,而不是架构评审里。

9.1 【必须】一次提交一个主题

原因:生成速度让一次改动可以横跨多个主题;混合提交无法单独回滚,也无法用作二分定位的单元。收益:回滚半径等于一个提交;出问题时 git log 直接指向原因;评审者能一次看完一个完整意图。

9.2 【必须】依赖锁定

锁文件必须入库;安装依赖时必须使用锁定模式(而非"解析最新兼容版本");升级依赖是一个显式提交,不是安装时的副产品。
"依赖锁定文件入库"是什么意思:依赖有两层表述。第一层是声明——pyproject.toml / package.json 里写"需要某库不低于某版本"或"允许某大版本内的更新",它给的是一个范围;第二层是解析结果——uv.lock / poetry.lock / package-lock.json / pnpm-lock.yaml,它记录这次实际选中了哪个版本、每个传递依赖选了哪个版本,通常还带哈希。入库的是第二层。只入库声明,等于每次安装都重新求一次解,两台机器、两个时间点就可能得到不同结果。
安装必须走锁定模式:uv sync --locked、npm ci、pnpm install --frozen-lockfile。普通 install 在声明与锁文件不一致时会顺手更新锁文件,把"环境不一致"变成"看起来装好了"。
升级是一个独立提交:锁文件的变更应当只包含升级本身,提交信息写明升了什么、为什么。这样出问题时能单独回滚,也能用二分定位"是哪次升级引入的"。安装时被动产生的锁文件变化,不要混进功能提交。
应用与库的边界:可部署的应用必须提交锁文件;要发布给别人安装的库通常不提交(或只保留一份供 CI 用),因为库的依赖范围必须由使用方求解,把解析结果强加给下游会制造冲突。
只锁直接依赖不够:传递依赖同样会变,锁文件的价值恰恰在于固定整张依赖图,而不只是顶层几行。
原因:没有锁文件时,同一天在两台机器上装出的依赖可能不同;Agent 尤其容易在"能跑就行"的目标下拉进新版本。可复现构建是一切验证的前提——本地过、CI 不过,多数时候是依赖差异。收益:本地、CI、生产三处一致;问题可复现;升级的影响范围是一个可以回滚的提交。

9.3 【必须】配置与密钥分离

仓库里只放 .env.example(键名 + 说明 + 安全默认值),真实值来自环境变量或密钥管理;应用启动时校验必需变量,缺失即失败,而不是使用隐藏默认值。
原因:密钥进入 Git 历史是不可逆的;而"缺失时悄悄用默认值"会让错误配置一路跑到生产才发现。收益:新环境十分钟内可起;配置错误在启动时就暴露;密钥泄露面收敛到运行环境。

9.4 【推荐】测试分层与定向运行

按 unit / contract / api / integration 分层,并提供"只跑某一层"的命令。
原因:全量测试一旦变慢,执行者(人和 Agent)就会跳过它;而不同层的失败含义完全不同,混在一起无法快速定位。收益:日常反馈在秒级,提交前跑全量;失败信息直接指向"逻辑错"还是"接口错"。

9.5 【推荐】高密度改动先写实施计划

触及数据模型、鉴权、跨模块边界、外部依赖、并发语义的改动,先写一页实施计划再动代码;其余改动直接做。计划放在 docs/changes/<日期>-.md,一个变更一个文件,目录里只放进行中的变更。
它在 AI 协作里的角色是外部记忆。大改动跨多个会话,而 Agent 没有跨会话记忆;计划是每个会话恢复上下文的唯一依据——同一意图、同一边界、同一剩余步骤。没有它,每个会话各自推导意图,推导结果还会彼此漂移。
一页内写六样:意图与明确不做什么;触及哪些长期载体(specs、契约、迁移、architecture.md 的哪一节);分解成原子步骤,每步配一条验证命令;回滚方式;完成定义;收尾时要更新哪些长期文档。最后一项保证变更结束时文档更新不被遗忘。
进行中的修改规则:步骤勾选可以原地更新,那是过程状态,不是事实;但方案本身若变(换设计、换边界),不要静默改写,在计划里追加一条决策记录——为什么换、放弃了什么,否则后续会话会把被否的方案再提一遍。若这个变化其实是单向门决策,升级为 ADR,而不是留在计划里。
边界:计划引用 specs、ADR、architecture.md,不复述它们;一旦开始复述需求或架构,说明它在漂移,该删的是复述部分。合并时的抽取与归宿见 4.9。
原因:这些地方的决策一旦埋进实现,只有写完才能评审,而那时推翻很贵。收益:在成本最低时对齐方向;跨会话拿到同一意图;计划不进入长期文档树,不会冒充现状。

9.6 【推荐】评审只看意图与风险

机械问题交给闸门,人的注意力放在四问:意图是否清楚?边界是否被跨过?失败路径是否处理?有没有引入新的隐式约定?
原因:评审是唯一不可自动化的环节,注意力必须花在机器判断不了的地方。收益:评审时间下降,结论更稳定;不会出现"格式争论三小时、设计问题没人看"。

9.7 【推荐】定期回滚演练

每季度挑一次最近发布,在预发环境真实走一遍回滚;数据库迁移要么提供可执行的降级,要么明确声明不可逆并配套备份策略。
原因:回滚路径平时不走,真出事时才发现它不可用——迁移不可逆、配置有顺序依赖、旧版本读不懂新数据。收益:把"可回滚"从假设变成已验证的事实;迁移设计阶段就会考虑不可逆的代价。

9.8 【视规模】提交模板与变更日志

团队超过五人,或需要对外发版时,再统一提交格式并自动生成变更日志。
原因:格式统一的价值随人数增长;两人团队手工记录更快。收益:发版说明可自动生成,追溯成本下降。

10. 【必须】落地顺序

顺序本身有讲究:先让现状可信,再装自动闸门,最后才写约定。反过来做,约定会写在流沙上。
步骤
动作
完成信号
为什么在这个位置
1
止血:让测试与构建恢复通过
验证命令返回成功
基线是红的,后面所有检查都没有参照系
2
装闸门:最小四道;有平台能力时同时开分支保护
故意提交一个失败用例,被拦住
先把"不可绕过"建立起来,再谈内容
3
写入口文件、架构现状与文档地图
新会话仅凭入口文件即可开工
此时路径与命令已稳定,不会写完就失效
4
补测试,接通契约生成校验(ER 生成可一并做)
核心逻辑有手写测试,契约重新生成后无 diff
前一步保证了新增检查会持续被执行
顺序上的一个陷阱:不要在仓库结构调整(例如合并前后端、移动目录)之前写入口文件与流水线,否则路径要改两遍,而失效路径比没有路径更糟——它看起来像权威。

11. 反模式

做法
为什么不好
后果
约定散落在各工具的私有配置文件里
换一个执行者就读不到,且容易写出只有某工具认识的路径
约定形同不存在,且互相矛盾
在约定文件里写绝对路径
换机器即失效
看起来权威、实际误导
把任务清单当规格
任务清单描述意图,需求一变即失效,且不会自动作废
按过期计划施工
手写接口字段文档
没有机制保证与代码同步
消费方按错误形状开发
全量修完 lint 才开闸
周期长,期间零保护
负债继续增长,闸门永远开不了
每个目录一个 README
内容空洞,且会被当作文档已补齐
真正的信息反而找不到
为每个架构元素写一条 ADR
得到的是清单,需要持续维护
没人再看,决策记录失效
手绘 ER 图或数据模型图
第一次加字段就与 schema 不符
图变成误导,且没人发现
把 docs/design/ 当现状依据
该目录没有更新触发点
按过期设计施工
只靠生成测试
生成测试编码的是现有实现
错误被永久固化
二进制依赖产物入库且不标注来源
无法 diff、无法复现
回滚时不知道该回滚什么
一次提交混合多个主题
无法单独回滚
修复成本高于重写
用覆盖率数字当质量目标
覆盖率可以被无意义的断言刷高
测试数量上升,回归漏检率不变
为两个包引入构建编排框架
解决的是不存在的规模问题
多一层会过期的配置
无锁文件安装依赖
同一天两台机器装出不同版本
"本地能过、CI 不过"成为常态
缺失配置用隐藏默认值
错误配置一路跑到生产才暴露
故障难以定位,且默认值本身就是隐式约定

12. 【视规模】什么时候才加东西

"最佳"不等于一次装完。下面是明确的升级阈值,达到再加,避免提前设计。
新增项
触发条件
原因
覆盖率门槛
核心模块已有稳定测试,且覆盖率长期不降
门槛的作用是防倒退,不是催生测试
端到端测试
出现跨越多层、单元测试覆盖不到的关键链路
补的是层间集成盲区
独立仓库
出现独立发布主体或不可协调的权限边界
原子性损失必须有正当理由
构建编排工具
包数量达到十几个且存在真实依赖图
两点之间的构建不值得编排
文档站点
文档读者超出仓库参与者
站点的成本只在有外部读者时才划算
多环境配置矩阵
环境间差异已多到无法用少量变量表达
过早抽象会让简单问题变复杂
提交模板与变更日志
团队超过五人,或需要对外发版
格式统一的价值随人数增长
合并队列
多人频繁同时合并主干
解决并发合并的组合回归
回滚演练机制化
有生产环境与不可逆迁移
回滚能力需要被验证,而不是假设

附录 A:检查清单

A.1 必须(最小集)

前后端在同一仓库,顶层按 backend/、frontend/ 分开
两条命令:一条启动、一条验证全部,且本地与流水线执行同一套检查
至少四道闸门:测试、构建、契约、密钥
接口契约由代码生成,手写接口文档已消除
数据库变更只走迁移,空库可从零重建,迁移单头
需求文档带状态字段,废弃时标注继任者
docs/architecture.md
存在,带负责人与最后核对日期,含"改动指南"
ADR 只记录单向门决策,只追加不修改
入口文件不超过 150 行,只写地图、命令、权威表、铁律、禁区
约定文件中没有绝对路径
一次提交一个主题,任意提交可单独回滚
锁文件入库,安装使用锁定模式
.env.example
齐全,密钥不进仓库,缺失配置启动即失败

A.2 推荐(有效可行)

以下推荐项按收益排序,可分批落地:
分层与依赖方向由检查强制(如 import 规则),而不只写在文档里
领域共同语言(术语、不变量、业务状态机)集中在 docs/domain/,全部手写
ER 图由 schema 生成到 docs/generated/erd.md,校验重新生成后无 diff
前端类型由 contracts/openapi.json 生成
设计按生命周期三分,不存在 docs/design/ 坟场
生成测试 + 少量手写不变量测试并存
测试分层:unit / contract / api / integration,可定向运行
高密度改动先写一页实施计划(docs/changes/),合并时抽取结论、残值进提交 message
lint 有基线,只拦新增
分支保护开启,闸门不可绕过
每个季度做一次回滚演练

附录 B:入口文件骨架

工程约定## 仓库地图| 路径 | 职责 | 技术栈 || --- | --- | --- |## 命令- 启动:make dev- 验证:make verify- 单层测试:make test-unit / make test-api## 文档权威表| 路径 | 内容 | 状态 || --- | --- | --- |## 铁律1. 新接口必须声明响应模型2. 数据库变更只走迁移脚本3. 一次提交一个主题,不改无关代码4. 契约由代码生成,不手写5. 不写凭据,不读凭据6. 演示数据不得进入正式链路7. 诊断类需求只输出结论,不改代码## 禁区- 不修改生成物- 不在仓库内保存密钥附录 C:ADR 模板# NNNN. 标题- 状态:提议 / 已接受 / 已废弃(被 NNNN 取代)- 日期:YYYY-MM-DD## 背景约束是什么,为什么现在必须决定。## 决策选了什么,具体到可执行。## 后果变简单的、变复杂的、放弃了什么。## 备选还考虑过什么,为什么不选。

相关学习资料