Version Management Guide¶
Comprehensive guide to version management in AgentiCraft's Tool Marketplace using semantic versioning.
Overview¶
AgentiCraft uses Semantic Versioning 2.0.0 (semver) for all plugins. This ensures: - Clear communication of changes - Predictable dependency resolution - Backward compatibility guarantees - Automated update strategies
Semantic Versioning¶
Version Format¶
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
Examples:
1.0.0 - First stable release
1.2.3 - Standard version
2.0.0-alpha - Alpha pre-release
2.0.0-beta.1 - Beta pre-release
2.0.0-rc.1 - Release candidate
1.0.0+20250615 - Version with build metadata
Version Components¶
- MAJOR: Incompatible API changes
- MINOR: Backward-compatible functionality
- PATCH: Backward-compatible bug fixes
- PRERELEASE: Optional pre-release identifier
- BUILD: Optional build metadata
Version Class¶
Basic Usage¶
from agenticraft.marketplace import Version
# Create versions
v1 = Version("1.2.3")
v2 = Version("2.0.0-beta.1")
v3 = Version("1.2.4")
# Compare versions
print(v1 < v2) # True
print(v1 < v3) # True
print(v2.is_prerelease) # True
# Version components
print(v1.major) # 1
print(v1.minor) # 2
print(v1.patch) # 3
Advanced Features¶
# Parse complex versions
v = Version("2.1.0-alpha.1+build.123")
print(v.prerelease) # ['alpha', 1]
print(v.build) # ['build', '123']
# Version incrementing
v = Version("1.2.3")
print(v.bump_major()) # 2.0.0
print(v.bump_minor()) # 1.3.0
print(v.bump_patch()) # 1.2.4
# Pre-release progression
v = Version("1.0.0-alpha")
print(v.bump_prerelease()) # 1.0.0-alpha.1
Version Ranges¶
Range Specifications¶
from agenticraft.marketplace import VersionRange
# Caret ranges (^) - Compatible versions
r1 = VersionRange("^1.2.3")
# Allows: 1.2.3, 1.2.4, 1.3.0, 1.9.9
# Denies: 2.0.0, 1.2.2
# Tilde ranges (~) - Patch-level changes
r2 = VersionRange("~1.2.3")
# Allows: 1.2.3, 1.2.4, 1.2.9
# Denies: 1.3.0, 1.2.2
# Comparison operators
r3 = VersionRange(">=1.0.0")
r4 = VersionRange("<2.0.0")
r5 = VersionRange(">=1.0.0,<2.0.0") # Combined
# Exact version
r6 = VersionRange("=1.2.3")
Range Validation¶
# Check if version satisfies range
v = Version("1.5.0")
r = VersionRange("^1.0.0")
print(r.allows(v)) # True
# Multiple ranges
ranges = [
VersionRange(">=1.0.0"),
VersionRange("<2.0.0"),
VersionRange("!=1.5.0")
]
# Check all ranges
valid = all(r.allows(v) for r in ranges)
Dependency Resolution¶
Dependency Specification¶
# In plugin.yaml
dependencies:
# Caret - most common, allows compatible updates
agenticraft: "^0.2.0"
# Tilde - more restrictive
requests: "~2.28.0"
# Range - explicit boundaries
numpy: ">=1.20.0,<2.0.0"
# Exact - use sparingly
critical-lib: "=1.0.0"
# Latest - not recommended for production
dev-tool: "*"
# Pre-release
beta-lib: "^1.0.0-beta"
Resolution Algorithm¶
from agenticraft.marketplace import DependencyResolver
# Create resolver
resolver = DependencyResolver()
# Add dependencies
resolver.add_dependency("my-plugin", "requests", "^2.28.0")
resolver.add_dependency("my-plugin", "numpy", ">=1.20.0")
resolver.add_dependency("other-plugin", "requests", "~2.28.1")
# Resolve
try:
solution = resolver.resolve()
print("Resolved versions:")
for package, version in solution.items():
print(f" {package}: {version}")
except VersionConflict as e:
print(f"Conflict: {e}")
Conflict Detection¶
class ConflictDetector:
"""Detect version conflicts in dependencies."""
def __init__(self):
self.dependencies = {}
def add_requirement(self, package: str, version_spec: str, required_by: str):
"""Add a version requirement."""
if package not in self.dependencies:
self.dependencies[package] = []
self.dependencies[package].append({
"spec": VersionRange(version_spec),
"required_by": required_by
})
def find_conflicts(self):
"""Find all version conflicts."""
conflicts = []
for package, requirements in self.dependencies.items():
# Check if requirements are compatible
if not self._are_compatible(requirements):
conflicts.append({
"package": package,
"requirements": requirements
})
return conflicts
def _are_compatible(self, requirements):
"""Check if all requirements can be satisfied."""
# Find intersection of all version ranges
allowed_versions = None
for req in requirements:
if allowed_versions is None:
allowed_versions = req["spec"]
else:
# Intersect ranges
allowed_versions = allowed_versions.intersect(req["spec"])
if allowed_versions.is_empty():
return False
return True
Version Strategies¶
Release Strategies¶
class ReleaseStrategy:
"""Different version release strategies."""
@staticmethod
def stable_release(current: Version) -> Version:
"""Standard stable release."""
if current.is_prerelease:
# Remove pre-release
return Version(f"{current.major}.{current.minor}.{current.patch}")
else:
# Bump patch
return current.bump_patch()
@staticmethod
def feature_release(current: Version) -> Version:
"""New feature release."""
return current.bump_minor()
@staticmethod
def breaking_release(current: Version) -> Version:
"""Breaking change release."""
return current.bump_major()
@staticmethod
def prerelease_cycle(current: Version, stage: str = "alpha") -> Version:
"""Pre-release cycle progression."""
base = f"{current.major}.{current.minor}.{current.patch}"
if not current.is_prerelease:
# Start pre-release
return Version(f"{base}-{stage}")
elif stage in str(current):
# Increment current stage
return current.bump_prerelease()
else:
# Move to new stage
return Version(f"{base}-{stage}")
Update Policies¶
from enum import Enum
class UpdatePolicy(Enum):
"""Plugin update policies."""
CONSERVATIVE = "conservative" # Only patch updates
BALANCED = "balanced" # Minor updates
AGGRESSIVE = "aggressive" # Major updates
MANUAL = "manual" # No automatic updates
class UpdateManager:
"""Manage plugin updates based on policy."""
def __init__(self, policy: UpdatePolicy = UpdatePolicy.BALANCED):
self.policy = policy
def should_update(self, current: Version, available: Version) -> bool:
"""Check if update should be applied."""
if self.policy == UpdatePolicy.MANUAL:
return False
if available.is_prerelease and not current.is_prerelease:
return False # Don't auto-update to pre-release
if self.policy == UpdatePolicy.CONSERVATIVE:
# Only patch updates
return (current.major == available.major and
current.minor == available.minor and
current.patch < available.patch)
elif self.policy == UpdatePolicy.BALANCED:
# Minor updates
return (current.major == available.major and
current < available)
elif self.policy == UpdatePolicy.AGGRESSIVE:
# Any newer version
return current < available
return False
def get_update_range(self, current: Version) -> str:
"""Get version range for updates."""
if self.policy == UpdatePolicy.CONSERVATIVE:
return f"~{current}"
elif self.policy == UpdatePolicy.BALANCED:
return f"^{current}"
elif self.policy == UpdatePolicy.AGGRESSIVE:
return f">={current}"
else:
return f"={current}"
Version Lifecycle¶
Pre-release Progression¶
class PrereleaseManager:
"""Manage pre-release versioning."""
STAGES = ["dev", "alpha", "beta", "rc"]
@classmethod
def next_prerelease(cls, current: Version) -> Version:
"""Get next pre-release version."""
if not current.is_prerelease:
# Start with dev
return Version(f"{current}-dev")
# Parse current stage
prerelease_str = str(current.prerelease[0])
for i, stage in enumerate(cls.STAGES):
if stage in prerelease_str:
if i < len(cls.STAGES) - 1:
# Move to next stage
next_stage = cls.STAGES[i + 1]
base = f"{current.major}.{current.minor}.{current.patch}"
return Version(f"{base}-{next_stage}")
else:
# At RC, next is stable
return Version(f"{current.major}.{current.minor}.{current.patch}")
# Increment current pre-release
return current.bump_prerelease()
@classmethod
def is_ready_for_stable(cls, version: Version) -> bool:
"""Check if ready for stable release."""
if not version.is_prerelease:
return False
# Must be at RC stage
return "rc" in str(version.prerelease[0])
Version History¶
from datetime import datetime
from typing import List, Dict
class VersionHistory:
"""Track version history and changes."""
def __init__(self):
self.releases = []
def add_release(
self,
version: Version,
changes: Dict[str, List[str]],
release_date: datetime = None
):
"""Add a release to history."""
self.releases.append({
"version": version,
"date": release_date or datetime.now(),
"changes": changes
})
def get_changelog(self, from_version: Version = None) -> str:
"""Generate changelog."""
changelog = ["# Changelog\n"]
for release in sorted(self.releases, key=lambda r: r["version"], reverse=True):
if from_version and release["version"] <= from_version:
break
version = release["version"]
date = release["date"].strftime("%Y-%m-%d")
changelog.append(f"\n## [{version}] - {date}\n")
for category, items in release["changes"].items():
if items:
changelog.append(f"\n### {category}")
for item in items:
changelog.append(f"- {item}")
return "\n".join(changelog)
def get_version_type(self, version: Version) -> str:
"""Determine version type from history."""
if not self.releases:
return "initial"
previous = self.releases[-1]["version"]
if version.major > previous.major:
return "major"
elif version.minor > previous.minor:
return "minor"
elif version.patch > previous.patch:
return "patch"
elif version.is_prerelease:
return "prerelease"
else:
return "unknown"
Best Practices¶
1. Version Incrementing¶
def determine_version_bump(changes: List[str]) -> str:
"""Determine version bump type from changes."""
# Keywords indicating breaking changes
breaking_keywords = ["BREAKING", "removed", "changed API"]
feature_keywords = ["added", "new feature", "enhancement"]
for change in changes:
if any(keyword in change for keyword in breaking_keywords):
return "major"
for change in changes:
if any(keyword in change for keyword in feature_keywords):
return "minor"
return "patch"
2. Dependency Specification¶
# Good practices
dependencies:
# Use caret for most dependencies
agenticraft: "^0.2.0"
# Use tilde for more stability
critical-lib: "~1.0.0"
# Be specific about major versions
breaking-lib: ">=2.0.0,<3.0.0"
# Avoid
dependencies:
# Too restrictive
some-lib: "=1.2.3"
# Too permissive
another-lib: "*"
# No version spec
unversioned-lib: ""
3. Pre-release Guidelines¶
# Pre-release naming convention
class PrereleaseNaming:
@staticmethod
def create_prerelease(base_version: str, stage: str, number: int = None):
"""Create consistent pre-release versions."""
if number is None:
return f"{base_version}-{stage}"
else:
return f"{base_version}-{stage}.{number}"
# Examples:
# 1.0.0-dev # Development
# 1.0.0-alpha # Alpha
# 1.0.0-alpha.1 # Alpha iteration
# 1.0.0-beta # Beta
# 1.0.0-beta.2 # Beta iteration
# 1.0.0-rc # Release candidate
# 1.0.0-rc.1 # RC iteration
Automation¶
Version Bumping Script¶
#!/usr/bin/env python3
# scripts/bump_version.py
import argparse
import re
from pathlib import Path
from agenticraft.marketplace import Version
def bump_version(bump_type: str, prerelease: str = None):
"""Bump version in all files."""
# Read current version
manifest_path = Path("plugin.yaml")
content = manifest_path.read_text()
# Extract current version
match = re.search(r"version: ([^\n]+)", content)
if not match:
raise ValueError("Version not found in manifest")
current = Version(match.group(1))
# Determine new version
if bump_type == "major":
new_version = current.bump_major()
elif bump_type == "minor":
new_version = current.bump_minor()
elif bump_type == "patch":
new_version = current.bump_patch()
elif bump_type == "prerelease":
if prerelease:
base = f"{current.major}.{current.minor}.{current.patch}"
new_version = Version(f"{base}-{prerelease}")
else:
new_version = current.bump_prerelease()
else:
raise ValueError(f"Unknown bump type: {bump_type}")
print(f"Bumping version: {current} → {new_version}")
# Update files
files_to_update = [
"plugin.yaml",
"setup.py",
"src/*/__init__.py"
]
for pattern in files_to_update:
for file_path in Path(".").glob(pattern):
if file_path.is_file():
update_version_in_file(file_path, str(current), str(new_version))
return new_version
def update_version_in_file(file_path: Path, old_version: str, new_version: str):
"""Update version in a single file."""
content = file_path.read_text()
updated = content.replace(old_version, new_version)
if updated != content:
file_path.write_text(updated)
print(f" Updated {file_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Bump plugin version")
parser.add_argument("type", choices=["major", "minor", "patch", "prerelease"])
parser.add_argument("--prerelease", help="Pre-release identifier")
args = parser.parse_args()
new_version = bump_version(args.type, args.prerelease)
print(f"\nNew version: {new_version}")
print("\nDon't forget to:")
print("1. Update CHANGELOG.md")
print("2. Commit changes")
print("3. Create git tag")
CI/CD Integration¶
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Parse version
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Check if pre-release
if [[ $VERSION =~ -(alpha|beta|rc) ]]; then
echo "prerelease=true" >> $GITHUB_OUTPUT
else
echo "prerelease=false" >> $GITHUB_OUTPUT
fi
- name: Validate version
run: |
python -c "
from agenticraft.marketplace import Version
v = Version('${{ steps.version.outputs.version }}')
print(f'Valid version: {v}')
"
- name: Build plugin
run: |
pip install build
python -m build
- name: Publish to marketplace
env:
MARKETPLACE_TOKEN: ${{ secrets.MARKETPLACE_TOKEN }}
run: |
agenticraft plugin publish \
--version ${{ steps.version.outputs.version }} \
--prerelease ${{ steps.version.outputs.prerelease }}
Troubleshooting¶
Common Issues¶
Version conflict errors:
# Debug version conflicts
from agenticraft.marketplace import debug_version_conflict
debug_version_conflict("package-name")
Invalid version format:
# Validate version string
try:
v = Version("1.2.3.4") # Invalid
except ValueError as e:
print(f"Invalid version: {e}")
Range parsing errors:
# Test version ranges
from agenticraft.marketplace import test_version_range
test_version_range("^1.0.0", ["1.0.0", "1.1.0", "2.0.0"])
Next Steps¶
- Plugin Development - Create plugins
- Registry Setup - Host your own registry
- API Reference - Complete API documentation
- Examples - Working examples