---
name: release
description: Automate DevAIFlow release workflow with version management, CHANGELOG updates, and git operations
user-invocable: true
---
# DevAIFlow Release Skill
Automates the release management workflow for DevAIFlow.
## Authorization Notice
**IMPORTANT**: This repository uses a fork-based workflow.
- **Maintainers** (@itdove, @other-maintainers): Can create and push releases
- **Contributors**: Should NOT create or push production tags
- Fork the repository instead (see CONTRIBUTING.md)
- Submit pull requests with changes
- Update CHANGELOG.md in your PR
- Maintainers will handle releases
## Usage
```bash
/release minor # Create minor version release (1.1.0 -> 1.2.0)
/release patch # Create patch version release (1.1.0 -> 1.1.1)
/release major # Create major version release (1.0.0 -> 2.0.0)
/release hotfix v1.1.0 # Create hotfix from v1.1.0 tag
/release test # Create TestPyPI test release (if applicable)
## [Unreleased]
### Added
- New features
### Changed
- Changes to existing functionality
### Fixed
- Bug fixes
## [1.2.0] - 2026-04-08
### Added
- Feature X
[Unreleased]: https://github.com/itdove/devaiflow/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/itdove/devaiflow/releases/tag/v1.2.0
</details>
#### File 2: `release_helper.py` (Python Automation)
<details>
<summary>Click to expand release_helper.py</summary>
```python
#!/usr/bin/env python3
"""
Release Helper for DevAIFlow
Automates version updates, CHANGELOG management, and validation.
"""
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Tuple, Optional, List
class ReleaseHelper:
"""Helper class for managing DevAIFlow releases."""
def __init__(self, repo_path: str = "."):
"""Initialize ReleaseHelper with repository path."""
self.repo_path = Path(repo_path)
# TODO: Update these paths for your project
self.version_files = {
'setup.py': self._update_setup_py,
'pyproject.toml': self._update_pyproject_toml,
'src/package/__init__.py': self._update_init_py,
}
self.changelog_path = self.repo_path / "CHANGELOG.md"
def get_current_version(self) -> Tuple[Optional[str], bool]:
"""
Get current version from all files.
Returns:
Tuple of (version, all_match)
"""
versions = {}
for file_path, _ in self.version_files.items():
version = self._read_version_from_file(file_path)
if version:
versions[file_path] = version
# Check if all versions match
unique_versions = set(versions.values())
all_match = len(unique_versions) == 1
current_version = list(unique_versions)[0] if unique_versions else None
return current_version, all_match
def _read_version_from_file(self, file_path: str) -> Optional[str]:
"""Read version from a specific file."""
try:
full_path = self.repo_path / file_path
if not full_path.exists():
return None
content = full_path.read_text()
# Try different version patterns
patterns = [
r'version\s*=\s*["\']([^"\']+)["\']',
r'__version__\s*=\s*["\']([^"\']+)["\']',
r'"version":\s*"([^"]+)"',
]
for pattern in patterns:
match = re.search(pattern, content)
if match:
return match.group(1)
return None
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
return None
def update_version(self, new_version: str) -> bool:
"""Update version in all files."""
try:
for file_path, update_func in self.version_files.items():
full_path = self.repo_path / file_path
if full_path.exists():
update_func(full_path, new_version)
# Verify update
version, all_match = self.get_current_version()
return version == new_version and all_match
except Exception as e:
print(f"Error updating version: {e}", file=sys.stderr)
return False
def _update_setup_py(self, file_path: Path, new_version: str):
"""Update version in setup.py."""
content = file_path.read_text()
updated = re.sub(
r'version\s*=\s*["\'][^"\']+["\']',
f'version="{new_version}"',
content
)
file_path.write_text(updated)
def _update_pyproject_toml(self, file_path: Path, new_version: str):
"""Update version in pyproject.toml."""
content = file_path.read_text()
updated = re.sub(
r'^version\s*=\s*"[^"]+"',
f'version = "{new_version}"',
content,
flags=re.MULTILINE
)
file_path.write_text(updated)
def _update_init_py(self, file_path: Path, new_version: str):
"""Update version in __init__.py."""
content = file_path.read_text()
updated = re.sub(
r'^__version__\s*=\s*"[^"]+"',
f'__version__ = "{new_version}"',
content,
flags=re.MULTILINE
)
file_path.write_text(updated)
def calculate_next_version(self, current_version: str, release_type: str) -> Optional[str]:
"""Calculate next version based on release type."""
# Remove -dev or -test* suffix
base_version = re.sub(r'-dev|-test\d*', '', current_version)
# Parse semantic version
match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', base_version)
if not match:
print(f"Error: Invalid version format: {base_version}", file=sys.stderr)
return None
major, minor, patch = map(int, match.groups())
if release_type == "major":
return f"{major + 1}.0.0"
elif release_type == "minor":
return f"{major}.{minor + 1}.0"
elif release_type == "patch":
return f"{major}.{minor}.{patch + 1}"
elif release_type == "test":
return f"{major}.{minor}.{patch}-test1"
else:
print(f"Error: Invalid release type: {release_type}", file=sys.stderr)
return None
def update_changelog(self, version: str, date: Optional[str] = None) -> bool:
"""Update CHANGELOG.md by moving Unreleased section to new version."""
if date is None:
date = datetime.now().strftime("%Y-%m-%d")
try:
content = self.changelog_path.read_text()
# Check if Unreleased section exists
if "## [Unreleased]" not in content:
print("Warning: No [Unreleased] section found", file=sys.stderr)
return False
# Find Unreleased content
unreleased_pattern = r'## \[Unreleased\]\s*(.*?)(?=## \[|$)'
unreleased_match = re.search(unreleased_pattern, content, re.DOTALL)
if not unreleased_match:
return False
unreleased_content = unreleased_match.group(1).strip()
if not unreleased_content or unreleased_content.isspace():
print("Warning: Unreleased section is empty", file=sys.stderr)
return False
# Create new version section
version_section = f"## [{version}] - {date}\n\n{unreleased_content}\n\n"
# Replace Unreleased with new empty + version
new_content = re.sub(
r'## \[Unreleased\]\s*.*?(?=## \[|$)',
f"## [Unreleased]\n\n{version_section}",
content,
count=1,
flags=re.DOTALL
)
# Update version links
repo_url_match = re.search(
r'\[Unreleased\]: (https://github\.com/[^/]+/[^/]+)/compare/v([^.]+\.[^.]+\.[^.]+)\.\.\.HEAD',
new_content
)
if repo_url_match:
repo_url = repo_url_match.group(1)
new_content = re.sub(
r'\[Unreleased\]: .*?\n',
f'[Unreleased]: {repo_url}/compare/v{version}...HEAD\n',
new_content,
count=1
)
new_version_link = f'[{version}]: {repo_url}/releases/tag/v{version}\n'
new_content = re.sub(
r'(\[Unreleased\]: .*?\n)',
f'\\1{new_version_link}',
new_content,
count=1
)
self.changelog_path.write_text(new_content)
return True
except Exception as e:
print(f"Error updating CHANGELOG: {e}", file=sys.stderr)
return False
def validate_prerequisites(self, release_type: str = "regular") -> Tuple[bool, List[str]]:
"""Validate prerequisites for a release."""
errors = []
# Check version files exist
for file_path in self.version_files.keys():
full_path = self.repo_path / file_path
if not full_path.exists():
errors.append(f"{file_path} not found")
if not self.changelog_path.exists():
errors.append("CHANGELOG.md not found")
if errors:
return False, errors
# Check versions match
version, all_match = self.get_current_version()
if not all_match:
errors.append("Version mismatch between files")
# For regular releases, check CHANGELOG
if release_type == "regular":
try:
content = self.changelog_path.read_text()
if "## [Unreleased]" not in content:
errors.append("CHANGELOG.md missing [Unreleased] section")
else:
unreleased_pattern = r'## \[Unreleased\]\s*(.*?)(?=## \[|$)'
match = re.search(unreleased_pattern, content, re.DOTALL)
if match:
unreleased_content = match.group(1).strip()
if not unreleased_content or unreleased_content.isspace():
errors.append("CHANGELOG.md [Unreleased] section is empty")
except Exception as e:
errors.append(f"Error reading CHANGELOG.md: {e}")
return len(errors) == 0, errors
def main():
"""CLI interface for release helper."""
import argparse
parser = argparse.ArgumentParser(description="DevAIFlow Release Helper")
parser.add_argument("--repo", default=".", help="Repository path")
subparsers = parser.add_subparsers(dest="command", help="Command to run")
# Get version
subparsers.add_parser("get-version", help="Get current version")
# Update version
update_parser = subparsers.add_parser("update-version", help="Update version")
update_parser.add_argument("version", help="New version (e.g., 1.2.0)")
# Calculate next version
calc_parser = subparsers.add_parser("calc-version", help="Calculate next version")
calc_parser.add_argument("current", help="Current version")
calc_parser.add_argument("type", choices=["major", "minor", "patch", "test"])
# Update changelog
changelog_parser = subparsers.add_parser("update-changelog", help="Update CHANGELOG.md")
changelog_parser.add_argument("version", help="Version to release")
changelog_parser.add_argument("--date", help="Release date (YYYY-MM-DD)")
# Validate
validate_parser = subparsers.add_parser("validate", help="Validate prerequisites")
validate_parser.add_argument("--type", default="regular", choices=["regular", "hotfix", "test"])
args = parser.parse_args()
helper = ReleaseHelper(args.repo)
if args.command == "get-version":
version, all_match = helper.get_current_version()
print(f"Current version: {version}")
print(f"All files match: {all_match}")
sys.exit(0 if all_match else 1)
elif args.command == "update-version":
success = helper.update_version(args.version)
sys.exit(0 if success else 1)
elif args.command == "calc-version":
next_version = helper.calculate_next_version(args.current, args.type)
if next_version:
print(next_version)
sys.exit(0)
else:
sys.exit(1)
elif args.command == "update-changelog":
success = helper.update_changelog(args.version, args.date)
sys.exit(0 if success else 1)
elif args.command == "validate":
valid, errors = helper.validate_prerequisites(args.type)
if valid:
print("✓ All prerequisites validated")
sys.exit(0)
else:
print("✗ Validation failed:", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()
Overview
Implement fork-based workflow for release control and create a
/releaseskill to automate the release process. This provides technical enforcement for preventing unauthorized releases without requiring GitHub Pro.Reference Implementation: itdove/ai-guardian#16
Why This Matters
Current Risk
Solution Benefits
/releaseskill reduces human errorArchitecture
Implementation Tasks
Phase 1: Release Automation Skill
Create
~/.claude/skills/release/with the following files:File 1:
SKILL.md(Skill Documentation)Click to expand SKILL.md template
Workflow
When invoked, this skill:
Version Management
CRITICAL: DevAIFlow stores version in multiple locations that MUST stay in sync.
TODO: Document your version file locations:
setup.py? (version = "X.Y.Z")pyproject.toml? (version = "X.Y.Z")src/package/__init__.py? (version = "X.Y.Z")Version Format:
"1.0.0"(semantic versioning)"1.1.0-dev"(on main branch)"1.2.0-test1"(for TestPyPI)CHANGELOG.md Format
Location:
/path/to/CHANGELOG.mdFormat: Keep a Changelog format (https://keepachangelog.com/)
Safety Checks
Before starting any release:
Post-Release Checklist
Before pushing tag:
pytestor equivalentAfter creating release tag:
git push origin vX.Y.ZTODO for release_helper.py:
self.version_filesdict with your actual version file locationsPhase 2: Tag Monitoring Workflow
File:
.github/workflows/tag-monitor.ymlClick to expand tag-monitor.yml
TODO for tag-monitor.yml:
AUTHORIZED_USERSarray with your maintainersPhase 3: CODEOWNERS File
File:
.github/CODEOWNERSClick to expand CODEOWNERS
TODO for CODEOWNERS:
Phase 4: Fork-Based Workflow Documentation
File:
CONTRIBUTING.md(New)Click to expand CONTRIBUTING.md structure
Detailed Setup
Fork the Repository
gh repo fork itdove/devaiflow --cloneConfigure Remotes
Keep Fork Synced
Branch Naming
feature/description- New featuresfix/description- Bug fixesdocs/description- Documentationrefactor/description- Code refactoringtest/description- TestsCommit Messages
Types:
feat,fix,docs,refactor,test,chorePull Request Process
[Unreleased]Release Process
Contributors CANNOT create releases.
Maintainers handle all releases.
Testing
Code Style
TODO: Add your project's code style guidelines
Getting Help
Version Numbering
Semantic Versioning (MAJOR.MINOR.PATCH):
Release Workflow
Prerequisites
Steps
/release <type>skillgit tag -a vX.Y.Z -m "Release X.Y.Z"git push origin vX.Y.ZHotfix Workflow
/release hotfix vX.Y.ZTag Monitoring
The
tag-monitor.ymlworkflow:Protected Files (CODEOWNERS)
Changes require maintainer approval:
.github/workflows/*.ymlRELEASING.mdImportant
See CONTRIBUTING.md for details.
Implementation Checklist
Preparation
Release Skill
~/.claude/skills/release/directorySKILL.mdwith project-specific detailsrelease_helper.pyand update version file pathschmod +x release_helper.pytests/test_release_helper.pyTag Monitoring
.github/workflows/tag-monitor.ymlAUTHORIZED_USERSarrayCODEOWNERS
.github/CODEOWNERSDocumentation
CONTRIBUTING.mdRELEASING.mdREADME.mdwith Contributing sectionVerification
pytestor equivalent/releaseskill with test releaseDeployment
Questions to Answer First
Before starting implementation, answer these:
Version Files: Where is version stored?
Package Registry: Where do you publish?
CHANGELOG Format: What format?
Current Maintainers: Who should be authorized?
Test Strategy: How to test?
Existing Workflows: Any release workflows?
Reference Files from ai-guardian
All implementation files from ai-guardian#16 are available for reference:
Skill files:
~/.claude/skills/release/SKILL.md~/.claude/skills/release/release_helper.py~/.claude/skills/release/README.md~/.claude/skills/release/EXAMPLE_USAGE.mdRepository files:
.github/workflows/tag-monitor.yml.github/CODEOWNERSCONTRIBUTING.mdRELEASING.md(updated)tests/test_release_helper.pySuccess Metrics
After implementation:
/releaseskill reduces manual errorsTimeline Estimate
Support
Questions or issues during implementation?
Labels
enhancement,tooling,developer-experience,release-management,security,automation