Merge pull request #217 from svnlto/main

feat: streamline GitHub Actions workflows and improve contributor experience
This commit is contained in:
Beehive Innovations
2025-08-20 08:36:21 +04:00
committed by GitHub
13 changed files with 503 additions and 434 deletions

View File

@@ -50,7 +50,6 @@ pytest.ini
.env
.env.local
examples/
scripts/bump_version.py
code_quality_checks.sh
run_integration_tests.sh

View File

@@ -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: <description>` - New features (triggers MINOR version bump)
- `fix: <description>` - Bug fixes (triggers PATCH version bump)
- `breaking: <description>` or `BREAKING CHANGE: <description>` - Breaking changes (triggers MAJOR version bump)
- `perf: <description>` - Performance improvements (triggers PATCH version bump)
- `refactor: <description>` - Code refactoring (triggers PATCH version bump)
### Version Bumping Types (trigger semantic release):
- `feat: <description>` - New features **MINOR** version bump (1.1.0 → 1.2.0)
- `fix: <description>` - Bug fixes **PATCH** version bump (1.1.0 → 1.1.1)
- `perf: <description>` - Performance improvements → **PATCH** version bump (1.1.0 → 1.1.1)
### Non-Version Prefixes (no version bump):
- `docs: <description>` - 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!: <description>`**MAJOR** version bump (1.1.0 → 2.0.0)
- `fix!: <description>`**MAJOR** version bump (1.1.0 → 2.0.0)
### Non-Versioning Types (no release):
- `build: <description>` - Build system changes
- `chore: <description>` - Maintenance tasks
- `test: <description>` - Test additions/changes
- `ci: <description>` - CI/CD changes
- `style: <description>` - Code style changes
- `docs: <description>` - Documentation only
- `refactor: <description>` - Code refactoring (no functional changes)
- `style: <description>` - Code style/formatting changes
- `test: <description>` - Test additions/changes
### Docker Build Options:
- `docker: <description>` - Force Docker build without version bump
- `docs+docker: <description>` - Documentation + Docker build
- `chore+docker: <description>` - Maintenance + Docker build
- `test+docker: <description>` - Tests + Docker build
- `ci+docker: <description>` - CI changes + Docker build
- `style+docker: <description>` - 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

View File

@@ -1,248 +0,0 @@
name: Auto Version
on:
pull_request:
types: [closed]
branches: [main]
jobs:
version:
# Only run if PR was merged (not just closed)
if: github.event.pull_request.merged == true
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 }}
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**
This PR triggered a Docker image build because of the \`+docker\` suffix in the title.
**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 }}\`
**To test the image after build completes:**
\`\`\`bash
docker pull ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}
\`\`\`
**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\"
}
}
}
}
\`\`\`
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
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

129
.github/workflows/docker-pr.yml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: PR Docker Build
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
paths:
- '**.py'
- 'requirements*.txt'
- 'pyproject.toml'
- 'Dockerfile'
- 'docker-compose.yml'
- '.dockerignore'
permissions:
contents: read
packages: write
pull-requests: write
jobs:
docker:
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
uses: actions/checkout@v4
- name: Set up Docker Buildx
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
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- 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 }}
- 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: .
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: 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: Add Docker build comment (internal 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
**PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}`
```
${{ steps.meta.outputs.tags }}
```
**Test:** `docker pull ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}`
**Claude config:**
```json
{
"mcpServers": {
"zen": {
"command": "docker",
"args": ["run", "--rm", "-i", "-e", "GEMINI_API_KEY", "ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}"],
"env": { "GEMINI_API_KEY": "your-key" }
}
}
}
```
💡 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
run: |
{
echo "## 🐳 Docker Build Complete"
echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}"
echo '```'
echo "${{ steps.meta.outputs.tags }}"
echo '```'
} >> $GITHUB_STEP_SUMMARY
- name: Update job summary (fork PRs)
if: github.event.pull_request.head.repo.full_name != github.repository
run: |
{
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

116
.github/workflows/docker-release.yml vendored Normal file
View File

@@ -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

47
.github/workflows/semantic-pr.yml vendored Normal file
View File

@@ -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

61
.github/workflows/semantic-release.yml vendored Normal file
View File

@@ -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 }}

View File

@@ -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

38
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,38 @@
---
default_stages: [pre-commit, pre-push]
repos:
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
- 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]
# 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/
)

View File

@@ -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.
Thank you for contributing to Zen MCP Server! Your efforts help make this tool better for everyone.

View File

@@ -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 = [
@@ -84,6 +84,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"

View File

@@ -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
isort>=5.12.0
python-semantic-release>=10.3.0
build>=1.0.0

View File

@@ -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 <major|minor|patch>")
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()