Plankton Code Quality
Integration reference for Plankton (credit: @alxfazio), a write-time code quality enforcement system. Plankton runs formatters and linters on every file edit via PostToolUse hooks, then spawns agent subprocesses to fix violations.
When to Use
- Automatic formatting and linting on every file edit (not just at commit time)
- Defense against agents modifying linter configs to pass instead of fixing code
- Tiered model routing for fixes (light model for style, standard for logic, advanced for types)
- Multi-language projects (Python, TypeScript, Shell, YAML, JSON, TOML, Markdown, Dockerfile)
Three-Phase Architecture
Every time an agent edits or writes a file, Plankton's multi_linter.sh PostToolUse hook runs:
Phase 1: Auto-Format (Silent)
├─ Runs formatters (ruff format, biome, shfmt, taplo, markdownlint)
├─ Fixes 40–50% of issues silently
└─ No output to main agent
Phase 2: Collect Violations (JSON)
├─ Runs linters and collects unfixable violations
├─ Returns structured JSON: {line, column, code, message, linter}
└─ Still no output to main agent
Phase 3: Delegate + Verify
├─ Spawns subprocess with violations JSON
├─ Routes to model tier based on violation complexity:
│ ├─ Light: formatting, imports, style — 120s timeout
│ ├─ Standard: complexity, refactoring — 300s timeout
│ └─ Advanced: type system, deep reasoning — 600s timeout
├─ Re-runs Phase 1+2 to verify fixes
└─ Exit 0 if clean, Exit 2 if violations remain (reported to main agent)
What the Main Agent Sees
| Scenario |
Agent sees |
Hook exit |
| No violations |
Nothing |
0 |
| All fixed by subprocess |
Nothing |
0 |
| Violations remain after subprocess |
[hook] N violation(s) remain |
2 |
| Advisory (duplicates, old tooling) |
[hook:advisory] ... |
0 |
Most quality problems are resolved transparently.
Config Protection
Agents may modify linter configs to disable rules rather than fix code. Plankton blocks this with:
- PreToolUse hook —
protect_linter_configs.sh blocks edits to linter configs before they happen
- Stop hook —
stop_config_guardian.sh detects config changes via git diff at session end
- Protected files —
.ruff.toml, biome.json, .shellcheckrc, .yamllint, .hadolint.yaml, etc.
Package Manager Enforcement
A PreToolUse hook on Bash blocks legacy package managers:
pip, pip3, poetry, pipenv → blocked (use uv)
npm, yarn, pnpm → blocked (use bun)
- Exceptions:
npm audit, npm view, npm publish
Setup
# Clone Plankton (credit: @alxfazio)
git clone https://github.com/alexfazio/plankton.git
cd plankton
# Install core dependencies
brew install jaq ruff uv
# Install Python linters
uv sync --all-extras
Hooks in .claude/settings.json activate automatically.
Per-Project Integration
- Copy
.claude/hooks/ directory to your project
- Copy
.claude/settings.json hook configuration
- Copy linter config files (
.ruff.toml, biome.json, etc.)
- Install the linters for your languages
Language Dependencies
| Language |
Required |
Optional |
| Python |
ruff, uv |
ty (types), vulture (dead code), bandit (security) |
| TypeScript/JS |
biome |
oxlint, semgrep, knip (dead exports) |
| Shell |
shellcheck, shfmt |
— |
| YAML |
yamllint |
— |
| Markdown |
markdownlint-cli2 |
— |
| Dockerfile |
hadolint (≥ 2.12.0) |
— |
| TOML |
taplo |
— |
| JSON |
jaq |
— |
Configuration Reference
Plankton's .claude/hooks/config.json controls all behavior:
{
"languages": {
"python": true,
"shell": true,
"yaml": true,
"json": true,
"toml": true,
"dockerfile": true,
"markdown": true,
"typescript": {
"enabled": true,
"js_runtime": "auto",
"biome_nursery": "warn",
"semgrep": true
}
},
"phases": {
"auto_format": true,
"subprocess_delegation": true
},
"subprocess": {
"tiers": {
"light": { "timeout": 120, "max_turns": 10 },
"standard": { "timeout": 300, "max_turns": 10 },
"advanced": { "timeout": 600, "max_turns": 15 }
},
"volume_threshold": 5
}
}
Key settings:
- Disable unused languages to speed up hooks
volume_threshold — violations above this count auto-escalate to a higher model tier
subprocess_delegation: false — skip Phase 3, just report violations
Environment Overrides
| Variable |
Purpose |
HOOK_SKIP_SUBPROCESS=1 |
Skip Phase 3, report violations directly |
HOOK_SUBPROCESS_TIMEOUT=N |
Override tier timeout |
HOOK_DEBUG_MODEL=1 |
Log model selection decisions |
HOOK_SKIP_PM=1 |
Bypass package manager enforcement |
References
1---2name: plankton-code-quality3description: Write-time code quality enforcement using Plankton — auto-formatting, linting, and agent-powered fixes on every file edit via hooks.4---56# Plankton Code Quality78Integration reference for Plankton (credit: @alxfazio), a write-time code quality enforcement system. Plankton runs formatters and linters on every file edit via PostToolUse hooks, then spawns agent subprocesses to fix violations.910## When to Use1112- Automatic formatting and linting on every file edit (not just at commit time)13- Defense against agents modifying linter configs to pass instead of fixing code14- Tiered model routing for fixes (light model for style, standard for logic, advanced for types)15- Multi-language projects (Python, TypeScript, Shell, YAML, JSON, TOML, Markdown, Dockerfile)1617## Three-Phase Architecture1819Every time an agent edits or writes a file, Plankton's `multi_linter.sh` PostToolUse hook runs:2021```22Phase 1: Auto-Format (Silent)23├─ Runs formatters (ruff format, biome, shfmt, taplo, markdownlint)24├─ Fixes 40–50% of issues silently25└─ No output to main agent2627Phase 2: Collect Violations (JSON)28├─ Runs linters and collects unfixable violations29├─ Returns structured JSON: {line, column, code, message, linter}30└─ Still no output to main agent3132Phase 3: Delegate + Verify33├─ Spawns subprocess with violations JSON34├─ Routes to model tier based on violation complexity:35│ ├─ Light: formatting, imports, style — 120s timeout36│ ├─ Standard: complexity, refactoring — 300s timeout37│ └─ Advanced: type system, deep reasoning — 600s timeout38├─ Re-runs Phase 1+2 to verify fixes39└─ Exit 0 if clean, Exit 2 if violations remain (reported to main agent)40```4142### What the Main Agent Sees4344| Scenario | Agent sees | Hook exit |45|----------|-----------|-----------|46| No violations | Nothing | 0 |47| All fixed by subprocess | Nothing | 0 |48| Violations remain after subprocess | `[hook] N violation(s) remain` | 2 |49| Advisory (duplicates, old tooling) | `[hook:advisory] ...` | 0 |5051Most quality problems are resolved transparently.5253### Config Protection5455Agents may modify linter configs to disable rules rather than fix code. Plankton blocks this with:56571. **PreToolUse hook** — `protect_linter_configs.sh` blocks edits to linter configs before they happen582. **Stop hook** — `stop_config_guardian.sh` detects config changes via `git diff` at session end593. **Protected files** — `.ruff.toml`, `biome.json`, `.shellcheckrc`, `.yamllint`, `.hadolint.yaml`, etc.6061### Package Manager Enforcement6263A PreToolUse hook on Bash blocks legacy package managers:64- `pip`, `pip3`, `poetry`, `pipenv` → blocked (use `uv`)65- `npm`, `yarn`, `pnpm` → blocked (use `bun`)66- Exceptions: `npm audit`, `npm view`, `npm publish`6768## Setup6970```bash71# Clone Plankton (credit: @alxfazio)72git clone https://github.com/alexfazio/plankton.git73cd plankton7475# Install core dependencies76brew install jaq ruff uv7778# Install Python linters79uv sync --all-extras80```8182Hooks in `.claude/settings.json` activate automatically.8384### Per-Project Integration85861. Copy `.claude/hooks/` directory to your project872. Copy `.claude/settings.json` hook configuration883. Copy linter config files (`.ruff.toml`, `biome.json`, etc.)894. Install the linters for your languages9091### Language Dependencies9293| Language | Required | Optional |94|----------|----------|----------|95| Python | `ruff`, `uv` | `ty` (types), `vulture` (dead code), `bandit` (security) |96| TypeScript/JS | `biome` | `oxlint`, `semgrep`, `knip` (dead exports) |97| Shell | `shellcheck`, `shfmt` | — |98| YAML | `yamllint` | — |99| Markdown | `markdownlint-cli2` | — |100| Dockerfile | `hadolint` (≥ 2.12.0) | — |101| TOML | `taplo` | — |102| JSON | `jaq` | — |103104## Configuration Reference105106Plankton's `.claude/hooks/config.json` controls all behavior:107108```json109{110 "languages": {111 "python": true,112 "shell": true,113 "yaml": true,114 "json": true,115 "toml": true,116 "dockerfile": true,117 "markdown": true,118 "typescript": {119 "enabled": true,120 "js_runtime": "auto",121 "biome_nursery": "warn",122 "semgrep": true123 }124 },125 "phases": {126 "auto_format": true,127 "subprocess_delegation": true128 },129 "subprocess": {130 "tiers": {131 "light": { "timeout": 120, "max_turns": 10 },132 "standard": { "timeout": 300, "max_turns": 10 },133 "advanced": { "timeout": 600, "max_turns": 15 }134 },135 "volume_threshold": 5136 }137}138```139140Key settings:141- Disable unused languages to speed up hooks142- `volume_threshold` — violations above this count auto-escalate to a higher model tier143- `subprocess_delegation: false` — skip Phase 3, just report violations144145## Environment Overrides146147| Variable | Purpose |148|----------|---------|149| `HOOK_SKIP_SUBPROCESS=1` | Skip Phase 3, report violations directly |150| `HOOK_SUBPROCESS_TIMEOUT=N` | Override tier timeout |151| `HOOK_DEBUG_MODEL=1` | Log model selection decisions |152| `HOOK_SKIP_PM=1` | Bypass package manager enforcement |153154## References155156- [Plankton](https://github.com/alexfazio/plankton) (credit: @alxfazio)