semver-analyzer
分析代码变更并确定语义版本更新。自动检测破坏性变更,建议版本更新(主版本/次版本/补丁),生成变更日志条目,并验证版本一致性。
允许使用的工具:Bash(*) 读写编辑 Grep WebFetch 元数据: 作者:babysitter-sdk 版本:“1.0.0” 类别:版本兼容性 待办事项ID:SK-SDK-004
semver-analyzer
你是 semver-analyzer - 一个专门分析代码变更并确定适当语义版本更新的技能,确保一致的SDK版本控制和清晰地向消费者传达变更影响。
概览
这项技能支持AI驱动的语义版本控制,包括:
- 自动检测破坏性变更
- 建议版本更新(主版本/次版本/补丁)
- 从提交生成变更日志条目
- 在SDK之间验证版本一致性
- 强制执行传统提交标准
- 自动创建发布说明
- 跟踪版本依赖关系
前提条件
- 带有版本历史的Git仓库
- 传统提交消息(推荐)
- 包清单文件(package.json, pyproject.toml等)
- semantic-release或类似工具(可选)
能力
1. 破坏性变更检测
自动检测SDK代码中的破坏性变更:
// src/analyzer/breaking-changes.ts
import { parse } from '@typescript-eslint/parser';
import { diff } from 'deep-object-diff';
接口 BreakingChange {
type: 'removed' | 'signature-changed' | 'type-changed' | 'behavior-changed';
location: 字符串;
description: 字符串;
severity: 'major' | 'warning';
migration?: 字符串;
}
接口 AnalysisResult {
hasBreakingChanges: 布尔值;
breakingChanges: BreakingChange[];
suggestedBump: 'major' | 'minor' | 'patch';
confidence: 数字;
}
export async 函数 analyzeChanges(
oldVersion: 字符串,
newVersion: 字符串,
options: AnalyzerOptions
): Promise<AnalysisResult> {
常量 oldApi = 等待 extractPublicApi(oldVersion);
常量 newApi = 等待 extractPublicApi(newVersion);
常量 breakingChanges: BreakingChange[] = [];
// 检查已移除的导出
对于 (常量 [name, oldExport] of Object.entries(oldApi.exports)) {
如果 (!(name in newApi.exports)) {
breakingChanges.push({
type: 'removed',
location: 名字,
description: `导出 '${name}' 已被移除`,
severity: 'major',
migration: `移除 '${name}' 的使用或使用替代品`
});
}
}
// 检查函数签名变更
对于 (常量 [name, newFunc] of Object.entries(newApi.functions)) {
常量 oldFunc = oldApi.functions[name];
如果 (!oldFunc) 继续;
// 检查参数变更
如果 (newFunc.requiredParams > oldFunc.requiredParams) {
breakingChanges.push({
type: 'signature-changed',
location: 名字,
description: `函数 '${name}' 有新的必需参数`,
severity: 'major',
migration: `更新对 '${name}' 的调用以包含新的必需参数`
});
}
// 检查返回类型变更
如果 (newFunc.returnType !== oldFunc.returnType) {
如果 (!isTypeCompatible(oldFunc.returnType, newFunc.returnType)) {
breakingChanges.push({
type: 'type-changed',
location: 名字,
description: `函数 '${name}' 的返回类型从 '${oldFunc.returnType}' 变更为 '${newFunc.returnType}'`,
severity: 'major'
});
}
}
}
// 检查模型中的类型变更
对于 (常量 [name, newModel] of Object.entries(newApi.models)) {
常量 oldModel = oldApi.models[name];
如果 (!oldModel) 继续;
// 检查已移除的字段
对于 (常量 field of Object.keys(oldModel.fields)) {
如果 (!(field in newModel.fields)) {
breakingChanges.push({
type: 'removed',
location: `${name}.${field}`,
description: `字段 '${field}' 已从模型 '${name}' 中移除`,
severity: 'major'
});
}
}
// 检查字段类型变更
对于 (常量 [field, newField] of Object.entries(newModel.fields)) {
常量 oldField = oldModel.fields[field];
如果 (oldField && oldField.type !== newField.type) {
breakingChanges.push({
type: 'type-changed',
location: `${name}.${field}`,
description: `字段 '${name}.${field}' 类型从 '${oldField.type}' 变更为 '${newField.type}'`,
severity: 'major'
});
}
}
}
常量 hasBreakingChanges = breakingChanges.length > 0;
返回 {
hasBreakingChanges,
breakingChanges,
suggestedBump: hasBreakingChanges ? 'major' : 等待 analyzeFeaturesAndFixes(oldVersion, newVersion),
confidence: calculateConfidence(breakingChanges)
};
}
2. 传统提交分析
解析和分析传统提交:
// src/analyzer/commits.ts
import { execSync } from 'child_process';
接口 CommitInfo {
hash: 字符串;
type: 字符串;
scope?: 字符串;
description: 字符串;
body?: 字符串;
breaking: 布尔值;
footers: 记录<string, 字符串>;
}
接口 CommitAnalysis {
commits: CommitInfo[];
suggestedBump: 'major' | 'minor' | 'patch';
changelog: ChangelogSection[];
}
常量 COMMIT_PATTERN = /^(?<type>\w+)(?:\((?<scope>[^)]+)\))?(?<breaking>!)?: (?<description>.+)$/;
export 函数 analyzeCommits(fromRef: 字符串, toRef: 字符串): CommitAnalysis {
常量 log = execSync(
`git log ${fromRef}..${toRef} --format="%H|||%s|||%b|||%N" --no-merges`,
{ encoding: 'utf8' }
);
常量 commits: CommitInfo[] = [];
让 suggestedBump: 'major' | 'minor' | 'patch' = 'patch';
对于 (常量 entry of log.split('
').filter(Boolean)) {
常量 [hash, subject, body, notes] = entry.split('|||');
常量 match = COMMIT_PATTERN.exec(subject);
如果 (!match?.groups) 继续;
常量 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);
// 确定版本更新
如果 (commit.breaking) {
suggestedBump = 'major';
} 否则如果 (commit.type === 'feat' && suggestedBump !== 'major') {
suggestedBump = 'minor';
}
}
返回 {
commits,
suggestedBump,
changelog: generateChangelog(commits)
};
}
函数 parseFooters(body?: 字符串): 记录<string, 字符串> {
如果 (!body) 返回 {};
常量 footers: 记录<string, 字符串> = {};
常量 lines = body.split('
');
对于 (常量 line of lines) {
常量 match = /^(?<key>[\w-]+): (?<value>.+)$/.exec(line);
如果 (match?.groups) {
footers[match.groups.key] = match.groups.value;
}
}
返回 footers;
}
函数 generateChangelog(commits: CommitInfo[]): ChangelogSection[] {
常量 sections: 记录<string, CommitInfo[]> = {
'Breaking Changes': [],
'Features': [],
'Bug Fixes': [],
'Performance': [],
'Documentation': [],
'Other': []
};
对于 (常量 commit of commits) {
如果 (commit.breaking) {
sections['Breaking Changes'].push(commit);
}
开关 (commit.type) {
案例 'feat':
sections['Features'].push(commit);
打破;
案例 'fix':
sections['Bug Fixes'].push(commit);
打破;
案例 'perf':
sections['Performance'].push(commit);
打破;
案例 'docs':
sections['Documentation'].push(commit);
打破;
默认:
sections['Other'].push(commit);
}
}
返回 Object.entries(sections)
.filter(([_, commits]) => commits.length > 0)
.map(([标题, commits]) => ({
标题,
项目: commits.map(c => ({
scope: c.scope,
description: c.description,
hash: c.hash.substring(0, 7)
}))
}));
}
3. 语义发布配置
配置语义发布以自动化版本控制:
// 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', release: 'patch' },
{ type: 'perf', release: 'patch' },
{ type: 'refactor', release: 'patch' },
{ type: 'docs', scope: 'api', release: 'patch' },
{ breaking: true, release: 'major' }
],
parserOpts: {
noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING']
}
}],
['@semantic-release/release-notes-generator', {
preset: 'conventionalcommits',
presetConfig: {
types: [
{ type: 'feat', section: 'Features' },
{ type: 'fix', section: 'Bug Fixes' },
{ type: 'perf', section: 'Performance' },
{ type: 'refactor', section: 'Code Refactoring' },
{ type: 'docs', section: 'Documentation' },
{ type: 'chore', hidden: true }
]
}
}],
['@semantic-release/changelog', {
changelogFile: 'CHANGELOG.md'
}],
['@semantic-release/npm'],
['@semantic-release/git', {
assets: ['CHANGELOG.md', 'package.json'],
message: 'chore(release): ${nextRelease.version} [skip ci]
${nextRelease.notes}'
}],
['@semantic-release/github']
]
};
4. 版本一致性验证
验证SDK实现之间的版本一致性:
// src/validator/version-consistency.ts
import { readFileSync } from 'fs';
import semver from 'semver';
接口 SDKVersion {
语言: 字符串;
版本: 字符串;
路径: 字符串;
}
接口 ValidationResult {
isConsistent: 布尔值;
版本: SDKVersion[];
问题: ValidationIssue[];
建议: 字符串;
}
export 函数 validateVersionConsistency(
sdkPaths: 记录<string, 字符串>
): ValidationResult {
常量 versions: SDKVersion[] = [];
常量 issues: ValidationIssue[] = [];
// 从每个SDK提取版本
对于 (常量 [language, basePath] of Object.entries(sdkPaths)) {
常量 version = extractVersion(language, basePath);
versions.push({ 语言, 版本, 路径: basePath });
}
// 检查一致性
常量 uniqueVersions = 新 Set(versions.map(v => v.version));
如果 (uniqueVersions.size > 1) {
issues.push({
类型: 'version-mismatch',
消息: `SDKs有不同的版本:${Array.from(uniqueVersions).join(', ')}`,
严重性: 'error'
});
}
// 检查semver有效性
对于 (常量 { 语言, 版本 } of versions) {
如果 (!semver.valid(version)) {
issues.push({
类型: 'invalid-semver',
消息: `${语言} SDK有无效的semver:${版本}`,
严重性: 'error'
});
}
}
// 检查预发布一致性
常量 prereleases = versions.filter(v => semver.prerelease(v.version));
常量 releases = versions.filter(v => !semver.prerelease(v.version));
如果 (prereleases.length > 0 && releases.length > 0) {
issues.push({
类型: 'prerelease-mismatch',
消息: '一些SDK是预发布版本,而其他不是',
严重性: 'warning'
});
}
返回 {
isConsistent: issues.filter(i => i.severity === 'error').length === 0,
版本,
问题,
建议: generateRecommendation(versions, issues)
};
}
函数 extractVersion(language: 字符串, basePath: 字符串): 字符串 {
开关 (language) {
案例 'typescript':
案例 'javascript': {
常量 pkg = JSON.parse(readFileSync(`${basePath}/package.json`, 'utf8'));
返回 pkg.version;
}
案例 'python': {
常量 toml = readFileSync(`${basePath}/pyproject.toml`, 'utf8');
常量 match = /version\s*=\s*"([^"]+)"/.exec(toml);
返回 match?.[1] ?? '0.0.0';
}
案例 'java': {
常量 pom = readFileSync(`${basePath}/pom.xml`, 'utf8');
常量 match = /<version>([^<]+)<\/version>/.exec(pom);
返回 match?.[1] ?? '0.0.0';
}
案例 'go': {
// Go模块使用git标签,检查go.mod
常量 mod = readFileSync(`${basePath}/go.mod`, 'utf8');
// 版本通常来自git标签
返回 getLatestGitTag(basePath);
}
默认:
抛出新 Error(`不支持的语言:${language}`);
}
}
5. 变更日志生成
生成全面的变更日志:
// src/changelog/generator.ts
import { analyzeCommits } from '../analyzer/commits';
接口 Changelog选项 {
版本: 字符串;
日期: 字符串;
从Ref: 字符串;
到Ref: 字符串;
仓库Url?: 字符串;
includeCommitLinks?: 布尔值;
}
export 函数 generateChangelog(options: Changelog选项): 字符串 {
常量 analysis = analyzeCommits(options.fromRef, options.toRef);
常量 lines: 字符串[] = [];
lines.push(`## [${options.version}](${options.repositoryUrl}/compare/${options.fromRef}...${options.toRef}) (${options.date})`);
lines.push('');
对于 (常量 section of analysis.changelog) {
lines.push(`### ${section.title}`);
lines.push('');
对于 (常量 item of section.items) {
常量 scope = item.scope ? `**${item.scope}:** ` : '';
常量 link = options.includeCommitLinks && options.repositoryUrl
? ` ([${item.hash}](${options.repositoryUrl}/commit/${item.hash}))`
: '';
lines.push(`* ${scope}${item.description}${link}`);
}
lines.push('');
}
返回 lines.join('
');
}
// 示例输出:
// ## [2.0.0](https://github.com/org/sdk/compare/v1.5.0...v2.0.0) (2026-01-24)
//
// ### Breaking Changes
//
// * **api:** 移除弃用的getUsers方法 ([abc1234](https://github.com/org/sdk/commit/abc1234))
//
// ### Features
//
// * **users:** 添加批量操作 ([def5678](https://github.com/org/sdk/commit/def5678))
// * **orders:** 实现分页支持 ([ghi9012](https://github.com/org/sdk/commit/ghi9012))
//
// ### Bug Fixes
//
// * **auth:** 修复令牌刷新竞态条件 ([jkl3456](https://github.com/org/sdk/commit/jkl3456))
6. 版本更新自动化
跨SDK自动化版本更新:
// src/automation/bump.ts
import { execSync } from 'child_process';
import semver from 'semver';
接口 Bump选项 {
sdkPaths: 记录<string, 字符串>;
更新类型: 'major' | 'minor' | 'patch' | 'prerelease';
预发布标签?: 字符串;
干运行?: 布尔值;
}
export 异步 函数 bumpVersions(options: Bump选项): Promise<Bump结果> {
常量 results: 记录<string, { 旧: 字符串; 新: 字符串 }> = {};
// 获取当前版本
常量 currentVersions = getVersions(options.sdkPaths);
// 计算新版本(以TypeScript SDK为基准)
常量 baseVersion = currentVersions['typescript'];
常量 newVersion = semver.inc(
baseVersion,
options.bumpType,
options.prereleaseTag
);
如果 (!newVersion) {
抛出新 Error(`无法从 ${baseVersion} 计算新版本`);
}
如果 (options.dryRun) {
console.log(`[DRY RUN] 将把所有SDK更新到 ${newVersion}`);
返回 { 干运行: 真, 新版本, 更改: {} };
}
// 更新每个SDK
对于 (常量 [language, path] of Object.entries(options.sdkPaths)) {
常量 oldVersion = currentVersions[language];
updateVersion(language, path, newVersion);
results[language] = { 旧: oldVersion, 新: newVersion };
}
返回 {
干运行: 假,
新版本,
更改: results
};
}
函数 updateVersion(language: 字符串, basePath: 字符串, version: 字符串): 空 {
开关 (language) {
案例 'typescript':
execSync(`npm version ${version} --no-git-tag-version`, { cwd: basePath });
打破;
案例 'python':
// 更新pyproject.toml
常量 tomlPath = `${basePath}/pyproject.toml`;
常量 toml = readFileSync(tomlPath, 'utf8');
常量 updated = toml.replace(/version\s*=\s*"[^"]+"/, `version = "${version}"`);
writeFileSync(tomlPath, updated);
打破;
案例 'java':
execSync(`mvn versions:set -DnewVersion=${version}`, { cwd: basePath });
打破;
案例 'go':
// Go版本来自git标签
execSync(`git tag v${version}`, { cwd: basePath });
打破;
}
}
7. CI/CD集成
GitHub Actions工作流以进行版本控制:
名称:版本分析
on:
pull_request:
分支:[main]
push:
分支:[main]
作业:
分析:
运行在:ubuntu-latest
步骤:
- 使用:actions/checkout@v4
带有:
获取深度:0
- 名称:设置Node.js
使用:actions/setup-node@v4
带有:
node-version: '20'
- 名称:安装依赖项
运行:npm ci
- 名称:分析变更
id: analyze
运行: |
结果=$(node scripts/analyze-version.js)
回声 "bump_type=$(echo $result | jq -r '.suggestedBump')" >> $GITHUB_OUTPUT
回声 "has_breaking=$(echo $result | jq -r '.hasBreakingChanges')" >> $GITHUB_OUTPUT
- 名称:在PR上评论
如果:github.event_name == 'pull_request'
使用:actions/github-script@v7
带有:
脚本: |
常量 bumpType = '${{ steps.analyze.outputs.bump_type }}';
常量 hasBreaking = '${{ steps.analyze.outputs.has_breaking }}';
常量 body = `## 版本分析
**建议的版本更新:** \`${bumpType}\``
**有破坏性变更:** ${hasBreaking}
${hasBreaking === 'true' ? '⚠️ 此PR包含破坏性变更,将需要主版本更新。' : ''}
`;
github.rest.issues.createComment({
所有者:context.repo.owner,
仓库:context.repo.repo,
问题编号:context.issue.number,
正文
});
发布:
如果:github.event_name == 'push' && github.ref == 'refs/heads/main'
需要:analyze
运行在:ubuntu-latest
步骤:
- 使用:actions/checkout@v4
带有:
获取深度:0
- 名称:设置Node.js
使用:actions/setup-node@v4
带有:
node-version: '20'
- 名称:安装依赖项
运行:npm ci
- 名称:语义发布
环境:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
运行:npx semantic-release
MCP服务器集成
这项技能可以利用以下MCP服务器:
| 服务器 | 描述 | 安装 |
|---|---|---|
| changelog-generator | 从提交生成变更日志 | ComposioHQ |
| Specmatic MCP | 检测破坏性变更 | GitHub |
最佳实践
- 传统提交 - 使用标准提交格式
- 自动化发布 - 使用semantic-release
- 版本锁定SDKs - 保持所有SDK版本同步
- 记录破坏性变更 - 清晰的迁移指南
- 预发布版本 - 用于测试的beta/alpha
- 保护主线 - 需要PR审查
- CI验证 - 自动分析版本
- 变更日志自动化 - 从提交生成
流程集成
这项技能与以下流程集成:
sdk-versioning-release-management.js- 发布工作流backward-compatibility-management.js- 破坏性变更api-versioning-strategy.js- API版本对齐package-distribution.js- 发布发布
输出格式
{
"operation": "analyze",
"currentVersion": "1.5.0",
"suggestedVersion": "2.0.0",
"suggestedBump": "major",
"hasBreakingChanges": 真,
"breakingChanges": [
{
"type": "removed",
"location": "UsersApi.getUsers",
"description": "方法getUsers已被移除",
"migration": "使用list()代替"
}
],
"commits": {
"features": 3,
"fixes": 5,
"breaking": 1
},
"changelogPreview": "## [2.0.0] - 2026-01-24
### Breaking Changes
..."
}
错误处理
- 验证semver格式
- 处理缺失版本文件
- 报告无效提交格式
- 警告版本不一致
- 支持回滚场景
约束
- 需要git历史访问
- 推荐传统提交
- 多语言SDK需要同步
- 破坏性变更需要协调
- 预发布需要清晰的标记