From af3a81543c38eebc210b508ace84673a1c2db2b7 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Fri, 8 Aug 2025 17:33:51 +0700 Subject: [PATCH 01/10] feat: streamline GitHub Actions workflows and improve contributor experience - Replace complex auto-version.yml with simple PR Docker build workflow - Builds Docker images for all PRs using pr-number-sha tagging - Removes redundant versioning logic (semantic-release handles this) - Adds automatic PR comments with Docker usage instructions - Optimize test.yml workflow triggers - Remove redundant push triggers on main branch - Focus on PR testing only for better developer feedback - Add docker-release.yml for production releases - Triggers on GitHub release publication - Multi-platform builds (linux/amd64, linux/arm64) - Updates release notes with Docker installation instructions - Add semantic-release.yml workflow for automated versioning - Uses conventional commits for version bumping - Automatically generates releases and tags - Integrates with Docker workflow via release triggers - Add pre-commit configuration for automatic code quality - Includes ruff (with auto-fix), black, isort - Provides faster development workflow option - Enhance contribution documentation - Add pre-commit hook option as recommended approach - Keep manual script option for comprehensive testing - Improve developer workflow guidance Fixes #215 (automatic changelog generation) Addresses #110 (Docker builds automation) References #107 (improved version tracking) This creates a clean, modern CI/CD pipeline that eliminates redundancy while addressing multiple community requests around changelog generation, Docker builds, and release automation. --- .github/workflows/auto-version.yml | 323 +++++++------------------ .github/workflows/docker-release.yml | 116 +++++++++ .github/workflows/semantic-release.yml | 61 +++++ .github/workflows/test.yml | 81 +++---- .pre-commit-config.yaml | 40 +++ docs/contributions.md | 26 +- pyproject.toml | 29 ++- requirements-dev.txt | 4 +- 8 files changed, 397 insertions(+), 283 deletions(-) create mode 100644 .github/workflows/docker-release.yml create mode 100644 .github/workflows/semantic-release.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index 343d61d..5cb4c42 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -1,248 +1,111 @@ -name: Auto Version +name: PR Docker Build on: pull_request: - types: [closed] - branches: [main] + types: [opened, synchronize, reopened] + +permissions: + contents: read + packages: write + pull-requests: write jobs: - version: - # Only run if PR was merged (not just closed) - if: github.event.pull_request.merged == true + docker: + name: Build PR Docker Image runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: read steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.PAT }} - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Determine version bump type - id: bump_type - run: | - PR_TITLE="${{ github.event.pull_request.title }}" - echo "PR Title: $PR_TITLE" - - # Convert to lowercase for case-insensitive matching - PR_TITLE_LOWER=$(echo "$PR_TITLE" | tr '[:upper:]' '[:lower:]') - - # Determine bump type based on PR title prefix - if [[ "$PR_TITLE_LOWER" =~ ^(breaking|breaking[[:space:]]change): ]]; then - echo "Detected BREAKING CHANGE - major version bump" - echo "bump_type=major" >> $GITHUB_OUTPUT - echo "should_bump=true" >> $GITHUB_OUTPUT - echo "should_build_docker=true" >> $GITHUB_OUTPUT - elif [[ "$PR_TITLE_LOWER" =~ ^feat: ]]; then - echo "Detected new feature - minor version bump" - echo "bump_type=minor" >> $GITHUB_OUTPUT - echo "should_bump=true" >> $GITHUB_OUTPUT - echo "should_build_docker=true" >> $GITHUB_OUTPUT - elif [[ "$PR_TITLE_LOWER" =~ ^(fix|perf|refactor): ]]; then - echo "Detected fix/perf/refactor - patch version bump" - echo "bump_type=patch" >> $GITHUB_OUTPUT - echo "should_bump=true" >> $GITHUB_OUTPUT - echo "should_build_docker=true" >> $GITHUB_OUTPUT - elif [[ "$PR_TITLE_LOWER" =~ ^docker: ]]; then - echo "Detected docker build request - no version bump but build Docker" - echo "bump_type=none" >> $GITHUB_OUTPUT - echo "should_bump=false" >> $GITHUB_OUTPUT - echo "should_build_docker=true" >> $GITHUB_OUTPUT - elif [[ "$PR_TITLE_LOWER" =~ ^(docs|chore|test|ci|style)\+docker: ]]; then - echo "Detected non-versioned change with Docker build request" - echo "bump_type=none" >> $GITHUB_OUTPUT - echo "should_bump=false" >> $GITHUB_OUTPUT - echo "should_build_docker=true" >> $GITHUB_OUTPUT - elif [[ "$PR_TITLE_LOWER" =~ ^(docs|chore|test|ci|style): ]]; then - echo "Detected non-versioned change - no version bump" - echo "bump_type=none" >> $GITHUB_OUTPUT - echo "should_bump=false" >> $GITHUB_OUTPUT - echo "should_build_docker=false" >> $GITHUB_OUTPUT - else - echo "No recognized prefix - no version bump" - echo "bump_type=none" >> $GITHUB_OUTPUT - echo "should_bump=false" >> $GITHUB_OUTPUT - echo "should_build_docker=false" >> $GITHUB_OUTPUT - fi - - - name: Get current version - if: steps.bump_type.outputs.should_bump == 'true' - id: current_version - run: | - CURRENT_VERSION=$(python -c "from config import __version__; print(__version__)") - echo "Current version: $CURRENT_VERSION" - echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - - - name: Bump version - if: steps.bump_type.outputs.should_bump == 'true' - id: new_version - run: | - python scripts/bump_version.py ${{ steps.bump_type.outputs.bump_type }} - NEW_VERSION=$(python -c "from config import __version__; print(__version__)") - echo "New version: $NEW_VERSION" - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - - - name: Commit version change - if: steps.bump_type.outputs.should_bump == 'true' - run: | - git add config.py - git commit -m "chore: bump version to ${{ steps.new_version.outputs.version }} + - name: Checkout + uses: actions/checkout@v4 - Automated version bump from PR #${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }} - - Co-authored-by: ${{ github.event.pull_request.user.login }} <${{ github.event.pull_request.user.id }}+${{ github.event.pull_request.user.login }}@users.noreply.github.com>" - git push - - - name: Create git tag - if: steps.bump_type.outputs.should_bump == 'true' - run: | - git tag -a "v${{ steps.new_version.outputs.version }}" -m "Release v${{ steps.new_version.outputs.version }} - - Changes in this release: - - ${{ github.event.pull_request.title }} - - PR: #${{ github.event.pull_request.number }} - Author: @${{ github.event.pull_request.user.login }}" - git push origin "v${{ steps.new_version.outputs.version }}" - - - name: Generate release notes - if: steps.bump_type.outputs.should_bump == 'true' - id: release_notes - run: | - # Extract PR body for release notes - PR_BODY=$(cat << 'EOF' - ${{ github.event.pull_request.body }} - EOF - ) - - # Create release notes - RELEASE_NOTES=$(cat << EOF - ## What's Changed - - ${{ github.event.pull_request.title }} by @${{ github.event.pull_request.user.login }} in #${{ github.event.pull_request.number }} - - ### Details - - $PR_BODY - - ### Version Info - - Previous version: ${{ steps.current_version.outputs.version }} - - New version: ${{ steps.new_version.outputs.version }} - - Bump type: ${{ steps.bump_type.outputs.bump_type }} - - **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.current_version.outputs.version }}...v${{ steps.new_version.outputs.version }} - EOF - ) - - # Save to file for GitHub release - echo "$RELEASE_NOTES" > release_notes.md - - - name: Create GitHub release - if: steps.bump_type.outputs.should_bump == 'true' - uses: softprops/action-gh-release@v1 - with: - tag_name: v${{ steps.new_version.outputs.version }} - name: Release v${{ steps.new_version.outputs.version }} - body_path: release_notes.md - draft: false - prerelease: false - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Trigger Docker build - if: steps.bump_type.outputs.should_build_docker == 'true' - run: | - echo "đŸŗ Triggering Docker build and publish workflow" - # The Docker workflow will be triggered by the tag creation (if version bumped) - # or by repository_dispatch (if docker: prefix without version bump) - if [ "${{ steps.bump_type.outputs.should_bump }}" == "false" ]; then - # For docker: prefix without version bump, trigger via repository_dispatch - curl -X POST \ - -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${{ github.repository }}/dispatches" \ - -d '{"event_type":"docker-build","client_payload":{"pr_number":"${{ github.event.pull_request.number }}","pr_title":"${{ github.event.pull_request.title }}","commit_sha":"${{ github.sha }}"}}' - - # Add comment to PR about Docker build - COMMENT_BODY="đŸŗ **Docker Image Build Triggered** + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - This PR triggered a Docker image build because of the \`+docker\` suffix in the title. + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - **Expected Image Tags:** - - \`ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}\` - - \`ghcr.io/${{ github.repository_owner }}/zen-mcp-server:main-${{ github.sha }}\` + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + # PR-specific tag for testing + type=raw,value=pr-${{ github.event.number }}-${{ github.sha }} + type=raw,value=pr-${{ github.event.number }} - **To test the image after build completes:** - \`\`\`bash - docker pull ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }} - \`\`\` + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max - **Claude Desktop config for testing:** - \`\`\`json - { - \"mcpServers\": { - \"gemini\": { - \"command\": \"docker\", - \"args\": [ - \"run\", \"--rm\", \"-i\", - \"-e\", \"GEMINI_API_KEY\", - \"ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}\" - ], - \"env\": { - \"GEMINI_API_KEY\": \"your-api-key-here\" + - name: Comment on PR with Docker info + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request.number; + const sha = context.sha.substring(0, 7); + const repo = context.repo.repo; + const owner = context.repo.owner; + + const comment = `đŸŗ **Docker Image Built Successfully** + + This PR has been built and pushed as a Docker image for testing: + + **Available Tags:** + - \`ghcr.io/${owner}/${repo}:pr-${prNumber}-${context.sha}\` + - \`ghcr.io/${owner}/${repo}:pr-${prNumber}\` (latest for this PR) + + **Test the changes:** + \`\`\`bash + docker pull ghcr.io/${owner}/${repo}:pr-${prNumber} + \`\`\` + + **Claude Desktop configuration:** + \`\`\`json + { + "mcpServers": { + "zen-mcp-server": { + "command": "docker", + "args": [ + "run", "--rm", "-i", + "-e", "GEMINI_API_KEY", + "ghcr.io/${owner}/${repo}:pr-${prNumber}" + ], + "env": { + "GEMINI_API_KEY": "your-api-key-here" + } } } } - } - \`\`\` + \`\`\` + + The image will be updated automatically when you push new commits to this PR.`; + + github.rest.issues.createComment({ + issue_number: prNumber, + owner: owner, + repo: repo, + body: comment + }); - View the build progress in the [Actions tab](https://github.com/${{ github.repository }}/actions)." - - curl -X POST \ - -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ - -d "{\"body\":\"$COMMENT_BODY\"}" - fi - - - name: Summary - run: | - if [ "${{ steps.bump_type.outputs.should_bump }}" == "true" ]; then - echo "### ✅ Version Bumped Successfully" >> $GITHUB_STEP_SUMMARY + - name: Create deployment summary + run: | + echo "## đŸŗ PR Docker Build Complete" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Previous version**: ${{ steps.current_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **New version**: ${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Bump type**: ${{ steps.bump_type.outputs.bump_type }}" >> $GITHUB_STEP_SUMMARY - echo "- **Tag**: v${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY - echo "- **Docker**: Will build and publish with new tag" >> $GITHUB_STEP_SUMMARY - elif [ "${{ steps.bump_type.outputs.should_build_docker }}" == "true" ]; then - echo "### đŸŗ Docker Build Requested" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "No version bump but Docker image will be built and published." >> $GITHUB_STEP_SUMMARY - echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY - echo "- **Title**: ${{ github.event.pull_request.title }}" >> $GITHUB_STEP_SUMMARY - echo "- **Docker tag**: Based on commit SHA" >> $GITHUB_STEP_SUMMARY - else - echo "### â„šī¸ No Version Bump Required" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "PR title prefix did not require a version bump." >> $GITHUB_STEP_SUMMARY - echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY - echo "- **Title**: ${{ github.event.pull_request.title }}" >> $GITHUB_STEP_SUMMARY - fi - + echo "**PR**: #${{ github.event.number }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "**Images built:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml new file mode 100644 index 0000000..445052a --- /dev/null +++ b/.github/workflows/docker-release.yml @@ -0,0 +1,116 @@ +name: Docker Release Build + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Tag to build (leave empty for latest release)' + required: false + type: string + +permissions: + contents: read + packages: write + +jobs: + docker: + name: Build and Push Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # If triggered by workflow_dispatch with a tag, checkout that tag + ref: ${{ inputs.tag || github.event.release.tag_name }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + # Tag with the release version + type=semver,pattern={{version}},value=${{ inputs.tag || github.event.release.tag_name }} + type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag || github.event.release.tag_name }} + type=semver,pattern={{major}},value=${{ inputs.tag || github.event.release.tag_name }} + # Also tag as latest for the most recent release + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Update release with Docker info + if: github.event_name == 'release' + run: | + RELEASE_TAG="${{ github.event.release.tag_name }}" + DOCKER_TAGS=$(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' ') + + # Add Docker information to the release + gh release edit "$RELEASE_TAG" --notes-file - << EOF + ${{ github.event.release.body }} + + --- + + ## đŸŗ Docker Images + + This release is available as Docker images: + + $(echo "$DOCKER_TAGS" | sed 's/ghcr.io/- `ghcr.io/g' | sed 's/ /`\n/g') + + **Quick start with Docker:** + \`\`\`bash + docker pull ghcr.io/${{ github.repository }}:$RELEASE_TAG + \`\`\` + + **Claude Desktop configuration:** + \`\`\`json + { + "mcpServers": { + "zen-mcp-server": { + "command": "docker", + "args": [ + "run", "--rm", "-i", + "-e", "GEMINI_API_KEY", + "ghcr.io/${{ github.repository }}:$RELEASE_TAG" + ], + "env": { + "GEMINI_API_KEY": "your-api-key-here" + } + } + } + } + \`\`\` + EOF + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create deployment summary + run: | + echo "## đŸŗ Docker Release Build Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Release**: ${{ inputs.tag || github.event.release.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Images built:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml new file mode 100644 index 0000000..f9d0de3 --- /dev/null +++ b/.github/workflows/semantic-release.yml @@ -0,0 +1,61 @@ +name: Semantic Release + +on: + push: + branches: + - main + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + concurrency: release + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: true + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install python-semantic-release + + - name: Verify tests pass + run: | + pip install -r requirements.txt + pip install -r requirements-dev.txt + python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration" + + - name: Run semantic release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + semantic-release version + semantic-release publish + + - name: Upload build artifacts to release + if: hashFiles('dist/*') != '' + run: | + # Get the latest release tag + LATEST_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName') + if [ ! -z "$LATEST_TAG" ]; then + echo "Uploading artifacts to release $LATEST_TAG" + gh release upload "$LATEST_TAG" dist/* --clobber + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c70ff3..ffc28c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,8 @@ name: Tests on: - push: - branches: [ main, develop ] pull_request: - branches: [ main ] + branches: [main] jobs: test: @@ -14,47 +12,46 @@ jobs: python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install -r requirements-dev.txt - - - name: Run unit tests - run: | - # Run only unit tests (exclude simulation tests and integration tests) - # Integration tests require local-llama which isn't available in CI - python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration" - env: - # Ensure no API key is accidentally used in CI - GEMINI_API_KEY: "" - OPENAI_API_KEY: "" + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Run unit tests + run: | + # Run only unit tests (exclude simulation tests and integration tests) + # Integration tests require local-llama which isn't available in CI + python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration" + env: + # Ensure no API key is accidentally used in CI + GEMINI_API_KEY: "" + OPENAI_API_KEY: "" lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements-dev.txt - - - name: Run black formatter check - run: black --check . --exclude="test_simulation_files/" - - - name: Run ruff linter - run: ruff check . --exclude test_simulation_files + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Run black formatter check + run: black --check . --exclude="test_simulation_files/" + + - name: Run ruff linter + run: ruff check . --exclude test_simulation_files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ead23ff --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,40 @@ +--- +default_stages: [pre-commit, pre-push] +repos: + - repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black + exclude: ^test_simulation_files/ + + - repo: https://github.com/pycqa/isort + rev: 6.0.1 + hooks: + - id: isort + args: ["--profile", "black"] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.8 + hooks: + - id: ruff + args: [--fix] + exclude: ^test_simulation_files/ + +# Configuration for specific tools +default_language_version: + python: python3 + +# Exclude patterns +exclude: | + (?x)^( + \.git/| + \.venv/| + venv/| + \.zen_venv/| + __pycache__/| + \.pytest_cache/| + logs/| + dist/| + build/| + test_simulation_files/ + )$ diff --git a/docs/contributions.md b/docs/contributions.md index 185b727..12147d0 100644 --- a/docs/contributions.md +++ b/docs/contributions.md @@ -23,8 +23,16 @@ We maintain high code quality standards. **All contributions must pass our autom #### Required Code Quality Checks -Before submitting any PR, run our automated quality check script: +**Option 1 - Automated (Recommended):** +```bash +# Install pre-commit hooks (one-time setup) +pre-commit install +# Now linting runs automatically on every commit +# Includes: ruff (with auto-fix), black, isort +``` + +**Option 2 - Manual:** ```bash # Run the comprehensive quality checks script ./code_quality_checks.sh @@ -32,7 +40,7 @@ Before submitting any PR, run our automated quality check script: This script automatically runs: - Ruff linting with auto-fix -- Black code formatting +- Black code formatting - Import sorting with isort - Complete unit test suite (361 tests) - Verification that all checks pass 100% @@ -56,7 +64,7 @@ python -m pytest -xvs python communication_simulator_test.py ``` -**Important**: +**Important**: - **Every single test must pass** - we have zero tolerance for failing tests in CI - All linting must pass cleanly (ruff, black, isort) - Import sorting must be correct @@ -69,12 +77,12 @@ python communication_simulator_test.py 1. **New features MUST include tests**: - Add unit tests in `tests/` for new functions or classes - Test both success and error cases - + 2. **Tool changes require simulator tests**: - Add simulator tests in `simulator_tests/` for new or modified tools - Use realistic prompts that demonstrate the feature - Validate output through server logs - + 3. **Bug fixes require regression tests**: - Add a test that would have caught the bug - Ensure the bug cannot reoccur @@ -136,14 +144,14 @@ def process_model_response( max_tokens: Optional[int] = None ) -> ProcessedResult: """Process and validate model response. - + Args: response: Raw response from the model provider max_tokens: Optional token limit for truncation - + Returns: ProcessedResult with validated and formatted content - + Raises: ValueError: If response is invalid or exceeds limits """ @@ -237,4 +245,4 @@ Contributors are recognized in: - Release notes for significant contributions - Special mentions for exceptional work -Thank you for contributing to Zen MCP Server! Your efforts help make this tool better for everyone. \ No newline at end of file +Thank you for contributing to Zen MCP Server! Your efforts help make this tool better for everyone. diff --git a/pyproject.toml b/pyproject.toml index b3e715b..25e9edc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "zen-mcp-server" -version = "0.1.0" +version = "1.1.0" description = "AI-powered MCP server with multiple model providers" requires-python = ">=3.9" dependencies = [ @@ -83,6 +83,33 @@ ignore = [ "tests/*" = ["B011"] "tests/conftest.py" = ["E402"] # Module level imports not at top of file - needed for test setup +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +branch = "main" +build_command = "python -m pip install --upgrade build && python -m build" +dist_path = "dist/" +upload_to_vcs_release = true +upload_to_repository = false +remove_dist = false +commit_version_number = true +commit_message = "chore(release): {version}\n\nAutomatically generated by python-semantic-release" +tag_format = "v{version}" + +[tool.semantic_release.branches.main] +match = "main" +prerelease = false + +[tool.semantic_release.changelog] +exclude_commit_patterns = [] + +[tool.semantic_release.commit_parser_options] +allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] + +[tool.semantic_release.remote.token] +env = "GH_TOKEN" + [build-system] requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] build-backend = "setuptools.build_meta" diff --git a/requirements-dev.txt b/requirements-dev.txt index 86e039d..43273b4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,6 @@ pytest-asyncio>=0.21.0 pytest-mock>=3.11.0 black>=23.0.0 ruff>=0.1.0 -isort>=5.12.0 \ No newline at end of file +isort>=5.12.0 +python-semantic-release>=10.3.0 +build>=1.0.0 From fcaf0d691755a973ca08c676fcf7fdd221d5a800 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Fri, 8 Aug 2025 17:39:25 +0700 Subject: [PATCH 02/10] fix: correct pre-commit global exclude pattern and remove redundant excludes - Remove $ anchor from global exclude pattern to properly exclude files within directories - Remove redundant exclude properties from black and ruff hooks since global exclude now handles it - Ensures isort hook also excludes test_simulation_files/ consistently Addresses reviewer feedback on PR #217 --- .pre-commit-config.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ead23ff..d17cf3c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,6 @@ repos: rev: 25.1.0 hooks: - id: black - exclude: ^test_simulation_files/ - repo: https://github.com/pycqa/isort rev: 6.0.1 @@ -18,7 +17,6 @@ repos: hooks: - id: ruff args: [--fix] - exclude: ^test_simulation_files/ # Configuration for specific tools default_language_version: @@ -37,4 +35,4 @@ exclude: | dist/| build/| test_simulation_files/ - )$ + ) From a5597bbcde03acf6c4b13e8a60ed48a7c113f56f Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Fri, 8 Aug 2025 17:48:53 +0700 Subject: [PATCH 03/10] feat: add semantic PR validation workflow - Validates PR titles against conventional commit standards - Adds helpful comments when titles don't follow format - Auto-deletes comments when titles are fixed - Uses standard GITHUB_TOKEN (no special app setup needed) - Enforces consistency with semantic-release workflow --- .github/workflows/semantic-pr.yml | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/semantic-pr.yml diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml new file mode 100644 index 0000000..48c3aa3 --- /dev/null +++ b/.github/workflows/semantic-pr.yml @@ -0,0 +1,47 @@ +--- +name: Semantic PR + +on: + pull_request: + types: [opened, edited, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + semantic-pr: + name: Validate PR + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check PR Title + id: lint-pr-title + uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Add PR error comment + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 + if: always() && (steps.lint-pr-title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. + + Details: + + ``` + ${{ steps.lint-pr-title.outputs.error_message }} + ``` + + - name: Delete PR error comment + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 + if: ${{ steps.lint-pr-title.outputs.error_message == null }} + with: + header: pr-title-lint-error + delete: true \ No newline at end of file From bbf4afa3278968f03ae9ffe436e0cc706b5f89ff Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Fri, 8 Aug 2025 17:51:24 +0700 Subject: [PATCH 04/10] feat: optimize PR Docker build to only run for relevant changes - Add path filters to only trigger Docker builds for Python/Docker-related changes - Includes: **.py, requirements*.txt, pyproject.toml, Dockerfile, docker-compose.yml, .dockerignore - Avoids unnecessary builds for documentation-only or workflow-only PRs - Saves CI resources and avoids permission issues for irrelevant changes --- .github/workflows/auto-version.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index 5cb4c42..5607be0 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -3,6 +3,15 @@ name: PR Docker Build on: pull_request: types: [opened, synchronize, reopened] + paths: + - '**.py' + - 'requirements*.txt' + - 'pyproject.toml' + - 'Dockerfile' + - 'docker-compose.yml' + - '.dockerignore' + - 'server.py' + - 'config.py' permissions: contents: read From ce2e15e406cb928006f2ce3a63491db03499ad73 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 13:58:46 +0700 Subject: [PATCH 05/10] fix: handle fork PR permissions in Docker workflow - Only push to registry for internal PRs (same repository) - Build-only for fork PRs to test Docker compatibility - Separate comments and summaries for different PR types - Fixes permission issues with fork contributions --- .github/workflows/auto-version.yml | 56 ++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index 5607be0..60e7b9c 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -31,6 +31,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry + if: github.event.pull_request.head.repo.full_name == github.repository uses: docker/login-action@v3 with: registry: ghcr.io @@ -47,7 +48,8 @@ jobs: type=raw,value=pr-${{ github.event.number }}-${{ github.sha }} type=raw,value=pr-${{ github.event.number }} - - name: Build and push Docker image + - name: Build and push Docker image (internal PRs) + if: github.event.pull_request.head.repo.full_name == github.repository uses: docker/build-push-action@v5 with: context: . @@ -58,7 +60,20 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Comment on PR with Docker info + - name: Build Docker image (fork PRs) + if: github.event.pull_request.head.repo.full_name != github.repository + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: false + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Comment on PR with Docker info (internal PRs) + if: github.event.pull_request.head.repo.full_name == github.repository uses: actions/github-script@v7 with: script: | @@ -108,7 +123,33 @@ jobs: body: comment }); - - name: Create deployment summary + - name: Comment on PR with Docker info (fork PRs) + if: github.event.pull_request.head.repo.full_name != github.repository + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request.number; + const sha = context.sha.substring(0, 7); + + const comment = `đŸŗ **Docker Build Complete (Build Only)** + + **PR**: #${prNumber} | **Commit**: \`${sha}\` + + ✅ **Docker build successful** - Multi-platform image built and tested + + **Note**: Fork PRs only build (no push) for security. Images will be available once PR is merged. + + The Docker build confirms your changes are compatible with the containerized deployment.`; + + github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + - name: Create deployment summary (internal PRs) + if: github.event.pull_request.head.repo.full_name == github.repository run: | echo "## đŸŗ PR Docker Build Complete" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -118,3 +159,12 @@ jobs: echo "\`\`\`" >> $GITHUB_STEP_SUMMARY echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + - name: Create deployment summary (fork PRs) + if: github.event.pull_request.head.repo.full_name != github.repository + run: | + echo "## đŸŗ Docker Build Complete (Build Only)" >> $GITHUB_STEP_SUMMARY + echo "** PR**: #${{ github.event.number }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "✅ Multi-platform Docker build successful" >> $GITHUB_STEP_SUMMARY + echo "Note: Fork PRs only build (no push) for security" >> $GITHUB_STEP_SUMMARY From 1d1cd44a424bff6eeb1ceb3744740c08c1ac6b12 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 14:00:09 +0700 Subject: [PATCH 06/10] =?UTF-8?q?rename:=20auto-version.yml=20=E2=86=92=20?= =?UTF-8?q?docker-pr.yml=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reflects actual functionality (Docker PR builds) - Consistent with improved workflow naming --- .github/workflows/{auto-version.yml => docker-pr.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{auto-version.yml => docker-pr.yml} (100%) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/docker-pr.yml similarity index 100% rename from .github/workflows/auto-version.yml rename to .github/workflows/docker-pr.yml From 31585b3bfa39255925582c6f32a6b5350558c192 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 14:05:09 +0700 Subject: [PATCH 07/10] feat: streamline docker-pr workflow with label support - Add docker-build label triggering for manual builds - Use sticky comments for cleaner PR feedback - Streamlined job conditions and messaging - Consistent with improved workflow design --- .github/workflows/docker-pr.yml | 118 +++++++++++++------------------- 1 file changed, 46 insertions(+), 72 deletions(-) diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 60e7b9c..093e455 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -2,7 +2,7 @@ name: PR Docker Build on: pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, labeled, unlabeled] paths: - '**.py' - 'requirements*.txt' @@ -10,8 +10,6 @@ on: - 'Dockerfile' - 'docker-compose.yml' - '.dockerignore' - - 'server.py' - - 'config.py' permissions: contents: read @@ -20,8 +18,13 @@ permissions: jobs: docker: - name: Build PR Docker Image + name: Build Docker Image runs-on: ubuntu-latest + if: | + github.event.action == 'opened' || + github.event.action == 'synchronize' || + github.event.action == 'reopened' || + contains(github.event.pull_request.labels.*.name, 'docker-build') steps: - name: Checkout @@ -72,99 +75,70 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Comment on PR with Docker info (internal PRs) + - name: Add Docker build comment (internal PRs) if: github.event.pull_request.head.repo.full_name == github.repository - uses: actions/github-script@v7 + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 with: - script: | - const prNumber = context.payload.pull_request.number; - const sha = context.sha.substring(0, 7); - const repo = context.repo.repo; - const owner = context.repo.owner; + header: docker-build + message: | + ## đŸŗ Docker Build Complete - const comment = `đŸŗ **Docker Image Built Successfully** + **PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}` - This PR has been built and pushed as a Docker image for testing: + ``` + ${{ steps.meta.outputs.tags }} + ``` - **Available Tags:** - - \`ghcr.io/${owner}/${repo}:pr-${prNumber}-${context.sha}\` - - \`ghcr.io/${owner}/${repo}:pr-${prNumber}\` (latest for this PR) + **Test:** `docker pull ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}` - **Test the changes:** - \`\`\`bash - docker pull ghcr.io/${owner}/${repo}:pr-${prNumber} - \`\`\` - - **Claude Desktop configuration:** - \`\`\`json + **Claude config:** + ```json { "mcpServers": { - "zen-mcp-server": { + "zen": { "command": "docker", - "args": [ - "run", "--rm", "-i", - "-e", "GEMINI_API_KEY", - "ghcr.io/${owner}/${repo}:pr-${prNumber}" - ], - "env": { - "GEMINI_API_KEY": "your-api-key-here" - } + "args": ["run", "--rm", "-i", "-e", "GEMINI_API_KEY", "ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}"], + "env": { "GEMINI_API_KEY": "your-key" } } } } - \`\`\` + ``` - The image will be updated automatically when you push new commits to this PR.`; - - github.rest.issues.createComment({ - issue_number: prNumber, - owner: owner, - repo: repo, - body: comment - }); + 💡 Add `docker-build` label to manually trigger builds - - name: Comment on PR with Docker info (fork PRs) + - name: Add Docker build comment (fork PRs) if: github.event.pull_request.head.repo.full_name != github.repository - uses: actions/github-script@v7 + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 with: - script: | - const prNumber = context.payload.pull_request.number; - const sha = context.sha.substring(0, 7); + header: docker-build + message: | + ## đŸŗ Docker Build Complete (Build Only) - const comment = `đŸŗ **Docker Build Complete (Build Only)** - - **PR**: #${prNumber} | **Commit**: \`${sha}\` + **PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}` ✅ **Docker build successful** - Multi-platform image built and tested **Note**: Fork PRs only build (no push) for security. Images will be available once PR is merged. - The Docker build confirms your changes are compatible with the containerized deployment.`; - - github.rest.issues.createComment({ - issue_number: prNumber, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); + 💡 Add `docker-build` label to manually trigger builds - - name: Create deployment summary (internal PRs) + - name: Update job summary (internal PRs) if: github.event.pull_request.head.repo.full_name == github.repository run: | - echo "## đŸŗ PR Docker Build Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**PR**: #${{ github.event.number }}" >> $GITHUB_STEP_SUMMARY - echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - echo "**Images built:**" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + { + echo "## đŸŗ Docker Build Complete" + echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}" + echo '```' + echo "${{ steps.meta.outputs.tags }}" + echo '```' + } >> $GITHUB_STEP_SUMMARY - - name: Create deployment summary (fork PRs) + - name: Update job summary (fork PRs) if: github.event.pull_request.head.repo.full_name != github.repository run: | - echo "## đŸŗ Docker Build Complete (Build Only)" >> $GITHUB_STEP_SUMMARY - echo "** PR**: #${{ github.event.number }}" >> $GITHUB_STEP_SUMMARY - echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY - echo "✅ Multi-platform Docker build successful" >> $GITHUB_STEP_SUMMARY - echo "Note: Fork PRs only build (no push) for security" >> $GITHUB_STEP_SUMMARY + { + echo "## đŸŗ Docker Build Complete (Build Only)" + echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}" + echo "✅ Multi-platform Docker build successful" + echo "Note: Fork PRs only build (no push) for security" + } >> $GITHUB_STEP_SUMMARY From ab397d7b6a6d7add2e9764634054128b2417d032 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 14:08:43 +0700 Subject: [PATCH 08/10] fix: remove fork PR comment step due to permission restrictions Fork PRs cannot write comments due to security restrictions. Job summary provides build status information instead. --- .github/workflows/docker-pr.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 093e455..c05519e 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -106,21 +106,6 @@ jobs: 💡 Add `docker-build` label to manually trigger builds - - name: Add Docker build comment (fork PRs) - if: github.event.pull_request.head.repo.full_name != github.repository - uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3 - with: - header: docker-build - message: | - ## đŸŗ Docker Build Complete (Build Only) - - **PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}` - - ✅ **Docker build successful** - Multi-platform image built and tested - - **Note**: Fork PRs only build (no push) for security. Images will be available once PR is merged. - - 💡 Add `docker-build` label to manually trigger builds - name: Update job summary (internal PRs) if: github.event.pull_request.head.repo.full_name == github.repository From 3c80850d8aea5491f238a6f227f067de29ebc1cd Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 14:59:17 +0700 Subject: [PATCH 09/10] chore: remove obsolete scripts directory The manual version bumping script (scripts/bump_version.py) is now obsolete since PR #217 introduced semantic-release automation for version management. - Removed scripts/ directory and bump_version.py script - Updated .dockerignore to remove reference to deleted script Semantic versioning is now handled automatically by GitHub Actions workflows using conventional commits and semantic-release tooling. --- .dockerignore | 1 - scripts/bump_version.py | 114 ---------------------------------------- 2 files changed, 115 deletions(-) delete mode 100755 scripts/bump_version.py diff --git a/.dockerignore b/.dockerignore index 488723c..5199434 100644 --- a/.dockerignore +++ b/.dockerignore @@ -50,7 +50,6 @@ pytest.ini .env .env.local examples/ -scripts/bump_version.py code_quality_checks.sh run_integration_tests.sh diff --git a/scripts/bump_version.py b/scripts/bump_version.py deleted file mode 100755 index 57e34ad..0000000 --- a/scripts/bump_version.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python3 -""" -Version bumping utility for Gemini MCP Server - -This script handles semantic version bumping for the project by: -- Reading current version from config.py -- Applying the appropriate version bump (major, minor, patch) -- Updating config.py with new version and timestamp -- Preserving file structure and formatting -""" - -import re -import sys -from datetime import datetime -from pathlib import Path - - -def parse_version(version_string: str) -> tuple[int, int, int]: - """Parse semantic version string into tuple of integers.""" - match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version_string) - if not match: - raise ValueError(f"Invalid version format: {version_string}") - return int(match.group(1)), int(match.group(2)), int(match.group(3)) - - -def bump_version(version: tuple[int, int, int], bump_type: str) -> tuple[int, int, int]: - """Apply version bump according to semantic versioning rules.""" - major, minor, patch = version - - if bump_type == "major": - return (major + 1, 0, 0) - elif bump_type == "minor": - return (major, minor + 1, 0) - elif bump_type == "patch": - return (major, minor, patch + 1) - else: - raise ValueError(f"Invalid bump type: {bump_type}") - - -def update_config_file(new_version: str) -> None: - """Update version and timestamp in config.py while preserving structure.""" - config_path = Path(__file__).parent.parent / "config.py" - - if not config_path.exists(): - raise FileNotFoundError(f"config.py not found at {config_path}") - - # Read the current content - content = config_path.read_text() - - # Update version using regex to preserve formatting - version_pattern = r'(__version__\s*=\s*["\'])[\d\.]+(["\'])' - content = re.sub(version_pattern, rf"\g<1>{new_version}\g<2>", content) - - # Update the __updated__ field with current date - current_date = datetime.now().strftime("%Y-%m-%d") - updated_pattern = r'(__updated__\s*=\s*["\'])[\d\-]+(["\'])' - content = re.sub(updated_pattern, rf"\g<1>{current_date}\g<2>", content) - - # Write back the updated content - config_path.write_text(content) - print(f"Updated config.py: version={new_version}, updated={current_date}") - - -def get_current_version() -> str: - """Extract current version from config.py.""" - config_path = Path(__file__).parent.parent / "config.py" - - if not config_path.exists(): - raise FileNotFoundError(f"config.py not found at {config_path}") - - content = config_path.read_text() - match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content) - - if not match: - raise ValueError("Could not find __version__ in config.py") - - return match.group(1) - - -def main(): - """Main entry point for version bumping.""" - if len(sys.argv) != 2: - print("Usage: python bump_version.py ") - sys.exit(1) - - bump_type = sys.argv[1].lower() - if bump_type not in ["major", "minor", "patch"]: - print(f"Invalid bump type: {bump_type}") - print("Valid types: major, minor, patch") - sys.exit(1) - - try: - # Get current version - current = get_current_version() - print(f"Current version: {current}") - - # Parse and bump version - version_tuple = parse_version(current) - new_version_tuple = bump_version(version_tuple, bump_type) - new_version = f"{new_version_tuple[0]}.{new_version_tuple[1]}.{new_version_tuple[2]}" - - # Update config file - update_config_file(new_version) - - # Output new version for GitHub Actions - print(f"New version: {new_version}") - - except Exception as e: - print(f"Error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() From 9f5c77fbadd517dd2b4e77fcd10eda83c5014c72 Mon Sep 17 00:00:00 2001 From: Sven Lito Date: Tue, 12 Aug 2025 15:20:34 +0700 Subject: [PATCH 10/10] fix: align PR template with actual semantic-release and Docker workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PR template was outdated and misaligned with the actual workflow behavior introduced in PR #217. Key fixes: - **Semantic Release**: Now matches pyproject.toml configuration - feat → MINOR, fix/perf → PATCH (not refactor) - Added missing 'build' type from allowed_tags - Fixed breaking change syntax (feat\!, BREAKING CHANGE: in body) - Removed incorrect 'breaking:' prefix format - **Docker Builds**: Clarified independence from versioning - Builds trigger on file changes (Python, Docker files) - Manual triggering via 'docker-build' label - Removed misleading 'trigger Docker build + version bump' claims - **Conventional Commits**: Added link to official specification The template now accurately reflects the semantic-release config and docker-pr.yml workflow implementation, preventing contributor confusion. --- .github/pull_request_template.md | 43 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0b8a086..0e46604 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,28 +1,35 @@ ## PR Title Format -**Please ensure your PR title follows one of these formats:** +**Please ensure your PR title follows [Conventional Commits](https://www.conventionalcommits.org/) format:** -### Version Bumping Prefixes (trigger Docker build + version bump): -- `feat: ` - New features (triggers MINOR version bump) -- `fix: ` - Bug fixes (triggers PATCH version bump) -- `breaking: ` or `BREAKING CHANGE: ` - Breaking changes (triggers MAJOR version bump) -- `perf: ` - Performance improvements (triggers PATCH version bump) -- `refactor: ` - Code refactoring (triggers PATCH version bump) +### Version Bumping Types (trigger semantic release): +- `feat: ` - New features → **MINOR** version bump (1.1.0 → 1.2.0) +- `fix: ` - Bug fixes → **PATCH** version bump (1.1.0 → 1.1.1) +- `perf: ` - Performance improvements → **PATCH** version bump (1.1.0 → 1.1.1) -### Non-Version Prefixes (no version bump): -- `docs: ` - Documentation only +### Breaking Changes (trigger MAJOR version bump): +For breaking changes, use any commit type above with `BREAKING CHANGE:` in the commit body or `!` after the type: +- `feat!: ` → **MAJOR** version bump (1.1.0 → 2.0.0) +- `fix!: ` → **MAJOR** version bump (1.1.0 → 2.0.0) + +### Non-Versioning Types (no release): +- `build: ` - Build system changes - `chore: ` - Maintenance tasks -- `test: ` - Test additions/changes - `ci: ` - CI/CD changes -- `style: ` - Code style changes +- `docs: ` - Documentation only +- `refactor: ` - Code refactoring (no functional changes) +- `style: ` - Code style/formatting changes +- `test: ` - Test additions/changes -### Docker Build Options: -- `docker: ` - Force Docker build without version bump -- `docs+docker: ` - Documentation + Docker build -- `chore+docker: ` - Maintenance + Docker build -- `test+docker: ` - Tests + Docker build -- `ci+docker: ` - CI changes + Docker build -- `style+docker: ` - Style changes + Docker build +### Docker Build Triggering: + +Docker builds are **independent** of versioning and trigger based on: + +**Automatic**: When PRs modify relevant files: +- Python files (`*.py`), `requirements*.txt`, `pyproject.toml` +- Docker files (`Dockerfile`, `docker-compose.yml`, `.dockerignore`) + +**Manual**: Add the `docker-build` label to force builds for any PR. ## Description