# Release Management Optimization Implementation Plan

**Date**: 2026-01-06
**Status**: Ready to implement
**Total Optimizations**: 9

---

## Implementation Order

### Phase 1: High Priority (Critical Issues) - 5 hours
1. Git status enhancement for unpushed tags (1 hour)
2. Automated tag pushing (1 hour)
3. CHANGELOG validation in release flow (2 hours)
4. Version-tag consistency check (1 hour)

### Phase 2: Medium Priority (UX & Automation) - 5.5 hours
5. CHANGELOG section generation (3 hours)
6. Explicit version command (30 minutes)
7. Release summary auto-generation (2 hours)

### Phase 3: Low Priority (Nice to Have) - 3 hours
8. Schema auto-ingestion (1 hour)
9. Release notes from CHANGELOG (2 hours)

**Total Estimated Time**: 13.5 hours

---

## Optimization #1: Git Status Enhancement for Unpushed Tags

**Priority**: HIGH
**Estimated**: 1 hour
**Files**: `capabilities/release-management/src/release_management/core/status.py`

### Implementation Approach

**Option 1**: Enhance `release status` command (RECOMMENDED)
- Add unpushed tag detection to ReleaseStatus class
- Compare local tags with remote tags
- Display unpushed tags prominently

**Option 2**: Git post-commit hook
- Create .git/hooks/post-commit script
- Automatic check after each commit
- Less portable (per-clone setup)

**Option 3**: Git alias
- Add custom git alias in .gitconfig
- User needs to remember to use it

### Implementation Details

```python
# In ReleaseStatus class
def get_unpushed_tags(self) -> List[str]:
    """Get list of tags not pushed to origin."""
    # Get local tags
    local_tags = subprocess.run(
        ['git', 'tag', '-l'],
        capture_output=True, text=True
    ).stdout.strip().split('\n')

    # Get remote tags
    remote_tags = subprocess.run(
        ['git', 'ls-remote', '--tags', 'origin'],
        capture_output=True, text=True
    ).stdout

    remote_tag_names = [
        line.split('refs/tags/')[1]
        for line in remote_tags.split('\n')
        if 'refs/tags/' in line
    ]

    return [tag for tag in local_tags if tag and tag not in remote_tag_names]
```

### Success Criteria
- ✅ `release status` shows unpushed tags
- ✅ Clear warning when tags haven't been pushed
- ✅ Works with multiple remotes

---

## Optimization #2: Automated Tag Pushing

**Priority**: HIGH
**Estimated**: 1 hour
**Files**: `capabilities/release-management/src/release_management/cli/main.py`

### Implementation

Add `--push` flag to `release tag` command:

```python
@click.command()
@click.argument('version')
@click.option('--push/--no-push', default=False,
              help='Automatically push tag to origin after creating')
@click.option('--message', '-m', help='Tag annotation message')
def tag(version, push, message):
    """Create git tag for version."""
    # Existing tag creation logic

    if push:
        click.echo(f"Pushing tag {tag_name} to origin...")
        git_manager.push_tag(tag_name)
        click.echo("✅ Tag pushed successfully")
```

### Success Criteria
- ✅ `release tag v0.11.0 --push` creates AND pushes tag
- ✅ Works with existing tag logic
- ✅ Error handling for push failures

---

## Optimization #3: CHANGELOG Validation in Release Flow

**Priority**: HIGH
**Estimated**: 2 hours
**Files**:
- `capabilities/release-management/src/release_management/validators/changelog_validator.py` (new)
- `capabilities/release-management/src/release_management/cli/main.py`

### Implementation

Create ChangelogValidator class:

```python
class ChangelogValidator:
    """Validates CHANGELOG.md using changelog schema."""

    def __init__(self, changelog_path: Path = Path("CHANGELOG.md")):
        self.changelog_path = changelog_path
        self.schema_path = Path("markitect/schemas/changelog-schema-v1.0.md")

    def validate(self) -> ValidationResult:
        """Validate CHANGELOG with schema."""
        # Use markitect validate command
        result = subprocess.run([
            'markitect', 'validate', str(self.changelog_path),
            '--schema', str(self.schema_path),
            '--semantic'
        ], capture_output=True, text=True)

        return ValidationResult.from_output(result.stdout, result.returncode)

    def check_version_exists(self, version: str) -> bool:
        """Check if version section exists in CHANGELOG."""
        with open(self.changelog_path) as f:
            content = f.read()
        return f"## [{version}]" in content
```

Integrate into `release validate` command:

```python
@click.command()
def validate():
    """Validate repository state for release readiness."""
    # Existing validations...

    # Add CHANGELOG validation
    changelog_validator = ChangelogValidator()
    result = changelog_validator.validate()

    if not result.is_valid:
        click.echo("❌ CHANGELOG validation failed:")
        for error in result.errors:
            click.echo(f"  - {error}")
        sys.exit(1)

    click.echo("✅ CHANGELOG is valid")
```

### Success Criteria
- ✅ `release validate` checks CHANGELOG.md
- ✅ Validates using changelog-schema-v1.0.md
- ✅ Reports errors clearly
- ✅ Prevents release if invalid

---

## Optimization #4: Version-Tag Consistency Check

**Priority**: HIGH
**Estimated**: 1 hour
**Files**: `capabilities/release-management/src/release_management/validators/changelog_validator.py`

### Implementation

Add to ChangelogValidator:

```python
def check_version_tag_consistency(self, target_version: str) -> ConsistencyResult:
    """Check CHANGELOG version matches git describe."""
    # Check CHANGELOG has section
    if not self.check_version_exists(target_version):
        return ConsistencyResult(
            is_consistent=False,
            message=f"CHANGELOG missing section for {target_version}"
        )

    # Check git tag exists
    tags = subprocess.run(
        ['git', 'tag', '-l', f'v{target_version}'],
        capture_output=True, text=True
    ).stdout.strip()

    if not tags:
        return ConsistencyResult(
            is_consistent=False,
            message=f"Git tag v{target_version} doesn't exist"
        )

    # Check Unreleased section exists
    with open(self.changelog_path) as f:
        if "## [Unreleased]" not in f.read():
            return ConsistencyResult(
                is_consistent=False,
                message="CHANGELOG missing [Unreleased] section"
            )

    return ConsistencyResult(is_consistent=True)
```

### Success Criteria
- ✅ Detects CHANGELOG/tag mismatches
- ✅ Ensures Unreleased section exists
- ✅ Integrated into `release validate`

---

## Optimization #5: CHANGELOG Section Generation

**Priority**: MEDIUM
**Estimated**: 3 hours
**Files**:
- `capabilities/release-management/src/release_management/changelog/editor.py` (new)
- `capabilities/release-management/src/release_management/cli/main.py`

### Implementation

Create ChangelogEditor class:

```python
class ChangelogEditor:
    """Edit CHANGELOG.md programmatically."""

    def create_version_section(self, version: str, date: str = None):
        """Create new version section and move Unreleased content."""
        if date is None:
            date = datetime.now().strftime("%Y-%m-%d")

        with open(self.changelog_path) as f:
            lines = f.readlines()

        # Find Unreleased section
        unreleased_idx = None
        for i, line in enumerate(lines):
            if line.strip() == "## [Unreleased]":
                unreleased_idx = i
                break

        if unreleased_idx is None:
            raise ValueError("No [Unreleased] section found")

        # Find next version section or end
        next_section_idx = None
        for i in range(unreleased_idx + 1, len(lines)):
            if lines[i].startswith("## ["):
                next_section_idx = i
                break

        # Extract Unreleased content
        if next_section_idx:
            unreleased_content = lines[unreleased_idx+1:next_section_idx]
        else:
            unreleased_content = lines[unreleased_idx+1:]

        # Create new version section
        new_section = [
            f"## [{version}] - {date}\n",
            "\n"
        ] + unreleased_content + ["\n"]

        # Insert after Unreleased
        new_lines = (
            lines[:unreleased_idx+2] +  # Keep Unreleased header + blank line
            new_section +
            (lines[next_section_idx:] if next_section_idx else [])
        )

        # Write back
        with open(self.changelog_path, 'w') as f:
            f.writelines(new_lines)
```

Add `release prepare` command:

```python
@click.command()
@click.argument('version')
@click.option('--date', default=None, help='Release date (YYYY-MM-DD)')
def prepare(version, date):
    """Prepare CHANGELOG for new version release."""
    editor = ChangelogEditor()
    editor.create_version_section(version, date)

    # Validate result
    validator = ChangelogValidator()
    result = validator.validate()

    if result.is_valid:
        click.echo(f"✅ Created [{version}] section in CHANGELOG.md")
    else:
        click.echo("⚠️ CHANGELOG validation failed after edit")
```

### Success Criteria
- ✅ `release prepare v0.11.0` creates section
- ✅ Moves Unreleased content to new section
- ✅ Validates result
- ✅ Preserves formatting

---

## Optimization #6: Explicit Version Command

**Priority**: MEDIUM
**Estimated**: 30 minutes
**Files**: `markitect/cli.py`

### Implementation

Add version subcommand to markitect CLI:

```python
@cli.command()
def version():
    """Show detailed version information."""
    from markitect.__version__ import get_version_info

    info = get_version_info()

    click.echo(f"MarkiTect version: {info['version']}")
    click.echo(f"Latest git tag: {info.get('latest_tag', 'N/A')}")
    click.echo(f"Commits since tag: {info.get('commits_since_tag', 'N/A')}")
    click.echo(f"Working tree: {'clean' if info.get('clean', False) else 'dirty'}")
    click.echo(f"Current commit: {info.get('commit_hash', 'N/A')}")
```

### Success Criteria
- ✅ `markitect version` works
- ✅ Shows more detail than `--version`
- ✅ Backwards compatible with `--version`

---

## Optimization #7: Release Summary Auto-Generation

**Priority**: MEDIUM
**Estimated**: 2 hours
**Files**:
- `capabilities/release-management/src/release_management/summary/generator.py` (new)
- `capabilities/release-management/src/release_management/cli/main.py`

### Implementation

Create SummaryGenerator:

```python
class SummaryGenerator:
    """Generate release summary from CHANGELOG and git metadata."""

    def generate(self, version: str) -> str:
        """Generate RELEASE_SUMMARY.md content."""
        # Extract CHANGELOG section
        changelog_section = self.extract_changelog_section(version)

        # Get git statistics
        stats = self.get_git_statistics(version)

        # Build summary
        template = f"""# MarkiTect {version} Release Summary

**Release Date**: {stats['release_date']}
**Tag**: v{version}

## Changes

{changelog_section}

## Git Statistics

- **Commits**: {stats['commit_count']}
- **Files Changed**: {stats['files_changed']}
- **Insertions**: +{stats['insertions']}
- **Deletions**: -{stats['deletions']}

## Build Artifacts

{self.list_build_artifacts()}

## Validation

{self.get_validation_results()}
"""
        return template
```

Add `release summary` command:

```python
@click.command()
@click.argument('version')
@click.option('--output', '-o', default='RELEASE_SUMMARY.md',
              help='Output file path')
def summary(version, output):
    """Generate release summary document."""
    generator = SummaryGenerator()
    content = generator.generate(version)

    Path(output).write_text(content)
    click.echo(f"✅ Generated {output}")
```

### Success Criteria
- ✅ Extracts CHANGELOG section
- ✅ Includes git statistics
- ✅ Lists build artifacts
- ✅ Saves to file

---

## Optimization #8: Schema Auto-Ingestion

**Priority**: LOW
**Estimated**: 1 hour
**Files**: `markitect/schema_loader.py`

### Implementation

Add auto-ingestion on build/install:

```python
def auto_ingest_schemas():
    """Automatically ingest schemas from markitect/schemas/."""
    schema_dir = Path(__file__).parent / "schemas"

    for schema_file in schema_dir.glob("*-schema-v*.md"):
        # Check if already ingested
        if not is_schema_ingested(schema_file):
            ingest_schema(schema_file)
```

Call from setup.py or as post-install hook.

### Success Criteria
- ✅ New schemas auto-ingested on install
- ✅ Doesn't re-ingest existing schemas
- ✅ Works in development mode

---

## Optimization #9: Release Notes from CHANGELOG

**Priority**: LOW
**Estimated**: 2 hours
**Files**: `capabilities/release-management/src/release_management/changelog/parser.py` (new)

### Implementation

Create ChangelogParser:

```python
class ChangelogParser:
    """Parse CHANGELOG.md and extract sections."""

    def extract_version_section(self, version: str) -> str:
        """Extract content for specific version."""
        # Parse CHANGELOG
        # Find version section
        # Extract content until next version
        # Return formatted for release notes
```

Add `release notes` command:

```python
@click.command()
@click.argument('version')
@click.option('--format', type=click.Choice(['markdown', 'plain', 'html']),
              default='markdown')
def notes(version, format):
    """Extract release notes from CHANGELOG."""
    parser = ChangelogParser()
    content = parser.extract_version_section(version)

    if format == 'html':
        # Convert to HTML
        pass

    click.echo(content)
```

### Success Criteria
- ✅ Extracts version section
- ✅ Multiple output formats
- ✅ Can pipe to gh release or gitea

---

## Testing Strategy

### Per-Optimization Testing
1. Unit tests for each new class/function
2. Integration tests for CLI commands
3. Manual testing with real scenarios

### End-to-End Testing
1. Test full release workflow: prepare → validate → tag → build → summary
2. Test error cases (invalid CHANGELOG, missing tags, etc.)
3. Test with v0.11.0 as real-world scenario

### Regression Testing
- Ensure existing release commands still work
- Backward compatibility with current workflows
- No breaking changes to public APIs

---

## Rollout Plan

### Phase 1: Foundation (Day 1, 5 hours)
Implement high-priority items that prevent errors:
1. Git status enhancement
2. Automated tag pushing
3. CHANGELOG validation
4. Version-tag consistency

**Deliverable**: Robust validation preventing v0.10.0-style issues

### Phase 2: Automation (Day 2, 5.5 hours)
Implement medium-priority UX improvements:
5. CHANGELOG section generation
6. Explicit version command
7. Release summary auto-generation

**Deliverable**: Streamlined release workflow

### Phase 3: Polish (Day 3, 3 hours)
Implement low-priority nice-to-haves:
8. Schema auto-ingestion
9. Release notes extraction

**Deliverable**: Complete automated release toolchain

---

## Success Metrics

### Before Optimizations (v0.10.0)
- Manual steps: 8
- Errors: 2 (forgotten tags, version detection)
- Time: ~3 hours
- Documentation: Manual

### After Optimizations (Target)
- Manual steps: 2-3 (review, approve)
- Errors: 0 (automated validation)
- Time: ~1.5 hours (50% reduction)
- Documentation: Auto-generated

### Quality Improvements
- ✅ No forgotten tag pushes (status + auto-push)
- ✅ CHANGELOG always valid (schema validation)
- ✅ Version consistency guaranteed (automated checks)
- ✅ Consistent documentation (auto-generation)

---

**Plan Created**: 2026-01-06
**Estimated Total Time**: 13.5 hours (3 days @ 4-5 hours/day)
**Next Step**: Begin Phase 1 implementation
