gstack /document-release 实战指南:发布后文档同步、Diataxis 覆盖度地图与文档债务上报
【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack
gstack 的/document-release技能是发布流水线中“Technical Writer”角色:它在/ship提交代码之后、PR 合并之前运行,自动读取全部项目文档并与 diff 交叉比对,构建 Diataxis(reference / how-to / tutorial / explanation)覆盖度地图,修补 README、ARCHITECTURE、CONTRIBUTING、CLAUDE.md、TODOS.md 与 CHANGELOG 的漂移,把文档债务写进 PR body。读完本文,你将完整掌握这条“post-ship docs”工作流的每一步命令、判定规则与底层防护机制(redaction 扫描、banner tripwire、标题同步、跨模型复审),并能在自己的项目里复现同样的自动化文档审计。
技能定位:自动为主、风险才停
/document-release的运行时机被明确框定在两个节点之间:after/ship(代码已提交、PR 已存在或即将存在)但before the PR merges。它的工作目标是让项目中每个文档文件都准确、最新、且以“友好、面向用户”的语气撰写(见 document-release/SKILL.md)。
该技能在 gstack 技能表中被定位为“Technical Writer”:Update all project docs to match what you just shipped. Catches stale READMEs automatically.(见 README.md 技能列表)。其 frontmatter 声明了触发词与版本(见 SKILL.md 头部):
name: document-release,version: 1.0.0,preamble-tier: 2triggers:update docs after ship、document what changed、post-ship docsallowed-tools:Bash、Read、Write、Edit、Grep、Glob、AskUserQuestion
行为基调是“mostly automated”:明显的 factual 更新直接执行,只在风险性或主观决策上停下来问。技能把“何时停、何时不停、何事绝不干”写成了三张清单,这是理解整套工作流安全边界的关键:
Only stop for(仅以下情况停下来问):
- 风险性/存疑的文档改动(narrative、philosophy、security、删除、大段重写)
- VERSION 升级决策(若尚未升级)
- 需要新增的 TODOS 条目
- 跨文档的叙事性(非事实性)矛盾
Never stop for(以下情况绝不打断用户):
- 明显来自 diff 的事实性更正
- 向表格/列表添加条目
- 更新路径、计数、版本号
- 修复过期的交叉引用
- CHANGELOG 语气润色(轻微措辞调整)
- 标记 TODOS 完成
- 跨文档事实性不一致(如版本号不匹配)
NEVER do(三条绝对禁令):
- 覆写、替换或重新生成 CHANGELOG 条目——只做措辞润色,保留全部内容
- 未经询问就 bump VERSION——版本变更必须走 AskUserQuestion
- 对 CHANGELOG.md 使用
Write工具——必须用Edit做精确old_string匹配
这三张清单贯穿后续所有 Step,是技能“自动化但不失控”的设计核心。
工程结构:骨架 + 按需加载的 Section
/document-release是 gstack 中典型的“carved skill”:SKILL.md 只是决策树骨架,真正的步骤正文放在按需读取的 section 里。SKILL.md 顶部标注了生成来源:
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->即 SKILL.md 由 SKILL.md.tmpl 经bun run gen:skill-docs渲染生成,{{SECTION_INDEX:document-release}}占位符对应 sections/manifest.json 中的 section 注册表。该 manifest 声明了唯一的 section:
| id | 文件 | 覆盖范围 |
|---|---|---|
release-body | release-body.md | Steps 2-9:逐文件审计、自动更新、风险变更询问、CHANGELOG 润色、跨文档一致性、TODOS 清理、VERSION bump、提交与 PR body |
骨架中对应的 STOP 指令要求:在进入 Steps 2-9 之前,必须完整读取sections/release-body.md并逐步执行,“Do not work from memory — that section is the source of truth for this step”。这种设计把技能主体保持在 token 预算内,同时保证执行时依据的是完整正文而非模型的“记忆”。
Step 0:检测平台与 base branch
工作流第一步是检测 git 托管平台,因为它决定了后续所有 PR/MR 命令的形态:
git remote get-url origin 2>/dev/null判定顺序:
- URL 含
github.com→GitHub - URL 含
gitlab→GitLab - 否则看 CLI 可用性:
gh auth status成功 → GitHub(覆盖 GitHub Enterprise);glab auth status成功 → GitLab(覆盖自托管);都失败 →unknown,仅用 git 原生命令
随后确定“base branch”——即该 PR/MR 的目标分支,若无 PR 则用仓库默认分支。按平台分别探测:
GitHub:
gh pr view --json baseRefName -q .baseRefNamegh repo view --json defaultBranchRef -q .defaultBranchRef.name
GitLab:
glab mr view -F json提取target_branchglab repo view -F json提取default_branch
Git 原生兜底(unknown 平台或 CLI 失败时):
git symbolic-ref refs/remotes/origin/HEAD | sed 's|refs/remotes/origin/||'- 失败则
git rev-parse --verify origin/main→ 用main - 再失败则
git rev-parse --verify origin/master→ 用master - 全部失败回退到
main
检测出的 base 分支名要在后续所有git diff、git log、git fetch、git merge与 PR/MR 创建命令中替换掉指令里的<base>/<default>占位符。
Step 1:Pre-flight 与 Diff 分析
前置检查:若当前正处在 base 分支上,直接中止——“You're on the base branch. Run from a feature branch.”
然后收集变更上下文(三条命令构成审计的输入面):
git diff <base>...HEAD --stat git log <base>..HEAD --oneline git diff <base>...HEAD --name-only接着发现仓库内所有文档文件(限定 maxdepth 2,排除.git、node_modules、.gstack、.context):
find . -maxdepth 2 -name "*.md" -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.gstack/*" -not -path "./.context/*" | sort最后把变更归入四类文档相关类别:
- New features— 新文件、新命令、新技能、新能力
- Changed behavior— 修改的服务、更新的 API、配置变化
- Removed functionality— 删除的文件、移除的命令
- Infrastructure— 构建系统、测试基础设施、CI
输出一句摘要:“Analyzing N files changed across M commits. Found K documentation files to review.”
Step 1.5:Diataxis 覆盖度地图(爆炸半径分析)
这是/document-release最有辨识度的设计:在触碰任何文档文件之前,先构建“已发布内容 vs 已文档化内容”的覆盖度地图。它借用了 Diataxis 框架(tutorial / how-to / reference / explanation)——但作为**审计透镜(audit lens)**而非生成工具。gstack 对 Diataxis 的完整论证见 docs/explanation-diataxis-in-gstack.md,配套的端到端用法见 docs/howto-document-a-shipped-feature.md。
第 1 步:从 diff 提取 public surface 变化。扫描git diff <base>...HEAD中的:
- 新导出的函数、类、命令、CLI flag、配置项、API 端点
- 新技能、工作流或用户可见能力
- 重命名或被移除的 public surface(模块、命令、功能)
- 新环境变量、feature flag、配置旋钮
第 2 步:对每个新增/变化的 public surface 条目评估四象限覆盖:
Coverage map: [entity] [reference?] [how-to?] [tutorial?] [explanation?] /new-skill ✅ AGENTS.md ❌ ❌ ❌ --new-flag ✅ README ✅ README ❌ ❌ FooProcessor ❌ ❌ ❌ ❌四个象限的定义(注意 reference 标注的是“在哪”而不是简单打勾):
- Reference— 事实性描述:它是什么、API、选项(README 表格、AGENTS.md 技能列表、API 文档)
- How-to— 任务导向:“如何用这个做 X”(README 示例、CONTRIBUTING 工作流)
- Tutorial— 学习导向:给新手的分步演练(getting started 指南)
- Explanation— 理解导向:“为什么这样设计”(ARCHITECTURE 决策、设计理据)
第 3 步:输出覆盖度地图并分级。零覆盖条目是critical gaps(进入 Step 3 重点处理);仅有 reference 覆盖的条目是common gaps(记入 PR body)。
第 4 步:架构图漂移检测。若 ARCHITECTURE.md(或任何文档)含 ASCII 图或 Mermaid 块,从中提取实体名(模块、服务、数据流),与 diff 交叉比对,标记出在代码中被重命名、拆分、移除或迁移的图内实体。
覆盖度地图同时喂给 Steps 2-3(审什么、改什么)和 Step 9(PR body 中的文档债务总结)。关键纪律是:Do NOT auto-generate missing documentation pages — flag gaps only(只标记缺口、绝不自动生成缺失文档页);发现显著缺口时,建议用户运行/document-generate补齐。
Steps 2-4:逐文件审计、自动更新与风险变更询问
进入这一段前,技能要求完整读取 sections/release-body.md。以下规则是通用启发式,适用于任何仓库,并非 gstack 专属。
Step 2:Per-File Documentation Audit
逐个读取文档文件并与 diff 交叉比对:
README.md:
- 是否描述了 diff 中可见的全部功能与能力?
- 安装/搭建说明与变更是否一致?
- 示例、demo、用法描述是否仍然有效?
- 排障步骤是否仍然准确?
ARCHITECTURE.md:
- ASCII 图与组件描述是否与当前代码一致?
- 设计决策与“why”解释是否仍然准确?
- 保持保守——只更新被 diff 明确推翻的内容。架构文档描述的是低频变化物。
CONTRIBUTING.md(新贡献者冒烟测试):
- 像一个全新贡献者那样走一遍搭建步骤;
- 列出的命令是否准确?每一步是否会成功?
- 测试分层描述是否与当前测试基础设施一致?
- 工作流描述(dev setup、operational learnings 等)是否最新?
- 标记任何会让首次贡献者失败或困惑的内容。
CLAUDE.md / 项目指令文件:
- 项目结构章节是否匹配实际文件树?
- 列出的命令与脚本是否准确?
- 构建/测试说明是否与 package.json(或等价物)一致?
其他任意 .md 文件:读取文件、判断其目的与受众,与 diff 交叉比对是否矛盾。
对每个文件,把需要的更新分为两类:
- Auto-update— diff 明确支持的事实性更正:向表格加一行、更新文件路径、修正计数、更新项目结构树
- Ask user— 叙事性变更、章节删除、安全模型变更、大段重写(单节超过约 10 行)、相关性强歧义、新增整节
Step 3:Apply Auto-Updates
用 Edit 工具直接应用所有清晰、事实性的更新。每个被修改的文件必须输出一行“具体改了什么”的摘要——不是“Updated README.md”,而是“README.md: added /new-skill to skills table, updated skill count from 9 to 10.”
绝不自动更新的内容:
- README 的开头介绍或项目定位
- ARCHITECTURE 的哲学或设计理据
- 安全模型描述
- 任何文档的整节删除
Step 4:Ask About Risky/Questionable Changes
对 Step 2 识别出的每个风险/存疑更新,走 AskUserQuestion,包含:项目名、分支、哪个文档文件、正在审什么、具体的文档决策、RECOMMENDATION: Choose [X] because [一句话理由],以及包含C) Skip — leave as-is的选项。每次得到回答后立即应用被批准的修改。
Step 5:CHANGELOG Voice Polish(sell-test 评分)
文档中用加粗的 “CRITICAL” 强调:绝不 clobber CHANGELOG 条目。这一步只润色语气,不重写、不替换、不再生成内容。技能还引用了一起真实事故作为约束依据——某次代理把本应保留的 CHANGELOG 条目替换掉了——因此这里把规则写死:
- 先通读整个 CHANGELOG.md,理解已有内容
- 只修改既有条目内部的措辞;绝不删除、重排、替换条目
- 绝不从零再生成条目——条目由
/ship基于真实 diff 和提交历史写出,是 source of truth;你在润色散文,不是在重写历史 - 某条目看起来错误或残缺时,走 AskUserQuestion,不要悄悄修
- 用 Edit 工具做精确
old_string匹配——绝不用 Write 覆写 CHANGELOG.md
若本分支未修改 CHANGELOG:跳过本步。若修改了,则用sell-test(Diataxis 评分量规)逐条打分 0-3:
- 1 分— 回答了“What changed?”(reference:点名了功能/修复)
- 1 分— 回答了“Why should I care?”(explanation:用户影响、消除了什么痛点)
- 1 分— 回答了“How do I use it?”(how-to:命令、flag 或文档链接)
低于 2 分的条目需要重写,3 分是 gold。配套语气规则:
- 以用户“现在能做什么”开头,而不是实现细节
- “You can now...” 而不是 “Refactored the...”
- 把读起来像 commit message 的条目标记并重写
- 内部/贡献者变更归入独立的
### For contributors小节 - 轻微语气调整自动修;若重写会改变语义,走 AskUserQuestion
这一声调标准与仓库的 docs/CHANGELOG_STYLE.md 呼应:后者规定每个## [X.Y.Z]条目的 release-summary 结构(两行粗体标题、3-5 句导语、“The X numbers that matter” 指标表、“What this means for [audience]” 收尾段)与禁用词表(无 em dash、无 AI 词汇、真实数字真实文件真实命令),以及### Itemized changes下必须署名社区贡献者(Contributed by @username)。/document-release的 sell-test 是对同一套声音纪律的“逐条打分”实现。
Step 6:跨文档一致性与可发现性
单文件审计之后做全局一致性 pass,共五项检查:
- README 的功能/能力列表是否与 CLAUDE.md(或项目指令)的描述一致?
- ARCHITECTURE 的组件列表是否与 CONTRIBUTING 的项目结构描述一致?
- CHANGELOG 的最新版本是否与 VERSION 文件一致?
- Discoverability(可发现性):每个文档文件是否都能从 README.md 或 CLAUDE.md 到达?若 ARCHITECTURE.md 存在但两个入口文件都没链接到它,标记之——每个文档都应可从两个入口文件之一发现
- 标记文档间矛盾:清晰的事实性不一致(如版本号不匹配)自动修;叙事性矛盾走 AskUserQuestion
Step 7:TODOS.md 清理
这是与/shipStep 5.5 互补的第二遍。规范的 TODO 条目格式定义在 review/TODOS-format.md:按 skill/组件分节(## Browse、## Ship…),节内按优先级 P0→P4 排序;每个条目是 H3,必填 What / Why / Context / Effort / Priority,可选 Depends on / Blocked by;完成项移入## Completed并附**Completed:** vX.Y.Z.W (YYYY-MM-DD)。
若 TODOS.md 不存在,跳过本步。存在则做三件事:
- 已完成但未标记的条目:把 diff 与 open TODO 交叉比对。若某 TODO 显然被本分支的变更完成,移入 Completed 区并附
**Completed:** vX.Y.Z.W (YYYY-MM-DD)。保持保守——只标记 diff 中有明确证据的条目 - 描述需要更新的条目:若某 TODO 引用的文件/组件被大改,其描述可能过期。走 AskUserQuestion 确认该 TODO 应更新、完成还是保持原样
- 新的延期工作:检查 diff 中的
TODO、FIXME、HACK、XXX注释,凡代表有意义延期工作(非琐碎内联备注)的,走 AskUserQuestion 询问是否收进 TODOS.md
Step 8:VERSION Bump 决策
标题同样标了CRITICAL — NEVER BUMP VERSION WITHOUT ASKING。
- VERSION 文件不存在:静默跳过
- 先检查本分支是否已改过 VERSION:
git diff <base>...HEAD -- VERSION未 bump 时,走 AskUserQuestion,选项:
- RECOMMENDATION: Choose C (Skip)——纯文档变更很少值得 bump
- A) Bump PATCH (X.Y.Z+1) —— 文档变更与代码变更一起发布
- B) Bump MINOR (X.Y+1.0) —— 若这是一次独立的重大发布
- C) Skip — no version bump needed
已 bump 时——不要静默跳过,要验证 bump 是否仍覆盖本分支全部变更范围:
- a. 读当前 VERSION 对应的 CHANGELOG 条目,它描述了哪些功能?
- b. 读完整 diff(
--stat和--name-only):是否存在重要变更(新功能、新技能、新命令、大重构)却没有出现在该版本条目中? - c. 若 CHANGELOG 条目覆盖了一切:输出 “VERSION: Already bumped to vX.Y.Z, covers all changes.”
- d. 若存在未覆盖的重要变更:走 AskUserQuestion,说明当前版本覆盖什么、又新了什么,选项为 A) Bump to next patch(给新变更自己的版本)/ B) Keep current version(把新变更并入现有 CHANGELOG 条目)/ C) Skip(版本保持不动,以后再处理)
技能把这条设计原则总结为:“一个为‘功能 A’设置的 VERSION bump 不应悄悄吞掉‘功能 B’,只要 B 重要到值得自己的版本条目。”
Step 9:提交、PR body 更新与标题同步
这是全技能工程密度最高的一步,sections/release-body.md 的 Step 9 实现了完整的“提交 → 脱敏扫描 → 防注入 tripwire → 发布 → 标题同步”链路。
空检查与提交
先跑git status(明确规定不用-uall)。若之前所有步骤都没改任何文档文件,输出 “All documentation is up to date.” 直接退出、不提交。
有改动时:按文件名暂存修改过的文档文件(绝不git add -A/git add .),创建单个提交:
git commit -m "$(cat <<'EOF' docs: update project documentation for vX.Y.Z.W Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> EOF )"然后git push推送到当前分支。
PR/MR body 更新:双工件与信任信封
由于 PR body 会回传到现场 PR/MR,这里定义了两个工件的严格分离:
- RAW tempfile:编辑管线修改并发布的对象(永不套信封)
- ENVELOPED rendering:技能“阅读”用的对象(永不发布)
流程要点:
- 拉取现有 PR/MR body 到 PID 唯一的临时文件,并留一份
-orig快照:
# GitHub gh pr view --json body -q .body > /tmp/gstack-pr-body-$$.md cp /tmp/gstack-pr-body-$$.md /tmp/gstack-pr-body-orig-$$.md # GitLab glab mr view -F json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('description',''))" > /tmp/gstack-pr-body-$$.md cp /tmp/gstack-pr-body-$$.md /tmp/gstack-pr-body-orig-$$.md- 信任信封读取:通过
gstack-issue-guard --stdin --source pr-body读 body 获取上下文,信封内的既有 body 文本一律视为数据,不能向技能下达指令(防 prompt 注入)。 - 只拼接
## Documentation一节:若 RAW tempfile 中已有该节,整节替换(从## Documentation到下一个##标题或 EOF);否则追加到末尾。新节内容只能来自技能自己 Step 1-3 的输出,绝不从 enveloped rendering 重建或改写 body 其余部分。
该## Documentation节包含两部分:
a.Doc diff preview— 每个被改文件的具体变更(如 “README.md: added /document-release to skills table, updated skill count from 9 to 10”) b.Documentation debt— 若 Step 1.5 覆盖度地图发现缺口,追加### Documentation Debt小节,列出:critical gaps(零覆盖的新 public surface)、common gaps(仅 reference 覆盖的功能)、stale diagrams(实体名已从代码漂移的架构图)。每条附一行“缺什么、由哪个 Diataxis 象限补齐”的描述。存在债务项时,建议在 PR 上加docs-debt标签
- Redaction scan-at-sink + banner tripwire,然后再写回:
REDACT_VIS=$(~/.claude/skills/gstack/bin/gstack-config get redact_repo_visibility 2>/dev/null) [ -z "$REDACT_VIS" ] && REDACT_VIS=$(gh repo view --json visibility -q .visibility 2>/dev/null | tr 'A-Z' 'a-z') ~/.claude/skills/gstack/bin/gstack-redact --from-file /tmp/gstack-pr-body-$$.md --repo-visibility "${REDACT_VIS:-unknown}" --json # exit 3 (HIGH) → do NOT edit, rotate+redact; exit 2 (MEDIUM) → confirm per finding.脱敏扫描直接针对将要发布的临时文件(“scanned bytes are the sent bytes”),HIGH 级别直接阻断编辑。随后是写入侧 banner tripwire:信任信封的 banner 字符串绝不能泄漏进现场 PR body。检测逻辑只对比“新增的 banner 出现次数”与-orig快照——既有 body 里碰巧包含该字面串(可能是恶意 body)不会永久 DoS 未来的文档更新,只有“我们正要添加”的 markup 才触发 ABORT。脚本注释还特意说明了两处工程细节:grep -c无匹配时本身打印 0(追加 fallback echo 会双打印并让-gt比较走进干净分支、恰好在这个防护上 fail open);每个 bash 块在独立 shell 中运行、$$不同,因此 fetch/splice/scan/tripwire/edit 必须在同一个 shell 内完成,tripwire 对缺失文件 fail closed。
- 发布:GitHub 用
gh pr edit --body-file;GitLab 用 Read 工具读取文件后经 heredoc 传给glab mr update -d,避免 shell 元字符问题。 - 清理临时文件;若
gh pr view/glab mr view失败(无 PR/MR)则跳过并提示;若 edit 命令失败则警告 “Could not update PR/MR body — documentation changes are in the commit.” 并继续。
PR/MR 标题同步
PR 标题必须始终以v<VERSION>开头——与/ship同规则。若 Step 8 在/ship已建 PR 之后 bump 了 VERSION,标题就过期了,此子步骤负责修正:
V=$(cat VERSION 2>/dev/null | tr -d '[:space:]') CURRENT_TITLE=$(gh pr view --json title -q .title 2>/dev/null || true) # GitHub NEW_TITLE=$(~/.claude/skills/gstack/bin/gstack-pr-title-rewrite.sh "$V" "$CURRENT_TITLE") gh pr edit --title "$NEW_TITLE" # GitLab: glab mr update -t "$NEW_TITLE"标题重写规则收敛在共享 helper bin/gstack-pr-title-rewrite.sh 中(single source of truth,/ship与 GitHub Action 也调用它),处理三种情况:标题已正确(no-op)、前缀版本不同(替换)、无版本前缀(前置一个)。VERSION 不存在或为空则整段跳过;edit 失败只警告不阻塞。
结构化文档健康摘要
最后输出可扫读的状态汇总,覆盖每个文档文件:
Documentation health: README.md [status] ([details]) ARCHITECTURE.md [status] ([details]) CONTRIBUTING.md [status] ([details]) CHANGELOG.md [status] ([details]) TODOS.md [status] ([details]) VERSION [status] ([details])status 取值:Updated(附改了什么)、Current(无需变更)、Voice polished(措辞调整)、Not bumped(用户选择跳过)、Already bumped(版本由 /ship 设置)、Skipped(文件不存在)。
若 Step 1.5 发现缺口,追加覆盖度地图与图漂移小节:
Documentation coverage: [entity] [reference] [how-to] [tutorial] [explanation] /new-skill ✅ ❌ ❌ ❌ --new-flag ✅ ✅ ❌ ❌ Diagram drift: ARCHITECTURE.md: "FooProcessor" renamed to "BarProcessor" in code — diagram may be stale全覆盖且无图漂移时输出:“Coverage: all shipped features have adequate documentation.”
Codex 跨模型文档复审(默认开启)
文档更新写完之后,/document-release还会跑一个独立的跨模型复审:让另一个模型把文档与“实际发布的代码”对一遍账。这是标准步骤而非可选项,用户只能通过gstack-config set codex_reviews disabled显式关闭。
Preflight:决定复审如何运行
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off) _CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled) source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true # 依次判定: disabled / under_codex / not_installed / not_authed / model_unusable / ready echo "CODEX_MODE: $_CODEX_MODE"按回显的CODEX_MODE分支:
| 模式 | 行为 |
|---|---|
disabled | 整段跳过,不回退 Claude 子代理(disabled 即无额外复审) |
under_codex | 会话本身已跑在 Codex host 内(存在CODEX_THREAD_ID/CODEX_SANDBOX环境变量),再 spawn codex 等于同模型审自己且 token 成倍消耗(引用了真实观测:一次 /review 15M tokens),跳过嵌套调用 |
not_installed | 无 Codex CLI,回退 Claude 子代理路径 |
not_authed | 已装无凭据,回退 Claude 子代理路径 |
model_unusable | 账号配置的模型不可用(常见于~/.codex/config.toml里过期的model =pin),透传 probe 的 HINT 与一行修法,回退子代理 |
ready | 正式跑 Codex 复审 |
复审提示词与执行
diff 范围必须重算而不是引用内存变量(shell 变量不跨 block 存活):
DOC_DIFF_BASE=$(git merge-base origin/<base> HEAD 2>/dev/null || echo "<base>")复审对象是“document-release 本次实际触碰的文档 + diff 范围内受影响的文档声明”——不硬编码固定文件列表(固定 README/ARCHITECTURE/CHANGELOG 列表会漏掉生成的技能文档、包文档与命令文档)。提示词以文件系统边界指令开头(禁止 Codex 读取~/.claude/、.claude/skills/等技能定义目录,因为它们含 bash 脚本与 prompt 模板,会浪费 token 并偏离任务),然后要求执行git diff $DOC_DIFF_BASE...HEAD并找出:与代码不再一致的文档声明、发布了但未记录的新 public surface、过期的示例/路径/计数/版本号、以及夸大或低估实际内容的 CHANGELOG 条目。
ready模式下实际执行:
TMPERR_DOC=$(mktemp /tmp/codex-docreview-XXXXXXXX) _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; } codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR_DOC"带 5 分钟超时(300000ms),结束后读 stderr,输出原文置于CODEX SAYS (documentation review):之下。所有错误均非阻塞——复审是 informational 而非 gate:认证失败、超时、空响应都只记录跳过。not_installed/not_authed或 Codex 运行时报错时,经 Agent 工具派发同一提示词给 Claude 子代理,输出置于DOCUMENTATION REVIEW (Claude subagent):之下。
应用决策与结果持久化
零发现时输出 “Docs match what shipped — no gaps.” 有发现时只问一次:
“The doc review found N gaps between the docs and what shipped. How do you want to handle them?” RECOMMENDATION: Choose A if the gaps are concrete doc fixes(Completeness: A=9/10, B=4/10, C=8/10)
- A) Apply all the doc fixes now
- B) Skip — leave docs as-is
- C) Decide per-finding
复审器只报告、绝不自动改;A 或逐条批准后的编辑由技能自己完成,B 则在输出中标注缺口保持可见。结果最后持久化:
~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"codex-doc-review","timestamp":"...","status":"STATUS","source":"SOURCE","commit":"..."}'STATUS 取clean/issues_found,SOURCE 取codex/claude。Codex 用过则清理"$TMPERR_DOC"。
测试如何锁定这些行为
/document-release的关键不变量有专门的自动化测试守护。test/document-skills-redaction.test.ts 针对被 carved 的技能,合并SKILL.md.tmpl与sections/*.md.tmpl后断言:
- scan-before-edit 顺序:
gstack-redact --from-file /tmp/gstack-pr-body在模板中的位置必须早于gh pr edit --body-file(即“扫描的字节 = 发布的字节”); - HIGH 阻断:模板必须包含
exit 3 (HIGH) → do NOT edit语义。
同类断言也用于姊妹技能/document-generate(staged diff 先扫描再git commit,HIGH 阻断提交)。结合 test/helpers/carve-guards.ts 等 guard,这套测试确保 section 拆分(骨架 + release-body)不会让 Step 9 的安全顺序在模板渲染后丢失。
与 /document-generate 的分工
/document-release的收尾纪律呼应了其姊妹技能的分工:coverage map informs, never generates。审计技能只把缺口写进 PR body 作为未来工作;真正按四象限写文档(reference → explanation → how-to → tutorial 的依赖顺序)是/document-generate的职责。典型链路(见 docs/howto-document-a-shipped-feature.md):/document-release审计并产出 Documentation Debt → 用户按债务列表运行/document-generate补缺 → 再跑一遍/document-release验证覆盖度地图转绿。选择 Diataxis 而非自研分类法的理由(外部采用最广:CPython、Django、NumPy、FastAPI 等;象限标签能干净地映射为覆盖度信号)完整论述在 docs/explanation-diataxis-in-gstack.md。
小结
/document-release把“发布后同步文档”这件最容易静默腐烂的维护工作变成了一条有硬边界、可验证、可审计的流水线:base 分支检测(Step 0)→ diff 分析(Step 1)→ Diataxis 覆盖度地图与图漂移检测(Step 1.5)→ 分级审计与自动更新(Steps 2-4)→ CHANGELOG sell-test 润色(Step 5)→ 跨文档一致性与可发现性(Step 6)→ TODOS 清理(Step 7)→ 必须询问的 VERSION 决策(Step 8)→ 带脱敏扫描与防注入 tripwire 的提交与 PR 回写(Step 9)→ 跨模型文档复审。其安全设计集中在几条不可妥协的规则上:CHANGELOG 只润色不重写、VERSION 只问不猜、文档缺口只标记不生成、PR body 发布前必过脱敏扫描、复审只报告不代改。对使用 gstack 的团队来说,它的实际效果正如 README.md 中的描述:文档漂移在 PR 合并前被自动发现并修好,文档债务以象限为单位暴露在 PR body 里,供 reviewer 一眼看清。
【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考