goose 文档自动化管线:用「确定性脚本 + AI Recipe」让 CLI 参考文档与代码自动同步
本文基于 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_changes 为 true 时才继续执行两个 AI recipe 阶段,否则直接输出「No Changes Detected」并结束。AI 阶段调用 goose run --recipe ... 时,脚本会用 sed/grep 过滤掉 ANSI 转义和会话日志行(starting session、session 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 的算法分为三步:
- 展平:
flatten_commands()把嵌套的命令树按全路径(如session list)展开为字典,便于按路径逐一对比; - 逐字段比对:
compare_commands()对同一路径的旧新命令比较about、aliases、usage,选项层面由compare_options()按「长标志优先、否则短标志」作为键,逐一比对short / long / value_name / help / default / possible_values六个字段,任一字段不同即记入modified; - 破坏性变更归类:
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):
- 只有当
cli-changes.md明确写「命令 X 被移除」时才删除整节命令; - 绝不改分区标题(如
### Task Execution、### Session Management); - 绝不在没有明确记录的情况下重命名选项;
- 绝不重复创建已存在的分区,只原地更新;
- 绝不删除分区之间的水平线
---; - 绝不重写示例,只更新实际变化的那个标志/选项。
更新策略上,它要求按「读 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.json、new-cli-structure.json、cli-changes.json、cli-changes.md、pipeline.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.py 中
run_help_command的timeout=10; - diff 出现意外变更:可能是帮助文本排版格式变了,直接对两版二进制的原始
--help输出做diff对比; - AI recipe 失败:先用
ls -lh与jq 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 文件交接——每一步失败都可以定位到具体阶段,每一步的产物都可以人工复核,这正是它能在发布流程中无人值守运行的前提。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00