首页
/ gstack API Contract Specialist:/review 如何按 6 个维度审查 API 契约破坏风险

gstack API Contract Specialist:/review 如何按 6 个维度审查 API 契约破坏风险

2026-09-06 17:49:24作者:秋泉律Samson

在 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_APIbin/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 规范文件

几个值得注意的实现细节,均能在源码与测试中得到印证:

  1. 类别之间相互独立。每个文件独立地对 9 个类别各跑一次 case,而不是"首个命中即跳出"。因此 src/lib/auth.ts 会同时点亮 SCOPE_AUTHSCOPE_BACKEND(见 test/diff-scope.test.ts 的表驱动用例)。
  2. 变更文件集是三路并集git diff BASE...HEAD(已提交)+ git diff HEAD(工作区)+ git ls-files --others --exclude-standard(未跟踪文件,bin/gstack-diff-scope)。原因是 /ship 在检测 scope 时尚未提交,未提交的 controller 或新 API 路由必须可见,否则专家会被静默跳过。
  3. 失败要"响",不能"绿"。脚本定义了退出码契约: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)、pathcategory(固定 api-contract)、summaryspecialist
  • 可选字段linefixfingerprintevidencetest_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):

  1. Fingerprint 去重:多个专家命中同一 path:line:category 时,保留置信度最高的 finding,标注 "MULTI-SPECIALIST CONFIRMED (api-contract + ...)",置信度 +1(上限 10)。例如 API Contract 专家报告"新端点缺少限流",Security 专家报告了同一位置,结论就会被强化;
  2. 置信度闸门:7+ 正常展示;5-6 附"Medium confidence — verify this is actually an issue";3-4 降级到附录;1-2 直接抑制;
  3. PR Quality Score:合并后按 quality_score = max(0, 10 - (critical_count * 2 + informational_count * 0.5)) 计算 0-10 的质量分,并写入 review-log,供 /retro 做趋势分析;
  4. Fix-First 分类:api-contract 的 findings 与 CRITICAL pass 的 findings 走同一条 AUTO-FIX / ASK 分流。破坏性变更这类"critical"倾向 ASK(需用户拍板是否加版本别名),而文档漂移这类 informational 倾向 AUTO-FIX(例如补一段 OpenAPI 描述或 sunset 注释);
  5. 统计留痕:每个专家都会在 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 触发、结构化输出、可跨会话统计(命中率、质量分趋势)的机械流程。

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