This file is for AI coding assistants (Claude Code, Copilot, etc.) working on this repository itself — the copier template source.
⚠️ Do not confuse withtemplate/AGENTS.md, which is the (empty) AGENTS.md that ships inside generated projects.
A personal copier template for bootstrapping
Python projects with uv, ruff, ty, and pytest pre-configured.
- Author / sole user: Jaewoo Park (
kaparoo) - Audience of generated projects: also the author
- Public consumption: not expected — design for the author's workflow first
Two branches, two independent version lines — never confuse them:
| Branch | Tag form | copier resolution |
Default use_pytest |
|---|---|---|---|
main |
vX.Y.Z (PEP 440) |
Latest v* tag — used when no --vcs-ref |
true |
pytorch |
pytorch-vX.Y.Z (not PEP 440 → ignored by copier auto-resolve) |
Requires explicit --vcs-ref pytorch (or --vcs-ref pytorch-vX.Y.Z) |
false (DL workflows are non-deterministic) |
The pytorch-v* tags are deliberately not PEP 440 so they never
shadow the base template's copier copy / copier update resolution —
otherwise a default copier copy gh:kaparoo/python-project-template
would hijack to the highest tag regardless of branch.
Variant policy: shared changes land on main first, then are
merged forward into pytorch. The reverse direction is never used —
pytorch-only features (torch dependencies, compute-backend questions,
PyTorch wheel index) stay on pytorch. See
Merging main into pytorch below for
the recurring CHANGELOG.md resolution pattern.
.
├── copier.yml ← template configuration (questions, tasks, etc.)
├── pyproject.toml ← WORKSPACE tooling only (copier + pytest + ruff)
├── tests/ ← tests for the template itself
├── template/ ← THE TEMPLATE — every file here ships to users
│ ├── pyproject.toml.jinja ← rendered into generated projects
│ ├── ...
│ └── {{ package_name }}/ ← directory name is a Jinja variable
└── AGENTS.md ← this file
Never confuse pyproject.toml (workspace dev tooling) with
template/pyproject.toml.jinja (what generated projects receive).
Changes meant for downstream projects belong in template/.
Copier's "dirty changes" handling has a known issue: untracked files at
the workspace root can cause .vscode/extensions.json to be silently
dropped during rendering. Tests will fail with confusing
FileNotFoundErrors.
Workflow: commit (or stash) all changes → run uv run pytest. On
main the suite is currently 22 cases (~60–90 s); on pytorch it
is 25 cases (~80–100 s) because of the extra torch / index
assertions and the uv lock integration test.
When adding a new option (e.g., use_xyz), add at least one test per
branch in tests/test_generate.py. Default-vs-enabled coverage is the
minimum bar.
uv run pytest runs copier in-process for every scenario in
tests/test_generate.py. If even one file in template/ changes,
run the suite before committing.
test_tasks_initialize_git_repo_with_initial_commit actually executes
git init, uv lock, uv sync, git add, git commit. It needs:
gitanduvon PATH- A configured git identity (
user.email,user.name) - Internet access (uv may download Python or wheels)
Follow the commit convention documented in
README.md — emoji prefix, backticked
tool names, single-purpose commits, no rewriting of published history,
no skipped hooks.
AI-specific requirement: append a Co-Authored-By trailer with the
acting assistant's own published identity — do not hardcode one vendor.
Co-Authored-By: <agent-name> <agent-email>
Examples:
- Claude Code →
Co-Authored-By: Claude <noreply@anthropic.com> - GitHub Copilot →
Co-Authored-By: Copilot <198982749+Copilot@users.noreply.github.com>
This workspace itself is versioned with SemVer (see
CHANGELOG.md):
MAJOR— changes that need a_migrationsentry incopier.yml(so existing generated projects can update across them).MINOR— new copier options, new generated files, new sections in generated docs.PATCH— fixes, doc tweaks, behavior-preserving cleanup.
For both branches the procedure is the same shape; only the tag form
differs (vX.Y.Z on main, pytorch-vX.Y.Z on pytorch).
- Land all changes in
[Unreleased]first. Verify withuv run pyteston a clean tree. - Promote: rename
## [Unreleased]content to a dated## [X.Y.Z] - YYYY-MM-DDheading, add a fresh empty## [Unreleased]above, append the new comparison link at the bottom of the file. - Commit:
📝 Cut release \vX.Y.Z`` (body summarizes the bundle). - Tag:
git tag -a vX.Y.Z -m "vX.Y.Z". - Push:
git push origin <branch>thengit push origin vX.Y.Z.
For shared work, the order is main first, then pytorch:
release vX.Y.Z on main, merge main into pytorch (resolving
the CHANGELOG conflict per below), then promote pytorch's own
[Unreleased] to [X.Y.Z] and tag pytorch-vX.Y.Z. The two
version sequences are independent — pytorch-v1.1.0 is not
required to mirror v1.1.0 numerically once they diverge.
After every main change cycle, merge forward:
git switch pytorch
git merge main --no-ff -m "🔀 Merge \`main\` into \`pytorch\`"Recurring CHANGELOG.md conflict — pytorch keeps a variant-only
changelog (it does NOT mirror main's release history). Resolution
principle:
- Take main's new
[Unreleased]bullets (Added / Changed / Fixed) — the underlying template change applies to both branches. - Drop main's
## [X.Y.Z]heading and any basev*comparison links — pytorch has its own release line. - Keep pytorch's existing
## [X.Y.Z]headings (the variant's prior releases) and thepytorch-v*comparison links at the bottom.
tests/test_generate.py may also conflict when the default values
diverge (notably use_pytest): keep pytorch's {"use_pytest": True}
override in tests that assume the pytest layer; keep pytorch's
expected-files list (no tests/__init__.py when use_pytest is
default-false there).
| Concern | Choice | Why |
|---|---|---|
| Package manager / lock | uv |
Fast, single tool for env + lock + run |
| Build backend | uv_build |
Astral consistency, 10–35× faster than alternatives |
| Linter / formatter | ruff |
Single binary, comprehensive ruleset |
| Type checker | ty (beta) |
Astral, plugin-free; Ruff ANN rules cover annotation enforcement |
| Test runner | pytest |
Standard |
| Template engine | copier (>=9.15.1) |
Update-friendly via .copier-answers.yml |
| AI agent config | AGENTS.md + CLAUDE.md (just @AGENTS.md) |
Editor-agnostic with Claude Code shim |
- Append the question to
copier.ymlunder the appropriate# ─── ... ───section (e.g.# ─── Project nature ───). - Add conditional Jinja blocks in
template/pyproject.toml.jinja,template/.vscode/extensions.json.jinja, etc., as needed. - Add tests in
tests/test_generate.pycovering both branches. - Commit changes, then run
uv run pytest(clean tree required). - Update
README.mdoptions table if applicable.
Edit template/pyproject.toml.jinja. Run the test suite to confirm
the rendered output still passes uv build, ruff check, ty check,
and pytest in the integration test.
copier copy --vcs-ref HEAD --UNSAFE . ~/tmp/dogfood-$(date +%s) -d project_name=dogfood
cd ~/tmp/dogfood-*
uv run ruff check .
uv run ty check
uv run pytest # may exit 5 = no tests collected, that's expected
uv build--vcs-ref HEAD renders the current working commit; without it copier
falls back to the latest tag and silently misses post-tag changes.
Periodic review of projects generated from this template (notably
kaparoo-python) produces a list of patterns worth back-porting.
The current in-flight cycle is tracked in
../template-improvements.md (out-of-tree, not committed). Apply
items as small focused commits accumulating in [Unreleased], then
cut a MINOR (or PATCH) release per the procedure above.
- Editing
template/.vscode/extensions.json(no extension) — the active file istemplate/.vscode/extensions.json.jinja. The same applies to every.jinjafile. - Adding Python plugin-based tools (
mypy plugins,django-stubs) to generated projects —tydeliberately has no plugin system. Use PEP 681dataclass_transformor direct stubs instead. - Committing
dist/,.venv/,.cache/,_gen_*/test artifacts. - Force-pushing to
mainorpytorchwithout explicit user authorization. Tags (v*,pytorch-v*) are equally off-limits to rewrites once pushed. - Cherry-picking
pytorch-only commits back ontomain. The variant flow is one-directional (main→pytorch); back-porting would put torch dependencies on the base template.