{"id":"2e1bb54b-b4bc-4ac7-a1b6-75ffa23336aa","shortId":"zzuWsu","kind":"skill","title":"review-api-compat","tagline":"Use when the user asks for an API compatibility review, breaking change review, proto breaking change review, buf breaking review, OpenAPI compatibility check, gRPC backwards compatibility audit, \"are these API changes breaking\", \"did we break the wire\", contract evolution review","description":"# API Compatibility Review\n\nStructured review of API contract evolution. Detects breaking changes in protobuf services, OpenAPI specs, and (where applicable) GraphQL schemas; classifies them as wire-breaking, behavior-breaking, or policy-breaking; recommends remediation (deprecate-then-remove, version bump, new field).\n\n**Out of scope** (defer to siblings):\n- Internal Go API surface (function signatures, struct fields) — `review-code` covers this through SOLID/coupling lens\n- New field validation correctness — `review-code` / `review-security`\n- Protobuf style / lint pass-fail (naming, comments, package layout) — `review-code` runs `buf lint`\n\n`review-api-compat` is the **diff-aware** counterpart: same proto/OpenAPI files, but compared against a base ref to catch what *changed*.\n\n## Workflow\n\n### 1. Scope and explore\n\n- Confirm scope with the user: changed files (PR or branch diff — the default), explicit paths to a previous version (e.g. a tag), or full codebase against `main`.\n- **Resolve scope.** This skill is fundamentally diff-based:\n  - **Default**: `base_ref` is `main` (or the PR base). Compute changed files via `git diff --name-only --diff-filter=d <base>...HEAD`. Filter to API spec files (see below).\n  - **PR**: use `gh pr view <num> --json baseRefName -q .baseRefName` for the base, `gh pr diff <num> --name-only` for changed files.\n  - **Tag-vs-tag**: user supplies `<from-tag>...<to-tag>`; same diff machinery.\n- **If invoked from review-all**: receive `file_list`, `has_changes`, `base_ref`, `REVIEW_DIR`, and `pr_url`. If `has_changes` is false, exit early with status \"no API spec changes in scope\" — there is nothing to compare.\n\n**File classification:**\n- **Protobuf**: `*.proto` files; presence of `buf.yaml` / `buf.gen.yaml` / `buf.lock` confirms a buf workspace.\n- **OpenAPI**: `openapi.yml`, `openapi.yaml`, `openapi.json`, `swagger.yml`, `swagger.yaml`, `swagger.json`, files with top-level `openapi:` or `swagger:` keys.\n- **GraphQL** (lighter coverage): `*.graphql`, `*.gql`, files with `schema { ... }` or `type Query { ... }`.\n\n### 2. Determine compatibility policy\n\nDetect the project's stance from configuration; ask the user if unclear:\n- **Protobuf**: `buf.yaml` `breaking:` block (rule set: `FILE`, `PACKAGE`, `WIRE`, `WIRE_JSON`); `breaking.except:` exclusions; `buf.yaml` modules with versioning (`v1`, `v1beta1`, `v2`).\n- **OpenAPI**: SemVer in `info.version`; declared compatibility policy in repo docs (e.g. `API.md`); existing release notes for prior breaks.\n- **GraphQL**: deprecation policy; usage of `@deprecated` directive.\n\nRecord the resolved policy. Findings are graded against it (a `WIRE` policy allows JSON-but-not-wire breaks; a `WIRE_JSON` policy doesn't).\n\n### 3. Run the breaking-change tools\n\n```sh\n# Protobuf — against the base ref (preferred, integrates with buf.yaml policy)\ncommand -v buf >/dev/null && buf breaking --against \"$(git rev-parse $base_ref).git#format=git\"\n\n# Protobuf — alternative when buf is not configured\ncommand -v protolock >/dev/null && protolock status --lockdir . --uptodate\n\n# OpenAPI — multiple options; pick whichever is on PATH\ncommand -v oasdiff >/dev/null && oasdiff breaking <base-spec> <head-spec>\ncommand -v openapi-diff >/dev/null && openapi-diff <base-spec> <head-spec>\n\n# GraphQL\ncommand -v graphql-inspector >/dev/null && graphql-inspector diff <base-schema> <head-schema>\n```\n\nTo get the base version of a spec file: `git show $base_ref:<path>` into a temp file, then diff.\n\nTool output is **input** to the review; the subagent triages and contextualizes.\n\n### 4. Launch investigation subagent\n\nLaunch a single investigation subagent (`subagent_type=\"generalPurpose\"`, `model: sonnet` per `subagent-model-routing`) with: resolved policy, tool outputs, and the changed-spec file list.\n\nPrompt it to:\n- For each detected change, classify as: **wire-breaking** / **behavior-breaking** / **policy-breaking** / **non-breaking**. See [reference.md](reference.md) for the matrix.\n- For each breaking change, recommend remediation: deprecate-then-remove, version bump, new alternative field/method, additive change instead.\n- For each non-breaking change, sanity-check that it's truly additive (no enum value reuse, no field number reuse, no required→optional flip in a strict consumer).\n- Cite the wire/behavior rule violated.\n- Search the repo for any documented intent (`CHANGELOG.md`, `MIGRATION.md`, doc comments) that legitimizes a deliberate break.\n\n### 5. Present results\n\nResolve the review output directory (same pattern as siblings):\n\n```sh\nREVIEW_DATE=$(date +%Y-%m-%d)\nREVIEW_DIR=\"reviews/${REVIEW_DATE}\"\nif [ -d \"$REVIEW_DIR\" ]; then REVIEW_DIR=\"reviews/${REVIEW_DATE}-$(date +%H%M)\"; fi\nmkdir -p \"$REVIEW_DIR\"\n```\n\nCapture run metadata (see [Run metadata header](#run-metadata-header)) and prepend to `${REVIEW_DIR}/API-COMPAT-REVIEW.md`.\n\nOutput structure:\n1. Run metadata header\n2. Resolved policy (which rule set + exclusions)\n3. Findings table (grouped by surface: proto / openapi / graphql)\n4. Tool availability notes\n5. Recommended remediation order\n\nPresent the report to the user.\n\n---\n\n## Run metadata header\n\n```sh\nRUN_DATETIME=$(date -u +\"%Y-%m-%d %H:%M UTC\")\nGIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\nGIT_COMMIT=$(git rev-parse --short HEAD)\nGIT_COMMIT_FULL=$(git rev-parse HEAD)\nGIT_SUBJECT=$(git log -1 --pretty=%s)\nBASE_REF=<base>; BASE_COMMIT=$(git rev-parse --short \"$BASE_REF\")\n```\n\n```markdown\n> **Run:** {RUN_DATETIME}\n> **Branch:** {GIT_BRANCH} @ {GIT_COMMIT} (`{GIT_COMMIT_FULL}`)\n> **Subject:** {GIT_SUBJECT}\n> **Base:** {BASE_REF} @ {BASE_COMMIT}\n> **Scope:** {scope description}\n> **Policy:** {resolved policy, e.g. buf FILE + WIRE}\n```\n\n---\n\n## Finding link wrapping (PR mode)\n\nWhen `pr_url` is provided (or `gh pr view --json url -q .url 2>/dev/null` returns one for standalone runs), wrap every `path:line` reference inside the finding tables as a Markdown link:\n\n```sh\n~/.claude/scripts/pr-deeplink.sh \"$pr_url\" <path> <line>\n```\n\nThe display text stays `path:line`. Pass `L` for findings about removed code. Findings follow `terse-comments`: concrete fix, optional `bug:`/`risk:`/`nit:`/`unsure:` prefix.\n\n---\n\n## Output Templates\n\n### API compatibility findings\n\n```markdown\n| Priority | Surface | Change | Class | Recommendation | Tracked |\n|----------|---------|--------|-------|----------------|---------|\n| P0 | proto | `pkg.Service.Method` removed at file:line | wire-breaking | Deprecate first; remove in next major version | — |\n| P1 | openapi | `GET /users` response field `name` type changed | behavior-breaking | Add new field, keep old; remove in next major | #123 |\n| P2 | proto | New field `created_at` added | non-breaking | OK; document in changelog | — |\n```\n\n**Class column values:** `wire-breaking`, `behavior-breaking`, `policy-breaking`, `non-breaking`.\n\n### Re-evaluation table (for follow-up reviews)\n\n```markdown\n| Finding | Status | What Changed |\n|---------|--------|--------------|\n| ~~1. Description~~ | RESOLVED | Reverted; field re-added |\n| 2. Description | Still applicable | No changes |\n```\n\n---\n\n## Guidelines\n\n- The default position on a wire-breaking change is **block** unless an explicit version bump or migration plan is in the PR.\n- A `non-breaking` classification still warrants a changelog entry if the change is user-visible.\n- Search the organization's codebase (Sourcegraph, GitHub) for consumers of the changed surface before scoring a break as low-impact. A removed proto field that nobody reads is annoying; one that 200 services read is a P0.\n- When the user asks for a follow-up review, find the most recent review directory and append the re-evaluation table.\n- For detailed framework categories, see [reference.md](reference.md).\n- **REVIEW.md integration**: If a `REVIEW.md` context section was provided by the review-all orchestrator (or exists at the repository root when running standalone), treat its rules as additional review criteria. \"Always check\" items are HIGH severity; domain-specific items (API compatibility section) are MEDIUM severity. \"Skip\" patterns exclude matching files from review scope.\n- Findings must cite probed evidence (`path:line`, grep output, command result), not pattern-matched suspicion. Per `~/.claude/rules/probe-not-assume.md`.","tags":["review","api","compat","skill","issue","paultyng","agent-skills","ai-tools","claude-code","cursor","dotfiles"],"capabilities":["skill","source-paultyng","skill-review-api-compat","topic-agent-skills","topic-ai-tools","topic-claude-code","topic-cursor","topic-dotfiles"],"categories":["skill-issue"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/paultyng/skill-issue/review-api-compat","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add paultyng/skill-issue","source_repo":"https://github.com/paultyng/skill-issue","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (8,273 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:01.495Z","embedding":null,"createdAt":"2026-05-18T13:21:26.689Z","updatedAt":"2026-05-18T19:09:01.495Z","lastSeenAt":"2026-05-18T19:09:01.495Z","tsv":"'-1':807 '/.claude/rules/probe-not-assume.md':1208 '/.claude/scripts/pr-deeplink.sh':890 '/api-compat-review.md':726 '/dev/null':448,471,487,495,505,870 '/users':951 '1':162,729,1013 '123':969 '2':341,733,869,1021 '200':1100 '3':427,740 '4':541,749 '5':668,753 'abbrev':784 'abbrev-ref':783 'ad':976,1020 'add':960 'addit':614,630,1164 'allow':414 'altern':462,612 'alway':1167 'annoy':1097 'api':3,12,34,45,51,98,140,227,290,921,1177 'api.md':388 'append':1123 'applic':64,1024 'ask':9,352,1109 'audit':31 'avail':751 'awar':146 'backward':29 'base':155,201,203,210,243,273,438,456,513,521,810,812,819,836,837,839 'baserefnam':238,240 'behavior':74,585,958,991 'behavior-break':73,584,957,990 'block':360,1038 'branch':175,778,825,827 'break':15,19,23,36,39,55,72,75,79,359,394,420,431,450,489,583,586,589,592,601,621,667,940,959,979,989,992,995,998,1035,1054,1084 'breaking-chang':430 'breaking.except':368 'buf':22,136,312,447,449,464,848 'buf.gen.yaml':308 'buf.lock':309 'buf.yaml':307,358,370,443 'bug':914 'bump':87,610,1043 'captur':710 'catch':158 'categori':1132 'chang':16,20,35,56,160,171,212,251,272,282,292,432,568,578,602,615,622,927,956,1012,1026,1036,1063,1079 'changed-spec':567 'changelog':983,1059 'changelog.md':659 'check':27,625,1168 'cite':647,1193 'class':928,984 'classif':301,1055 'classifi':67,579 'code':106,118,134,905 'codebas':190,1072 'column':985 'command':445,468,484,490,500,1200 'comment':129,662,910 'commit':788,796,813,829,831,840 'compar':152,299 'compat':4,13,26,30,46,141,343,382,922,1178 'comput':211 'concret':911 'configur':351,467 'confirm':166,310 'consum':646,1076 'context':1141 'contextu':540 'contract':42,52 'correct':115 'counterpart':147 'cover':107 'coverag':332 'creat':974 'criteria':1166 'd':223,686,693,773 'date':682,683,691,701,702,769 'datetim':768,824 'declar':381 'default':178,202,1029 'defer':93 'deliber':666 'deprec':83,396,400,606,941 'deprecate-then-remov':82,605 'descript':843,1014,1022 'detail':1130 'detect':54,345,577 'determin':342 'diff':145,176,200,216,221,246,260,494,498,509,528 'diff-awar':144 'diff-bas':199 'diff-filt':220 'dir':276,688,695,698,709,725 'direct':401 'directori':675,1121 'display':894 'doc':386,661 'document':657,981 'doesn':425 'domain':1174 'domain-specif':1173 'e.g':185,387,847 'earli':286 'entri':1060 'enum':632 'evalu':1001,1127 'everi':877 'evid':1195 'evolut':43,53 'exclud':1185 'exclus':369,739 'exist':389,1152 'exit':285 'explicit':179,1041 'explor':165 'fail':127 'fals':284 'fi':705 'field':89,103,113,636,953,962,973,1017,1092 'field/method':613 'file':150,172,213,229,252,269,300,304,321,335,363,518,526,570,849,936,1187 'filter':222,225 'find':406,741,851,883,902,906,923,1009,1116,1191 'first':942 'fix':912 'flip':642 'follow':907,1005,1113 'follow-up':1004,1112 'format':459 'framework':1131 'full':189,797,832 'function':100 'fundament':198 'generalpurpos':552 'get':511,950 'gh':234,244,862 'git':215,452,458,460,519,777,779,787,789,795,798,803,805,814,826,828,830,834 'github':1074 'go':97 'gql':334 'grade':408 'graphql':65,330,333,395,499,503,507,748 'graphql-inspector':502,506 'grep':1198 'group':743 'grpc':28 'guidelin':1027 'h':703,774 'head':224,786,794,802 'header':716,720,732,765 'high':1171 'impact':1088 'info.version':380 'input':532 'insid':881 'inspector':504,508 'instead':616 'integr':441,1137 'intent':658 'intern':96 'investig':543,548 'invok':263 'item':1169,1176 'json':237,367,416,423,865 'json-but-not-wir':415 'keep':963 'key':329 'l':900 'launch':542,545 'layout':131 'legitim':664 'len':111 'level':325 'lighter':331 'line':879,898,937,1197 'link':852,888 'lint':124,137 'list':270,571 'lockdir':474 'log':806 'low':1087 'low-impact':1086 'm':685,704,772,775 'machineri':261 'main':192,206 'major':946,968 'markdown':821,887,924,1008 'match':1186,1205 'matrix':598 'medium':1181 'metadata':712,715,719,731,764 'migrat':1045 'migration.md':660 'mkdir':706 'mode':855 'model':553,558 'modul':371 'multipl':477 'must':1192 'name':128,218,248,954 'name-on':217,247 'new':88,112,611,961,972 'next':945,967 'nit':916 'nobodi':1094 'non':591,620,978,997,1053 'non-break':590,619,977,996,1052 'note':391,752 'noth':297 'number':637 'oasdiff':486,488 'ok':980 'old':964 'one':872,1098 'openapi':25,60,314,326,377,476,493,497,747,949 'openapi-diff':492,496 'openapi.json':317 'openapi.yaml':316 'openapi.yml':315 'option':478,641,913 'orchestr':1150 'order':756 'organ':1070 'output':530,564,674,727,919,1199 'p':707 'p0':931,1105 'p1':948 'p2':970 'packag':130,364 'pars':455,782,792,801,817 'pass':126,899 'pass-fail':125 'path':180,483,878,897,1196 'pattern':677,1184,1204 'pattern-match':1203 'per':555,1207 'pick':479 'pkg.service.method':933 'plan':1046 'polici':78,344,383,397,405,413,424,444,562,588,735,844,846,994 'policy-break':77,587,993 'posit':1030 'pr':173,209,232,235,245,278,854,857,863,891,1050 'prefer':440 'prefix':918 'prepend':722 'presenc':305 'present':669,757 'pretti':808 'previous':183 'prior':393 'prioriti':925 'probe':1194 'project':347 'prompt':572 'proto':18,303,746,932,971,1091 'proto/openapi':149 'protobuf':58,122,302,357,435,461 'protolock':470,472 'provid':860,1144 'q':239,867 'queri':340 're':1000,1019,1126 're-ad':1018 're-evalu':999,1125 'read':1095,1102 'receiv':268 'recent':1119 'recommend':80,603,754,929 'record':402 'ref':156,204,274,439,457,522,785,811,820,838 'refer':880 'reference.md':594,595,1134,1135 'releas':390 'remedi':81,604,755 'remov':85,608,904,934,943,965,1090 'repo':385,654 'report':759 'repositori':1155 'requir':640 'resolv':193,404,561,671,734,845,1015 'respons':952 'result':670,1201 'return':871 'reus':634,638 'rev':454,781,791,800,816 'rev-pars':453,780,790,799,815 'revert':1016 'review':2,14,17,21,24,44,47,49,105,117,120,133,139,266,275,535,673,681,687,689,690,694,697,699,700,708,724,1007,1115,1120,1148,1165,1189 'review-al':265,1147 'review-api-compat':1,138 'review-cod':104,116,132 'review-secur':119 'review.md':1136,1140 'risk':915 'root':1156 'rout':559 'rule':361,650,737,1162 'run':135,428,711,714,718,730,763,767,822,823,875,1158 'run-metadata-head':717 'saniti':624 'sanity-check':623 'schema':66,337 'scope':92,163,167,194,294,841,842,1190 'score':1082 'search':652,1068 'section':1142,1179 'secur':121 'see':230,593,713,1133 'semver':378 'servic':59,1101 'set':362,738 'sever':1172,1182 'sh':434,680,766,889 'short':793,818 'show':520 'sibl':95,679 'signatur':101 'singl':547 'skill':196 'skill-review-api-compat' 'skip':1183 'solid/coupling':110 'sonnet':554 'source-paultyng' 'sourcegraph':1073 'spec':61,228,291,517,569 'specif':1175 'stanc':349 'standalon':874,1159 'status':288,473,1010 'stay':896 'still':1023,1056 'strict':645 'struct':102 'structur':48,728 'style':123 'subag':537,544,549,550,557 'subagent-model-rout':556 'subject':804,833,835 'suppli':258 'surfac':99,745,926,1080 'suspicion':1206 'swagger':328 'swagger.json':320 'swagger.yaml':319 'swagger.yml':318 'tabl':742,884,1002,1128 'tag':187,254,256 'tag-vs-tag':253 'temp':525 'templat':920 'ters':909 'terse-com':908 'text':895 'tool':433,529,563,750 'top':324 'top-level':323 'topic-agent-skills' 'topic-ai-tools' 'topic-claude-code' 'topic-cursor' 'topic-dotfiles' 'track':930 'treat':1160 'triag':538 'truli':629 'type':339,551,955 'u':770 'unclear':356 'unless':1039 'unsur':917 'uptod':475 'url':279,858,866,868,892 'usag':398 'use':5,233 'user':8,170,257,354,762,1066,1108 'user-vis':1065 'utc':776 'v':446,469,485,491,501 'v1':374 'v1beta1':375 'v2':376 'valid':114 'valu':633,986 'version':86,184,373,514,609,947,1042 'via':214 'view':236,864 'violat':651 'visibl':1067 'vs':255 'warrant':1057 'whichev':480 'wire':41,71,365,366,412,419,422,582,850,939,988,1034 'wire-break':70,581,938,987,1033 'wire/behavior':649 'workflow':161 'workspac':313 'wrap':853,876 'y':684,771","prices":[{"id":"6a50bc91-936d-4a65-9a5d-16bed477a85f","listingId":"2e1bb54b-b4bc-4ac7-a1b6-75ffa23336aa","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"paultyng","category":"skill-issue","install_from":"skills.sh"},"createdAt":"2026-05-18T13:21:26.689Z"}],"sources":[{"listingId":"2e1bb54b-b4bc-4ac7-a1b6-75ffa23336aa","source":"github","sourceId":"paultyng/skill-issue/review-api-compat","sourceUrl":"https://github.com/paultyng/skill-issue/tree/main/skills/review-api-compat","isPrimary":false,"firstSeenAt":"2026-05-18T13:21:26.689Z","lastSeenAt":"2026-05-18T19:09:01.495Z"}],"details":{"listingId":"2e1bb54b-b4bc-4ac7-a1b6-75ffa23336aa","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"paultyng","slug":"review-api-compat","github":{"repo":"paultyng/skill-issue","stars":8,"topics":["agent-skills","ai-tools","claude-code","cursor","dotfiles"],"license":"mit","html_url":"https://github.com/paultyng/skill-issue","pushed_at":"2026-05-18T18:26:54Z","description":"Personal Claude Code / Cursor agent skills, rules, and config","skill_md_sha":"470819314b456564360db25814d63369e829980b","skill_md_path":"skills/review-api-compat/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/paultyng/skill-issue/tree/main/skills/review-api-compat"},"layout":"multi","source":"github","category":"skill-issue","frontmatter":{"name":"review-api-compat","description":"Use when the user asks for an API compatibility review, breaking change review, proto breaking change review, buf breaking review, OpenAPI compatibility check, gRPC backwards compatibility audit, \"are these API changes breaking\", \"did we break the wire\", contract evolution review, or backwards-compatibility audit on an API surface."},"skills_sh_url":"https://skills.sh/paultyng/skill-issue/review-api-compat"},"updatedAt":"2026-05-18T19:09:01.495Z"}}