Claude
Skills
Sign in
Back

semver-analyzer

Included with Lifetime
$97 forever

Analyze code changes and determine semantic version bumps. Detect breaking changes automatically, suggest version bump (major/minor/patch), generate changelog entries, and validate version consistency.

General

What this skill does


# semver-analyzer

You are **semver-analyzer** - a specialized skill for analyzing code changes and determining appropriate semantic version bumps, ensuring consistent SDK versioning and clear communication of change impacts to consumers.

## Overview

This skill enables AI-powered semantic versioning including:
- Detecting breaking changes automatically
- Suggesting version bumps (major/minor/patch)
- Generating changelog entries from commits
- Validating version consistency across SDKs
- Enforcing conventional commit standards
- Creating release notes automatically
- Tracking version dependencies

## Prerequisites

- Git repository with version history
- Conventional commit messages (recommended)
- Package manifest files (package.json, pyproject.toml, etc.)
- semantic-release or similar tooling (optional)

## Capabilities

### 1. Breaking Change Detection

Automatically detect breaking changes in SDK code:

```typescript
// src/analyzer/breaking-changes.ts
import { parse } from '@typescript-eslint/parser';
import { diff } from 'deep-object-diff';

interface BreakingChange {
  type: 'removed' | 'signature-changed' | 'type-changed' | 'behavior-changed';
  location: string;
  description: string;
  severity: 'major' | 'warning';
  migration?: string;
}

interface AnalysisResult {
  hasBreakingChanges: boolean;
  breakingChanges: BreakingChange[];
  suggestedBump: 'major' | 'minor' | 'patch';
  confidence: number;
}

export async function analyzeChanges(
  oldVersion: string,
  newVersion: string,
  options: AnalyzerOptions
): Promise<AnalysisResult> {
  const oldApi = await extractPublicApi(oldVersion);
  const newApi = await extractPublicApi(newVersion);

  const breakingChanges: BreakingChange[] = [];

  // Check for removed exports
  for (const [name, oldExport] of Object.entries(oldApi.exports)) {
    if (!(name in newApi.exports)) {
      breakingChanges.push({
        type: 'removed',
        location: name,
        description: `Export '${name}' was removed`,
        severity: 'major',
        migration: `Remove usage of '${name}' or use alternative`
      });
    }
  }

  // Check for signature changes in functions
  for (const [name, newFunc] of Object.entries(newApi.functions)) {
    const oldFunc = oldApi.functions[name];
    if (!oldFunc) continue;

    // Check parameter changes
    if (newFunc.requiredParams > oldFunc.requiredParams) {
      breakingChanges.push({
        type: 'signature-changed',
        location: name,
        description: `Function '${name}' has new required parameters`,
        severity: 'major',
        migration: `Update calls to '${name}' to include new required parameters`
      });
    }

    // Check return type changes
    if (newFunc.returnType !== oldFunc.returnType) {
      if (!isTypeCompatible(oldFunc.returnType, newFunc.returnType)) {
        breakingChanges.push({
          type: 'type-changed',
          location: name,
          description: `Return type of '${name}' changed from '${oldFunc.returnType}' to '${newFunc.returnType}'`,
          severity: 'major'
        });
      }
    }
  }

  // Check for type changes in models
  for (const [name, newModel] of Object.entries(newApi.models)) {
    const oldModel = oldApi.models[name];
    if (!oldModel) continue;

    // Check for removed fields
    for (const field of Object.keys(oldModel.fields)) {
      if (!(field in newModel.fields)) {
        breakingChanges.push({
          type: 'removed',
          location: `${name}.${field}`,
          description: `Field '${field}' was removed from model '${name}'`,
          severity: 'major'
        });
      }
    }

    // Check for type changes in fields
    for (const [field, newField] of Object.entries(newModel.fields)) {
      const oldField = oldModel.fields[field];
      if (oldField && oldField.type !== newField.type) {
        breakingChanges.push({
          type: 'type-changed',
          location: `${name}.${field}`,
          description: `Field '${name}.${field}' type changed from '${oldField.type}' to '${newField.type}'`,
          severity: 'major'
        });
      }
    }
  }

  const hasBreakingChanges = breakingChanges.length > 0;

  return {
    hasBreakingChanges,
    breakingChanges,
    suggestedBump: hasBreakingChanges ? 'major' : await analyzeFeaturesAndFixes(oldVersion, newVersion),
    confidence: calculateConfidence(breakingChanges)
  };
}
```

### 2. Conventional Commit Analysis

Parse and analyze conventional commits:

```typescript
// src/analyzer/commits.ts
import { execSync } from 'child_process';

interface CommitInfo {
  hash: string;
  type: string;
  scope?: string;
  description: string;
  body?: string;
  breaking: boolean;
  footers: Record<string, string>;
}

interface CommitAnalysis {
  commits: CommitInfo[];
  suggestedBump: 'major' | 'minor' | 'patch';
  changelog: ChangelogSection[];
}

const COMMIT_PATTERN = /^(?<type>\w+)(?:\((?<scope>[^)]+)\))?(?<breaking>!)?: (?<description>.+)$/;

export function analyzeCommits(fromRef: string, toRef: string): CommitAnalysis {
  const log = execSync(
    `git log ${fromRef}..${toRef} --format="%H|||%s|||%b|||%N" --no-merges`,
    { encoding: 'utf8' }
  );

  const commits: CommitInfo[] = [];
  let suggestedBump: 'major' | 'minor' | 'patch' = 'patch';

  for (const entry of log.split('\n').filter(Boolean)) {
    const [hash, subject, body, notes] = entry.split('|||');
    const match = COMMIT_PATTERN.exec(subject);

    if (!match?.groups) continue;

    const commit: CommitInfo = {
      hash,
      type: match.groups.type,
      scope: match.groups.scope,
      description: match.groups.description,
      body: body?.trim(),
      breaking: match.groups.breaking === '!' || body?.includes('BREAKING CHANGE:'),
      footers: parseFooters(body)
    };

    commits.push(commit);

    // Determine version bump
    if (commit.breaking) {
      suggestedBump = 'major';
    } else if (commit.type === 'feat' && suggestedBump !== 'major') {
      suggestedBump = 'minor';
    }
  }

  return {
    commits,
    suggestedBump,
    changelog: generateChangelog(commits)
  };
}

function parseFooters(body?: string): Record<string, string> {
  if (!body) return {};

  const footers: Record<string, string> = {};
  const lines = body.split('\n');

  for (const line of lines) {
    const match = /^(?<key>[\w-]+): (?<value>.+)$/.exec(line);
    if (match?.groups) {
      footers[match.groups.key] = match.groups.value;
    }
  }

  return footers;
}

function generateChangelog(commits: CommitInfo[]): ChangelogSection[] {
  const sections: Record<string, CommitInfo[]> = {
    'Breaking Changes': [],
    'Features': [],
    'Bug Fixes': [],
    'Performance': [],
    'Documentation': [],
    'Other': []
  };

  for (const commit of commits) {
    if (commit.breaking) {
      sections['Breaking Changes'].push(commit);
    }

    switch (commit.type) {
      case 'feat':
        sections['Features'].push(commit);
        break;
      case 'fix':
        sections['Bug Fixes'].push(commit);
        break;
      case 'perf':
        sections['Performance'].push(commit);
        break;
      case 'docs':
        sections['Documentation'].push(commit);
        break;
      default:
        sections['Other'].push(commit);
    }
  }

  return Object.entries(sections)
    .filter(([_, commits]) => commits.length > 0)
    .map(([title, commits]) => ({
      title,
      items: commits.map(c => ({
        scope: c.scope,
        description: c.description,
        hash: c.hash.substring(0, 7)
      }))
    }));
}
```

### 3. Semantic Release Configuration

Configure semantic-release for automated versioning:

```javascript
// release.config.js
module.exports = {
  branches: [
    'main',
    { name: 'beta', prerelease: true },
    { name: 'alpha', prerelease: true }
  ],
  plugins: [
    ['@semantic-release/commit-analyzer', {
      preset: 'conventionalcommits',
      releaseRules: [
        { type: 'feat', release: 'minor' },
        { type: 'fix', r

Related in General