在多仓库 wiki 跑起来之后,继续引入 SDD 协议:让 spec、代码、决策、PR 和 CI 互相追踪,减少跨仓协作里的失真。
案例:同一个前后端分离的多仓库业务项目 完成时间:2026-05-26 上一篇:《多仓库 Wiki 与 AI 上下文管理实践(起步篇)》 本篇定位:在 wiki 体系跑了一段时间之后,把项目从“AI 能拿到上下文”升级到“AI 改的代码能反向追到 spec”。
核心结论
起步篇解决的是 “AI 怎么读懂项目”。这套 wiki 跑了几个月后,新的痛点开始浮出来:
- 决策都做了,但 “当初为什么这么选” 没地方查;
- spec 写得很细,可改代码的人不知道**“我这一改影响哪些 spec”**;
- PR 评审只看测试覆盖率,**“这个 PR 到底实现了哪条 spec 里的哪条验收”**说不清;
- 后端悄悄删了一个字段,前端要等到联调挂了才发现。
这一篇做的事,可以压成一句话:
给项目加一套 SDD(Spec-Driven Development)协议,让 spec 和代码双向绑定、决策可追溯、PR 强制说清楚自己实现的是哪个能力的哪条 AC,CI 在你乱改的时候自动喊停。
听起来像一套很重的流程,其实新增的东西不多,主要是 4 个新概念加一套自动校验。下面展开说。
一、为什么 wiki 不够用了
起步篇的 wiki 体系(.wiki/*.md + ARCHITECTURE.md + openapi.yaml)解决了**“项目长啥样”**这件事。但跑着跑着发现:
痛点 1:决策都做了,但没人记得为什么
比如某天有人问“我们为什么用 Sa-Token,而不是直接用 JWT?”,回答只能是“我翻一下 spec”。结果 spec 里就一句"D2 决策:Sa-Token + /auth/refresh",理由半行带过,对比过哪些方案、为什么淘汰,全都没了。
模块多了以后,跨模块决策(要不要引 RabbitMQ、要不要用 Nacos、CQRS 怎么拆)尤其麻烦:每个决策都散落在某个模块的 spec §1.5 决策表里,跨模块的人根本找不到。
痛点 2:spec 和代码会“无声漂移”
spec 写完拍板,代码开始写。过了三个月,spec 还是当初那版,代码已经迭代过 N 轮——这俩还一致吗?没人知道。
更典型的:spec 里写"P1 任务勾选完成",但这个勾选具体对应哪几个测试通过?说不出来。PR 评审只看 TDD 覆盖率 80%,但**“覆盖了什么"和"spec 里要求什么”**没有显式映射。
痛点 3:改代码不知道影响面
接手别人的 controller,类头没注释。改一个返回字段后才发现:
- 这个字段是某个能力的核心 AC(炸了)
- 前端有 3 个页面在用(联调爆炸)
- spec 里写了"返回 auth 域最小集"(漂移了)
——这些如果都靠人脑记忆,迟早会撑不住。
痛点 4:API 契约只是个 YAML 文件
openapi.yaml 是契约真相源,但没有契约测试。后端 controller 改了返回结构 → openapi.yaml 自动重生 → 前端 codegen 拉新版 types.ts → ……前端代码用旧字段还能编译过,运行才报错。
二、SDD 体系是什么
SDD = Spec-Driven Development。我的理解很简单:
把 spec 当一等公民,每行代码都能反向回到某个 spec 和某条 acceptance;spec 改了,工具能告诉你哪些代码受影响;代码改了,工具能告诉你哪些 spec 该 review 了。
升级后的项目结构变成这样(在原有 wiki 体系上叠加):
docs/
├── design/<module>/01-architecture.md (L2 模块设计 — 沿用)
├── reference/capabilities/<module>.<slug>.md ← 新增 L1 能力清单
├── adr/NNNN-kebab-title.md ← 新增 ADR 决策记录
├── sdd/SDD-PROTOCOL.md ← 新增 5 min 协议总览
└── _archive/initial-prd/ ← 老 PRD 归档
scripts/sdd/
├── check-anchor-integrity.mjs ← 新增 4 个 CI 脚本
├── check-capability-coverage.mjs
├── check-spec-freshness.mjs
└── check-wiki-freshness.mjs
各仓代码里:
- 每个 controller/service/view 类头加 @spec + @capability javadoc/JSDoc 注释
- 每个测试方法上方加 // @validates AC-PX.Y
各仓 .github/:
- workflows/sdd-checks.yml (CI 自动跑浅检查)
- PULL_REQUEST_TEMPLATE.md (强制填 Spec / Capability / AC 字段)
这些文件、注释和模板看起来有点零碎,但每一个都对应一个具体痛点:
| 新东西 | 解决什么 |
|---|---|
docs/adr/ 25 个 ADR |
跨模块决策有独立锚点,rationale + alternatives 不再丢 |
docs/reference/capabilities/ 8 个能力清单 |
spec 和代码之间多一层"业务可识别能力",含可执行 AC |
@spec / @capability 代码注释 |
改代码秒查影响 spec / 能力 |
| 4 个 SDD check 脚本 | spec ↔ code 双向漂移自动检测 |
| OpenAPI 契约测试 + codegen guard | 后端删字段、前端用旧字段,能在 build 阶段就拦住 |
| PR 模板 §1 + CI workflow | 评审有据可依,乱改有人拦 |
三、开发过程多了哪些流程
这是这次升级最直接影响日常开发的部分。下面按角色拆。
3.1 后端 dev 加一个 controller 端点
改造前的节奏:
- 写 controller
- 写 service
- 写测试,跑 mvn verify 80% 覆盖率
- 提 PR,填 TDD checklist
- CI 跑测试,绿了就 merge
改造后多了 4 步(粗体是新增):
- 写 controller,类头 javadoc 加
@spec docs/design/.../#section+@capability user-auth.login-password+@since P1 - 写 service,同理
- 写测试,测试方法上方加
// @validates AC-P1.4 密码登录 happy path - 如果加了新能力 → 在
docs/reference/capabilities/user-auth.login-password.md的anchors:字段加这两个文件 - 如果是跨模块决策(如换 SMS 厂商)→ 开 ADR
docs/adr/NNNN-...md,5 段写清楚 - 跑 mvn verify
- 本地跑
node scripts/sdd/check-{anchor-integrity,capability-coverage}.mjs,全绿 - 提 PR,PR 模板 §1 强制填 Spec 路径 / Capability slug / AC 编号
- CI 自动跑
sdd-checks.yml(语法浅检),跑 TDD 测试 - merge
单次代价:每个端点多花 5-10 分钟(一次性 anchor 注解 + 维护 capability yaml)。 长期收益:3 个月后接手的人,看 javadoc 一眼就知道这是哪条能力的实现。
3.2 前端 dev 改一个 view
改造前:改 view → 改 service → 跑 vitest + Playwright → 提 PR。
改造后多了 3 步:
- 改 view,
<script setup>顶部 JSDoc 加@spec+@capability+@ac - 如果改了接口调用,改 service.js 顶部 JSDoc 同样的字段
- 跑 vitest + Playwright
- 本地跑 anchor 检查脚本
- 跑
npm run gen:api时留意输出:如果 openapi.yaml 删了路径/字段,会 WARN suspected breaking change - 提 PR,模板 §1 填齐
单次代价:每个 view 多 3-5 分钟。
3.3 TL / 架构师做技术决策
改造前:在 spec 决策表里加一行,理由两句话带过。
改造后:
- 决策是不是跨模块的?(影响 ≥2 模块、改技术栈、引外部依赖、改安全模型)
- 是 → 必须开 ADR:
cp docs/adr/_template.md docs/adr/NNNN-kebab-title.md- 写 5 段:Status / Context / Decision / Consequences / Alternatives Considered(Alternatives 是个表格,至少列 2 个备选方案 + 为啥没选)
- 在被影响的 L2 spec frontmatter
adrs:字段反向引用 ADR-NNNN
- 否(模块内部决策) → 写进对应 spec §1.5 决策表即可
- 是 → 必须开 ADR:
- 决策被推翻了?不删旧 ADR,新写一个,标
supersedes: ADR-NNNN。
单次代价:每个 ADR 30-60 分钟(要认真写 Alternatives)。 长期收益:6 个月后有人质疑"为啥不用 X",5 秒答得出。
3.4 PM / QA 验收
改造前:看 L2 spec §11 P1-P6 任务勾选,跑手测,靠经验猜测试是否覆盖。
改造后:
- 打开
docs/reference/capabilities/user-auth.login-password.md - 看
acceptance:列表,每条都有id: AC-P1.X+desc:+test:路径 - 直接打开 test 文件 → 看
// @validates AC-P1.X注释找到对应测试方法 → 跑它 - 想看这个能力涉及哪些代码?看
anchors:字段,列得清清楚楚
QA 不再"猜哪个测试覆盖哪个 AC"。
3.5 PR 评审人
改造前:看 diff,跑测试,看 TDD checkbox。
改造后:
- 打开 PR 第一眼看 §1:作者填了 Spec / Capability / AC 编号?没填 → 打回
- 看 §4 SDD checklist 是否勾齐
- 看 diff,重点关注:改的代码有没有同步更新 anchor / capability yaml
- CI 跑了 sdd-checks 浅检查 → 看是否绿
- 看 TDD(沿用)
评审人现在大约 30 秒就能判断:这个 PR 到底有没有资格动这块代码。
四、新增的"门禁"清单
总结一下,整套升级在开发链路里加了 8 个门禁:
| # | 门禁 | 触发时机 | 失败行为 |
|---|---|---|---|
| 1 | 决策门禁(ADR) | 跨模块决策时 | 没开 ADR 评审会被打回 |
| 2 | 能力门禁(capability) | 加新业务能力时 | 没建 capability yaml 评审会被打回 |
| 3 | 代码锚点门禁 | 改/加 controller/service/view 时 | 没加 @spec @capability 评审会被打回 |
| 4 | CI 浅检查门禁 | PR + push | 锚点语法不对 → CI FAIL 阻断 merge |
| 5 | 本地全检门禁 | 提交前 | spec 路径错 / capability 未注册 / 双向漂移 → 本地 FAIL |
| 6 | PR 模板门禁 | 提 PR 时 | §1 spec/capability/AC 没填 → 评审打回 |
| 7 | API 契约门禁 | 后端集成测试 / 前端 codegen | 后端返回不符合 openapi.yaml → 测试 FAIL;前端检测删字段 → WARN/FAIL |
| 8 | Wiki 同步门禁 | 定期跑 | wiki ingest 距今 >7 天且有命中触发路径 → WARN |
其中 1-3 是人审门禁(靠 PR 模板 + 评审),4-7 是自动门禁(脚本/CI),8 是提醒门禁(warn-only)。
不是所有都阻断 merge。漂移(spec-freshness / wiki-freshness)只 warn,避免太硬把开发节奏卡死。
五、什么没做(避免 SDD 膨胀)
SDD 圈子里有些重型实践,这次故意没做:
| 没做的 | 为什么 |
|---|---|
| spec 的正式 ratification 流程(签字、状态机) | 当前一个人主导,状态机太重;frontmatter 留了 status 字段,等团队大了再启用 |
| 完整的 Behavior-Driven Development(Cucumber/Gherkin) | AC 用 markdown + test 路径足够;Gherkin 学习成本高,ROI 不划算 |
| 全量契约测试覆盖 | SddOpenApiContractValidator 提供基础设施,但不强制每个集成测试都挂;按需启用 |
| 把 docs/ 发布为 npm package 让 CI 跑 full check | 当前 docs/ 是本地共享目录非 git repo,full check 只能本地跑;CI 跑浅检查 + PR 模板 checkbox 约束 |
给现有 95+ 后端 test 全量加 @validates AC-XX |
太多;只在新写的 / 改动的测试上加,逐步补 |
我的原则是:先把骨架立起来,让新代码遵守;存量再逐步迁移。
六、跑起来的样子(实际场景)
假设接到一个新需求:“给后台管理员加一个’强制下线某用户全部设备’按钮”。
改造前的流程:
- 后端写 endpoint
POST /admin/users/{id}/sessions/revoke-all - 前端 PC 写按钮 + 调接口
- 写测试
- 提 PR,TDD checklist 勾齐,merge
改造后的流程:
- 查 spec:这是 user-auth 模块 P4 RBAC 的能力,对应 capability
user-auth.rbac-admin-ops - 看 capability yaml:
看open docs/reference/capabilities/user-auth.rbac-admin-ops.mdacceptance:列表,找到AC-P4.5: POST /admin/users/{id}/sessions/revoke-all看anchors:列表,知道要改:fab-3d-world-api/.../AdminController.javafab-3d-world-api/.../AdminService.javafab-3d-world-pc/src/components/admin/AdminStream.vuefab-3d-world-pc/src/service/admin.js
- TDD 写测试:
红 → 写实现 → 绿// @validates AC-P4.5 super_admin 强制下线某用户全设备 @Test void revokeAllSessions_super_admin_success() { ... } - 写后端实现,类头 javadoc 已经有了:
不用改 javadoc,因为 capability 已经覆盖这个文件。/** * @spec docs/design/user-auth/01-architecture.md#5.3.3 * @capability user-auth.rbac-admin-ops * @since P4 */ public class AdminController { ... } - 写前端按钮,AdminStream.vue 的 JSDoc 头同样已经有
@capability user-auth.rbac-admin-ops - 本地校验:
cd ~/project/fab-3d-world node scripts/sdd/check-anchor-integrity.mjs # 全绿 node scripts/sdd/check-capability-coverage.mjs # 全绿 - 提 PR,PR 模板自动渲染,§1 填:
- Spec: docs/design/user-auth/01-architecture.md#5.3.3 - Capability: user-auth.rbac-admin-ops - AC 编号: AC-P4.5 - CI 跑 sdd-checks.yml → anchor 浅检查通过 + TDD 通过 → merge
整套下来比改造前多大概 15 分钟,但留下的痕迹是:
- 接手的人扫一眼 commit + PR 就知道是什么能力的什么 AC
- 6 个月后这个按钮挂了,能 5 秒定位是哪个 spec / capability 的事
- super_admin 视为关键路径,覆盖率门禁自动是 95% 不是 80%
七、跟起步篇的关系
| 起步篇 | 进阶篇 |
|---|---|
| 解决“AI 怎么读懂项目” | 解决"AI 改的代码怎么追回 spec" |
| CLAUDE.md 瘦身 | CLAUDE.md 加 SDD 协议入口(§0.5) |
.wiki/ 承接项目语义 |
.wiki/ 不变,加 wiki freshness watcher 防漂移 |
| ARCHITECTURE.md C4 图 | 不变 |
| openapi.yaml 契约源 | 加 contract test + codegen breaking guard |
| 用户说“更新 wiki”,AI 半自动维护 | 同上 + 4 个 SDD check 脚本自动跑 |
| —— | 加 ADR 体系 25 个 |
| —— | 加 L1 capability 体系 8 个 |
| —— | 代码加 @spec @capability 双向锚点 |
| —— | PR 模板 + CI workflow |
进阶篇是叠加,不是替换。原来的所有东西继续用。
八、踩过的坑 / Onboarding 警示
把这套体系交接给新人 / 新 AI session 时要特别说清楚的几条:
- 共享
docs/不在任何 git repo。它是 3 个独立 git repo 的共享父目录。这导致 CI 只能跑浅检查(语法对不对),全检(spec 是否真存在 / capability 是否真注册)必须本地跑。PR 模板有 checkbox 强制承诺。 - ADR vs L2 spec §1.5 决策表:跨模块决策才开 ADR;模块内部实现细节继续写决策表即可。别什么都开 ADR,会膨胀。
- AC 编号在 phase 维度唯一:
AC-P1.1和AC-P2.1可以共存,只要 phase 内 seq 唯一。 - Anchor seed 脚本是一次性的:
/tmp/sdd-seed-anchors.mjs跑一次给 60+ 文件批量加注解,之后不用了。后续改 capability anchors 要么手工加 javadoc 要么重跑 seed(脚本是 idempotent 的)。 - 契约测试不强制全量:
SddOpenApiContractValidator只是基础设施,按需在集成测试基类挂上。不是每个 test 都必须 validate。 - Capability 的 anchors 是"权威列表":代码里没有
@capability标签 → CI 不知道;capability yaml 里没列文件 → bidirectional check 不会查它。所以改代码 + 改 capability yaml 是一对,缺一不可。
九、一句话总结
起步篇让 AI 能看懂项目,进阶篇让 AI 改的代码能反向追回 spec。
没有银弹。只是把项目里原本散落各处的“为什么这么做 / 现在做到哪了 / 哪段代码对应哪条规则”,用统一格式、双向锚点和自动校验绑在一起,让机器能读、人能查、CI 能拦。
整个体系不依赖任何外部平台,全是本地 markdown + node 脚本 + GitHub Actions。改回去也只需要删几个文件,零厂商锁定。
完整协议可以继续看
docs/sdd/SDD-PROTOCOL.md(5 分钟上手)+docs/adr/README.md(决策清单)+docs/reference/capabilities/README.md(能力清单)。
