Add composable multi-language linting and formatting system
PostToolUse hook auto-formats files after Edit/Write/MultiEdit with git-blob checkpoints for safe revert. Pre-commit hook for staged files. Canonical formatter scripts for py, sh, ts, sql, json (+ symlinks for js, yaml, md). Install and setup scripts for project opt-in. Includes best-practices/linting.md, HOOKS.md docs, README.md, and session log. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,3 +15,4 @@ Generalised best practices extracted from real project work. Each topic file is
|
||||
- [Milestones & Reflections](milestones.md) — Milestone workflow, verification, reflection process
|
||||
- [Debugging Methodology](debugging.md) — Systematic diagnosis, full-chain testing, common pitfalls
|
||||
- [Claude Code Skills](skills-development.md) — Skill authoring, context injection, tool restrictions
|
||||
- [Linting & Formatting](linting.md) — Tool choices per language, PostToolUse hook, pre-commit integration, formatter contract
|
||||
|
||||
94
best-practices/linting.md
Normal file
94
best-practices/linting.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Linting & Formatting
|
||||
|
||||
Automated code formatting and linting integrated into the Claude Code workflow via PostToolUse hooks and git pre-commit hooks.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
claude-foundations/formatters/ ← canonical formatter scripts (one per extension)
|
||||
project/formatters/ ← symlinks to the formatters this project uses
|
||||
~/.claude/hooks/post-edit-lint.sh ← dispatches to project formatters on Edit/Write
|
||||
.git/hooks/pre-commit ← symlink to pre-commit-lint.sh
|
||||
```
|
||||
|
||||
Projects opt in by symlinking only the formatters they need. No formatter = no-op.
|
||||
|
||||
## Tool Choices by Language
|
||||
|
||||
| Language | Formatter | Linter | Config File |
|
||||
|----------|-----------|--------|-------------|
|
||||
| Python | `ruff format` | `ruff check` | `pyproject.toml` |
|
||||
| Shell | `shfmt` | `shellcheck` | `.editorconfig` |
|
||||
| TypeScript/JavaScript | `biome format` | `biome check` | `biome.json` |
|
||||
| SQL | `sqlfluff fix` | `sqlfluff lint` | `.sqlfluff` |
|
||||
| JSON/YAML/Markdown | `prettier` | — | `.prettierrc` |
|
||||
|
||||
### Why these tools?
|
||||
- **ruff**: Rust-based, extremely fast, replaces black + isort + flake8 + pyflakes in one tool
|
||||
- **biome**: Rust-based, replaces prettier + eslint for JS/TS in one tool
|
||||
- **shfmt + shellcheck**: The standard combo for shell scripts; shfmt formats, shellcheck catches bugs
|
||||
- **prettier**: Handles JSON/YAML/MD well; biome doesn't cover these yet
|
||||
|
||||
## Formatter Script Contract
|
||||
|
||||
Every script in `formatters/` follows the same interface:
|
||||
|
||||
- **Input:** `$1` = absolute file path
|
||||
- **Behaviour:** format the file in place, then lint it
|
||||
- **Stdout:** suppressed
|
||||
- **Stderr:** lint warnings/errors that couldn't be auto-fixed
|
||||
- **Exit code:** `0` if clean, `1` if lint errors remain
|
||||
- **Missing tools:** exit `0` silently (`command -v` check)
|
||||
- **No `set -e`:** individual commands may fail; execution must continue
|
||||
|
||||
## PostToolUse Integration
|
||||
|
||||
The `post-edit-lint.sh` hook fires on Edit/Write/MultiEdit:
|
||||
|
||||
1. Extracts `file_path` and `cwd` from stdin JSON
|
||||
2. Walks up from `cwd` to find `formatters/` directory
|
||||
3. Creates a checkpoint using `git hash-object` (fast, no commits)
|
||||
4. Runs the matching formatter
|
||||
5. On clean pass: removes checkpoint, exits 0 (silent)
|
||||
6. On lint errors: keeps checkpoint, exits 2 (feeds errors to Claude)
|
||||
|
||||
Exit code 2 is special for PostToolUse — it feeds stderr back to Claude as feedback without blocking the edit.
|
||||
|
||||
## Checkpoint Mechanism
|
||||
|
||||
Uses `git hash-object -w` to store pre-format content as an orphan blob (~1ms, no commits, no stash). The `.pre-lint` file stores only a 40-char SHA. Falls back to `cp` outside git repos.
|
||||
|
||||
```bash
|
||||
# Revert after lint errors:
|
||||
git cat-file blob "$(cat file.py.pre-lint)" > file.py
|
||||
rm file.py.pre-lint
|
||||
```
|
||||
|
||||
## Subagent Fix Pattern
|
||||
|
||||
When lint errors occur, Claude sees the errors via stderr. The recommended workflow:
|
||||
|
||||
1. Claude reports the errors to the user
|
||||
2. If the user says "fix", Claude spawns a subagent via the Agent tool
|
||||
3. The subagent reads the file and errors, makes Edit calls to fix them
|
||||
4. Each Edit triggers the hook again (re-format, re-lint) in the subagent
|
||||
5. Fix iterations stay in the subagent's context, not the main conversation
|
||||
|
||||
## Pre-commit Integration
|
||||
|
||||
`pre-commit-lint.sh` reuses the same formatter scripts:
|
||||
|
||||
1. Iterates staged files (Added/Modified only)
|
||||
2. Runs matching formatters
|
||||
3. Re-stages formatted files
|
||||
4. Exits non-zero if lint errors remain (blocks commit)
|
||||
|
||||
Install: symlink `.git/hooks/pre-commit` → `pre-commit-lint.sh`, or use `setup-formatters.sh` which does this automatically.
|
||||
|
||||
## Setup Checklist
|
||||
|
||||
1. Run `scripts/setup-formatters.sh <project> <ext> [<ext> ...]` to symlink formatters
|
||||
2. Install the required tools (`ruff`, `shfmt`, `shellcheck`, `biome`, `prettier`, `sqlfluff`)
|
||||
3. Create per-project config files as needed (`pyproject.toml`, `.editorconfig`, etc.)
|
||||
4. Run `scripts/install-hooks.sh` to install the PostToolUse hook (one-time global setup)
|
||||
5. Use `/linter scan` to verify everything is connected
|
||||
Reference in New Issue
Block a user