name: Python CI
on:
push:
branches: [ "*" ]
pull_request:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
jobs:
check-forbidden-folders:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Check for forbidden folders in PR
run: |
echo "Checking for forbidden folders in pull request..."
# Function to check for folder existence case-insensitively
check_forbidden_folder() {
local pattern="$1"
local description="$2"
# Use find with case-insensitive matching
if find . -type d -ipath "*${pattern}*" -print -quit | grep -q .; then
echo "❌ ERROR: Found forbidden folder matching '${pattern}'"
echo "Description: ${description}"
find . -type d -ipath "*${pattern}*" | sed 's/^/ Found: /'
return 1
else
echo "✅ No forbidden folder found for pattern: ${pattern}"
return 0
fi
}
# Function to check if folder is empty (or doesn't exist)
check_empty_folder() {
local pattern="$1"
local description="$2"
# Find matching folders
local folders=$(find . -type d -path "*${pattern}*" 2>/dev/null)
if [ -z "$folders" ]; then
echo "✅ Folder not found (acceptable): ${pattern}"
return 0
fi
# Check if any found folder is not empty
local has_content=0
while IFS= read -r folder; do
if [ -n "$(find "$folder" -mindepth 1 -print -quit)" ]; then
echo "❌ ERROR: Folder should be empty: ${folder}"
echo "Description: ${description}"
find "$folder" -mindepth 1 -maxdepth 1 | sed 's/^/ Contains: /'
has_content=1
fi
done <<< "$folders"
if [ $has_content -eq 1 ]; then
return 1
else
echo "✅ Folder exists and is empty: ${pattern}"
return 0
fi
}
# Check for forbidden folders
ERROR=0
check_forbidden_folder "pr_info/steps" "Temporary development steps folder" || ERROR=1
check_forbidden_folder "pr_info/.conversations" "Temporary conversation history folder" || ERROR=1
# Check for forbidden commit message file
if [ -f "pr_info/.commit_message.txt" ]; then
echo "❌ ERROR: Found forbidden file pr_info/.commit_message.txt"
echo "Description: Transient commit message file should not be committed"
ERROR=1
else
echo "✅ No forbidden commit message file found"
fi
if [ $ERROR -eq 1 ]; then
echo ""
echo "❌ PULL REQUEST BLOCKED: Forbidden folders detected!"
echo "These folders should not be included in pull requests."
echo "Please remove them and update your .gitignore if necessary."
echo "Note: These folders are allowed in regular commits, just not in PRs."
exit 1
else
echo ""
echo "✅ All checks passed! No forbidden folders found."
fi
# Matrix approach: Each check runs as independent job with individual pass/fail status
# fail-fast: false ensures all checks complete even if one fails
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
check:
- {name: "black", cmd: "black --version && black --check src tests"}
- {name: "isort", cmd: "isort --version && isort --check --profile=black --float-to-top src tests"}
- {name: "pylint", cmd: "pylint --version && pylint -E ./src ./tests"}
- {name: "pytest", cmd: "pytest --version && pytest tests"}
- {name: "mypy", cmd: "mypy --version && mypy --strict src tests"}
name: ${{ matrix.check.name }}
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install dependencies
run: uv pip install --system ".[dev]"
- name: Environment info
run: |
uname -a
python --version
uv --version
git --version
- name: Run ${{ matrix.check.name }}
run: ${{ matrix.check.cmd }}