Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Consumer-driven contract testing for SDK-API compatibility. Generate Pact consumer tests, verify provider contracts, configure Pact broker, and implement can-i-deploy checks.
.claude/skills/a5c-ai-contract-test-framework/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 311% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 409% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 337% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 217% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 190% | 0% |
You are contract-test-framework - a specialized skill for consumer-driven contract testing between SDKs and APIs, ensuring compatibility and preventing breaking changes through automated verification.
This skill enables AI-powered contract testing including:
Generate contracts from SDK tests:
typescript// tests/contracts/user-api.pact.ts import { PactV3, MatchersV3 } from '@pact-foundation/pact'; import { MyServiceSDK } from '@company/myservice-sdk'; const { like, eachLike, regex, uuid, datetime, integer } = MatchersV3; const provider = new PactV3({ consumer: 'myservice-typescript-sdk', provider: 'myservice-api', logLevel: 'info' }); describe('MyService SDK Contracts', () => { describe('Users API', () => { it('should get user by ID', async () => { const expectedUser = { id: uuid(), email: like('user@example.com'), name: like('John Doe'), createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), status: regex(/active|inactive|pending/, 'active') }; await provider .given('a user with ID exists', { userId: 'user-123' }) .uponReceiving('a request to get user by ID') .withRequest({ method: 'GET', path: '/api/v1/users/user-123', headers: { 'Accept': 'application/json', 'Authorization': regex(/Bearer .+/, 'Bearer test-token') } }) .willRespondWith({ status: 200, headers: { 'Content-Type': 'application/json' }, body: expectedUser }); await provider.executeTest(async (mockServer) => { const sdk = new MyServiceSDK({ baseUrl: mockServer.url, accessToken: 'test-token' }); const user = await sdk.users.get('user-123'); expect(user).toBeDefined(); expect(user.email).toMatch(/@/); }); }); it('should list users with pagination', async () => { await provider .given('users exist') .uponReceiving('a request to list users') .withRequest({ method: 'GET', path: '/api/v1/users', query: { page: '1', limit: '20' } }) .willRespondWith({ status: 200, body: { data: eachLike({ id: uuid(), email: like('user@example.com'), name: like('User Name') }), pagination: { page: integer(1), limit: integer(20), total: integer(100), hasMore: like(true) } } }); await provider.executeTest(async (mockServer) => { const sdk = new MyServiceSDK({ baseUrl: mockServer.url }); const response = await sdk.users.list({ page: 1, limit: 20 }); expect(response.data).toBeInstanceOf(Array); expect(response.pagination.page).toBe(1); }); }); it('should create a new user', async () => { await provider .given('the system is ready') .uponReceiving('a request to create a user') .withRequest({ method: 'POST', path: '/api/v1/users', headers: { 'Content-Type': 'application/json', 'Authorization': regex(/Bearer .+/, 'Bearer test-token') }, body: { email: like('newuser@example.com'), name: like('New User'), password: like('securePassword123') } }) .willRespondWith({ status: 201, body: { id: uuid(), email: like('newuser@example.com'), name: like('New User'), createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") } }); await provider.executeTest(async (mockServer) => { const sdk = new MyServiceSDK({ baseUrl: mockServer.url, accessToken: 'test-token' }); const user = await sdk.users.create({ email: 'newuser@example.com', name: 'New User', password: 'securePassword123' }); expect(user.id).toBeDefined(); }); }); it('should handle 404 for non-existent user', async () => { await provider .given('user does not exist', { userId: 'nonexistent' }) .uponReceiving('a request for non-existent user') .withRequest({ method: 'GET', path: '/api/v1/users/nonexistent' }) .willRespondWith({ status: 404, body: { error: { code: like('USER_NOT_FOUND'), message: like('User not found') } } }); await provider.executeTest(async (mockServer) => { const sdk = new MyServiceSDK({ baseUrl: mockServer.url }); await expect(sdk.users.get('nonexistent')) .rejects .toThrow('User not found'); }); }); }); });
Test contracts for multiple SDK implementations:
yaml# pact-config.yaml consumers: - name: myservice-typescript-sdk language: typescript version: ${GIT_COMMIT} branch: ${GIT_BRANCH} - name: myservice-python-sdk language: python version: ${GIT_COMMIT} branch: ${GIT_BRANCH} - name: myservice-java-sdk language: java version: ${GIT_COMMIT} branch: ${GIT_BRANCH} provider: name: myservice-api baseUrl: http://localhost:3000 broker: url: https://your-broker.pactflow.io token: ${PACT_BROKER_TOKEN} publishResults: true verification: enablePending: true wipPactsSince: '2024-01-01' consumerVersionSelectors: - matchingBranch: true - mainBranch: true - deployedOrReleased: true
Verify API against all SDK contracts:
typescript// tests/contracts/provider-verification.ts import { Verifier } from '@pact-foundation/pact'; import { startServer, stopServer, resetDatabase } from '../test-utils'; describe('Provider Verification', () => { beforeAll(async () => { await startServer(); }); afterAll(async () => { await stopServer(); }); it('should verify all SDK contracts', async () => { const verifier = new Verifier({ provider: 'myservice-api', providerBaseUrl: 'http://localhost:3000', // Pact Broker configuration pactBrokerUrl: process.env.PACT_BROKER_URL, pactBrokerToken: process.env.PACT_BROKER_TOKEN, // Provider version providerVersion: process.env.GIT_COMMIT || '1.0.0', providerVersionBranch: process.env.GIT_BRANCH || 'main', // Consumer selection consumerVersionSelectors: [ { matchingBranch: true }, { mainBranch: true }, { deployedOrReleased: true } ], // State handlers for test setup stateHandlers: { 'a user with ID exists': async (params) => { await resetDatabase(); await db.users.create({ id: params.userId, email: 'user@example.com', name: 'John Doe' }); }, 'users exist': async () => { await resetDatabase(); await db.users.createMany([ { id: 'user-1', email: 'user1@example.com', name: 'User 1' }, { id: 'user-2', email: 'user2@example.com', name: 'User 2' } ]); }, 'user does not exist': async (params) => { await resetDatabase(); // Ensure user doesn't exist await db.users.delete(params.userId).catch(() => {}); }, 'the system is ready': async () => { await resetDatabase(); } }, // Request filters requestFilter: (req, res, next) => { // Add test authentication if (!req.headers.authorization) { req.headers.authorization = 'Bearer test-token'; } next(); }, // Publish results publishVerificationResult: true, enablePending: true, includeWipPactsSince: '2024-01-01' }); await verifier.verifyProvider(); }); });
Complete GitHub Actions workflow:
yamlname: SDK Contract Testing on: push: branches: [main, develop] pull_request: branches: [main] env: PACT_BROKER_URL: https://your-broker.pactflow.io PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }} jobs: # Consumer SDK contract tests sdk-contracts: runs-on: ubuntu-latest strategy: matrix: sdk: [typescript, python, java] steps: - uses: actions/checkout@v4 - name: Setup SDK environment uses: ./.github/actions/setup-${{ matrix.sdk }} - name: Install dependencies run: | cd sdks/${{ matrix.sdk }} ${{ matrix.sdk == 'typescript' && 'npm ci' || matrix.sdk == 'python' && 'pip install -e .[dev]' || 'mvn install -DskipTests' }} - name: Run contract tests run: | cd sdks/${{ matrix.sdk }} ${{ matrix.sdk == 'typescript' && 'npm run test:contract' || matrix.sdk == 'python' && 'pytest tests/contracts' || 'mvn test -Dtest=*Pact*' }} - name: Publish contracts if: github.event_name == 'push' run: | npx @pact-foundation/pact-cli publish \ sdks/${{ matrix.sdk }}/pacts \ --consumer-app-version ${{ github.sha }} \ --branch ${{ github.ref_name }} \ --broker-base-url $PACT_BROKER_URL \ --broker-token $PACT_BROKER_TOKEN # Provider verification provider-verification: runs-on: ubuntu-latest needs: sdk-contracts steps: - uses: actions/checkout@v4 with: repository: your-org/myservice-api - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Start provider run: npm run start:test & - name: Wait for provider run: npx wait-on http://localhost:3000/health - name: Verify contracts run: npm run test:contract:provider env: PROVIDER_VERSION: ${{ github.sha }} PROVIDER_BRANCH: ${{ github.ref_name }} # Deployment safety check can-i-deploy: runs-on: ubuntu-latest needs: [sdk-contracts, provider-verification] if: github.ref == 'refs/heads/main' strategy: matrix: participant: - myservice-typescript-sdk - myservice-python-sdk - myservice-java-sdk - myservice-api steps: - name: Can I deploy? run: | docker run --rm pactfoundation/pact-cli \ broker can-i-deploy \ --pacticipant ${{ matrix.participant }} \ --version ${{ github.sha }} \ --to-environment production \ --broker-base-url $PACT_BROKER_URL \ --broker-token ${{ secrets.PACT_BROKER_TOKEN }} # Record deployment record-deployment: runs-on: ubuntu-latest needs: can-i-deploy if: github.ref == 'refs/heads/main' strategy: matrix: participant: - myservice-typescript-sdk - myservice-python-sdk - myservice-java-sdk - myservice-api steps: - name: Record deployment run: | docker run --rm pactfoundation/pact-cli \ broker record-deployment \ --pacticipant ${{ matrix.participant }} \ --version ${{ github.sha }} \ --environment production \ --broker-base-url $PACT_BROKER_URL \ --broker-token ${{ secrets.PACT_BROKER_TOKEN }}
Set up automated verification webhooks:
bash# Create webhook for SDK changes pact-broker create-webhook \ 'https://api.github.com/repos/your-org/myservice-api/dispatches' \ --request=POST \ --header 'Accept: application/vnd.github.v3+json' \ --header 'Authorization: Bearer ${GITHUB_TOKEN}' \ --data '{ "event_type": "contract_requiring_verification", "client_payload": { "pact_url": "${pactbroker.pactUrl}", "consumer_name": "${pactbroker.consumerName}", "provider_name": "${pactbroker.providerName}" } }' \ --description "Trigger API verification on SDK contract change" \ --contract-content-changed \ --provider myservice-api \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN # Create webhook for verification results pact-broker create-webhook \ 'https://api.github.com/repos/your-org/myservice-sdk/statuses/${pactbroker.consumerVersionNumber}' \ --request=POST \ --header 'Authorization: Bearer ${GITHUB_TOKEN}' \ --data '{ "state": "${pactbroker.verificationResultSuccess ? \"success\" : \"failure\"}", "description": "Contract verification ${pactbroker.verificationResultSuccess ? \"passed\" : \"failed\"}", "context": "pact/provider-verification" }' \ --description "Update SDK commit status on verification" \ --provider-verification-published \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN
Detect and handle breaking changes:
typescript// scripts/check-breaking-changes.ts import { PactBrokerClient } from '@pact-foundation/pact'; async function checkBreakingChanges( provider: string, newVersion: string ): Promise<BreakingChangeReport> { const client = new PactBrokerClient({ brokerBaseUrl: process.env.PACT_BROKER_URL!, token: process.env.PACT_BROKER_TOKEN }); // Get current production version const prodVersion = await client.getLatestVersionForEnvironment( provider, 'production' ); // Compare contracts const comparison = await client.compareVersions( provider, prodVersion, newVersion ); const breakingChanges: BreakingChange[] = []; for (const diff of comparison.differences) { if (diff.isBreaking) { breakingChanges.push({ type: diff.type, path: diff.path, description: diff.description, affectedConsumers: diff.consumers }); } } return { hasBreakingChanges: breakingChanges.length > 0, breakingChanges, recommendation: breakingChanges.length > 0 ? 'Major version bump required' : 'Safe to release' }; }
This skill can leverage the following MCP servers:
| Server | Description | Installation | |--------|-------------|--------------| | PactFlow MCP Server | AI-powered contract testing | PactFlow Blog | | Specmatic MCP Server | Contract testing and mocks | GitHub |
This skill integrates with the following processes:
sdk-testing-strategy.js - SDK testing patternscompatibility-testing.js - Cross-SDK compatibilitybackward-compatibility-management.js - Breaking change managementsdk-versioning-release-management.js - Release coordinationjson{ "operation": "verify", "provider": "myservice-api", "providerVersion": "abc123", "consumers": [ { "name": "myservice-typescript-sdk", "version": "def456", "status": "passed", "interactions": 12, "passed": 12, "failed": 0 }, { "name": "myservice-python-sdk", "version": "ghi789", "status": "passed", "interactions": 10, "passed": 10, "failed": 0 } ], "canDeploy": true, "environment": "production", "verificationUrl": "https://broker.pactflow.io/matrix/provider/myservice-api/version/abc123" }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 21,265 | 17,549 | -17% | 1 | 1 | 0% | 1,683 | 6,920 | +311% | 0 | 0 | — |
case-02 | fail→pass | 5,553 | 7,620 | +37% | 1 | 1 | 0% | 1,276 | 6,494 | +409% | 0 | 0 | — |
case-03 | fail→pass | 6,574 | 11,734 | +78% | 1 | 1 | 0% | 1,433 | 6,260 | +337% | 0 | 0 | — |
case-04 | pass→pass | 18,782 | 18,551 | -1% | 1 | 1 | 0% | 2,887 | 7,629 | +164% | 0 | 0 | — |
case-05 | pass→pass | 6,052 | 10,402 | +72% | 1 | 1 | 0% | 1,231 | 5,952 | +384% | 0 | 0 | — |
case-06 | pass→pass | 10,145 | 8,813 | -13% | 1 | 1 | 0% | 914 | 5,530 | +505% | 0 | 0 | — |
case-07 | pass→pass | 10,059 | 16,197 | +61% | 1 | 1 | 0% | 2,093 | 7,005 | +235% | 0 | 0 | — |
case-08 | pass→pass | 7,655 | 12,764 | +67% | 1 | 1 | 0% | 1,397 | 6,072 | +335% | 0 | 0 | — |
case-09 | pass→pass | 15,269 | 16,471 | +8% | 1 | 1 | 0% | 2,803 | 6,901 | +146% | 0 | 0 | — |
case-10 | fail→pass | 14,813 | 11,407 | -23% | 1 | 1 | 0% | 1,969 | 6,245 | +217% | 0 | 0 | — |
case-11 | fail→pass | 11,638 | 9,146 | -21% | 1 | 1 | 0% | 2,315 | 6,716 | +190% | 0 | 0 | — |
case-12 | fail→fail | 22,273 | 14,494 | -35% | 1 | 1 | 0% | 3,560 | 7,370 | +107% | 0 | 0 | — |
case-13 | fail→pass | 21,019 | 10,701 | -49% | 1 | 1 | 0% | 2,750 | 6,013 | +119% | 0 | 0 | — |
case-14 | pass→pass | 3,452 | 8,714 | +152% | 1 | 1 | 0% | 647 | 5,532 | +755% | 0 | 0 | — |
case-15 | pass→pass | 13,947 | 13,752 | -1% | 1 | 1 | 0% | 1,511 | 6,415 | +325% | 0 | 0 | — |
case-16 | pass→pass | 17,358 | 9,901 | -43% | 1 | 1 | 0% | 2,511 | 6,933 | +176% | 0 | 0 | — |
case-17 | pass→pass | 17,293 | 11,939 | -31% | 1 | 1 | 0% | 2,548 | 7,185 | +182% | 0 | 0 | — |
case-18 | pass→pass | 12,832 | 10,482 | -18% | 1 | 1 | 0% | 1,432 | 6,097 | +326% | 0 | 0 | — |
case-19 | pass→pass | 15,165 | 16,245 | +7% | 1 | 1 | 0% | 2,181 | 7,201 | +230% | 0 | 0 | — |
case-20 | pass→fail | 14,271 | 13,104 | -8% | 1 | 1 | 0% | 2,037 | 6,652 | +227% | 0 | 0 | — |
case-21 | pass→pass | 11,715 | 6,948 | -41% | 1 | 1 | 0% | 2,530 | 6,170 | +144% | 0 | 0 | — |
case-22 | pass→pass | 17,709 | 22,954 | +30% | 1 | 1 | 0% | 2,986 | 8,089 | +171% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.