首页
/ goose 文档自动化管线:用「确定性脚本 + AI Recipe」让 CLI 参考文档与代码自动同步

goose 文档自动化管线:用「确定性脚本 + AI Recipe」让 CLI 参考文档与代码自动同步

2026-09-06 11:59:35作者:魏侃纯Zoe

本文基于 goose 仓库 documentation/automation/ 目录的官方文档与配套脚本源码,讲解 goose 如何构建一套文档自动化管线:在新版本发布时,通过「构建二进制 → 解析 --help → 确定性 diff → AI 合成变更说明 → AI 外科手术式更新文档」五步流程,自动检测 CLI 命令与选项的变更,并把 CLI Commands Guide 保持与代码一致。读完后你可以完整理解这套「脚本负责确定性、AI 负责语义合成」的混合管线设计,并能在本地或 GitHub Actions 中复现整条管线。

文档自动化的总体设计

goose 的 documentation/automation/ 目录存放一组自动化管线,目标是「让 goose 文档与代码变更保持同步」。每个自动化项目追踪特定类型的代码变更,并更新对应的文档:

项目 状态 追踪对象 更新对象
cli-command-tracking Planned CLI 命令与选项 CLI 文档
provider-tracking Planned 受支持的 AI Provider Provider 文档
extension-tracking Planned 内置扩展 扩展文档

目前仓库中真正落地的完整案例是 cli-command-tracking,其余项目仍在规划中。所有自动化项目遵循统一的标准目录结构:

project-name/
├── README.md             # 项目专属文档
├── TESTING.md            # 该自动化如何测试
├── config/               # 配置文件
├── scripts/              # 确定性的抽取/diff 脚本
└── recipes/              # AI 驱动的合成/更新 recipe

其设计原则可以概括为四点:模块化(每个项目自包含)、可测试(每阶段输入/输出清晰)、透明(中间文件可人工检查)、可复用(跨项目共用同一模式)。而贯穿所有项目的核心手法是混合架构(Hybrid Approach):

  • Shell/Python 脚本:负责确定性的抽取与比对——构建二进制、运行 --help、解析输出、JSON 结构比对,全程不做任何解释与推断;
  • AI Recipe:负责语义合成与文档更新——解释变更影响、生成迁移指引、以正确的格式更新文档。

之所以这样划分,是因为「抽取什么变了」必须是可复现的事实问题,而「这变更对用户意味着什么、文档该怎么改」是需要语言理解的能力问题。两者通过 JSON/Markdown 中间文件解耦,每一步都可以单独重跑、单独检查。

CLI 命令追踪管线的架构

cli-command-tracking/README.md 描述了管线的四阶段流水线,目标是让 CLI Commands Guide 始终与代码同步:

┌─────────────────────────────────────────────────────────────────┐
│                    EXTRACTION (确定性)                             │
├─────────────────────────────────────────────────────────────────┤
│  extract-cli-structure.sh → extract-cli-structure.py             │
│  ↓                                                               │
│  cli-structure.json (commands, options, subcommands, aliases)    │
└─────────────────────────────────────────────────────────────────┘
                                  ↓
┌─────────────────────────────────────────────────────────────────┐
│                    DIFFING (确定性)                                │
├─────────────────────────────────────────────────────────────────┤
│  diff-cli-structures.py                                          │
│  ↓                                                               │
│  cli-changes.json (added, removed, modified commands/options)    │
└─────────────────────────────────────────────────────────────────┘
                                  ↓
┌─────────────────────────────────────────────────────────────────┐
│                    SYNTHESIS (AI 驱动)                             │
├─────────────────────────────────────────────────────────────────┤
│  synthesize-cli-changes.yaml                                     │
│  ↓                                                               │
│  cli-changes.md (人类可读的变更文档)                                │
└─────────────────────────────────────────────────────────────────┘
                                  ↓
┌─────────────────────────────────────────────────────────────────┐
│                    UPDATE (AI 驱动)                               │
├─────────────────────────────────────────────────────────────────┤
│  update-cli-commands.yaml                                        │
│  ↓                                                               │
│  goose-cli-commands.md (已更新) + update-summary.md              │
└─────────────────────────────────────────────────────────────────┘

各阶段之间全部通过 output/ 目录下的 JSON/Markdown 文件通信,这是整个管线「透明、可测试」的关键:

文件 生产者 消费者 用途
old-cli-structure.json extract-cli-structure.sh diff-cli-structures.py 旧版本 CLI 结构
new-cli-structure.json extract-cli-structure.sh diff-cli-structures.py 新版本 CLI 结构
cli-changes.json diff-cli-structures.py synthesize-cli-changes.yaml 检测到的变更(结构化)
cli-changes.md synthesize-cli-changes.yaml update-cli-commands.yaml 人类可读的变更文档
update-summary.md update-cli-commands.yaml 人工审查 文档更新摘要

版本如何被确定

管线支持自动版本检测,逻辑实现在 run-pipeline.sh 中:

  • 旧版本:通过 gh release list 取最近第二个 release tag;gh 不可用时回退到 git tag --sort=-v:refname 取第二个匹配 vX.Y.Z 的 tag;
  • 新版本:取最近的 release tag,或读取 CI 注入的 RELEASE_TAG 环境变量;
  • 测试未发布变更:显式传 HEAD 即可从当前代码构建二进制。

实操:本地运行整条管线

环境变量

变量 是否必需 默认值 说明
GOOSE_REPO 本地运行时必需 无(脚本默认 $HOME/Development/goose goose 仓库根目录路径
CLI_COMMANDS_PATH $GOOSE_REPO/documentation/docs/guides/goose-cli-commands.md 目标文档文件完整路径
RELEASE_TAG GitHub Actions 指定新版本时使用

前置条件(来自 TESTING.md):Python 3.7+、Rust 工具链(构建 goose 时)、jq(JSON 处理)、已安装 goose CLI(运行 recipe)、可访问 goose 仓库的 Git。

一键运行

# 设置 goose 仓库路径
export GOOSE_REPO=/path/to/goose

# 自动检测版本,跑完整管线
./scripts/run-pipeline.sh

# 或显式指定新旧版本
./scripts/run-pipeline.sh v1.17.0 v1.19.0

# 测试未发布的变更
./scripts/run-pipeline.sh v1.19.0 HEAD

run-pipeline.sh 的执行流程是:先对旧版本、新版本分别抽取结构并统计命令数(jq '.commands | length'),再运行 diff 脚本得到 cli-changes.json只有当 has_changestrue才继续执行两个 AI recipe 阶段,否则直接输出「No Changes Detected」并结束。AI 阶段调用 goose run --recipe ... 时,脚本会用 sed/grep 过滤掉 ANSI 转义和会话日志行(starting sessionsession id:text_editor 等),避免噪声混入输出;同时用 PIPESTATUS[0] 检查 goose 进程本身是否失败,而不是被 grep 的退出码误导。

分步手动执行

# 1. 抽取 CLI 结构
./scripts/extract-cli-structure.sh v1.17.0 > output/old-cli-structure.json
./scripts/extract-cli-structure.sh v1.19.0 > output/new-cli-structure.json

# 2. 检测变更
python3 scripts/diff-cli-structures.py output/old-cli-structure.json \
                                       output/new-cli-structure.json \
                                       > output/cli-changes.json

# 3. 生成人类可读的变更文档
cd output && goose run --recipe ../recipes/synthesize-cli-changes.yaml

# 4. 更新 goose-cli-commands.md
cd output && goose run --recipe ../recipes/update-cli-commands.yaml

跳过命令配置

有些命令被有意排除在抽取与文档追踪之外,配置在 skip-commands.json

{
  "description": "Commands to skip during extraction (not documented intentionally)",
  "skip_commands": [
    {
      "name": "term",
      "reason": "Terminal integration documented via @goose/@g aliases"
    }
  ]
}

增删跳过命令只需编辑该配置,无需改代码——抽取脚本启动时通过 load_skip_commands() 读取该文件并据此跳过对应子命令(见 extract-cli-structure.py)。

确定性抽取阶段:脚本在做什么

获取指定版本的二进制

extract-cli-structure.sh 是抽取阶段的入口,按版本类型走两条路:

  • release tag(vX.Y.Z 格式):通过官方 download_cli.sh 下载对应版本预构建二进制(is_release_tag() 用正则 ^v[0-9]+\.[0-9]+\.[0-9]+$ 判断),省去编译时间;
  • HEAD 或其他 git ref:在 GOOSE_REPO 中构建。其中非 HEAD 的 ref 会先用 git rev-parse 校验版本存在,再通过 git worktree add 把该版本检出到临时目录执行 cargo build --release,构建完成后把二进制拷出并移除 worktree,保证不污染主工作区。

拿到二进制后,脚本打印 --version 输出确认版本,最后调用 python3 extract-cli-structure.py <binary> <version> 完成真正的解析。

解析 --help 输出为命令树

extract-cli-structure.py 是抽取的核心:它递归地对命令树中每个节点执行 <command> --help,用正则把 clap 风格的帮助文本解析为结构化 JSON。关键解析函数包括:

  • parse_about():取 Usage: 行之前的第一行作为命令描述;
  • parse_aliases():匹配 [aliases: x, y] 模式提取别名(从帮助文本前 500 字符中找);
  • parse_options():定位 Options: 段,按「行首为 - 的缩进行」切分选项块;parse_option_block() 再对每个块提取 短标志-f)、长标志--format)、值名<FORMAT>)、帮助文本(含首行内联帮助)、默认值[default: ...])、可选值[possible values: ...])六个字段;
  • parse_subcommands():解析 Commands: 段中的子命令名与别名,自动跳过 clap 生成的 help 子命令;
  • extract_command_structure():递归入口,对每个子命令先检查是否在 SKIP_COMMANDS 列表中,再深入其子树。

最终输出的 JSON 顶层结构为 {version, source_version, extracted_at, binary_path, commands: [...]},每个命令节点包含 name / about / aliases / usage / options / subcommands。所有 --help 调用带 10 秒超时,超时只会告警并返回空串而不中断整个抽取。

确定性 diff:变更如何被分类

diff-cli-structures.py 的算法分为三步:

  1. 展平flatten_commands() 把嵌套的命令树按全路径(如 session list)展开为字典,便于按路径逐一对比;
  2. 逐字段比对compare_commands() 对同一路径的旧新命令比较 aboutaliasesusage,选项层面由 compare_options() 按「长标志优先、否则短标志」作为键,逐一比对 short / long / value_name / help / default / possible_values 六个字段,任一字段不同即记入 modified
  3. 破坏性变更归类categorize_breaking_changes() 将变更打上类型与严重级别标签。
变更类型 严重级别 判定逻辑
command_removed high 命令路径从新版本消失
option_removed high 选项标志键从选项中消失
option_renamed high 短/长标志发生变化
default_changed medium 默认值改变(行为可能隐性变化)
enum_values_removed high [possible values] 中出现值被移除
alias_removed medium 命令别名被移除(可能破坏用户肌肉记忆)

输出 JSON 包含 has_changes 布尔值、summary(各计数,其中 breaking 只统计 high 级别)、changes.commands.{added,removed,modified}breaking_changes 数组。summary.breaking_changes 的计数口径在 main() 中可以确认:只统计 severity == 'high' 的条目。

AI 阶段:两个 goose Recipe

管线的后两步用 goose 的 recipe 机制实现,recipe 文件即提示词工程——instructions 定义系统级约束,prompt 触发执行,均依赖内置的 developer 扩展(提供 text_editor 等工具)。

synthesize-cli-changes.yaml:生成变更说明

synthesize-cli-changes.yaml 读取三份输入(cli-changes.json 变更 diff + 新旧两份结构 JSON 作上下文),产出 cli-changes.md。recipe 指令中规定输出必须包含:摘要统计、Breaking Changes(每条附影响说明与迁移指引,破坏性变更永远排在最前)、New Commands(含用途与关键选项)、Removed Commands(含替代方案)、Modified Commands(描述/选项/别名的逐项对比)、Non-Breaking Changes。

它的分析准则也很具体:从用户影响而非实现细节的角度解释变更;为破坏性变更给出新旧用法对照示例;利用命令名与选项名推断变更意图;跳过纯排版级的帮助文本微调;对空类别整节跳过。

update-cli-commands.yaml:外科手术式更新目标文档

update-cli-commands.yaml 是整条管线中约束最严格的一步。它的核心目标是:文档永远描述 CLI 的当前状态,而不是变更历史——选项被删就从文档删掉,选项被加就补上,绝不写「已移除」「已新增」这类措辞。为此 recipe 列出了一组硬性禁令(ABSOLUTE PROHIBITIONS):

  1. 只有当 cli-changes.md 明确写「命令 X 被移除」时才删除整节命令;
  2. 绝不改分区标题(如 ### Task Execution### Session Management);
  3. 绝不在没有明确记录的情况下重命名选项;
  4. 绝不重复创建已存在的分区,只原地更新;
  5. 绝不删除分区之间的水平线 ---
  6. 绝不重写示例,只更新实际变化的那个标志/选项。

更新策略上,它要求按「读 cli-changes.md 识别全部变更 → 逐条施加最小化编辑(surgical edits,用 str_replace 精确匹配)→ 保持目标文档既有风格(#### 命令标题、加粗选项名的 bullet 列表、带语言标识的代码块、admonition 提示框)→ 自查确认」的顺序执行,并在完成后额外生成 update-summary.md 供人工审查(含「已应用变更清单 + 更新分区 + 验证清单」)。目标文档路径优先取 CLI_COMMANDS_PATH 环境变量,否则回退为 $GOOSE_REPO/documentation/docs/guides/goose-cli-commands.md

从源码结构看,run-pipeline.sh 在调用该 recipe 前会显式 export CLI_COMMANDS_PATH="${GOOSE_REPO}/documentation/docs/guides/goose-cli-commands.md",保证 CI 与本地行为一致。

追踪范围与 GitHub Actions 集成

追踪什么

该管线检测的完整范围(来自 cli-command-tracking/README.md):

命令层面:新增/删除命令、描述变更、别名增删、子命令增删。 选项层面:新增/删除选项、帮助文本变更、默认值变更、可选值(枚举)变更、短/长标志变更。 破坏性变更:按上文严重级别表自动归类。

GitHub Actions 工作流

自动化通过 docs-update-cli-ref.yml 接入 GitHub Actions:

  • 触发:新版本发布时自动触发,或手动触发用于测试;
  • 流程:为两个版本分别构建 goose、抽取 CLI 结构、检测变更、更新文档;
  • 输出:检测到变更时创建一个包含更新后 goose-cli-commands.md 的 PR;
  • 工件old-cli-structure.jsonnew-cli-structure.jsoncli-changes.jsoncli-changes.mdpipeline.log 都会作为 artifacts 上传,可下载检查。

工作流支持三个输入参数:

输入 说明 默认值
old_version 旧版本 tag 从 release 自动检测
new_version 新版本 tag HEAD
dry_run 只生成文件、不创建 PR true

在 fork 中测试时,需要在 fork 的 Actions 设置里配置 ANTHROPIC_API_KEY secret,可选配置 GOOSE_PROVIDER(默认 anthropic)与 GOOSE_MODEL 变量;手动触发时建议 dry_run: true,跑完后从 workflow run 页面下载 artifacts ZIP 检查中间产物。

用已知变更做回归验证

TESTING.md 给出了三种典型测试用例,可直接复用:

# 用例 1:版本间新增了命令
./scripts/run-pipeline.sh v1.13.0 v1.14.0
jq '.changes.commands.added' output/cli-changes.json

# 用例 2:版本间修改了选项
./scripts/run-pipeline.sh v1.14.0 v1.15.0
jq '.changes.commands.modified' output/cli-changes.json

# 用例 3:同版本对比(应无变更)
./scripts/run-pipeline.sh v1.14.0 v1.14.0
jq '.has_changes' output/cli-changes.json   # 期望输出 false

抽取阶段的验证则用 jq 直接抽查结构文件:jq '.commands[] | select(.name == "session")' output/test-extraction.json 检查特定命令、jq '.commands[].name' | grep -v term 确认跳过命令已排除。

常见问题排查

TESTING.md 沉淀了几个典型故障的排查路径:

  • macOS Keychain 提示:goose 启动时可能尝试读取已存凭据而触发钥匙串访问提示;CI runner 没有 keychain,可能需通过 keyring: false 之类的配置或环境变量禁用凭据加载(文档中标注为待调查项);
  • 旧版本构建失败:先 git tag 确认版本存在,再手动 git worktree add /tmp/goose-test v1.14.0 && cargo build --release 定位依赖问题;
  • 抽取超时:调大 extract-cli-structure.pyrun_help_commandtimeout=10
  • diff 出现意外变更:可能是帮助文本排版格式变了,直接对两版二进制的原始 --help 输出做 diff 对比;
  • AI recipe 失败:先用 ls -lhjq empty 确认三份输入 JSON 存在且格式合法。

扩展指南与维护建议

按照总 README 的约定,新增一个自动化项目只需四步:创建 documentation/automation/your-project/ 子目录、遵循标准结构(README、TESTING、config、scripts、recipes)、按需创建 GitHub Actions 工作流、最后更新 automation 总 README 的项目表格。

维护 cli-command-tracking 本身时,README 给出的纪律是:先用测试版本本地跑 ./scripts/run-pipeline.sh;将生成文件与实际 CLI 变更逐一核对;在 fork 中用 dry-run 模式验证工作流;把设计决策回写进 README。此外 TESTING.md 还建议保存「已知正确」的输出作为回归测试数据(如 test-data/v1.14.0-to-v1.15.0-changes.json),防止后续脚本改动破坏既有行为。

这套管线值得借鉴的地方在于:它没有把「文档更新」整体交给 AI,而是把可复现的事实提取(构建、--help、解析、diff)留给脚本、把需要语言判断的合成与编辑留给 AI,中间用可检查的 JSON/Markdown 文件交接——每一步失败都可以定位到具体阶段,每一步的产物都可以人工复核,这正是它能在发布流程中无人值守运行的前提。

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