AI Native 文档工作流:让文档和代码一起演进

0阅读11分钟

2024 年底,我负责一个内部组件库的文档治理。那个库有 60 多个组件,文档在另一个仓库里用 Storybook 维护。每次发版前后,文档负责人会拉一个群,催各模块 owner 更新对应文档。大多数人会在发版日前两天集中补一段,复制一段代码片段,写几句用法说明,然后合进去。发版完事,群里安静三个月,等下一个版本再来一遍。

这样的循环跑了快两年,组件文档和实际 API 之间的偏差越来越大。新人按文档写代码,经常踩到已经废弃的 props 或者没写明的行为变化。我开始尝试用 AI 帮忙:让模型读 PR 的 diff 和 changelog,生成文档更新草稿,再让人 review。效果确实快,一周的文档积压一天就能清完。但两个月后回看,新文档里又有三成和代码对不上——AI 把 changelog 里「新增多选支持」理解成了一个新组件,而不是对已有组件 onSelect 签名的修改。

这件事让我意识到:问题不在 AI 生成得对不对,而在整个文档工作流从源头开始就没有稳定的事实锚点。AI 是一个放大器,输入的事实清楚,它能把产出放大几倍;输入模糊,它能把错误也放大几倍。这个判断改变了我后续所有文档建设的思路。

Docs as Code 的下一跳

Docs as Code 不是新概念。2010 年代中期,Write the Docs 社区和 Google 的工程实践博客把这套方法论推到了主流:文档放代码仓库、Markdown 写作、PR 审查、CI/CD 发布。它解决了版本控制、协作和发布自动化的问题,但没有解决文档的准确性问题——文档仍然是手写文本,和代码之间没有可验证的绑定关系。

DrExplain 在 2026 年 5 月提出 Docs-as-Code 2.0 框架时,把这种变化描述为从「人类便利性」到「机器便利性」的维度扩展。文档的消费者不再只是人,还有 AI agent。对应地,文档需要两层结构:给人看的可视化站点,给机器读的结构化数据层。llms.txt 放在站点根目录作为导航地图;MCP(Model Context Protocol)提供统一接口,让 LLM 按需获取文档片段;JSON-LD 在页面里嵌入结构化元数据,让 agent 一进来就知道这是哪个版本、什么类型的信息。

这个框架的启示是:文档不是静态产物,而是从代码、测试、配置、changelog 这些可验证事实派生出来的结构化数据。一旦把文档看成「派生数据」,它的准确性就不再取决于写文档的人有多认真,而取决于事实源头的质量和派生流程的自动化程度。

这和我写测试的直觉是一致的。测试的质量不取决于测试作者的文笔,而取决于被测代码的结构和断言的颗粒度。AI 在文档工作流里的角色,更接近一个「结构化信息处理管道」:

  1. 事实源:代码、类型定义、OpenAPI、测试、changelog、配置文件
  2. 提取与转换:从事实源抽取结构化中间表示(JSON Schema、AST、配置 schema)
  3. AI 生成:基于中间表示生成自然语言文档、代码示例、多语言版本
  4. 人类审查:确认准确性、上下文完整性、读者路径
  5. CI 验证:跑通示例代码、检查链接和版本一致性
  6. 发布与反馈:发布站点,收集搜索日志和错误报告,回流到事实源

这个管道里每一环都不能省。arXiv 2024 年的一篇论文用 LLM 给开源仓库自动生成 README,发现生成内容和人工文档的 BERT 语义相似度平均 85% 左右,但 BLEU 分数只有 20/100。也就是说 AI 能抓住大意,但具体细节需要人工校准。Kapa.ai 的工程博客里有一句话很准确:「当我们说一个 agent 在幻觉时,我们其实在说一个 agent 饿 context。」解决办法不是换更强的模型,而是把 API reference 从 OpenAPI spec 或类型定义派生出来,让「文档漂移」在结构上变得不可能。

哪些环节适合 AI,哪些不适合

在动手搭流程之前,我把文档工作拆成了两类,交给不同角色。

维度AI 擅长必须人来把关
初稿与改写README、changelog、API 描述、示例代码改写架构决策、权衡说明、业务上下文
结构化从类型定义生成 API 参考、生成 JSON Schema概念层次划分、读者路径设计
验证跑通代码示例、检查链接、术语一致性API 兼容性判断、破坏性变更评估
翻译与本地化多语言翻译、术语对齐文化差异、产品定位、敏感表述
维护检测过期文档、生成更新 PR决定是否删除、归档策略

这个分类决定了流程里哪些环节可以自动放行、哪些必须人审。AI 生成的初稿可以自动开 PR,但 PR 的合并必须由了解业务的人 review;AI 跑过的示例代码可以作为「可运行」的证据,但示例是否真的教了读者想学的东西,还是要人判断。

案例一:API 文档为什么总对不上代码

2025 年初,我在一个中大型 SaaS 项目里遇到一个典型的文档事故。组件库的一位维护者修改了 Select 组件的 onSelect 签名——从原来的单对象改成了数组,支持多选。这个改动在 PR 里写了,changelog 里也提了,但文档没人动。三个月后,一位新同事按文档里的旧签名写业务代码,上线后直接报错。

事故复盘时我意识到,根因不是谁疏忽了,而是文档和代码之间没有可验证的绑定。改代码不需要改文档也能合 PR。这种「双源真相」靠人盯不住。

修复方案是把 API 文档绑定到代码本身,让文档从类型定义派生。我写了一个脚本,从 TypeScript 类型定义生成 API 文档的骨架:

// ❌ 坏做法:文档手写,和类型定义分离
// docs/select.md
// > onSelect: (item: Item) => void
// 当用户选择一个选项时触发。
// (三个月后签名改了,这行没人记得同步)
 
// ✅ 好做法:文档从类型定义派生
// scripts/generate-api-docs.ts
import ts from 'typescript'
import fs from 'node:fs'
 
// 从源码读取类型定义,而不是人复述
const program = ts.createProgram(['src/components/Select.tsx'], {})
const checker = program.getTypeChecker()
const sourceFile = program.getSourceFile('src/components/Select.tsx')
 
function extractProps(node: ts.Node): PropMetadata[] {
  if (ts.isInterfaceDeclaration(node) && node.name.text === 'SelectProps') {
    return node.members.map((member) => {
      const symbol = checker.getSymbolAtLocation(member.name)
      const type = checker.getTypeOfSymbolAtLocation(symbol, member)
      return {
        name: member.name.getText(),
        type: checker.typeToString(type),
        // 取 JSDoc 注释作为描述,改类型时作者必须顺手更新注释
        description: ts.displayPartsToString(
          symbol?.getDocumentationComment(checker) ?? []
        ),
        required: !member.questionToken,
      }
    })
  }
  return []
}
 
// 生成 Markdown,CI 里跑,类型变了文档会自动 diff
const props = sourceFile.flatMap(extractProps)
const markdown = props
  .map((p) => `### \`${p.name}\`\n\n类型:\`${p.type}\`${p.required ? '(必填)' : ''}\n\n${p.description}`)
  .join('\n\n')
 
fs.writeFileSync('docs/select-api.md', markdown)

差异一目了然。坏做法里,文档是独立的 Markdown 文件,和 SelectProps 之间没有机器可读的关系,改一边不用动另一边。好做法里,文档是类型定义的派生数据:onSelect 签名变了,脚本重新跑一遍,新文档会出现在 PR diff 里。如果有人在 PR 里只改了类型不重新生成文档,CI 会失败——「文档漂移」在结构上变得不可能。

这对应了 Kapa.ai 工程博客里的建议:「API 文档应该从 OpenAPI spec 或类型定义派生,让文档漂移在结构上不可能发生。」arXiv 论文的实验也表明,从代码派生的文档在语义上能覆盖 85% 左右的关键信息,但具体的约束、边界条件和业务上下文仍然需要人工补充。把类型定义作为事实源,AI 作为改写器,人作为审查者,是一个更稳的组合。

案例二:文档里的代码示例为什么跑不通

2025 年中,我做了一次内部文档质量审查,发现一个尴尬的数字:120 篇教程里,34 篇的代码示例不能直接跑。问题各异——依赖版本过时、API 签名已变、缺环境变量、README 里的 npm install 命令和 package.json 对不上。这些文档的共同特征是:作者写的时候能跑,后来代码改了,文档没同步,作者离职了,没人敢删也没人敢改。

「跑不通的代码示例」比没有示例更糟糕——它给人一种「应该能跑」的错觉,让读者在自我怀疑和怀疑文档之间浪费一个小时。

修复思路是:把文档里的代码块当作一等公民对待,在 CI 里跑一遍。我用 Vitest 写了一个文档示例校验器:

// ❌ 坏做法:文档里的代码块当纯文本,没人验证
// docs/getting-started.md
// ```ts
// import { createClient } from './sdk'
// const client = createClient({ apiKey: 'xxx' })
// ```
// (sdk 的 createClient 签名早改了,但这篇文档三年没人动)
 
// ✅ 好做法:文档代码块当作可执行规范,CI 里跑一遍
// tests/docs-examples.test.ts
import { test, expect } from 'vitest'
import fs from 'node:fs'
import { extractCodeBlocks, compileAndRun } from './doc-test-utils'
 
const docFiles = fs
  .readdirSync('docs')
  .filter((f) => f.endsWith('.md'))
  .map((f) => `docs/${f}`)
 
for (const file of docFiles) {
  test(`${file} 中的代码示例可运行`, async () => {
    const content = fs.readFileSync(file, 'utf-8')
    const blocks = extractCodeBlocks(content, { lang: 'ts' })
 
    for (const [index, block] of blocks.entries()) {
      // 编译并执行代码块,捕获任何错误
      const result = await compileAndRun(block, {
        // 用测试环境的 mock,不依赖真实服务
        env: { API_KEY: 'test-key', BASE_URL: 'http://localhost:3000' },
        // 捕获 console.log 用于断言
        captureStdout: true,
      })
      expect(result.exitCode).toBe(0)
      // 可选:对输出做快照,发现文档行为变化立即报错
      expect(result.stdout).toMatchSnapshot(
        `${file}-block-${index}.stdout`
      )
    }
  })
}

差异的关键在于:坏做法把文档示例当文本处理,写的时候对、后面就再也无人问津;好做法把文档示例当可执行规范,每次代码变更都会重新验证。成本是每个文档 PR 多跑 30 秒到 2 分钟,收益是「合并进 main 的文档示例都是能跑的」。代价很低,但带来的信心差异非常大——读者打开文档,第一行 npm install 跑下去,就能进到正题,而不是先去排查为什么 createClient 报类型错。

这个方案还有一个副作用:一旦代码变更导致文档示例失效,PR 检查会立刻标红。维护者不需要等用户来报「文档跑不通」,问题在合并前就被拦住。

案例三:文档更新触发器怎么做

前面两个案例讲的都是「如何从代码派生文档」和「如何验证文档示例」。但还有一个更上游的问题:什么时候触发文档检查?

多数团队的文档更新触发器是被动的——用户报错了、新人踩坑了、发版前被催了,才去改文档。这种触发器的覆盖率很低,大量小改动悄悄累积,半年后文档就没人敢信了。

更合理的触发器应该是按风险分类:

代码变更类型是否需要触发文档检查理由
API 签名变化必须对外契约,文档必须同步
配置项变化必须影响部署和运行
CLI 命令变化必须用户直接操作
公开行为变化(错误码、返回值)必须影响集成方
内部重构通常不需要不改变对外契约
性能优化视情况如有可观测的指标变化,需要更新基准文档
测试改动不需要测试实现细节
CI 配置视情况如果影响贡献者流程则需要

分类之后,落地可以很简单。我在一套项目里用文件模式检测 + PR 评论提醒:

// ❌ 坏做法:文档检查靠人记得,靠发版前集中补
// 发版日,项目经理在群里 @ 所有人
// 「大家检查一下自己模块的文档是不是最新的」
// 结果:每次都漏几个,用户两周后来报错
 
// ✅ 好做法:CI 检测特定文件模式,自动触发文档检查
// scripts/check-docs-triggers.ts
import { execSync } from 'node:child_process'
 
// 拿到当前 PR 的变更文件列表
const changedFiles = execSync('git diff --name-only origin/main...HEAD')
  .toString()
  .trim()
  .split('\n')
 
// 触发文档检查的模式:API / 配置 / CLI / 错误码
const triggers = [
  { pattern: /src\/api\/.*\.ts$/, reason: 'API 签名可能变化' },
  { pattern: /src\/config\/.*\.ts$/, reason: '配置项可能变化' },
  { pattern: /src\/cli\/.*\.ts$/, reason: 'CLI 命令可能变化' },
  { pattern: /src\/.*errors\.ts$/, reason: '错误码可能变化' },
]
 
const triggered = triggers.flatMap(({ pattern, reason }) => {
  const matched = changedFiles.filter((f) => pattern.test(f))
  return matched.length ? [{ reason, files: matched }] : []
})
 
if (triggered.length > 0) {
  console.log('以下变更需要文档 owner 确认:')
  for (const t of triggered) {
    console.log(`- ${t.reason}: ${t.files.join(', ')}`)
  }
  // CI 里直接挂一个「docs-check」job,要求 docs owner review
  process.exit(1)
}

结合 PR 机器人提醒,效果比被动触发好得多:

// .github/workflows/docs-nudge.ts
// 当 PR 包含 API / 配置 / CLI 变更时,自动评论提醒
if (containsApiOrConfigChanges(pr.files)) {
  await github.rest.issues.createComment({
    owner, repo, issue_number: pr.number,
    body: [
      '⚠️ 此 PR 包含可能影响文档的变更:',
      ...changedApiFiles.map((f) => `- \`${f}\``),
      '',
      '请确认:',
      '1. 相关文档已更新',
      '2. 文档代码示例已验证可运行',
      '3. 如无需文档更新,请在此评论说明原因',
    ].join('\n'),
  })
}
触发方式覆盖率维护成本对开发流程的干扰
靠人记得低(但累积债务)
发版前集中补高(阻塞发版)
文件模式检测 + 提醒低(PR 内解决)
CI 强制 + docs owner 审查最高中(可能拖慢 PR)

AI Native 文档工作流全景

把前面几个案例拼在一起,整个流程可以画成这样:

流程图画布 · 115%
Mermaid 流程图加载中...

这个图里几个关键节点对应前面的案例:「提取脚本生成 JSON Schema」是案例一,「CI 触发文档检查」包含案例二和案例三,「回流」是整个闭环的维护机制。任何一个节点缺失,流程都会退化——没有事实源提取,AI 就凭印象写;没有 CI 验证,文档示例就慢慢过期;没有回流,半年后事实源本身也会过时。

在选型上,不同团队的起点不一样,可以按这个表判断该优先补哪一环:

方案适用场景局限性
纯手工 + AI 润色小项目、探索期依赖作者自觉,漂移不可避免
类型定义 → API 文档自动生成TypeScript / OpenAPI 项目概念层次和业务上下文仍需人写
文档示例 CI 验证任何有教程的项目增加 CI 时间,需要维护测试环境
文件模式检测 + PR 提醒多人协作、变更频繁会有误报,需要调阈值
Mintlify / Fern 这类 AI-native 平台有预算、希望端到端厂商绑定,定制受限
Docusaurus + 自研脚本想要完全控制维护成本高

几条可以带走的原则

写到这里,我把这一年的经验压缩成五条:

  1. 文档从代码派生,而不是凭记忆写。事实源永远应该是代码、类型、配置、测试,而不是「某个同事记得」。
  2. 文档里的代码示例必须能跑。跑不通的示例不如没有。
  3. 文档更新触发器要嵌在代码变更流程里。改代码时同步检查文档,而不是发版前集中补。
  4. AI 生成的文档要更严格地 review,而不是更宽松。AI 是加速器,不是质量关卡。
  5. 文档质量上限由流程决定,不由 AI 模型决定。事实源清楚、更新流程自动化、验证机制到位、审查标准合理——这些比用哪个 LLM 更重要。

检查清单

按阶段分,可以直接拿去对团队现状打分:

阶段一:事实源建立

  • API 文档从类型定义或 OpenAPI spec 派生,构建时自动生成
  • 配置文档从配置 schema 派生
  • 文档里的代码示例可被测试框架直接运行
  • 文档引用的文件路径和版本号有明确标注

阶段二:AI 辅助生成

  • 生成文档时提供完整上下文(文件路径、版本、变更历史)
  • 明确区分事实陈述(来自类型、测试)和建议性表述(来自 AI 推断)
  • AI 生成内容打标,方便后续审查聚焦

阶段三:验证与 CI 集成

  • 文档代码示例通过 vitest / 类似工具的可执行检查
  • 配置变化时触发相关文档检查
  • PR 包含 API / 配置变更时强制 docs owner review
  • 发布前做全量文档扫描,标记过期内容

阶段四:审查与维护

  • 文档审查清单覆盖准确性、结构、术语一致性、示例可运行性
  • AI 生成内容额外做事实核查
  • 定期清理过期文档,明确归档策略

参考资料

  1. arXiv, Free and Customizable Code Documentation with LLMs, 2024. https://arxiv.org/html/2412.00726v1
  2. Kapa.ai, Optimizing Technical Documentations for LLMs, 2024. https://www.kapa.ai/blog/optimizing-technical-documentation-for-llms
  3. Fern, Write LLM-Friendly Docs, 2026. https://buildwithfern.com/post/how-to-write-llm-friendly-documentation
  4. DrExplain, Docs-as-Code 2.0: a new standard for AI-ready user documentation, 2026. https://www.drexplain.com/press/articles/docs_as_code_2_0_a_new_standard_for_ai_ready_user_documentation/
  5. State of Docs, The State of Docs Report 2026 – AI and documentation creation, 2026. https://www.stateofdocs.com/2026/ai-and-documentation-creation
  6. Mintlify, Best Code Documentation Tools 2026, 2026. https://www.mintlify.com/library/best-code-documentation-tools
  7. Google, Google Developer Documentation Style Guide. https://developers.google.com/style

评论 0

0 / 1000