| name | api-diff-analyzer |
| description | Compare API specifications to detect breaking changes. Compare OpenAPI spec versions, categorize changes by severity, generate migration guides, and block breaking changes in CI. |
| allowed-tools | Bash(*) Read Write Edit Glob Grep WebFetch |
| metadata | {"author":"babysitter-sdk","version":"1.0.0","category":"versioning-compatibility","backlog-id":"SK-SDK-005"} |
| graph | {"domains":["domain:software-engineering"],"specializations":["specialization:sdk-platform-development"],"skillAreas":["skill-area:breaking-change-management","skill-area:semver-discipline"],"roles":["role:platform-engineer"],"topics":["topic:api-design","topic:developer-experience"]} |
api-diff-analyzer
You are api-diff-analyzer - a specialized skill for comparing API specifications and detecting breaking changes, ensuring SDK compatibility and safe API evolution.
Overview
This skill enables AI-powered API diff analysis including:
- Comparing OpenAPI spec versions
- Categorizing changes by severity
- Detecting breaking changes automatically
- Generating migration guides
- Blocking breaking changes in CI
- Supporting multiple spec formats (OpenAPI, GraphQL, gRPC)
- Creating detailed change reports
Prerequisites
- OpenAPI, GraphQL, or Protobuf specifications
- Version control with spec history
- oasdiff, openapi-diff, or similar tools
- CI/CD pipeline for automated checks
Capabilities
1. OpenAPI Diff Analysis
Compare OpenAPI specifications:
import { parseSpec, diffSpecs } from './parser';
interface ApiChange {
type: 'breaking' | 'non-breaking' | 'info';
category: string;
path: string;
method?: string;
description: string;
oldValue?: unknown;
newValue?: unknown;
migration?: string;
}
interface DiffResult {
hasBreakingChanges: boolean;
changes: ApiChange[];
summary: {
breaking: number;
nonBreaking: number;
info: number;
};
report: string;
}
export async function analyzeApiDiff(
oldSpec: string,
newSpec: string,
options: DiffOptions = {}
): Promise<DiffResult> {
const oldApi = (oldSpec);
newApi = (newSpec);
: [] = [];
( [path, oldPathItem] .(oldApi.)) {
newPathItem = newApi.[path];
(!newPathItem) {
changes.({
: ,
: ,
path,
: ,
:
});
;
}
( method [, , , , ]) {
oldOp = oldPathItem[method];
newOp = newPathItem[method];
(oldOp && !newOp) {
changes.({
: ,
: ,
path,
method,
:
});
;
}
(oldOp && newOp) {
(path, method, oldOp, newOp, changes);
(path, method, oldOp, newOp, changes);
(path, method, oldOp, newOp, changes);
}
}
}
( [path, newPathItem] .(newApi.)) {
(!oldApi.[path]) {
changes.({
: ,
: ,
path,
:
});
}
}
(oldApi.?., newApi.?., changes);
summary = {
: changes.( c. === ).,
: changes.( c. === ).,
: changes.( c. === ).
};
{
: summary. > ,
changes,
summary,
: (changes, summary)
};
}
(): {
oldParams = (oldOp.?.( [p., p]) || []);
newParams = (newOp.?.( [p., p]) || []);
( [name, oldParam] oldParams) {
(!newParams.(name)) {
changes.({
: oldParam. ? : ,
: ,
path,
method,
: ,
: oldParam
});
}
}
( [name, newParam] newParams) {
oldParam = oldParams.(name);
(!oldParam && newParam.) {
changes.({
: ,
: ,
path,
method,
: ,
: newParam,
:
});
}
(oldParam && !oldParam. && newParam.) {
changes.({
: ,
: ,
path,
method,
: ,
: oldParam,
: newParam
});
}
(oldParam && oldParam.?. !== newParam.?.) {
changes.({
: ,
: ,
path,
method,
: ,
: oldParam,
: newParam
});
}
}
}
(): {
(!oldSchemas || !newSchemas) ;
( [name, oldSchema] .(oldSchemas)) {
newSchema = newSchemas[name];
(!newSchema) {
changes.({
: ,
: ,
: ,
:
});
;
}
(oldSchema. && newSchema.) {
( prop .(oldSchema.)) {
(!(prop newSchema.)) {
changes.({
: ,
: ,
: ,
:
});
}
}
oldRequired = (oldSchema. || []);
newRequired = (newSchema. || []);
( prop newRequired) {
(!oldRequired.(prop) && oldSchema.[prop]) {
changes.({
: ,
: ,
: ,
:
});
}
}
}
}
}
2. Breaking Change Categories
Comprehensive breaking change detection:
export const BREAKING_CHANGE_RULES = {
'endpoint-removed': {
severity: 'major',
description: 'Removing an endpoint breaks all consumers',
autoFix: false
},
'method-removed': {
severity: 'major',
description: 'Removing an HTTP method breaks consumers using it',
autoFix: false
},
'required-parameter-added': {
severity: 'major',
description: 'Adding required parameter breaks existing calls',
autoFix: false
},
'parameter-removed': {
severity: 'minor',
description: 'Removing parameter may break consumers expecting it',
autoFix: 'Make parameter optional first'
},
'parameter-type-changed': {
severity: 'major',
description: 'Changing parameter type breaks serialization',
autoFix: false
},
'parameter-required': {
severity: 'major',
description: 'Making optional parameter required breaks calls',
autoFix:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
},
: {
: ,
: ,
:
}
};
3. Migration Guide Generation
Generate migration guides for breaking changes:
interface MigrationStep {
change: ApiChange;
action: string;
code?: {
before: string;
after: string;
language: string;
};
}
export function generateMigrationGuide(
oldVersion: string,
newVersion: string,
changes: ApiChange[]
): string {
const breakingChanges = changes.filter(c => c.type === 'breaking');
if (breakingChanges.length === 0) {
return `# Migration Guide: ${oldVersion} to ${newVersion}\n\nNo breaking changes! You can upgrade safely.`;
}
const sections: string[] = [
`# Migration Guide: ${oldVersion} to ${newVersion}`,
'',
'## Overview',
'',
`This release contains **${breakingChanges.length} breaking changes** that require updates to your code.`,
'',
'## Breaking Changes',
];
byCategory = (breakingChanges, );
( [category, categoryChanges] .(byCategory)) {
sections.();
sections.();
( change categoryChanges) {
sections.();
sections.();
sections.(change.);
sections.();
(change.) {
sections.();
sections.();
sections.(change.);
sections.();
}
codeExample = (change);
(codeExample) {
sections.();
sections.( + codeExample.);
sections.(codeExample.);
sections.();
sections.();
sections.();
sections.( + codeExample.);
sections.(codeExample.);
sections.();
sections.();
}
}
}
sections.();
}
(): | {
(change.) {
:
{
: ,
: ,
:
};
:
{
: ,
: ,
:
};
:
;
}
}
4. CI/CD Integration
Block breaking changes in CI:
name: API Compatibility Check
on:
pull_request:
paths:
- 'openapi/**'
- 'api/**'
jobs:
check-breaking-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get base spec
run: |
git show origin/${{ github.base_ref }}:openapi/openapi.yaml > old-spec.yaml
- name: Install oasdiff
run: |
curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
- name: Check for breaking changes
id: diff
run: |
oasdiff breaking old-spec.yaml openapi/openapi.yaml \
--fail-on ERR \
--format json > diff-result.json
echo "has_breaking=$(jq 'length > 0' diff-result.json)" >> $GITHUB_OUTPUT
- name:
5. oasdiff CLI Integration
Use oasdiff for comprehensive analysis:
#!/bin/bash
set -e
OLD_SPEC="${1:-main:openapi/openapi.yaml}"
NEW_SPEC="${2:-openapi/openapi.yaml}"
OUTPUT_FORMAT="${3:-text}"
echo "Comparing API specifications..."
echo "Old: $OLD_SPEC"
echo "New: $NEW_SPEC"
echo ""
echo "=== Breaking Changes ==="
oasdiff breaking "$OLD_SPEC" "$NEW_SPEC" --format "$OUTPUT_FORMAT"
echo ""
echo "=== Full Diff ==="
oasdiff diff "$OLD_SPEC" "$NEW_SPEC" --format "$OUTPUT_FORMAT"
echo ""
echo "=== Summary ==="
oasdiff summary "$OLD_SPEC" "$NEW_SPEC"
6. GraphQL Schema Diff
Compare GraphQL schemas:
import { buildSchema, printSchema, diff as graphqlDiff } from 'graphql';
interface GraphQLChange {
type: 'breaking' | 'dangerous' | 'non-breaking';
criticality: string;
message: string;
path: string;
}
export async function analyzeGraphQLDiff(
oldSchemaSDL: string,
newSchemaSDL: string
): Promise<GraphQLChange[]> {
const oldSchema = buildSchema(oldSchemaSDL);
const newSchema = buildSchema(newSchemaSDL);
const changes = graphqlDiff(oldSchema, newSchema);
return changes.map(change => ({
type: change.criticality.level,
criticality: change.criticality.reason || '',
message: change.message,
path: change.path || ''
}));
}
7. Protobuf/gRPC Diff
Compare Protobuf definitions:
import { execSync } from 'child_process';
interface ProtobufChange {
type: 'FILE' | 'MESSAGE' | 'FIELD' | 'ENUM' | 'SERVICE' | 'RPC';
category: 'ADDITION' | 'DELETION' | 'MODIFICATION';
breaking: boolean;
path: string;
description: string;
}
export function analyzeProtobufDiff(
oldProtoPath: string,
newProtoPath: string
): ProtobufChange[] {
const result = execSync(
`buf breaking ${newProtoPath} --against ${oldProtoPath} --format json`,
{ encoding: 'utf8' }
);
const bufOutput = JSON.parse(result);
const changes: ProtobufChange[] = [];
for (const issue of bufOutput) {
changes.push({
type: issue.,
: issue.,
: ,
: issue.,
: issue.
});
}
changes;
}
MCP Server Integration
This skill can leverage the following MCP servers:
| Server | Description | Installation |
|---|
| Specmatic MCP | Contract testing and diff | GitHub |
| mcp-openapi-schema | OpenAPI exploration | GitHub |
Best Practices
- Version specs - Keep specs in version control
- Automate checks - Run diff in CI/CD
- Block breaking - Fail builds on breaking changes
- Generate guides - Create migration docs
- Review carefully - Human review for edge cases
- Deprecate first - Deprecate before removing
- Communicate early - Notify SDK teams of changes
- Test migrations - Verify migration guides work
Process Integration
This skill integrates with the following processes:
api-versioning-strategy.js - API version management
backward-compatibility-management.js - Breaking change policy
sdk-versioning-release-management.js - SDK releases
api-design-specification.js - Spec management
Output Format
{
"operation": "diff",
"oldVersion": "1.0.0",
"newVersion": "2.0.0",
"hasBreakingChanges": true,
"summary": {
"breaking": 3,
"nonBreaking": 5,
"info": 2
},
"changes": [
{
"type": "breaking",
"category": "required-parameter-added",
"path": "/users",
"method": "POST",
"description": "New required parameter 'email' added",
Error Handling
- Handle invalid spec formats
- Report parse errors clearly
- Support partial comparisons
- Warn on deprecated features
- Log detailed change context
Constraints
- Requires spec access for both versions
- Complex schema changes may need manual review
- Some changes may be false positives
- Behavior changes not always detectable
- GraphQL/gRPC need separate tools