A multi-round automated code quality tool for Claude Code. Runs sequential quality rounds (testing, maintainability, refactoring, concurrency, fault tolerance, error handling, security, type safety, dead code elimination, dependency hygiene, simplification) with test verification and clean git commits.
# Install as a CLI tool
uv tool install /path/to/quality-pipeline
# Or run directly from the repo
uv run quality-pipeline --helpFrom any git project directory:
# Run all rounds
quality-pipeline
# Preview the plan
quality-pipeline --dry-run
# Cherry-pick rounds
quality-pipeline --rounds "audit-tests maintainability dead-code simplify"
# Resume from round 3
quality-pipeline --start-from 3
# Safe with uncommitted changes
quality-pipeline --worktreeOr without installing, using uv run from the quality-pipeline repo:
uv run quality-pipeline --project-dir /path/to/your/projectImportant: uv run changes the working directory to the quality-pipeline repo,
so --project-dir is required when targeting another project. Without it, the
pipeline runs on itself rather than your intended target.
When invoked from within a Claude Code session, the pipeline automatically unsets CLAUDECODE and CLAUDE_CODE_ENTRYPOINT before spawning claude -p subprocesses, so they don't fail with a recursive-run detection error.
| Option | Description |
|---|---|
--project-dir DIR |
Run in DIR instead of current directory |
--rounds "r1 r2 ..." |
Which rounds to run (default: all) |
--config FILE |
Path to pipeline.yaml config |
--start-from N |
Resume from round N (1-indexed) |
--dry-run |
Show plan without executing |
--worktree |
Run in an isolated git worktree (safe with uncommitted changes) |
--worktree-symlinks "d1 d2" |
Space-separated dirs to symlink into worktree |
--test-command "CMD" |
Override auto-detected test command |
--review |
Enable reviewer pass for all rounds |
--no-review |
Disable reviewer pass for all rounds |
--log-dir DIR |
Directory for log files |
| Round | Prefix | Turns | Time | Gate | Retries | Review | Description |
|---|---|---|---|---|---|---|---|
audit-tests |
test: |
40 | 20m | hard | 2 | Audit test quality and fill coverage gaps with substantial, independent tests | |
maintainability |
refactor: |
40 | 20m | soft | 1 | yes | Fix duplicate implementations, leaky interfaces, module-boundary drift, and brittle tests |
refactor |
refactor: |
40 | 20m | soft | 1 | Improve naming, structure, and clarity | |
concurrency |
fix: |
30 | 25m | hard | 0 | yes | Fix races, lost updates, and find parallelization opportunities |
fault-tolerance |
fix: |
30 | 25m | hard | 0 | yes | Fix non-atomic writes, lost updates, missing fsync, and idempotency bugs |
error-handling |
fix: |
30 | 15m | hard | 1 | Fix swallowed errors, missing error paths, and inconsistent patterns | |
security |
fix: |
40 | 20m | hard | 0 | yes | Fix hardcoded secrets, injection vectors, and insecure defaults |
type-safety |
refactor: |
30 | 15m | soft | 1 | Add missing type annotations and tighten overly broad types | |
dead-code |
chore: |
20 | 10m | soft | 1 | Remove unused imports, functions, and variables | |
dependency-hygiene |
chore: |
20 | 10m | soft | 1 | Remove unused dependencies and flag deprecated API usage | |
simplify |
style: |
20 | 10m | soft | 1 | Reduce unnecessary abstractions and complexity |
- Creates a branch
quality/YYYY-MM-DD-<hash>(optionally in an isolated worktree with--worktree) - For each round:
a. Runs static analysis tools (if configured) and injects results into the prompt
b. Invokes
claude -pwith a focused prompt c. Runs your test suite to verify nothing broke d. On test failure: retries with test output (ifmax_retries > 0) e. On final failure: rolls back and applies gate logic (hard=stop, soft=continue) f. On success: commits and optionally runs a reviewer pass - Moves to the next round
Rounds interact exclusively through git state — each round starts a fresh Claude session that sees the updated codebase.
Each round has a gate setting that controls what happens when tests fail:
- hard (default): Pipeline stops. Use for correctness-critical rounds (tests, concurrency, security).
- soft: Pipeline continues to the next round. Use for best-effort rounds (refactoring, dead code, simplify).
- none: Tests are skipped entirely. Use for rounds that don't affect behavior.
Unrecognized gate values (e.g., a typo like hardd) produce a warning and default to hard.
When tests fail after a round, the pipeline can retry by re-invoking Claude with the test output. Set max_retries in the round frontmatter (default: 0). Each retry uses half the round's budget/time and shows Claude the last 100 lines of test output.
Rounds can be augmented with static analysis results. The pipeline runs configured analyzers before Claude and injects their output into the prompt. Default mappings:
maintainability→ ruff-refactor, ruff-simplifyrefactor→ ruff-refactorsecurity→ bandit, semgreptype-safety→ mypy, pyright, tscdead-code→ vulture, ruff-dead-code, codegraph-unusedsimplify→ ruff-simplify
Override per-round with the analyzers frontmatter field or via overrides in pipeline.yaml.
Correctness-critical rounds (concurrency, fault-tolerance, error-handling, security) and the maintainability round include Behavior Contract sections that specify what MUST change and what MUST NOT change. These constrain Claude's changes and give the reviewer something concrete to check against.
After a round commits, an optional reviewer pass invokes a fresh Claude session to review the diff. The reviewer checks for correctness, contract compliance, scope creep, test quality, and subtle regressions. Enable per-round with review: true in frontmatter, or globally with --review.
Drop a .claude/pipeline.yaml in your project:
test_command: "pytest tests/"
rounds: [audit-tests, maintainability, refactor, concurrency, fault-tolerance, error-handling, security, type-safety, dead-code, dependency-hygiene, simplify]
branch_prefix: "quality/"
max_budget_usd: 20.00
max_time_minutes: 20
overrides:
audit-tests:
max_budget_usd: 8.00
max_time_minutes: 30
append_prompt: "Use pytest with fixtures"
dead-code:
gate: none
max_retries: 0
security:
review: true
analyzers: "bandit semgrep"For numeric fields like max_budget_usd and max_time_minutes, the priority order is:
- Per-round override (from the
overridessection in pipeline.yaml) — highest - Frontmatter (explicitly set in the round's
.mdfile) - Global config (top-level
max_budget_usd/max_time_minutesin pipeline.yaml) - Default ($5.00 budget, 30 turns, 15 minutes) — lowest
This means a round that explicitly sets max_budget_usd: 3.00 in its frontmatter won't be silently overwritten by a global max_budget_usd: 20.00 — the frontmatter value is preserved unless a per-round override is also present.
Add a markdown file to quality_pipeline/rounds/ with YAML frontmatter:
---
name: my-round
commit_message_prefix: "feat: "
max_budget_usd: 5.00
max_turns: 30
max_time_minutes: 15
gate: hard
max_retries: 1
review: false
analyzers: "mypy pyright"
---
# My Custom Round
Your prompt here...Frontmatter fields (all optional, with backward-compatible defaults):
| Field | Default | Description |
|---|---|---|
name |
"" |
Round identifier (used for config overrides and display) |
commit_message_prefix |
"chore: " |
Git commit message prefix |
max_budget_usd |
5.00 |
Claude API budget cap (for pay-per-token users) |
max_turns |
30 |
Maximum Claude conversation turns |
max_time_minutes |
15 |
Wall-clock timeout for the Claude invocation |
gate |
"hard" |
hard, soft, or none (see Gate Types) |
max_retries |
0 |
Retry attempts on test failure |
review |
false |
Run a reviewer pass after commit |
analyzers |
"" |
Space-separated static analysis tools to run |
The pipeline auto-detects your test runner by checking (in order):
- CLAUDE.md for test command mentions
- Makefile
testtarget - package.json
testscript (respects bun/pnpm/yarn lockfiles) - pyproject.toml / pytest configuration (uses
uv run pytestifuv.lockpresent) - go.mod →
go test ./... - Cargo.toml →
cargo test
Override with --test-command or test_command in pipeline.yaml.
The pipeline can be run on its own codebase. Use --worktree so the orchestrator isn't modified mid-run:
# Preview
uv run quality-pipeline --worktree --dry-run
# Run all rounds
uv run quality-pipeline --worktree
# Cherry-pick rounds
uv run quality-pipeline --worktree --rounds "audit-tests maintainability simplify"Test auto-detection works because the repo now has a pyproject.toml with pytest configured.
- If a round fails, previous rounds' commits are preserved on the branch
- Resume with
--start-from Nto skip completed rounds - Hard gate rounds: test failure rolls back changes and stops the pipeline
- Soft gate rounds: test failure rolls back changes but continues to the next round
- Retry loop (if configured) re-invokes Claude with test output before giving up