6条设计原则 + 5节模块模板 + 集成契约/不变量/症状导航机制 + 维护工作流 + AI辅助开发特殊考量 + 检查清单 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
12 KiB
项目 Wiki 知识库编制方法论
基于 ZCLAW 项目实战经验(10 crates + React 前端,~155KB wiki 重构)提炼。 适用于任何有 AI 辅助开发参与的中大型项目。 一句话总结:Wiki 只记录代码无法告诉你的东西。
一、设计原则
原则 1:Wiki 记录"代码不能告诉你的"
| 记录在 Wiki ✅ | 不记录在 Wiki ❌ |
|---|---|
| 为什么这样设计(WHY) | 字段列表、函数签名 |
| 跨模块数据流走向 | 单文件内的代码逻辑 |
| 历史踩坑和教训 | 可用 grep 直接查到的信息 |
| 必须始终成立的约束(不变量) | CRUD 操作、getter/setter |
| 模块间调用接口(集成契约) | 具体的行号、变量名 |
判断标准:如果 git log 或 grep 能在 30 秒内回答这个问题,就不需要写在 wiki 里。
原则 2:每个模块页统一 5 节结构
按阅读优先级排列(先给最重要的信息):
1. 设计决策 (WHY) — 为什么这样设计、历史背景、权衡取舍
2. 关键文件 + 数据流 — 3-7 个核心文件 + 跨模块接口
3. 代码逻辑 — 数据流走向 + 不变量 + 非显而易见的算法
4. 活跃问题 + 陷阱 — 当前未解决 + 历史教训
5. 变更记录 — 最近 5 条,超出的归入全局日志
为什么是这个顺序:新的 AI 会话(或开发者)首先需要知道"这个模块为什么存在"和"文件在哪",然后才是"怎么工作的",最后是"有什么问题"。
原则 3:页面大小必须有预算
| 页面类型 | 行数预算 | 原因 |
|---|---|---|
| 首页/索引 | ≤ 120 行 | 需要快速扫描,AI 一次加载 |
| 模块页 | 100-200 行 | AI 一次加载 2-3 个模块不爆 context |
| 全局日志 | ≤ 50 条活跃 | 防止无限膨胀,旧条目归档 |
超过预算怎么办:把详细内容归档到 archive/ 目录,模块页只保留摘要 + 链接。
原则 4:单一真相源
同一信息只出现在一个页面。其他需要该信息的地方只放引用。
错误:安全认证流程同时写在 saas.md、security.md、middleware.md
正确:security.md 拥有完整描述,saas.md 只写"详见 [[security]]"
检查方法:grep 关键内容,如果出现在 ≥ 3 个页面,就需要去重。
原则 5:Append-only 内容必须封顶
日志、问题列表等只增不减的内容,必须设置上限并定期归档。
活跃日志 ≤ 50 条 → 旧条目归入 archive/log-{YYYY-MM}.md
活跃问题 ≤ 5 条/模块 → 修复后立即移除
变更记录 ≤ 5 条/模块 → 旧记录在全局 log.md
原则 6:用症状导航补充模块导航
模块导航解决"这个模块是什么"的问题。但实际开发中,人们更多是在解决"出了问题该看哪里"。
症状导航表格式:
| 症状 | 先查 | 再查 | 常见根因 |
|---|---|---|---|
| 流式响应卡住 | routing | chat → middleware | 连接断开 / 超时 |
| 数据没持久化 | data-model | 对应模块 | 表结构 / 迁移缺失 |
放在首页/索引页,让新来的人(或 AI 会话)0 跳就能定位排查方向。
二、结构模板
2.1 三级层级
项目 Wiki
├── Level 1: index.md — 纯导航 + 症状索引(≤ 120 行)
├── Level 2: {module}.md — 每个功能模块一个页面(100-200 行)
├── Level 3: archive/ — 历史内容归档
└── (可选) known-issues.md — 活跃问题全局索引
2.2 首页模板 (index.md)
# {项目名} 知识库
> 一句话定位。使用方式说明。
## 关键数字
| 指标 | 值 | 验证方式 |
|------|-----|---------|
## 系统数据流
{ASCII 全景图}
## 模块导航
- [[module-a]] — 一句话说明
- [[module-b]] — 一句话说明
## 症状导航
| 症状 | 先查 | 再查 | 常见根因 |
|------|------|------|----------|
2.3 模块页模板 ({module}.md)
---
title: {模块名}
updated: {YYYY-MM-DD}
status: active | stable | developing
tags: [{tags}]
---
# {模块名}
> 从 [[index]] 导航。关联: [[related-1]] [[related-2]]
## 1. 设计决策
{为什么这样设计、历史背景、权衡取舍}
{用 Q&A 格式记录关键架构决策}
## 2. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `path/to/file` | 一句话说明 |
### 数据流
{ASCII 流程图}
### 集成契约
| 方向 | 模块 | 接口 | 触发时机 |
|------|------|------|---------|
| 调用 → | {module} | `{function/API}` | {when} |
| 被调用 ← | {module} | `{function/API}` | {when} |
## 3. 代码逻辑
### 关键数据流
{跨函数/跨文件的完整路径,附意图说明}
### 不变量
⚡ {不变量 1}: {必须始终成立的约束}
⚡ {不变量 2}: {描述}
### 非显而易见的算法
{读代码难以理解的逻辑}
## 4. 活跃问题 + 陷阱
### 活跃问题
| 问题 | 级别 | 状态 | 说明 |
|------|------|------|------|
{0-5 条,修复后移除}
### 历史教训
- {教训}: {一句话描述}
### 注意事项
⚠️ {易出错的地方}
## 5. 变更记录
| 日期 | 变更 |
|------|------|
{最近 5 条}
三、关键机制详解
3.1 集成契约
问题:跨模块边界的信息(谁调谁、接口形状)是最难从代码中获取的知识,也是 wiki 最大的结构性缺口。
做法:每个模块页的"关键文件"节下增加一个"集成契约"小表,回答四个问题:
| 问题 | 对应列 |
|---|---|
| 这个模块调用了谁? | 调用 → |
| 这个模块被谁调用? | 被调用 ← |
| 通过什么接口? | 接口(函数名/API路径) |
| 什么时候触发? | 触发时机 |
示例(中间件模块):
| 方向 | 模块 | 接口 | 触发时机 |
|---|---|---|---|
| 被调用 ← | kernel | create_middleware_chain() |
内核启动 |
| 调用 → | runtime | run_before_completion() |
每次聊天请求 |
| 提供 → | 所有模块 | AgentMiddleware trait |
14 个实现 |
3.2 不变量标记
问题:系统中有一些"必须始终成立的约束",它们不像代码那样显式存在,但一旦被违反就会产生隐蔽的 bug。
做法:用 ⚡ 标记不变量,放在"代码逻辑"节下。
⚡ Priority 是升序排列:0-999,数值越小越先执行
⚡ memories.db 和 data.db 是独立数据库,跨库查询需确认目标库
⚡ 记忆注入在中间件@150,在管家路由@80之后,技能索引@200之前
判断什么是好的不变量:
- 它描述的是一种关系或顺序,不是单个组件的行为
- 如果有人不知道这个约束,修改代码时很可能无意中违反它
- 违反的后果不会立即显现,而是演化几轮后变成隐性 bug
3.3 去重规则
| 重复类型 | 处理方式 |
|---|---|
| 完整描述出现在 A 和 B | 选择一个为真相源,另一个只引用 |
| 相同信息出现在 ≥ 3 页 | 必须去重,指定唯一归属 |
| 概述 vs 详情 | 概述页保留一句话 + 链接,详情页拥有完整描述 |
去重检查命令:
grep -l '关键内容' wiki/*.md | wc -l
# 结果 ≥ 3 → 需要去重
3.4 症状导航
为什么需要:模块导航是"模块→功能"方向,但排查问题时需要的是"症状→模块"方向。
编制方法:
- 收集团队/AI 会话中反复出现的调试场景(8-12 个)
- 每个场景记录:症状、先查哪个页面、再查哪个、最常见根因
- 放在首页,新会话/新人 0 跳可达
示例:
| 症状 | 先查 | 再查 | 常见根因 |
|---|---|---|---|
| API 返回 502 | saas | routing | Token 耗尽 / 服务超时 |
| 数据不持久 | data-model | 对应模块 | 表缺失 / 字段不匹配 |
| 流式中断 | chat | middleware | 连接断开 / 超时守护 |
四、维护工作流
4.1 什么时候更新 Wiki
| 触发事件 | 更新什么 |
|---|---|
| 修复 bug | 对应模块页"活跃问题" + 全局 known-issues 索引 |
| 架构变更 | 对应模块页"设计决策" + 集成契约 |
| 文件结构变化 | 对应模块页"核心文件"表 |
| 跨模块接口变化 | 涉及双方的"集成契约"表 |
| 发现新不变量 | 对应模块页"代码逻辑"节的 ⚡ 项 |
| 每次更新 | 模块页"变更记录"(保持5条) + 全局 log.md |
4.2 防止 drift 的策略
| 策略 | 做法 |
|---|---|
| 页面大小预算 | 超过 200 行强制裁剪,移入 archive/ |
| 活跃问题生命周期 | 修复后立即移除,不保留已修复项 |
| 变更记录滑动窗口 | 只保留最近 5 条,旧的自然滚入全局日志 |
| 数字验证 | 关键数字标注验证命令,定期执行确认 |
| "最后验证"日期 | 在 frontmatter 的 updated 字段记录,超过 30 天需要复查 |
4.3 重构 Wiki 的执行顺序
如果需要对已有 wiki 进行重构,按依赖关系分阶段:
Phase 1: 归档/封顶 — 压缩日志、归档旧内容(无依赖)
Phase 2: 确立真相源 — 最被其他页面引用的模块优先重构
Phase 3: 依赖页面 — 引用 Phase 2 模块的页面去重
Phase 4: 剩余模块 — 独立页面逐一重构
Phase 5: 首页/索引 — 最后改(依赖所有模块页完成)
关键约束:每个模块页独立提交,可安全 git revert 回滚单个页面。
五、AI 辅助开发的特殊考量
5.1 Wiki 的主要读者可能是 AI
在 AI 辅助开发中,wiki 的主要读者是每次新会话的 AI 实例(context 从零开始)。这改变了 wiki 的设计优先级:
| 传统 wiki | AI 辅助 wiki |
|---|---|
| 详细、全面 | 精炼、可快速加载 |
| 按主题组织 | 按任务场景导航 |
| 历史记录丰富 | 只保留活跃信息 |
| 人工索引 | 症状→页面直接映射 |
5.2 Context 预算思维
AI 的 context window 是有限资源。wiki 的每个字节都在消耗这个预算。
优化策略:
- 首页只放导航,不放内容(让 AI 按需读取模块页)
- 模块页控制在 100-200 行(一次加载 2-3 个不爆 context)
- 代码逻辑只写流向和不变量,不写可从代码读取的细节
- 使用
archive/存放低频需要的历史内容
5.3 Wiki 作为新会话的启动燃料
设计 wiki 时要问:一个全新的 AI 会话,读完首页后能定位问题吗?读完 2 个模块页后能开始工作吗?
如果答案是"不能",说明 wiki 的导航层不够好(首页缺症状导航)或模块页的结构不对(信息不在前两节)。
六、检查清单
创建 Wiki 时
- 首页 ≤ 120 行,包含:项目一句话定位、关键数字、模块导航、症状导航
- 每个模块页统一 5 节结构
- 每个模块页有集成契约表
- 每个模块页有 ⚡ 不变量
- 每个模块页 100-200 行
- 无内容重复出现在 ≥ 3 个页面
- 全局日志封顶 50 条,有归档机制
维护 Wiki 时
- 修复 bug 后更新对应模块"活跃问题"
- 架构变更后更新对应模块"设计决策"+ 集成契约
- 每次更新追加全局 log.md 条目
- 每次更新模块页变更记录(保持 5 条)
- 定期检查页面是否超过大小预算
附录:ZCLAW 重构效果
| 指标 | 重构前 | 重构后 | 变化 |
|---|---|---|---|
| 模块页总行数 | ~2,800 | ~1,547 | -45% |
| 重复内容 | 安全×3, 进化×3 | 各×1 | 消除 |
| 集成契约覆盖 | 0/10 页 | 10/10 页 | 全覆盖 |
| 症状导航 | 无 | 8 条路径 | 新增 |
| 首页 | 144 行 | 101 行 | +症状导航 |
| 最大单页 | 424 行 | 199 行 | 控住 |