A Python project template for development with AI agents (Claude Code).
Batteries included: UV + direnv per-service venvs, strict linting, TDD enforcement, Document Driven Design, and a one-shot setup wizard.
Maintainer note: After pushing, go to Settings → General → check "Template repository" so the green "Use this template" button appears for visitors.
| Layer | Tool / Convention |
|---|---|
| Dependency management | uv — fast, per-service virtualenvs |
| Venv auto-activation | direnv — cd into a service, venv activates automatically |
| Linting & formatting | ruff (E, F, I, B, UP, RUF rules) |
| Type checking | mypy strict mode |
| Code quality | pylint (min score 10, McCabe complexity, duplicate-code detection) |
| Pre-commit hooks | All of the above + TDD enforcement + branch protection |
| CI | GitHub Actions — quality gate + per-service test matrix |
| Design workflow | Document Driven Design (DDD) with design & solution doc templates |
| AI conventions | CLAUDE.md + .claude/{DDD,TDD}.md — NumPy docstrings, RED/GREEN TDD, DDD workflow, SOLID |
| AI guardrails | Claude Code hooks — reuse reminder before edits + auto-/simplify on stop |
| Setup wizard | init.py — interactive, replaces all placeholders, self-deletes |
brew install uv direnv python@3.12Add direnv to your shell (once, globally):
# bash
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
# zsh
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc- Go to github.com/ranitraj/agentic-python-template
- Click the green "Use this template" button → "Create a new repository"
- Name your new repo, set visibility, click "Create repository"
This creates a clean copy in your own GitHub account with no git history carried over.
Clone your new repo:
git clone https://github.com/<you>/<your-project>.git
cd <your-project>python3 init.pySee the full wizard walkthrough for an example of every prompt and what to enter.
The wizard asks five categories of questions, replaces all {{placeholders}} across every file, creates your first service, makes the initial git commit, and deletes itself.
make init # installs root dev deps + all service deps + git hooks
direnv allow . # approve the root .envrc (loads .env)cd services/<your-service>
direnv allow . # approve service .envrc — venv is created and activated automaticallyFrom now on, cd-ing into a service directory activates its venv. No manual source .venv/bin/activate.
A complete example run of python3 init.py for a simple calculator project:
Agentic Python Project Initialiser
======================================
Defaults shown in [brackets] — press Enter to accept.
Project identity
Project name (kebab-case) [my-project]: my-calculator
One-line description [A Python project]: A calculator service with an AI-agent interface
Available Python versions: 3.12, 3.11, 3.10
Python version [3.12]: ← press Enter to accept 3.12
First service
Service name (kebab-case) [api]: calculator ← creates services/calculator/
Code quality (press Enter to accept defaults)
Strict mypy? [Y/n]: ← press Enter = yes
Min pylint score [10]: ← press Enter = 10
Line length [119]: ← press Enter = 119
Max function args [5]: ← press Enter = 5
Workflow
Enforce TDD? (pre-commit blocks commit if test file missing) [Y/n]:
Use DDD design docs + solution docs? [Y/n]:
Protect main branch? (blocks direct commits to main) [Y/n]:
CI/CD
Add GitHub Actions? [Y/n]:
── Summary ──────────────────────────────────────────────
project : my-calculator
description : A calculator service with an AI-agent interface
python : 3.12
service : calculator (module: calculator)
strict mypy : True
pylint score : 10 line length: 119 max args: 5
enforce TDD : True
DDD docs : True
protect main : True
GitHub CI : True
Looks good? Proceed? [Y/n]: ← press Enter to apply
Initialising...
my-calculator is ready.
Next steps:
cd services/calculator && uv sync --group dev
make init
direnv allow .
Tips:
- All questions default to the recommended value — press Enter to accept.
- For
Service name, use kebab-case (calculator,wiki-agent,gateway). The wizard derives the Python module name automatically (wiki-agent→wiki_agent). init.pyruns once and deletes itself. To start over, re-clone from the template.
your-project/
├── CLAUDE.md # AI agent conventions (auto-read by Claude Code)
├── STRATEGY.md # Product direction — fill this in first
├── pyproject.toml # Shared ruff / mypy / pylint config
├── Makefile # make init | lint | clean | hooks-update
├── .pre-commit-config.yaml # ruff, mypy, pylint, TDD, branch protection
├── .github/workflows/ci.yml # Quality gate + per-service test matrix
├── .envrc # Root direnv — loads .env
│
├── services/
│ └── <your-service>/ # Created by init.py from _service-template/
│ ├── .envrc # layout uv — auto-creates .venv on cd
│ ├── pyproject.toml # Service dependencies
│ └── src/<module>/
│
└── .claude/
├── DDD.md # DDD workflow rules (referenced from CLAUDE.md)
├── TDD.md # TDD workflow rules (referenced from CLAUDE.md)
├── settings.json # Claude Code hooks config
├── hooks/ # Hook scripts: pre-edit reuse + stop /simplify
├── designs/_template.md # Design doc template (fill before coding)
└── solutions/
├── README.md # Solution-doc workflow
└── _template.md # Solution doc template (fill after shipping)
This template enforces a specific workflow. CLAUDE.md is the full reference — summary below.
- Copy
.claude/designs/_template.md→.claude/designs/<feature>.md - Fill in: purpose, public API, data flow, test scenarios, error cases, out of scope
- Get sign-off, then implement
Write failing test → commit test (RED)
Write implementation → commit impl (GREEN, pre-commit checks test file exists)
Refactor → keep tests green
The enforce-tdd pre-commit hook blocks committing a src/ file if no matching tests/test_<module>.py exists.
Exempting data-only files from TDD: Some modules hold only constants or configuration and don't have behaviour worth testing (e.g. constants.py). Open scripts/check_tdd.py and add the filename to the _no_test_required set:
_no_test_required = {"constants.py", "settings.py"}Reserve this for genuinely data-only files. Any module with logic — even a one-liner — should have a test.
You don't do anything. The moment Claude marks a design doc status: done, it automatically
creates .claude/solutions/<topic>.md — filling in what was built, decisions made, and what to
watch for next time. You review, it saves.
LLMs drift over long sessions — duplicate logic, mismatched parameter names, missed reuse. The template ships with two Claude Code hooks (.claude/settings.json + .claude/hooks/) that mechanically counter this:
- Reuse reminder before edits —
PreToolUseonEdit/Write/MultiEditinjects a one-line reminder: grep for an existing utility first, match parameter names of nearby functions, refactor rather than duplicate. - Auto-
/simplifyon stop — when Claude tries to stop with uncommitted changes, theStophook nudges it to run/simplifyfirst (an end-of-turn diff review for duplication, missed reuse, inconsistent params). A per-session marker prevents looping after/simplify's own edits.
Deterministic backup at commit time: pylint's similarities check (configured in pyproject.toml) flags 6+ identical lines across files — ignoring imports, signatures, comments, docstrings — so logic duplication can't slip past pre-commit.
To disable any of these: edit .claude/settings.json (hooks) or [tool.pylint.similarities] in pyproject.toml (duplicate check).
cp -r _service-template services/<new-service>
# rename src/service_module → src/<new_module>
# update services/<new-service>/pyproject.toml name + packages
# add "<new-service>" to the matrix in .github/workflows/ci.yml
cd services/<new-service> && direnv allow .Lint and type checks auto-discover the new service. scripts/lint_service.py is invoked by the mypy-services and pylint-services pre-commit hooks: it groups staged files by their service root and runs mypy/pylint inside each affected service's own venv via uv run --directory. No .pre-commit-config.yaml edits needed when adding a service.
If the service venv is missing, the hook prints an explicit make init instruction. If your service uses pydantic, add plugins = ["pydantic.mypy"] to its [tool.mypy] config.
CLAUDE.md is auto-read by Claude Code at the start of every session from the repo root.
If Claude isn't following it:
- Verify location —
CLAUDE.mdmust be at the repo root, not inside a subdirectory. - Start a new session —
CLAUDE.mdis only loaded at session start. Open a fresh Claude Code conversation so it reloads. - Re-anchor mid-session — If Claude drifts during a long session, say: "Re-read CLAUDE.md and confirm the conventions before continuing."
CLAUDE.md instructs Claude to proactively scan .claude/designs/ before every implementation
task. If it skips this:
- Long session context — On very long sessions Claude may lose track of earlier instructions.
Start a new session with
/clearto reloadCLAUDE.mdfrom scratch. - Re-anchor — Say: "Check CLAUDE.md's DDD rules and follow them before continuing."
The DDD workflow is two-phase: open exploration until you have a clear direction, then exactly 3 targeted rounds (gaps, edge cases, constraints) before the design doc. If Claude jumps straight to code, say:
"Stop. We haven't done the two-phase questioning yet. Start with exploration."
/clear in the Claude Code chat resets the conversation and forces a clean re-read of CLAUDE.md.
make init # full bootstrap (install + git hooks)
make lint # run all pre-commit hooks on every file
make hooks-update # update pre-commit hooks to latest versions
make clean # remove __pycache__, .mypy_cache, .ruff_cache, .pytest_cache