Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude/commands/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Run the full local CI pipeline for this Copier template repository and report re
Execute `just ci` which runs in this order:
1. `just fix` — auto-fix ruff lint issues
2. `just fmt` — ruff formatting
3. `just ci-check` — read-only mirror of GitHub Actions, which runs:
3. `just check` — read-only mirror of GitHub Actions, which runs:
- `uv sync --frozen --extra dev`
- `just fmt-check` — verify formatting (read-only)
- `ruff check .` — lint
Expand Down
2 changes: 1 addition & 1 deletion .claude/hooks/post-edit-copier-migration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ echo "│"
echo "│ □ _migrations — add a migration block if renaming or removing a"
echo "│ variable so existing generated projects can upgrade"
echo "│"
echo "│ □ tests/test_template.py — add or update parametrized tests that cover"
echo "│ □ tests/integration/test_template.py — add or update parametrized tests that cover"
echo "│ the new variable behaviour"
echo "│"
echo "│ □ CLAUDE.md — update 'Copier variable conventions' section if"
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/copier/template-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ When adding or modifying hooks, commands, or rules:
## Testing template changes

Every change to `copier.yml` or a template file requires a test update in
`tests/test_template.py`. Run:
`tests/integration/test_template.py`. Run:

```bash
just test # run all template tests
Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/jinja/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## What to test

Every Jinja2 template change requires a corresponding test in `tests/test_template.py`.
Every Jinja2 template change requires a corresponding test in `tests/integration/test_template.py`.
Tests render the template with `copier copy` and assert on the output.

Scenarios to cover for each template file:
Expand Down Expand Up @@ -82,7 +82,7 @@ rm -rf /tmp/test-output
## Update testing

Test `copier update` scenarios when changing `_skip_if_exists` or the `.copier-answers.yml`
template. The `tests/test_template.py` file includes update scenario tests; add new ones
template. The `tests/integration/test_template.py` file includes update scenario tests; add new ones
when you add new `_skip_if_exists` entries.

## Coverage for template branches
Expand Down
33 changes: 33 additions & 0 deletions .claude/rules/python/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@ tests/
└── test_imports.py # smoke test: every public symbol is importable
```

### Meta-repo (this template repository)

Generated projects follow the layout above. **This Copier meta-repository** has no `src/` tree;
Python under test lives in `scripts/`. Keep pytest modules organized as:

```
tests/
├── conftest.py # top-level shared fixtures
├── unit/
│ ├── conftest.py
│ └── test_<script>.py # mirrors scripts/<script>.py
├── integration/
│ ├── conftest.py
│ └── test_template.py # Copier copy/update integration suite
└── e2e/
└── conftest.py # placeholder for future e2e tests
```

**No `__init__.py` files** — the flat layout (`pythonpath = ["."]`) avoids package nesting.

**No shared constants file** — define path constants directly in each test file that needs them:

```python
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent.parent
TEMPLATE_ROOT = REPO_ROOT / "template"
COPIER_YAML = REPO_ROOT / "copier.yml"
```

Do not flatten new tests into the top level of `tests/` unless they truly have no script or
integration home.

Files must be named `test_<module>.py`. Test functions must start with `test_`.

## Running tests
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ on:
permissions:
contents: read

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

jobs:
dependency-review:
name: Review Dependencies
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
name: Labeler
on: [pull_request_target]

permissions:
contents: read
pull-requests: write

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

jobs:
label:

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- uses: actions/labeler@v6
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
name: PR policy

on:
pull_request:
types: [opened, edited, synchronize, reopened]
# Temporarily disabled: manual runs only.
workflow_dispatch:

permissions:
contents: read
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pre-commit-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ permissions:
contents: write
pull-requests: write

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

jobs:
autoupdate:
name: Update pre-commit hooks
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ permissions:
issues: write
pull-requests: write

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

jobs:
stale:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/sync-skip-if-exists.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
permissions:
contents: read

concurrency:
group: sync-skip-if-exists-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
sync:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ TASK*.md
temp_*.py
temp_*.ipynb
temp_*.md
temp_*.sh
*.tmp
*.temp
*.bak
Expand Down
11 changes: 6 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repos:

- id: ruff-check
name: Ruff Lint (auto-fix)
entry: uv run ruff check --fix
entry: uv run ruff check --fix --exit-non-zero-on-fix
language: system
types: [python]
pass_filenames: true
Expand All @@ -39,11 +39,11 @@ repos:
language: system
types: [python]
pass_filenames: false
stages: [pre-commit]
stages: [pre-push]

- id: just-ci-check
name: just ci-check (read-only CI gate before push)
entry: just ci-check
- id: just-check
name: just check (read-only CI gate before push)
entry: just check
language: system
pass_filenames: false
always_run: true
Expand Down Expand Up @@ -74,6 +74,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: debug-statements
- id: no-commit-to-branch
args: ["--branch", "main", "--branch", "master"]

Expand Down
8 changes: 5 additions & 3 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
"keyword_exclude": "example|test|dummy|sample|fake|placeholder|changeme|your[-_]|localhost|template"
},
{
"name": "MailchimpDetector"
Expand Down Expand Up @@ -124,10 +124,12 @@
{
"path": "detect_secrets.filters.regex.should_exclude_file",
"pattern": [
"template/.*\\.jinja$"
"template/.*\\.jinja$",
"env\\.example$",
"\\.secrets\\.baseline$"
]
}
],
"results": {},
"generated_at": "2026-04-07T10:18:44Z"
"generated_at": "2026-04-13T23:59:48Z"
}
File renamed without changes.
36 changes: 21 additions & 15 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@ destination folder.
│ ├── .claude/ # Claude hooks/commands/rules/skills for generated projects
│ ├── .github/workflows/ # Generated CI/CD workflows
│ └── … # pyproject.toml.jinja, justfile.jinja, CLAUDE.md.jinja, …
├── tests/ # pytest tests that render the template and assert output
│ ├── test_template.py # Main integration suite — copier copy + assertions
│ ├── test_root_template_sync.py # Tests for check_root_template_sync.py
│ ├── test_repo_file_freshness.py # Unit tests for repo_file_freshness.py
│ ├── test_pr_commit_policy.py # PR body + conventional commit rules
│ ├── test_bump_version.py # Version bump + pyproject I/O
│ ├── test_sync_skip_if_exists.py # copier.yml _skip_if_exists helpers
│ └── test_check_root_template_sync.py # CLI smoke (see test_root_template_sync for scenarios)
├── tests/ # pytest suite for this meta-repo (see tests/CLAUDE.md)
│ ├── constants.py # REPO_ROOT / TEMPLATE_ROOT / COPIER_YAML for nested test modules
│ ├── conftest.py # top-level shared fixtures
│ ├── unit/ # fast isolated script tests
│ ├── integration/ # Copier copy/update integration suite
│ └── e2e/ # end-to-end tests (placeholder)
├── scripts/ # Automation scripts for CI or local tasks (see scripts/CLAUDE.md)
│ ├── repo_file_freshness.py # Git-based freshness dashboard (→ docs/ + assets/)
│ ├── bump_version.py # PEP 440 version bumper (patch/minor/major)
Expand Down Expand Up @@ -66,36 +64,44 @@ Prerequisites: Python 3.11+, `uv`, `just`, `git`.
| Run all tests | `just test` |
| Run tests in parallel | `just test-parallel` |
| Run slow tests only | `just slow` |
| Fast unit tests (no slow/integration) | `just test-fast` |
| Integration tests only | `just test-integration` |
| Tests for changed files only | `just test-changed` |
| Verbose tests | `just test-verbose` |
| Full debug test output | `just test-debug` |
| Re-run last failed tests | `just test-lf` |
| Re-run last failed tests (max verbosity) | `just test-failed-verbose` |
| Stop on first test failure | `just test-first-fail` |
| CI-style tests + coverage XML | `just test-ci` |
| Coverage report | `just coverage` |
| Lint | `just lint` |
| Lint changed files only | `just lint-changed` |
| Format | `just fmt` |
| Format check (read-only) | `just fmt-check` |
| Auto-fix lint issues | `just fix` |
| Type check | `just type` |
| Docstring check | `just docs-check` |
| MkDocs recipes (generated projects only) | `just docs-help` |
| Pre-merge review | `just review` |
| Full CI locally | `just ci` |
| Read-only CI check (no auto-fix) | `just ci-check` |
| Static checks only (fix+fmt+lint+type+docs) | `just static_check` |
| Pre-merge review (fix + lint + type + docs) | `just review` |
| Full CI locally (fix → check) | `just ci` |
| Read-only CI check (no auto-fix) | `just check` |
| Run pre-commit on all files | `just precommit` |
| Register git hooks | `just precommit-install` |
| Interactive conventional commit (Commitizen) | `just cz-commit` |
| Sync deps after lockfile change | `just sync` |
| Upgrade all deps | `just update` |
| Check for outdated dependencies | `just deps-outdated` |
| Verify lockfile integrity | `just lock-check` |
| Dependency security audit | `just audit` |
| Install all deps + pre-commit | `just install` |
| One-command developer onboarding | `just bootstrap` |
| Diagnose environment | `just doctor` |
| Generate freshness dashboard | `just freshness` |
| Root ↔ template sync validation | `just sync-check` |
| Suggested PR title + body (PR policy) | `just pr-draft` |
| Clean build artifacts | `just clean` |
| Build distribution | `just build` |
| Validate built distribution | `just check-dist` |
| Publish package | `just publish` |

**Always use `just` recipes.** Do not call `uv run ruff`, `pytest`, etc. directly —
Expand All @@ -107,9 +113,9 @@ the justfile handles the correct flags and order.
just ci
```

This runs: `fix` → `ci-check`.
This runs: `fix` → `check`.

`ci-check` bundles: `uv sync --frozen`, `fmt-check`, `ruff check`, `basedpyright`,
`check` bundles: `uv sync --frozen`, `fmt-check`, `ruff check`, `basedpyright`,
`sync-check`, `docs-check` (D-only; redundant with `ruff check` for enforcement), `test-ci`
(pytest + coverage XML), `pre-commit run --all-files --verbose`, `audit` (pip-audit).

Expand Down Expand Up @@ -209,7 +215,7 @@ file, add a corresponding test.

- Line length: 100 characters (set in `pyproject.toml` under `[tool.ruff]`).
- Target Python version: 3.11.
- Active ruff rules: `E`, `F`, `I`, `UP`, `B`, `SIM`, `C4`, `RUF`, `D`, `C90`, `PERF`, `T20`.
- Active ruff rules: `E`, `F`, `I`, `UP`, `B`, `SIM`, `C4`, `RUF`, `TCH`, `PGH`, `PT`, `ARG`, `D`, `C90`, `PERF`, `T20`.
Rule `E501` (line too long) is ignored (handled by the formatter).
- Docstring convention: **Google style** (`pydocstyle` via ruff `D` rules).
In this meta-repo, `tests/**` and `scripts/**` enforce `D` like other Python; only `T20` (`print`) is ignored there.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ Other useful commands:
- 🔁 **`just precommit`**: run pre-commit on all files
- 🩺 **`just doctor`**: print toolchain and project versions
- 🔗 **`just sync-check`**: validate root/template sync policy (`scripts/check_root_template_sync.py`)
- 🧱 **`just static_check`**: `fix` + `lint` + `type` + `docs-check` (no tests)
- **`just ci-check`**: read-only full gate (matches GitHub Actions lint + tests + security steps)
- **`just check`**: read-only full gate (matches GitHub Actions lint + tests + security steps)
- 🔄 **`just review`**: `fix` + `lint` + `type` + `docs-check` (no tests, pre-merge validation)

### Testing this template

The test suite (`tests/test_template.py`, `tests/test_root_template_sync.py`, `tests/test_repo_file_freshness.py`) uses pytest to:
The test suite (`tests/integration/test_template.py`, `tests/scripts/test_root_template_sync.py`, `tests/scripts/test_repo_file_freshness.py`) uses pytest to:
- Render the template with various configurations
- Validate generated project structure
- Check that generated projects have valid Python syntax
Expand Down
Loading
Loading