Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Summary

<!-- Briefly describe what this PR does and why -->

## Changes

<!-- List the key changes made -->

-

## Testing

<!-- Describe how you verified the changes -->

- [ ] Tests pass locally (`python -m pytest tests/ -v`)
- [ ] Pre-commit hooks pass (`pre-commit run --all-files`)

## Related Issues

<!-- Link any related issues: Fixes #123, Closes #456 -->
27 changes: 27 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: 2

updates:
# Python (pip) dependencies
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
commit-message:
prefix: "chore(deps)"

# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "ci"
commit-message:
prefix: "ci(deps)"
109 changes: 109 additions & 0 deletions .github/workflows/auto-populate-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Auto-populate PR Body

on:
pull_request:
types: [opened]

permissions:
pull-requests: write

jobs:
populate-body:
name: Generate PR Body
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Fetch base branch
run: git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1

- name: Generate and update PR body
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
# Fetch the current PR body
CURRENT_BODY=$(gh pr view "$PR_NUMBER" --json body -q '.body')

# Strip HTML comments, headers, whitespace, checklist items
STRIPPED=$(echo "$CURRENT_BODY" \
| sed 's/<!--.*-->//g' \
| sed '/^## /d' \
| sed '/^- \[.\]/d' \
| sed '/^-$/d' \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//' \
| sed '/^$/d')

if [[ -n "$STRIPPED" ]]; then
echo "PR body already has custom content. Skipping auto-populate."
exit 0
fi

echo "PR body is empty or template-only. Generating content..."

MERGE_BASE=$(git merge-base origin/"$BASE_REF" HEAD)

# --- Collect commit messages ---
COMMITS=$(git log --pretty=format:"- %s" "$MERGE_BASE"..HEAD)

# --- Collect changed files with stats ---
DIFF_STAT=$(git diff --stat "$MERGE_BASE"..HEAD)
FILE_LIST=$(git diff --name-only "$MERGE_BASE"..HEAD)

# --- Categorize changes ---
TOTAL=$(echo "$FILE_LIST" | grep -c '.' || true)
SRC_CHANGES=$(echo "$FILE_LIST" | grep -c '^src/' || true)
TEST_CHANGES=$(echo "$FILE_LIST" | grep -c '^tests/' || true)
DOC_CHANGES=$(echo "$FILE_LIST" | grep -cE '^(.github/.*\.md|.*README)' || true)
CI_CHANGES=$(echo "$FILE_LIST" | grep -cE '^\.github/(workflows|dependabot)' || true)

# --- Build summary line ---
SUMMARY_PARTS=()
[[ "$SRC_CHANGES" -gt 0 ]] && SUMMARY_PARTS+=("$SRC_CHANGES source file(s)")
[[ "$TEST_CHANGES" -gt 0 ]] && SUMMARY_PARTS+=("$TEST_CHANGES test file(s)")
[[ "$DOC_CHANGES" -gt 0 ]] && SUMMARY_PARTS+=("$DOC_CHANGES doc file(s)")
[[ "$CI_CHANGES" -gt 0 ]] && SUMMARY_PARTS+=("$CI_CHANGES CI file(s)")

if [[ ${#SUMMARY_PARTS[@]} -gt 0 ]]; then
SUMMARY_LINE="This PR touches $(IFS=', '; echo "${SUMMARY_PARTS[*]}") across $TOTAL file(s) total."
else
SUMMARY_LINE="This PR modifies $TOTAL file(s)."
fi

# --- Write PR body to a temp file ---
BODY_FILE=$(mktemp)
{
echo "## Summary"
echo ""
echo "$SUMMARY_LINE"
echo ""
echo "## Changes"
echo ""
echo "$COMMITS"
echo ""
echo "<details>"
echo "<summary>Diff stats</summary>"
echo ""
echo '```'
echo "$DIFF_STAT"
echo '```'
echo ""
echo "</details>"
echo ""
echo "## Testing"
echo ""
echo '- [ ] Tests pass locally (`python -m pytest tests/ -v`)'
echo '- [ ] Pre-commit hooks pass (`pre-commit run --all-files`)'
echo ""
echo "## Related Issues"
echo ""
echo "<!-- Link any related issues: Fixes #123, Closes #456 -->"
} > "$BODY_FILE"

# Update the PR body from file
gh pr edit "$PR_NUMBER" --body-file "$BODY_FILE"
rm -f "$BODY_FILE"
echo "PR body has been auto-populated."
80 changes: 80 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
pip install --upgrade pip
pip install pre-commit mypy bandit ruff

- name: Cache pre-commit hooks
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: pre-commit-

- name: Run pre-commit
run: pre-commit run --all-files --show-diff-on-failure

- name: Lint check with ruff
run: ruff check src/ tests/ apps/

- name: Type check with mypy
run: mypy src/ apps/

- name: Security check with bandit
run: bandit -c pyproject.toml -r src/ apps/

test:
name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: lint
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
pip install -e .

- name: Run tests
run: python -m pytest tests/ -v

- name: Run tests with coverage
run: |
python -m pytest tests/ \
-v \
--cov=template_project \
--cov-fail-under=90 \
--cov-report=term-missing \
--cov-report=xml
24 changes: 24 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Dependency Review

on:
pull_request:
paths:
- "requirements*.txt"
- "setup.py"
- "pyproject.toml"

permissions:
contents: read

jobs:
dependency-review:
name: Review Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
comment-summary-in-pr: always
54 changes: 54 additions & 0 deletions .github/workflows/pr-body.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: PR Body Validation

on:
pull_request:
# Runs on 'edited' and 'reopened' only — the 'opened' event is handled by
# the auto-populate workflow, which edits the body and triggers 'edited'.
types: [edited, reopened]

jobs:
validate-body:
name: Validate PR Body
runs-on: ubuntu-22.04
steps:
- name: Check PR body is not empty
run: |
BODY=$(cat <<'BODY_EOF'
${{ github.event.pull_request.body }}
BODY_EOF
)

# Strip HTML comments, whitespace, and section headers
CLEANED=$(echo "$BODY" | sed 's/<!--.*-->//g; /^## /d; /^- \[ \]/d; s/^[[:space:]]*//; s/[[:space:]]*$//; /^$/d; /^-$/d')

if [[ -z "$CLEANED" ]]; then
echo "::error::PR body is empty. Please provide a description of your changes."
echo ""
echo "A good PR description should include:"
echo " - What: A summary of the changes made"
echo " - Why: The motivation or issue being addressed"
echo " - How: Key implementation details (if non-obvious)"
echo " - Testing: How the changes were verified"
exit 1
fi

# Check minimum length (at least 20 characters of meaningful content)
CHAR_COUNT=${#CLEANED}
if [[ "$CHAR_COUNT" -lt 20 ]]; then
echo "::error::PR body is too short ($CHAR_COUNT chars). Please provide a meaningful description."
exit 1
fi

echo "PR body is present ($CHAR_COUNT chars)."

- name: Check for placeholder text
run: |
BODY=$(cat <<'BODY_EOF'
${{ github.event.pull_request.body }}
BODY_EOF
)

# Warn if common placeholder patterns are detected
if echo "$BODY" | grep -qiE '(TODO|FIXME|PLACEHOLDER|fill in|describe here|add description)'; then
echo "::warning::PR body may contain placeholder text. Please ensure all sections are filled in."
fi
63 changes: 63 additions & 0 deletions .github/workflows/pr-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: PR Policy

on:
pull_request:
types: [opened, edited, synchronize, labeled, unlabeled, reopened]

jobs:
title-convention:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- name: Check conventional commit format
run: |
TITLE="${{ github.event.pull_request.title }}"
PATTERN="^(feat|fix|docs|doc|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+"
if [[ ! "$TITLE" =~ $PATTERN ]]; then
echo "::error::PR title does not follow Conventional Commits format."
echo ""
echo "Expected format: <type>(<optional scope>): <description>"
echo " Types: feat, fix, docs, doc, style, refactor, perf, test, build, ci, chore, revert"
echo ""
echo "Examples:"
echo " feat: add decrement CLI command"
echo " fix(core): handle empty config file"
echo " docs: update installation instructions"
echo ""
echo "Got: '$TITLE'"
exit 1
fi
echo "PR title follows Conventional Commits format."

label-check:
name: Require Label
runs-on: ubuntu-latest
steps:
- name: Check for at least one label
run: |
LABEL_COUNT=$(echo '${{ toJson(github.event.pull_request.labels) }}' | jq 'length')
if [[ "$LABEL_COUNT" -eq 0 ]]; then
echo "::error::PR must have at least one label before merging."
echo "Consider adding a label such as: bug, enhancement, documentation, maintenance, etc."
exit 1
fi
echo "PR has $LABEL_COUNT label(s)."

branch-naming:
name: Validate Branch Name
runs-on: ubuntu-latest
steps:
- name: Check branch naming convention
run: |
BRANCH="${{ github.head_ref }}"
PATTERN="^(feature|fix|bugfix|hotfix|docs|chore|refactor|test|ci|release|claude)/"
if [[ ! "$BRANCH" =~ $PATTERN ]]; then
echo "::warning::Branch name '$BRANCH' does not follow the recommended naming convention."
echo ""
echo "Recommended format: <type>/<short-description>"
echo " Types: feature, fix, bugfix, hotfix, docs, chore, refactor, test, ci, release"
echo ""
echo "Examples:"
echo " feature/add-decrement-cli"
echo " fix/empty-config-handling"
fi
Loading
Loading