Lionel · 李豪个人兴趣、文章、摄影、视频和音乐
Article · 文章

多仓库 Wiki + SDD 协议升级实践(进阶篇)

Lionel
2026/05/26 · 12 min read

在多仓库 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 端点

改造前的节奏

  1. 写 controller
  2. 写 service
  3. 写测试,跑 mvn verify 80% 覆盖率
  4. 提 PR,填 TDD checklist
  5. CI 跑测试,绿了就 merge

改造后多了 4 步(粗体是新增):

  1. 写 controller,类头 javadoc 加 @spec docs/design/.../#section + @capability user-auth.login-password + @since P1
  2. 写 service,同理
  3. 写测试,测试方法上方加 // @validates AC-P1.4 密码登录 happy path
  4. 如果加了新能力 → 在 docs/reference/capabilities/user-auth.login-password.mdanchors: 字段加这两个文件
  5. 如果是跨模块决策(如换 SMS 厂商)→ 开 ADR docs/adr/NNNN-...md,5 段写清楚
  6. 跑 mvn verify
  7. 本地跑 node scripts/sdd/check-{anchor-integrity,capability-coverage}.mjs,全绿
  8. 提 PR,PR 模板 §1 强制填 Spec 路径 / Capability slug / AC 编号
  9. CI 自动跑 sdd-checks.yml(语法浅检),跑 TDD 测试
  10. merge

单次代价:每个端点多花 5-10 分钟(一次性 anchor 注解 + 维护 capability yaml)。 长期收益:3 个月后接手的人,看 javadoc 一眼就知道这是哪条能力的实现。

3.2 前端 dev 改一个 view

改造前:改 view → 改 service → 跑 vitest + Playwright → 提 PR。

改造后多了 3 步

  1. 改 view,<script setup> 顶部 JSDoc 加 @spec + @capability + @ac
  2. 如果改了接口调用,改 service.js 顶部 JSDoc 同样的字段
  3. 跑 vitest + Playwright
  4. 本地跑 anchor 检查脚本
  5. npm run gen:api 时留意输出:如果 openapi.yaml 删了路径/字段,会 WARN suspected breaking change
  6. 提 PR,模板 §1 填齐

单次代价:每个 view 多 3-5 分钟。

3.3 TL / 架构师做技术决策

改造前:在 spec 决策表里加一行,理由两句话带过。

改造后

  1. 决策是不是跨模块的?(影响 ≥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 决策表即可
  2. 决策被推翻了?不删旧 ADR,新写一个,标 supersedes: ADR-NNNN

单次代价:每个 ADR 30-60 分钟(要认真写 Alternatives)。 长期收益:6 个月后有人质疑"为啥不用 X",5 秒答得出。

3.4 PM / QA 验收

改造前:看 L2 spec §11 P1-P6 任务勾选,跑手测,靠经验猜测试是否覆盖。

改造后

  1. 打开 docs/reference/capabilities/user-auth.login-password.md
  2. acceptance: 列表,每条都有 id: AC-P1.X + desc: + test: 路径
  3. 直接打开 test 文件 → 看 // @validates AC-P1.X 注释找到对应测试方法 → 跑它
  4. 想看这个能力涉及哪些代码?看 anchors: 字段,列得清清楚楚

QA 不再"猜哪个测试覆盖哪个 AC"。

3.5 PR 评审人

改造前:看 diff,跑测试,看 TDD checkbox。

改造后

  1. 打开 PR 第一眼看 §1:作者填了 Spec / Capability / AC 编号?没填 → 打回
  2. 看 §4 SDD checklist 是否勾齐
  3. 看 diff,重点关注:改的代码有没有同步更新 anchor / capability yaml
  4. CI 跑了 sdd-checks 浅检查 → 看是否绿
  5. 看 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 太多;只在新写的 / 改动的测试上加,逐步补

我的原则是:先把骨架立起来,让新代码遵守;存量再逐步迁移


六、跑起来的样子(实际场景)

假设接到一个新需求:“给后台管理员加一个’强制下线某用户全部设备’按钮”。

改造前的流程

  1. 后端写 endpoint POST /admin/users/{id}/sessions/revoke-all
  2. 前端 PC 写按钮 + 调接口
  3. 写测试
  4. 提 PR,TDD checklist 勾齐,merge

改造后的流程

  1. 查 spec:这是 user-auth 模块 P4 RBAC 的能力,对应 capability user-auth.rbac-admin-ops
  2. 看 capability yaml
    open docs/reference/capabilities/user-auth.rbac-admin-ops.md
    
    acceptance: 列表,找到 AC-P4.5: POST /admin/users/{id}/sessions/revoke-allanchors: 列表,知道要改:
    • fab-3d-world-api/.../AdminController.java
    • fab-3d-world-api/.../AdminService.java
    • fab-3d-world-pc/src/components/admin/AdminStream.vue
    • fab-3d-world-pc/src/service/admin.js
  3. TDD 写测试
    // @validates AC-P4.5 super_admin 强制下线某用户全设备
    @Test void revokeAllSessions_super_admin_success() { ... }
    
    红 → 写实现 → 绿
  4. 写后端实现,类头 javadoc 已经有了:
    /**
     * @spec docs/design/user-auth/01-architecture.md#5.3.3
     * @capability user-auth.rbac-admin-ops
     * @since P4
     */
    public class AdminController { ... }
    
    不用改 javadoc,因为 capability 已经覆盖这个文件。
  5. 写前端按钮,AdminStream.vue 的 JSDoc 头同样已经有 @capability user-auth.rbac-admin-ops
  6. 本地校验
    cd ~/project/fab-3d-world
    node scripts/sdd/check-anchor-integrity.mjs       # 全绿
    node scripts/sdd/check-capability-coverage.mjs    # 全绿
    
  7. 提 PR,PR 模板自动渲染,§1 填:
    - Spec: docs/design/user-auth/01-architecture.md#5.3.3
    - Capability: user-auth.rbac-admin-ops
    - AC 编号: AC-P4.5
    
  8. 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 时要特别说清楚的几条:

  1. 共享 docs/ 不在任何 git repo。它是 3 个独立 git repo 的共享父目录。这导致 CI 只能跑浅检查(语法对不对),全检(spec 是否真存在 / capability 是否真注册)必须本地跑。PR 模板有 checkbox 强制承诺。
  2. ADR vs L2 spec §1.5 决策表:跨模块决策才开 ADR;模块内部实现细节继续写决策表即可。别什么都开 ADR,会膨胀。
  3. AC 编号在 phase 维度唯一AC-P1.1AC-P2.1 可以共存,只要 phase 内 seq 唯一。
  4. Anchor seed 脚本是一次性的/tmp/sdd-seed-anchors.mjs 跑一次给 60+ 文件批量加注解,之后不用了。后续改 capability anchors 要么手工加 javadoc 要么重跑 seed(脚本是 idempotent 的)。
  5. 契约测试不强制全量SddOpenApiContractValidator 只是基础设施,按需在集成测试基类挂上。不是每个 test 都必须 validate。
  6. 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(能力清单)。

#文章
Lionel Written by
我叫李豪,来自云南昭通,现居杭州 喜欢摄影、足球 、Vibe Coding 和老婆一起养了两只猫
Scinentistcoldplay