首页
/ Deno 仓库 doc/ 目录解析:为人类与 AI Agent 共建的架构文档体系、编辑规范与 docs-only CI 快速通道

Deno 仓库 doc/ 目录解析:为人类与 AI Agent 共建的架构文档体系、编辑规范与 docs-only CI 快速通道

2026-09-05 18:05:48作者:裘旻烁

Deno 仓库根部的 doc/ 目录是一个专门为"人类开发者 + AI 编码 Agent"双读者设计的长文本文档库:它存放关于代码库结构的、相对稳定的架构上下文,是进入仓库动手改代码前的必读背景。本文基于 doc/README.md 完整展开这套文档体系的定位与分工、六篇核心文档的覆盖范围、Markdown 编辑与格式化规范,并深入源码与 CI 配置,讲透"只改文档就只跑 lint"这条 docs-only 快速通道的完整实现链路。

doc/ 的定位:CLAUDE.md 的"长文伴读"

doc/README.md 开篇就明确了这个目录的读者对象与用途:

This directory holds long-form, mostly-stable context about how the Deno codebase is structured. It is meant to be read by both humans and AI coding agents before they start working in the repository.

也就是说,doc/ 里放的是长篇幅、变化较慢的结构性知识——"为什么这样分层"以及"什么东西在哪里"。原文认为这类背景知识体量大到不适合塞进 CLAUDE.md,但对节省前期探索时间至关重要。

仓库中与 doc/ 构成互补的是根目录的 CLAUDE.md。两者的分工在 README 中写得很直白:

文件 性质 内容
CLAUDE.md 简短的操作型指南 如何构建、哪条命令跑哪类测试、格式化与 lint 规则、git 工作流
doc/ 长篇幅的架构伴读 分层的"为什么"、各模块的目录地图,属于"太大放不进 CLAUDE.md、但能省大量探索时间"的背景知识

可以对照 CLAUDE.md 验证这一分工:其中 "High Level Overview" 一节给出了与 doc/ 架构文档一致的顶层结构——用户可见的界面与高层集成在 deno crate(cli/),包含 flag 解析(cli/args/flags.rs)与各工具(cli/tools/<tool>);JavaScript 运行时的组装在 deno_runtime crate(runtime/);暴露给 JS 的原生能力扩展在 ext/doc/ 的长文档则把这张"地图"展开到目录级别甚至文件级别。

这种"短操作指南 + 长架构文档"的双层结构,本质上是把 LLM/AI Agent 的上下文工程落到了仓库文件里:Agent 先读 CLAUDE.md 拿到命令级操作手册,再按需进入 doc/ 建立对代码库的分层认知,而不必自己盲目探索整个 monorepo。

Contents:六篇文档各自讲什么

doc/README.md 的 "Contents" 一节列出了目录下的全部文档及其主题,这一份清单本身就是理解 Deno 代码组织思路的索引:

  • doc/architecture.md —— 运行时的分层设计:deno CLI crate、deno_runtime crate、ext/* 扩展,以及 deno_core / libs/* 底座。README 建议"先读这篇"
  • doc/codebase-map.md —— 逐目录的地图,加上"在动任何其他东西之前值得先看懂的那几个文件"。
  • doc/testing.md —— 测试分类学(spec、unit、unit_node、node compat、WPT)以及每类测试分别由哪条命令驱动。
  • doc/ci.md —— CI 工作流如何生成、如何决定跑哪些 job,包括催生这个 doc/ 目录的 docs-only 快速通道。
  • doc/desktop-architecture.md —— deno desktop 如何把原生窗口接到 Deno 运行时上:加载模型、ABI 握手、两种传输方式与生命周期边界。
  • doc/package-management.md —— deno add / deno install <pkg> 如何添加依赖:两个 flag 解析器、配置写入器、共享安装路径,以及 --dev / --save-optional / --no-save 三个 flag(包括 optionalDependencies 为什么需要特殊处理)。

这份清单与仓库实际目录一一对应:doc/ 下确实只有 README.md 加上述六个 .md 文件。每篇都对应仓库中一个真实存在的子系统,例如 desktop-architecture 对应 cli/rt_desktop/cli/tools/desktop.rspackage-management 对应 libs/resolver/cli/tools/pmcli/tools/publish 等实现。

编辑规范:deno fmt、tools/format.js 与 80 列硬换行

README 的 "Editing these docs" 一节给出了维护这些文档的三条硬规则,这里逐一结合仓库实现展开。

1. 纯 Markdown,随仓库统一格式化

These files are plain Markdown and are formatted by deno fmt like the rest of the repository. Run tools/format.js before committing.

doc/ 下的文件不享受任何特殊待遇,与全仓库一样纳入统一格式化。入口脚本是 tools/format.js,从源码看它本质上是一个 dprint 的薄封装:

// tools/format.js(节选)
const subcommand = Deno.args.includes("--check") ? "check" : "fmt";
const configFile = join(ROOT_PATH, ".dprint.json");
const cmd = new Deno.Command("deno", {
  args: [
    "run", "-A", "--no-config",
    "npm:dprint@0.47.2",
    subcommand,
    "--config=" + configFile,
  ],
  ...
});
  • 不带参数运行 tools/format.js 执行格式化;加 --check 则只检查不落盘(CI 中的用法)。
  • 配置来自仓库根部的 .dprint.json,其中 "markdown": { "deno": true } 声明了 Markdown 使用 Deno 风格的格式化预设,并且通过 plugins 加载了 markdown-0.21.1.wasm 等 dprint 插件。
  • 注意 excludes 列表里并不包含 doc/,即 doc/ 目录整体受 .dprint.json 管辖。

2. 80 列硬换行

Keep prose hard-wrapped at 80 columns to match the existing style.

所有 doc/ 文档的正文都是硬换行到 80 列(打开 doc/README.md 可直接验证这一点)。这既是人工编辑约定,也与 dprint markdown 插件的换行行为相配合——保持硬换行意味着 diff 更稳定、代码评审时更容易逐行阅读。

docs-only 快速通道:只改文档就只跑 lint

doc/README.md 最有价值的段落是最后这段 CI 行为描述:

A pull request that only doc/ runs the lint job and nothing else. The build, test, bench and deno_core jobs are skipped, because editing Markdown cannot break the binary.

这条"编辑 Markdown 不可能弄坏二进制"的规则,在仓库里有完整、可验证的实现链路。doc/ci.md 给出了流程叙述,本节按执行顺序把它对应到源码。

第 1 步:pre-build 中的变更检测

CI 的 pre_build job 运行 tools/check_docs_only_changes.js,把结果写入 GitHub Actions 的 job output。该脚本的核心逻辑很短:

// tools/check_docs_only_changes.js(节选)
const DOCS_DIR = "doc/";
// ...
const { code, stdout, stderr } = await Deno.spawnAndWait("git", [
  "diff", "--name-only", `${baseSha}..HEAD`,
]);
// git diff 失败 -> docs_only=false,宁可多跑
if (code !== 0) { /* Defaulting to running the full CI */ }

// 仅当"至少改了一个文件且全部文件都在 doc/ 下"时才判定 docs_only
const docsOnly = changedFiles.length > 0 &&
  changedFiles.every((file) => file.startsWith(DOCS_DIR));

await writeOutput(docsOnly); // 追加 "docs_only=true|false" 到 $GITHUB_OUTPUT

两个防御性细节值得注意(与 doc/ci.md 的叙述一致):

  • 空 diff 默认 false:没有任何变更或 diff 命令失败时,宁可跑全量流水线,也不冒漏测的风险;
  • 严格前缀匹配:只有当每一个变更文件都以 doc/ 开头才判定为 docs-only,改动到 doc/ 之外任何一个文件都会让判定翻转为 false。

第 2 步:ci.ts 生成工作流并注入门控条件

doc/ci.md,工作流是生成出来的而不是手写 YAML:源头是 .github/workflows/ci.tsdeno run -A .github/workflows/ci.ts 会写出 .github/workflows/ci.generated.yml(文件头自带 GENERATED BY ./ci.ts -- DO NOT DIRECTLY EDIT 标记,仓库中可直接看到)。

ci.ts 中,检测步骤被声明为 pre_build job 的一个 step:

// .github/workflows/ci.ts(L528-L554 附近,节选)
const docsOnlyChangesCheckStep = step({
  id: "docs_only_changes",
  run: [`deno run -A tools/check_docs_only_changes.js ${{ github.event.pull_request.base.sha }}`],
  outputs: ["docs_only"] as const,
});
const preBuildJob = job("pre_build", {
  ...
  outputs: {
    skip_build: ...,
    skip_deno_core_test: ...,
    docs_only: docsOnlyChangesCheckStep.outputs.docs_only,
  },
});
// Jobs that compile or test code should not run when a PR only edits docs.
const notDocsOnly = preBuildJob.outputs.docs_only.notEquals("true");

生成后的 YAML 里能看到条件被展开到每个受门控的 job 上,例如 .github/workflows/ci.generated.yml 中:

bench:
  ...
  if: needs.pre_build.outputs.skip_build != 'true' && needs.pre_build.outputs.docs_only != 'true'

全文件中这条 docs_only != 'true' 条件重复出现在 build、build-libs、test、bench、deno-core-test 等每个"编译或测试代码"的 job 上(grep 该文件可数出十余处),与 doc/ci.md 描述的"第 2 步"完全吻合:test 依赖 build,因此跳过 build 会自动级联跳过 test。

第 3 步:lint 不受门控,ci-status 照常通过

流程的最后两步决定了为什么 docs-only PR 可以合并:

  1. lint job 刻意不挂在 docs_only 条件上,仍然会跑。deno fmt --check 覆盖 doc/,所以 Markdown 的格式与 lint 依然被检查——这就是 README 所说的"lint and nothing else"行为。
  2. ci-status 聚合 job 仍会运行并通过:它的语义是"没有任何依赖失败或被取消即通过",被 skip 的依赖既不算失败也不算取消。因此 docs-only PR 上:build/test/bench 全部 skipped,lint 成功,ci-status 变绿,分支保护要求的状态检查满足,PR 可以直接合并。

反过来,只要 PR 同时碰了 doc/ 之外的任何文件,docs_only 即为 false,完整流水线照常执行。

修改 CI 行为的正确姿势

doc/ci.md 末尾给出了一条操作规范:调整哪些 job 运行或新增门控时,ci.ts、重新生成 YAML、两者一起提交。因为生成文件是入库的,reviewer 可以直接在 diff 里看到最终生效的工作流,而不需要脑补生成逻辑。这条规范对 AI Agent 同样关键:直接编辑 .generated.yml 会在下次再生成时被覆盖,属于典型的"看似成功实则无效"的修改。

小结:一个可复用的"Agent 友好型"文档组织范式

doc/README.md 篇幅不长,但勾勒出 Deno 仓库三层文档协作的完整设计:

  1. 分层CLAUDE.md(命令级操作手册)+ doc/*(目录级架构地图),按"操作 vs 认知"切分上下文;
  2. 规范化:文档与代码同管——tools/format.js / .dprint.json 统一格式化、80 列硬换行,Markdown 一样进 lint 管线;
  3. 成本隔离:docs-only 变更通过 check_docs_only_changes.js + ci.ts 门控,让纯文档 PR 只付出一次 lint 的 CI 成本。

对想给自身仓库引入类似机制的开发者,这条链路值得整体借鉴:一个几十字节的判定脚本(tools/check_docs_only_changes.js)、一个"生成而非手写"的工作流源文件(.github/workflows/ci.ts),以及"失败默认全量、通过默认跳过"的保守判定策略。而对准备贡献 Deno 代码(包括 AI 编码 Agent)的读者,阅读顺序建议就按 doc/README.md 的推荐:先 doc/architecture.md,再按需查阅 doc/codebase-map.mddoc/testing.md,动手前先了解 doc/ci.md 中 PR 会触发哪些 job。

登录后查看全文
热门项目推荐
相关项目推荐