| name | python-project-scaffold |
| description | Scaffold a new Python project with production-ready tooling and structure. Use when the user says "new project", "set up a project", "initialize a Python project", "create a repo", "project template", or asks for boilerplate setup. Generates pyproject.toml, Makefile, pre-commit config, ruff/pyright config, test structure, HAC (.hac/), and directory layout following modern Python conventions with uv, ruff, pyright, and pytest. |
Python Project Scaffold
Generate a complete, production-ready Python project structure in one pass.
Core Principle
EVERY NEW PROJECT STARTS WITH THE SAME SOLID FOUNDATION.
Do not improvise project setup. Follow this skill to produce a consistent, opinionated scaffold that matches the team's established conventions.
Cross-Skill Integration
This skill generates the project structure and Python-specific tooling. For the .hac/ directory (Human-Agent Context), delegate to the hac-init skill — it owns the HAC templates and bootstrapping process. Always run hac-init as part of scaffolding.
Triage Gate
Before scaffolding, determine the project type:
- Library / package — Installable module, published or internal. Uses
[build-system], has src/ or top-level package directory.
- Application / service — Runnable entrypoint (CLI, API server, pipeline). Has
main.py or equivalent.
- Script / experiment — Single-file or small-scale. Does NOT need this skill — a simple
.py file with a pyproject.toml for deps is enough.
Ask which type if unclear. The scaffold below covers library and application.
Phase 1: Gather Requirements
Before generating any files, confirm:
- Project name — kebab-case for the repo, snake_case for the Python package
- Python version — Default to 3.13+ unless specified
- Project type — Library or application (see triage gate)
- Key dependencies — Any known runtime dependencies
- Extras — Docs (mkdocs), Docker, CI (GitHub Actions), or none
- Author full name — Used in
pyproject.toml authors field
- Author email — Used in
pyproject.toml authors field
- GitHub username — Used as default assignee in issue templates and in file signatures (leave blank to omit)
Phase 2: Generate Structure
Directory Layout
project-name/
├── .hac/ # Human-Agent Context → generated by hac-init skill
│ ├── README.md
│ ├── status.md
│ ├── decisions.md
│ └── tasks/
│ └── .gitkeep
├── .github/
│ ├── workflows/
│ │ └── lint.yml # Runs pre-commit on PR-changed files
│ ├── pull_request_template.md
│ └── ISSUE_TEMPLATE/
├── docs/ # mkdocs source (if docs enabled)
├── src/
│ └── PACKAGE_NAME/
│ ├── __init__.py
│ └── config.py # Configuration loading
├── tests/
│ ├── conftest.py
│ ├── unit/
│ │ └── __init__.py
│ ├── integration/
│ │ └── __init__.py
│ └── e2e/ # Optional
│ └── __init__.py
├── .env.sample
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .yamllint
├── CLAUDE.md
├── CONTRIBUTING.md
├── Makefile
├── README.md
└── pyproject.toml
Start flat — add subdirectories (e.g. tools/, models/, prompts/) only as the project grows. Do not pre-create core/, services/, or infrastructure/ directories.
For applications, also generate main.py at the root.
Phase 3: File Templates
Signature Rule
Every generated file that contains a non-empty docstring or header comment must include the following signature line:
Created by @GITHUB_USERNAME on YYYY.MM.DD
- Python
.py files — add it as the last line of the module-level docstring.
- YAML/workflow files — add it as a comment on the second line, after a one-line description comment.
- Omit the signature if the user did not provide a GitHub username.
CLAUDE.md
# CLAUDE.md
This file provides guidance to Claude Code when working with this repository.
## What This Is
PROJECT_NAME — PROJECT_DESCRIPTION
## Commands
```bash
# Setup
make setup # Create .venv and install all deps
# Testing
make test # Run all tests
make test-unit # Unit tests only
make test-cov # Tests with coverage report
uv run pytest tests/unit/test_x.py # Single file
uv run pytest -k "test_name" # By name pattern
# Code quality
make lint # Check with ruff
make format # Auto-format with ruff
Architecture
Describe the high-level architecture here once it stabilizes.
Conventions
- Python 3.13+, type hints everywhere (pyright strict)
- Google-style docstrings
- ruff for linting + formatting (line-length 100)
- uv for package management
- Tests: pytest with unit/integration separation
### pyproject.toml
```toml
[project]
name = "PROJECT_NAME"
dynamic = ["version"]
description = "PROJECT_DESCRIPTION"
authors = [
{name = "AUTHOR", email = "EMAIL"},
]
requires-python = ">=3.13.0"
readme = "README.md"
license = {text = "MIT"}
dependencies = []
[dependency-groups]
dev = [
"pytest",
"pre-commit",
"pytest-cov",
"pytest-mock",
"ruff",
]
docs = [
"mkdocs",
"mkdocs-material",
"mkdocstrings[python]",
]
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.build]
packages = ["src/PACKAGE_NAME"]
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "src/PACKAGE_NAME/_version.py"
[tool.interrogate]
fail-under = 100
exclude = ["tests", "build", "dist", "tmp", ".venv", ".ruff_cache"]
ignore-module = true
ignore-init-method = true
verbose = 2
omit-covered-files = true
[tool.ruff]
line-length = 100
target-version = "py313"
exclude = ["__pycache__"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"Q", # flake8-quotes
"A", # prevent clobbering python builtins
"ISC", # implicit string concatenation
"UP", # pyupgrade
"RUF", # ruff-specific rules
"D", # pydocstyle
"I", # isort
"ANN", # flake8-annotations
"T20", # flake8-print (no print statements)
]
ignore = [
"E501",
"A001",
"D100", "D101", "D102", "D103", "D104", "D105",
"D205", "D209", "D210", "D400", "D401", "D404",
]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["ANN401", "ANN201", "ANN202", "ANN001"]
"main.py" = ["T20"]
[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = 100
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.pytest.ini_options]
testpaths = ["tests/"]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"e2e: marks end-to-end tests (deselect with '-m \"not e2e\"')",
]
addopts = ["--durations=0", "--strict-markers", "--doctest-modules", "--ignore=tests/e2e"]
filterwarnings = ["ignore::DeprecationWarning", "ignore::UserWarning"]
[tool.coverage.run]
omit = [
"src/PACKAGE_NAME/config.py",
"src/PACKAGE_NAME/logging.py",
]
[tool.pyright]
include = ["src/PACKAGE_NAME"]
pythonVersion = "3.13"
typeCheckingMode = "strict"
reportMissingTypeArgument = "warning"
reportUnnecessaryCast = "warning"
reportUnusedIgnoreComment = "warning"
[[tool.pyright.executionEnvironments]]
root = "tests"
typeCheckingMode = "basic"
Makefile
SHELL := bash
.PHONY: setup install install-dev test test-cov test-unit test-integration lint format clean help
setup:
@echo "Setting up environment..."
uv sync --group dev --group docs
@echo "Done. Activate with: source .venv/bin/activate"
install:
uv sync
install-dev:
uv sync --group dev
test:
uv run pytest -x --tb=no -rs
test-cov:
uv run pytest -x --cov=src/PACKAGE_NAME --cov-report=term-missing
test-unit:
uv run pytest -v --tb=no -ra tests/unit/
test-integration:
uv run pytest -v --tb=no -ra tests/integration/
lint:
uv run ruff check .
format:
uv run ruff format .
clean:
@find . -type f -name ".DS_Store" -delete
@find . -type d -name "__pycache__" -exec rm -rf {} +
@find . -type d -name ".pytest_cache" -exec rm -rf {} +
@find . -type d -name ".ruff_cache" -exec rm -rf {} +
@find . -type d -name ".mypy_cache" -exec rm -rf {} +
@find . -type f -name "*.pyc" -delete
@echo "Clean complete."
help:
@echo "Commands: setup install install-dev test test-cov test-unit test-integration lint format clean"
.pre-commit-config.yaml
Before generating this file, check the latest stable release tag for each repo below. The versions hardcoded here are a baseline — use them only if you cannot confirm a newer release. Check via gh release list -R <owner>/<repo> --limit 1 or a quick web search for each hook repo.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- id: check-docstring-first
- id: check-yaml
args: ["--unsafe"]
- id: check-toml
- id: check-executables-have-shebangs
- id: check-case-conflict
- id: check-added-large-files
args: ["--maxkb=10000"]
- id: no-commit-to-branch
args: ["--branch", "main", "--branch", "develop"]
- repo: https://github.com/econchick/interrogate
rev: 1.5.0
hooks:
- id: interrogate
args: [--config=pyproject.toml]
pass_filenames: false
additional_dependencies: [setuptools]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
hooks:
- id: ruff
args: [--fix, --config, pyproject.toml]
- id: ruff-format
args: [--config, pyproject.toml]
- repo: https://github.com/codespell-project/codespell
rev: v2.4.0
hooks:
- id: codespell
args: ["--skip=*.lock,docs/site/assets/*"]
- repo: https://github.com/adrienverge/yamllint
rev: v1.38.0
hooks:
- id: yamllint
args: ["-c=.yamllint"]
.yamllint
yaml-files:
- "*.yaml"
- "*.yml"
- ".yamllint"
extends: default
rules:
line-length:
max: 120
document-start: disable
truthy: disable
.python-version
3.13
.env.sample
.gitignore (note on .hac/)
Ensure .hac/ is tracked in git (it should be). Task files contain project
knowledge that should persist across contributors and sessions. Do NOT gitignore .hac/.
.github/pull_request_template.md
# What is the type of this PR?
- [ ] Refactor (refactored code that neither fixes a bug nor adds a feature)
- [ ] Feature (non-breaking change which adds functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Test (including new or correcting previous tests)
- [ ] Style (changes to code formatting and styling)
- [ ] Optimization (performance improvements)
- [ ] Documentation Update (updates to the documentation (i.e. README, comments, docstrings, etc.))
- [ ] Revert (reverts a previous commit)
# Motivation and Context
**Why is this change required? What problem does it solve?**
**If it fixes an open issue, please link to the issue here.**
# Modifications
The following changes were made in this PR.
- Change A
- Change B
# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
Please also list any relevant details for your test configuration.
- Test A
- Test B
# Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
# Notes
Add any additional notes for the reviewers.
.github/ISSUE_TEMPLATE/bug_report.md
---
name: Bug report
about: Create a report to help us improve
title: '[BUG] Title'
labels: bug
assignees: 'GITHUB_USERNAME'
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
.github/ISSUE_TEMPLATE/feature_request.md
---
name: Feature request
about: Suggest an idea for this project
title: '[FEATURE] Title'
labels: enhancement
assignees: 'GITHUB_USERNAME'
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
If the user did not provide a GitHub username, omit the assignees line entirely from both issue templates.
.github/workflows/lint.yml
Always include this. It runs pre-commit on PR-changed files only (fast) and skips the no-commit-to-branch hook which doesn't apply in CI.
name: Lint
on:
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- name: Run pre-commit on PR changed files
uses: pre-commit/action@v3.0.1
with:
extra_args: --files $(git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD)
env:
SKIP: no-commit-to-branch
Phase 4: Post-Scaffold Steps
After generating files, run:
git init && git add . && git commit -m "chore: initial project scaffold"
make setup
uv run pre-commit install
make lint
make test
Report any failures and fix before declaring scaffold complete.
Customization Notes
- Replace all placeholders:
PROJECT_NAME, PACKAGE_NAME, PROJECT_DESCRIPTION, AUTHOR, EMAIL, GITHUB_USERNAME
- Adapt ruff rules: Add or remove lint rules based on project needs. The default set is opinionated but covers the most common issues.
- Docs are optional: Only generate
docs/ and mkdocs.yml if the user requests documentation.
lint.yml is always included: It's lightweight (runs pre-commit on changed files only) and universally useful. Always generate it.
- Additional CI workflows are optional: Only generate
test.yml, publish.yml, etc. if the user requests them.
- e2e tests are optional: Only create
tests/e2e/ if the project has external dependencies worth testing end-to-end.
.hac/ is always created: Delegate to the hac-init skill. Do not duplicate HAC templates here.
Process Violations — Red Flags
Stop and correct if you catch yourself doing any of the following:
- Generating files without confirming project name and type
- Skipping pyright or ruff config in
pyproject.toml
- Using
requirements.txt instead of pyproject.toml with dependency groups
- Creating a
setup.py or setup.cfg instead of pyproject.toml
- Putting the package directly at the root instead of under
src/
- Pre-creating
core/, services/, or infrastructure/ directories before they're needed
- Using static
version = "0.1.0" instead of dynamic = ["version"] with hatch-vcs
- Omitting
.pre-commit-config.yaml
- Using
pip or poetry instead of uv
- Putting test configuration in a separate
pytest.ini instead of pyproject.toml
- Generating a flat
tests/ directory without unit/ and integration/ separation
- Skipping the
.hac/ directory or duplicating its templates instead of using hac-init