gstack API Contract Specialist:/review 如何按 6 个维度审查 API 契约破坏风险
在 gstack 的 /review 工作流中,API Contract Specialist 是 "Review Army" 专家团里 7 个按范围(scope)条件触发的专家之一,它的职责是在代码合入前,专门检查这次 diff 是否悄悄破坏了对外 API 契约。读完本文,你可以掌握:该专家何时被触发、SCOPE_API=true 是如何由 diff 文件模式自动判定出来的、它 6 大检查类的完整清单,以及它的 JSON 发现(finding)如何与其余专家合并、去重并计入 PR Quality Score。
专家定位:Review Army 中的条件触发成员
review/specialists/api-contract.md 是写给一个独立子代理(subagent)的指令文件。在 /review 的 Step 4.5 "Review Army — Specialist Dispatch" 中,专家分为两类(见 review/SKILL.md):
- 常开专家(Testing、Maintainability):只要 diff 达到 50 行以上变更就会派发;
- 条件专家(Security、Performance、Data Migration、API Contract、Design):只有当
bin/gstack-diff-scope输出的对应 scope 信号为true时才派发。
API Contract 正是其中第 6 号条件专家,派发条件原文为:
API Contract — if SCOPE_API=true. Read
.../review/specialists/api-contract.md
如果 DIFF_LINES < 50,所有专家一律跳过(输出 "Small diff — specialists skipped"),直接进入 Step 5 的 Fix-First 流程。此外还有两级闸门:
- 自适应门控:若
gstack-specialist-stats显示某专家连续 10 次以上派发零发现([GATE_CANDIDATE]),会自动跳过; - 强制标志:用户 prompt 中带
--api-contract或--all-specialists时,无视门控强制派发(见 scripts/resolvers/review-army.ts)。
触发前提:SCOPE_API 是怎么判定的
SCOPE_API 由 bin/gstack-diff-scope 这个 bash 脚本输出,用法是 source <(gstack-diff-scope <base>),它为 9 个类别各输出一个布尔变量。针对 API 类别,脚本对每个变更文件逐一做 case 模式匹配(bin/gstack-diff-scope):
# API: routes, controllers, endpoints, GraphQL/OpenAPI schemas. Bare api/*
# covers root-level serverless layouts (Vercel functions, Next.js pages/api
# at root) that */api/* silently missed (#2526).
case "$f" in
api/*|*/api/*|*controller*|*route*|*endpoint*) m_api=true ;;
*.graphql|*.gql|openapi.*|swagger.*) m_api=true ;;
esac
也就是说,只要 diff 中出现以下任一形态的文件,SCOPE_API 即被置为 true:
| 模式 | 典型场景 |
|---|---|
api/*、*/api/* |
根级 serverless 布局(Vercel functions)、Next.js pages/api、嵌套 src/api/ |
*controller* |
Rails app/controllers/users_controller.rb |
*route*、*endpoint* |
src/routes/api.ts 等路由/端点文件 |
*.graphql、*.gql |
GraphQL schema 变更 |
openapi.*、swagger.* |
OpenAPI/Swagger 规范文件 |
几个值得注意的实现细节,均能在源码与测试中得到印证:
- 类别之间相互独立。每个文件独立地对 9 个类别各跑一次
case,而不是"首个命中即跳出"。因此src/lib/auth.ts会同时点亮SCOPE_AUTH和SCOPE_BACKEND(见 test/diff-scope.test.ts 的表驱动用例)。 - 变更文件集是三路并集:
git diff BASE...HEAD(已提交)+git diff HEAD(工作区)+git ls-files --others --exclude-standard(未跟踪文件,bin/gstack-diff-scope)。原因是/ship在检测 scope 时尚未提交,未提交的 controller 或新 API 路由必须可见,否则专家会被静默跳过。 - 失败要"响",不能"绿"。脚本定义了退出码契约:base 分支无法解析时输出
SCOPE_ERROR=no_base并以 exit 2 退出(浅克隆 CI 场景,避免"没看到"伪装成"没风险");有变更文件但零类别匹配时输出SCOPE_ERROR=unmatched并把不匹配路径以# unmatched: ...注释行列出,提醒可能出现了新的顶层目录布局(bin/gstack-diff-scope,对应测试在 test/diff-scope.test.ts)。
检查清单:6 大类别逐条继承
被派发的子代理拿到的正是 api-contract.md 全文作为 CHECKLIST。它的核心资产是 6 个检查类别,以下完整保留原文条目并补充审查视角:
1. Breaking Changes(破坏性变更)
- 响应体中被移除的字段(客户端可能仍依赖它们)
- 字段类型变更(string → number,object → array)
- 已有端点上新增必填参数
- HTTP 方法(GET → POST)或状态码(200 → 201)变更
- 端点重命名但未保留旧路径做重定向/别名
- 认证要求变更(public → authenticated)
这一类关注的是"客户端侧的可见行为":即使服务端逻辑自洽,只要 wire format 变了,旧客户端就可能直接崩。审查时应当把 diff 中序列化/反序列化层的改动与路由层的改动对照阅读。
2. Versioning Strategy(版本化策略)
- 做了破坏性变更但没有版本号提升(v1 → v2)
- 同一个 API 混用多种版本化策略(URL vs header vs query param)
- 端点被废弃但没有 sunset 时间表或迁移指南
- 版本相关的逻辑散落在各个 controller 里,而不是集中管理
3. Error Response Consistency(错误响应一致性)
- 新端点的错误格式与既有端点不一致
- 错误响应缺少标准字段(error code、message、details)
- HTTP 状态码与错误类型不匹配(错误返回 200、校验失败返回 500)
- 错误消息泄漏内部实现细节(stack trace、SQL 语句)
4. Rate Limiting & Pagination(限流与分页)
- 新端点在同类端点有限流的情况下却没有
- 分页方式变更(offset → cursor)且未做向后兼容
- 页面大小或默认 limit 变更但没有文档说明
- 分页响应缺少 total count 或 next-page 指示
5. Documentation Drift(文档漂移)
- OpenAPI/Swagger spec 未随新端点或参数变更同步更新
- README 或 API 文档仍描述旧行为
- 文档中的示例请求/响应已经不再可用
- 新端点或变更参数缺少文档
6. Backwards Compatibility(向后兼容)
- 旧版本客户端会因此次变更挂掉吗?
- 无法强制升级的移动端 App,API 对它们仍可用吗?
- Webhook payload 变更但没有通知订阅方
- 使用新功能是否需要 SDK 或客户端库同步升级
这 6 类覆盖了从"wire format 突变"到"周边生态(移动端、webhook、SDK、文档)滞后"的完整风险面,与 /review 主流程中"Step 5.6 Documentation staleness check"(文档过期检查,见 review/SKILL.md)形成互补:后者面向仓库根的 .md 文档,而本专家直接盯 OpenAPI spec 与 API 文档这类"契约文档"。
输出契约:每行一个 JSON finding
文档开头对输出格式有严格规定(api-contract.md):
Scope: When SCOPE_API=true
Output: JSON objects, one finding per line. Schema:
{"severity":"CRITICAL|INFORMATIONAL","confidence":N,"path":"file","line":N,"category":"api-contract","summary":"...","fix":"...","fingerprint":"path:line:api-contract","specialist":"api-contract"}
Optional: line, fix, fingerprint, evidence, test_stub.
If no findings: output `NO FINDINGS` and nothing else.
要点拆解:
- 行式 JSON:每个 finding 独占一行,便于父流程逐行解析,非 JSON 行直接丢弃(review/SKILL.md)。
- 必填字段:
severity(CRITICAL 或 INFORMATIONAL)、confidence(1-10)、path、category(固定api-contract)、summary、specialist; - 可选字段:
line、fix、fingerprint、evidence、test_stub。其中fingerprint固定形如path:line:api-contract,是跨专家去重的键; - test_stub:如果这条问题可以写一个测试来捕获,子代理应当生成一个最小测试骨架(使用 Step 4.5 探测到的测试框架 jest/vitest/rspec/pytest/go-test)。在 Fix-First 阶段,带
test_stub的 finding 会被强制归入 ASK 类——用户批准后同时写入修复和测试文件(review/SKILL.md); - 无发现时的唯一合法输出是
NO FINDINGS,且不允许任何前言、总结或评论,这保证了父流程能无歧义地判断"该专家静默"。
发现如何被消费:合并、去重与打分
API Contract 专家的输出不是终点,而是汇入统一的合并管道(Step 4.6,模板生成逻辑见 scripts/resolvers/review-army.ts):
- Fingerprint 去重:多个专家命中同一
path:line:category时,保留置信度最高的 finding,标注 "MULTI-SPECIALIST CONFIRMED (api-contract + ...)",置信度 +1(上限 10)。例如 API Contract 专家报告"新端点缺少限流",Security 专家报告了同一位置,结论就会被强化; - 置信度闸门:7+ 正常展示;5-6 附"Medium confidence — verify this is actually an issue";3-4 降级到附录;1-2 直接抑制;
- PR Quality Score:合并后按
quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5))计算 0-10 的质量分,并写入 review-log,供/retro做趋势分析; - Fix-First 分类:api-contract 的 findings 与 CRITICAL pass 的 findings 走同一条 AUTO-FIX / ASK 分流。破坏性变更这类"critical"倾向 ASK(需用户拍板是否加版本别名),而文档漂移这类 informational 倾向 AUTO-FIX(例如补一段 OpenAPI 描述或 sunset 注释);
- 统计留痕:每个专家都会在 review-log 中留下
{"dispatched":true,"findings":N,"critical":N,"informational":N}或{"dispatched":false,"reason":"scope"|"gated"}的记录(review/SKILL.md)。被 scope 跳过的 api-contract 记reason: "scope",被自适应门控跳过的记reason: "gated"。
实践小结
- 想强制审查 API 契约:在
/review请求中带上--api-contract,即使gstack-diff-scope没识别出 API 文件或专家已被门控,也会强制派发; - 想让新布局被识别:
SCOPE_API的判定完全基于文件名模式。如果你的 API 代码放在脚本未覆盖的目录(例如根级routes/之外的命名),脚本会以SCOPE_ERROR=unmatched(exit 2)大声报警并列出具体路径,而不是静默跳过所有专家(test/diff-scope.test.ts 验证了该行为); - 验证手段:
test/diff-scope.test.ts中的表驱动用例覆盖了api/ipospays/process-payment.ts(根级 serverless 布局)、src/api/foo.ts(嵌套布局)、openapi.yaml等全部 API 匹配分支,可作为理解该专家触发边界的权威参考。
对维护对外 API 的团队来说,这份清单的价值在于把"契约兼容性"从依赖资深工程师个人经验的主观判断,变成 diff 触发、结构化输出、可跨会话统计(命中率、质量分趋势)的机械流程。
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