From f2b8190fc6463d838d6f1e4626fd3c0a261474fb Mon Sep 17 00:00:00 2001 From: Junya Morioka Date: Tue, 8 Sep 2026 08:06:42 +0900 Subject: [PATCH] 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 --- README.md | 7 +++++- template/.agents/hooks/format-python.sh | 31 +++++++++++++++++++++++++ template/.claude/settings.json | 29 +++++++++++++++++++++++ template/.codex/hooks.json | 17 ++++++++++++++ template/.gitignore | 6 ++++- template/AGENTS.md | 16 ++++++++++--- 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100755 template/.agents/hooks/format-python.sh create mode 100644 template/.claude/settings.json create mode 100644 template/.codex/hooks.json diff --git a/README.md b/README.md index eebe2b1..ecc1ff9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ A simple modern Python project template powered by [Copier](https://copier.readt - 🐳 **Docker Support**: Complete Docker development environment - 📦 **Devcontainer Support**: VS Code devcontainer for consistent development - ✨ **AI Editor Support**: [AGENTS.md](https://agents.md) and - [CLAUDE.md](https://docs.anthropic.com/en/docs/claude-code/overview) included for AI-powered development + [CLAUDE.md](https://docs.anthropic.com/en/docs/claude-code/overview) included for AI-powered development, + plus shared Claude Code / Codex hooks that format and lint Python files as the agent edits them - 📝 **Type Hints**: Full type annotation support with modern Python features - 🔎 **Type Checking**: Pre-configured [ty](https://docs.astral.sh/ty/) for static type checking - 🔍 **Code Quality**: Pre-configured Ruff for linting and formatting @@ -125,6 +126,10 @@ your-project/ - [AGENTS.md(`./template/AGENTS.md`)](https://agents.md) - [CLAUDE.md(`./template/CLAUDE.md`)](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports) +- `.claude/settings.json`: Claude Code permissions (pre-approved `uv run --frozen`, `uv sync`, read-only git + commands; `.env` files denied) and a `PostToolUse` hook +- `.codex/hooks.json`: the same `PostToolUse` hook for Codex +- `.agents/hooks/format-python.sh`: the hook script, runs `ruff format` and `ruff check --fix` on the edited file ## Q&A diff --git a/template/.agents/hooks/format-python.sh b/template/.agents/hooks/format-python.sh new file mode 100755 index 0000000..8fc2350 --- /dev/null +++ b/template/.agents/hooks/format-python.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# PostToolUse hook shared by Claude Code and Codex: format and lint the Python +# files the agent just edited. Reads the hook input JSON from stdin. +# +# Claude Code (Edit/Write) reports the file in tool_input.file_path. +# Codex (apply_patch) reports the whole patch in tool_input.command. +# Remaining lint errors are sent back to the agent via exit code 2. + +set -euo pipefail + +input="$(cat)" + +files=() +while IFS= read -r file; do + [[ "${file}" == *.py ]] && files+=("${file}") +done < <(jq -r ' + (.tool_input.file_path // empty), + ((.tool_input.command // "") | scan("\\*\\*\\* (?:Add|Update) File: (.+)") | .[0]) +' <<<"${input}") + +# Avoid the array-length expansion here: its brace-hash prefix would start a Jinja +# comment when Copier renders this file. +[[ -n "${files[*]:-}" ]] || exit 0 + +cwd="$(jq -r '.cwd // empty' <<<"${input}")" +cd -- "${cwd:-$(git rev-parse --show-toplevel)}" + +uv run --frozen ruff format -- "${files[@]}" +if ! uv run --frozen ruff check --fix -- "${files[@]}" >&2; then + exit 2 +fi diff --git a/template/.claude/settings.json b/template/.claude/settings.json new file mode 100644 index 0000000..f9520fb --- /dev/null +++ b/template/.claude/settings.json @@ -0,0 +1,29 @@ +{ + "permissions": { + "allow": [ + "Bash(uv run --frozen *)", + "Bash(uv sync *)", + "Bash(uv lock *)", + "Bash(git status *)", + "Bash(git diff *)", + "Bash(git log *)" + ], + "deny": [ + "Read(.env)", + "Read(*.env)" + ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "bash \"$(git rev-parse --show-toplevel)/.agents/hooks/format-python.sh\"" + } + ] + } + ] + } +} diff --git a/template/.codex/hooks.json b/template/.codex/hooks.json new file mode 100644 index 0000000..ef1c7f1 --- /dev/null +++ b/template/.codex/hooks.json @@ -0,0 +1,17 @@ +{ + "description": "Format and lint Python files after the agent edits them.", + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "bash \"$(git rev-parse --show-toplevel)/.agents/hooks/format-python.sh\"", + "statusMessage": "Formatting Python" + } + ] + } + ] + } +} diff --git a/template/.gitignore b/template/.gitignore index 3a6c197..0590a5f 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -287,4 +287,8 @@ tags .history .ionide -# End of https://www.toptal.com/developers/gitignore/api/linux,macos,visualstudiocode,vim,python,jupyternotebooks,venv,virtualenv \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/linux,macos,visualstudiocode,vim,python,jupyternotebooks,venv,virtualenv + +### AI coding agents ### +# Personal Claude Code overrides (machine-specific settings, never shared) +.claude/settings.local.json diff --git a/template/AGENTS.md b/template/AGENTS.md index f94d257..31b5ed2 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -9,22 +9,28 @@ Follow these guidelines precisely. - ONLY use uv, NEVER pip - Installation: `uv add package` - Upgrading: `uv add --dev package --upgrade-package package` - - FORBIDDEN: `uv pip install`, `@latest` syntax + - FORBIDDEN: `uv pip install`, `@latest` syntax, editing `uv.lock` by hand 2. Code Quality - Type hints required for all code + - Imports used only in type annotations go under `if TYPE_CHECKING:` with + `from __future__ import annotations` at the top of the module (Ruff `TC` rules) - Follow existing patterns exactly - Use Google style for docstring 3. Testing Requirements - - Framework: `uv run --frozen pytest` + - Framework: `uv run --frozen pytest` (runs in parallel; use `-n 0` for `--pdb`) - Coverage: test edge cases and errors - - Coverage report: `uv run --frozen pytest --cov` + - Coverage report: `uv run --frozen pytest --cov` (CI fails below 80% branch coverage of `src/`) - New features require tests - Bug fixes require regression tests 4. Git - Follow the Conventional Commits style on commit messages. + - NEVER use `git commit --no-verify`; fix what the hooks report instead. + +5. Running + - Application: `uv run {{project_name}}` (or `python -m {{package_name}}`) ## Code Formatting and Linting @@ -39,3 +45,7 @@ Follow these guidelines precisely. - Install: `uv run prek install` - Runs: on git commit - Tools: uv lock, Ruff, ty +4. Agent Hooks + - `.agents/hooks/format-python.sh` formats and lints every Python file right + after Claude Code or Codex edits it (wired in `.claude/settings.json` and + `.codex/hooks.json`). Do not re-run the formatter manually after edits.