Skip to content

Commit f2b8190

Browse files
committed
feat: add shared agent hooks and Claude Code permissions to the template
- Add .agents/hooks/format-python.sh, a PostToolUse hook that runs ruff format and ruff check --fix on the Python file an agent just edited - Wire the hook for Claude Code (.claude/settings.json) and Codex (.codex/hooks.json) using the same event schema - Pre-approve uv run --frozen, uv sync, uv lock and read-only git commands for Claude Code and deny reading .env files - Ignore .claude/settings.local.json in generated projects - Record the TYPE_CHECKING import rule, the parallel pytest note, the --no-verify ban, the run command and the agent hooks in AGENTS.md
1 parent eb39ae7 commit f2b8190

6 files changed

Lines changed: 101 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ A simple modern Python project template powered by [Copier](https://copier.readt
1414
- 🐳 **Docker Support**: Complete Docker development environment
1515
- 📦 **Devcontainer Support**: VS Code devcontainer for consistent development
1616
- ✨ **AI Editor Support**: [AGENTS.md](https://agents.md) and
17-
[CLAUDE.md](https://docs.anthropic.com/en/docs/claude-code/overview) included for AI-powered development
17+
[CLAUDE.md](https://docs.anthropic.com/en/docs/claude-code/overview) included for AI-powered development,
18+
plus shared Claude Code / Codex hooks that format and lint Python files as the agent edits them
1819
- 📝 **Type Hints**: Full type annotation support with modern Python features
1920
- 🔎 **Type Checking**: Pre-configured [ty](https://docs.astral.sh/ty/) for static type checking
2021
- 🔍 **Code Quality**: Pre-configured Ruff for linting and formatting
@@ -125,6 +126,10 @@ your-project/
125126

126127
- [AGENTS.md(`./template/AGENTS.md`)](https://agents.md)
127128
- [CLAUDE.md(`./template/CLAUDE.md`)](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports)
129+
- `.claude/settings.json`: Claude Code permissions (pre-approved `uv run --frozen`, `uv sync`, read-only git
130+
commands; `.env` files denied) and a `PostToolUse` hook
131+
- `.codex/hooks.json`: the same `PostToolUse` hook for Codex
132+
- `.agents/hooks/format-python.sh`: the hook script, runs `ruff format` and `ruff check --fix` on the edited file
128133

129134
## Q&A
130135

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
# PostToolUse hook shared by Claude Code and Codex: format and lint the Python
3+
# files the agent just edited. Reads the hook input JSON from stdin.
4+
#
5+
# Claude Code (Edit/Write) reports the file in tool_input.file_path.
6+
# Codex (apply_patch) reports the whole patch in tool_input.command.
7+
# Remaining lint errors are sent back to the agent via exit code 2.
8+
9+
set -euo pipefail
10+
11+
input="$(cat)"
12+
13+
files=()
14+
while IFS= read -r file; do
15+
[[ "${file}" == *.py ]] && files+=("${file}")
16+
done < <(jq -r '
17+
(.tool_input.file_path // empty),
18+
((.tool_input.command // "") | scan("\\*\\*\\* (?:Add|Update) File: (.+)") | .[0])
19+
' <<<"${input}")
20+
21+
# Avoid the array-length expansion here: its brace-hash prefix would start a Jinja
22+
# comment when Copier renders this file.
23+
[[ -n "${files[*]:-}" ]] || exit 0
24+
25+
cwd="$(jq -r '.cwd // empty' <<<"${input}")"
26+
cd -- "${cwd:-$(git rev-parse --show-toplevel)}"
27+
28+
uv run --frozen ruff format -- "${files[@]}"
29+
if ! uv run --frozen ruff check --fix -- "${files[@]}" >&2; then
30+
exit 2
31+
fi

‎template/.claude/settings.json‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(uv run --frozen *)",
5+
"Bash(uv sync *)",
6+
"Bash(uv lock *)",
7+
"Bash(git status *)",
8+
"Bash(git diff *)",
9+
"Bash(git log *)"
10+
],
11+
"deny": [
12+
"Read(.env)",
13+
"Read(*.env)"
14+
]
15+
},
16+
"hooks": {
17+
"PostToolUse": [
18+
{
19+
"matcher": "Edit|Write",
20+
"hooks": [
21+
{
22+
"type": "command",
23+
"command": "bash \"$(git rev-parse --show-toplevel)/.agents/hooks/format-python.sh\""
24+
}
25+
]
26+
}
27+
]
28+
}
29+
}

‎template/.codex/hooks.json‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"description": "Format and lint Python files after the agent edits them.",
3+
"hooks": {
4+
"PostToolUse": [
5+
{
6+
"matcher": "Edit|Write",
7+
"hooks": [
8+
{
9+
"type": "command",
10+
"command": "bash \"$(git rev-parse --show-toplevel)/.agents/hooks/format-python.sh\"",
11+
"statusMessage": "Formatting Python"
12+
}
13+
]
14+
}
15+
]
16+
}
17+
}

‎template/.gitignore‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,8 @@ tags
287287
.history
288288
.ionide
289289

290-
# End of https://www.toptal.com/developers/gitignore/api/linux,macos,visualstudiocode,vim,python,jupyternotebooks,venv,virtualenv
290+
# End of https://www.toptal.com/developers/gitignore/api/linux,macos,visualstudiocode,vim,python,jupyternotebooks,venv,virtualenv
291+
292+
### AI coding agents ###
293+
# Personal Claude Code overrides (machine-specific settings, never shared)
294+
.claude/settings.local.json

‎template/AGENTS.md‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,28 @@ Follow these guidelines precisely.
99
- ONLY use uv, NEVER pip
1010
- Installation: `uv add package`
1111
- Upgrading: `uv add --dev package --upgrade-package package`
12-
- FORBIDDEN: `uv pip install`, `@latest` syntax
12+
- FORBIDDEN: `uv pip install`, `@latest` syntax, editing `uv.lock` by hand
1313

1414
2. Code Quality
1515
- Type hints required for all code
16+
- Imports used only in type annotations go under `if TYPE_CHECKING:` with
17+
`from __future__ import annotations` at the top of the module (Ruff `TC` rules)
1618
- Follow existing patterns exactly
1719
- Use Google style for docstring
1820

1921
3. Testing Requirements
20-
- Framework: `uv run --frozen pytest`
22+
- Framework: `uv run --frozen pytest` (runs in parallel; use `-n 0` for `--pdb`)
2123
- Coverage: test edge cases and errors
22-
- Coverage report: `uv run --frozen pytest --cov`
24+
- Coverage report: `uv run --frozen pytest --cov` (CI fails below 80% branch coverage of `src/`)
2325
- New features require tests
2426
- Bug fixes require regression tests
2527

2628
4. Git
2729
- Follow the Conventional Commits style on commit messages.
30+
- NEVER use `git commit --no-verify`; fix what the hooks report instead.
31+
32+
5. Running
33+
- Application: `uv run {{project_name}}` (or `python -m {{package_name}}`)
2834

2935
## Code Formatting and Linting
3036

@@ -39,3 +45,7 @@ Follow these guidelines precisely.
3945
- Install: `uv run prek install`
4046
- Runs: on git commit
4147
- Tools: uv lock, Ruff, ty
48+
4. Agent Hooks
49+
- `.agents/hooks/format-python.sh` formats and lints every Python file right
50+
after Claude Code or Codex edits it (wired in `.claude/settings.json` and
51+
`.codex/hooks.json`). Do not re-run the formatter manually after edits.

0 commit comments

Comments
 (0)