A cookie-cutter Claude Code project template that enforces a strict
architect → engineer → commit → multi-model review → squash-merge loop.
Every commit on a feature branch is automatically reviewed by Pi
running Deepseek and Kimi (in parallel, with different lenses); the engineer
agent waits for verdict: approve from every reviewer before proceeding.
On the machine that will host derived projects:
| Tool | Why |
|---|---|
git |
All workflow is git-driven. |
bash |
Hooks and helper scripts are bash. |
python3 |
Only needed for the bootstrap script (stdlib only — no pip deps). |
pi |
Runs the code reviewers. Must be configured with providers for the models you choose (default: Deepseek + Moonshot/Kimi). Verify with pi --list-models. |
gh |
Optional — only needed if you want the bootstrap to create the GitHub repo for you. |
Set git identity once globally (recommended) so the bootstrap commit can run without prompting you:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"# Direct execution (recommended — uv resolves the right Python via PEP 723)
/path/to/reference_project/apply-standard.py <target_dir> [options]
# Or explicit:
uv run /path/to/reference_project/apply-standard.py <target_dir> [options]
# Or fallback (no uv available):
python3 /path/to/reference_project/apply-standard.py <target_dir> [options]The script declares its Python requirement (≥3.10) via PEP 723
inline metadata, so uv run handles version management automatically. It has
zero external dependencies, so python3 also works for anyone without uv.
This single command brings any project — greenfield or pre-existing — up to the agentic development standard. It is idempotent: run it as many times as you like. The script asserts a declarative spec ("the target should look like this") and makes it true.
| Action | Behaviour |
|---|---|
Managed files (CLAUDE.md, .claude/agents/, .claude/hooks/, .claude/scripts/, .claude/settings.json) |
Copied/overwritten from this reference; files deleted from the reference are deleted from the target too |
Runtime dirs (.plans/, .reviews/, references/) |
Created if missing; contents never touched |
.gitignore |
Required entries appended if missing; existing lines preserved (additive only) |
.claude/settings.local.json |
Per-user state, never touched |
| Git | git init -b main if no .git/; core.hooksPath set to .claude/hooks; hooks and shell scripts marked executable |
| Identity | Verified; warns if missing, never modifies your git config |
| Alignment commit | Made on the current branch (with ALLOW_MAIN_COMMIT=1 REVIEW_SKIP=1) only if managed files actually changed |
| GitHub repo | Created via gh only with --gh-create, only if no remote exists, only after a first commit |
| Flag | Effect |
|---|---|
--dry-run |
Report what would change; apply nothing. Always safe to run first. |
--no-commit |
Skip the alignment commit even if files changed (so you can review with git diff before committing yourself) |
--gh-create |
Create a private GitHub repo if no remote exists |
--gh-public |
With --gh-create, public visibility |
--gh-name NAME |
With --gh-create, repo name (default: target dir basename) |
Run the script. Edit something in this reference. Run the script again — only that file gets propagated. Re-run with no further changes — output is "Target is already at the standard. Nothing changed." That is the contract.
Anything you put inside a managed directory (.claude/agents/,
.claude/hooks/, .claude/scripts/) becomes part of the standard's scope:
the script will delete it on re-apply if it's not in the reference. Keep
project-local extensions outside those directories.
cd <target_dir>
git switch -c feat/initial-scaffolding
# Then ask Claude: "Use the architect role to plan <thing>."From there, the loop runs itself — see CLAUDE.md in the new
project for the full workflow.
The template installs a workflow that is enforced by a combination of:
- Claude Code subagent definitions (
.claude/agents/) — architect and engineer roles, each with their own contract - Git hooks (
.claude/hooks/) activated viacore.hooksPath - Shell helpers (
.claude/scripts/) that implement the review-wait loop and branch-completion checks - Per-project instructions (
CLAUDE.md) loaded into Claude's context every session - Permission rules (
.claude/settings.json) that block the worst bypass patterns at the harness layer
| Role | What it does |
|---|---|
| Architect | A Claude subagent. Designs strategy, splits work into commit-sized units, writes a plan to .plans/<id>.md, updates .plans/ACTIVE, commits, and drives the plan-review loop to approve. Produces a plan, not code. |
| Engineer | A Claude subagent. Implements exactly one commit-sized unit per turn, self-reviews against .claude/scripts/self-review-checklist.md, commits, and drives the code-review loop to approve per commit. When every unit is approved, performs the squash-merge to main. |
| Reviewers | External Pi processes invoked by the post-commit hook. By default: Deepseek (correctness lens) and Kimi (design/maintainability lens). Each runs concurrently with different system prompts depending on commit type. |
- Never commit to
main. The pre-commit hook blocks it. Override only for the final squash-merge:ALLOW_MAIN_COMMIT=1. - One feature branch per plan. The branch holds many small commits (one per
unit + any
fix: address reviewfollow-ups). - When every commit is approved, the engineer squash-merges into
mainas a single human-readable commit. The squash itself goes through an integration-level review (separate prompts focused on whole-feature coherence rather than per-commit detail).
git commit
│
▼
┌─────────────────────────────────────────────────────────────┐
│ pre-commit hook │
│ • blocks if on main (unless ALLOW_MAIN_COMMIT=1) │
│ • greps staged ADDED lines for: │
│ - debug prints (console.log, debugger, pdb, │
│ breakpoint, dbg!) │
│ - TODO without owner (use TODO(name) or TODO @name) │
│ - obvious secret patterns (AWS, GitHub, OpenAI, │
│ Anthropic, Slack, PEM private keys) │
│ • fast (<1s), deterministic, language-agnostic │
│ • override: SKIP_PRECOMMIT_CHECKS=1 (rare) │
└─────────────────────────────────────────────────────────────┘
│ commit lands
▼
┌─────────────────────────────────────────────────────────────┐
│ post-commit hook │
│ • classifies the commit: │
│ - touches only .plans/ → plan-review prompts │
│ - on main branch → main-merge (integration) │
│ - otherwise → code-review prompts │
│ • spawns one Pi process per reviewer (setsid-detached), │
│ so `git commit` returns immediately │
│ • appends a diagnostic line to .reviews/.hook.log │
└─────────────────────────────────────────────────────────────┘
│ reviewers run in background
▼
┌─────────────────────────────────────────────────────────────┐
│ Each reviewer writes one immutable file to .reviews/ │
│ .reviews/<YYYY-MM-DD-HH-MM-SS>_<commit>_<agent>.md │
│ with a `verdict: approve | request_changes | error` line │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ The engineer subagent calls: │
│ bash .claude/scripts/wait-for-reviews.sh │
│ which polls .reviews/ until every expected reviewer has │
│ landed a file for HEAD, then exits: │
│ 0 = all approve 3 = some request_changes │
│ 1 = timeout 4 = some error (Pi failure) │
│ Exit 3 ⇒ engineer reads the open reviews, addresses each, │
│ makes a NEW commit (never --amend, never --no-verify), │
│ which triggers a fresh review cycle. Loop continues until 0.│
└─────────────────────────────────────────────────────────────┘
The post-commit hook classifies each commit and dispatches the matching prompt files:
| Commit type | When | Prompts |
|---|---|---|
plan |
Commit touches only .plans/ |
plan-review-{agent}.md — review approach & decomposition |
code |
Any other commit on a feature branch | code-review-{agent}.md — review correctness & design per commit |
main-merge |
Commit is on main (the squash-merge) |
main-merge-review-{agent}.md — integration review of the whole feature |
Each prompt explicitly differentiates lenses (Deepseek = correctness; Kimi = design) so two reviews on the same diff produce non-redundant signal.
An agent participates in a given commit type iff the matching prompt file
exists in .claude/scripts/prompts/. To remove an agent from a commit type,
delete the corresponding <type>-review-<agent>.md file. The dispatcher,
the wait script, and the branch-completion check all honour the same rule.
Current roster:
| Agent | plan | code | main-merge |
|---|---|---|---|
| deepseek | ✓ | ✓ | ✓ |
| kimi | — | ✓ | ✓ |
Plan commits go through a single reviewer (deepseek) — see
.claude/scripts/prompts/plan-review-deepseek.md, which carries the full
four-tier status semantics (Approve / Approve with changes / Needs rework
/ Reject) collapsed to the binary verdict: line.
wait-for-reviews.sh counts cumulative request_changes verdicts across
every commit on the current branch. When the count crosses ITERATION_WARN
(default 5), it prints a stderr warning. Iteration is not blocked —
the warning surfaces to the engineer (and to you), prompting a step back
and likely a hand-off to the architect for a plan revision rather than
yet another patch commit.
bash .claude/scripts/branch-all-approved.sh # exits 0 iff
# every commit on the branch is fully approved by every reviewer
# AND every `**Subject:**` line in the active plan has a matching
# commit subject on the branch
git switch main
git merge --squash <branch>
ALLOW_MAIN_COMMIT=1 git commit -m '<type>(<scope>): <user-facing summary>'
# triggers the main-merge integration review| Variable | Purpose |
|---|---|
ALLOW_MAIN_COMMIT=1 |
Allow a commit directly on main (intended use: the squash-merge step only). |
SKIP_PRECOMMIT_CHECKS=1 |
Bypass pre-commit grep checks. Rare — defeats the safety net. |
REVIEW_SKIP=1 |
Skip Pi review for one commit. Intended for the bootstrap commit; nothing else. |
ITERATION_WARN=N |
Override the request_changes warning threshold (default 5). |
PI_THINKING=<level> |
Override per-agent thinking level for one invocation (off/minimal/low/medium/high/xhigh). |
PI_TIMEOUT=<seconds> |
Override the per-Pi-call timeout (default 300s). |
.reviews/.hook.log (gitignored) captures one line per dispatcher and
reviewer event: when the dispatcher fires, what reviewers were spawned,
and the final verdict each reviewer wrote (or the error it hit). If a
review file fails to appear for a commit you just made, this is where to
look first — the wait script would otherwise just time out without
telling you why.
reference_project/
├── README.md this file
├── bootstrap-new-project.py interactive scaffolder
├── CLAUDE.md project-instructions loaded into every Claude session
├── .gitignore template defaults
├── .plans/ architect plans (tracked; .ACTIVE per branch)
├── .reviews/ reviewer output (gitignored, including .hook.log)
├── references/ reading material (gitignored)
└── .claude/
├── settings.json permissions (allow/deny) for Claude Code
├── agents/
│ ├── architect.md architect subagent definition
│ └── engineer.md engineer subagent definition
├── hooks/ git hooks (activated via core.hooksPath)
│ ├── pre-commit branch check + grep-based content checks
│ └── post-commit classify commit + spawn reviewers
└── scripts/
├── install-hooks.sh one-time setup (run on derived projects)
├── hooklib.sh shared log helper
├── reviewers.txt roster: name:pi_model:thinking
├── review-dispatch.sh spawns one detached reviewer per agent
├── run-reviewer.sh single reviewer invocation (Pi → review file)
├── wait-for-reviews.sh blocks until HEAD is fully approved
├── open-reviews.sh lists request_changes/error files for HEAD
├── branch-all-approved.sh branch-level gate before squash-merge
├── self-review-checklist.md walk-through for engineers pre-commit
└── prompts/
├── code-review-{deepseek,kimi}.md
├── plan-review-{deepseek,kimi}.md
└── main-merge-review-{deepseek,kimi}.md
| Want to change… | Edit |
|---|---|
| Roster, models, or thinking levels | .claude/scripts/reviewers.txt |
| What each reviewer is told to look for | .claude/scripts/prompts/*.md |
| Architect / engineer behaviour or model | .claude/agents/{architect,engineer}.md |
| Pre-commit checks (add lint, typecheck) | .claude/hooks/pre-commit |
| Override default thinking / timeout | env vars (see above) or per-row in reviewers.txt |
| Workflow conventions | CLAUDE.md |
- Per-agent immutable files, not a shared ledger. Two reviewers run concurrently and each writes its own file — no concurrent-write hazard, no locking, an inherent audit trail.
- Verdict, not state machine. Each review file has
verdict: approve | request_changes | errorand is never mutated. Iteration happens through new commits (which produce new review files), not by reviewers updating prior verdicts. - Hard prohibitions enforced at the permissions layer.
--no-verify,--amend,--forcepushes, deletion of.reviews/, and edits to hook files are denied in.claude/settings.json— Claude Code refuses these as tool calls, not just by convention. - Plan reviewed before code. A bad plan is far cheaper to fix at plan-review time than after five commits embody it.
- Lens differentiation per reviewer. Same diff, different prompts: Deepseek looks at correctness/security/regressions; Kimi looks at scope/naming/maintainability/commit-message quality. Two reviews on identical input produce non-redundant signal.
- Integration review for the squash-merge. Per-commit review can't see whole-feature concerns (intermediate-state leakage, public-surface coherence, plan fidelity, squash-message-as-changelog). A separate prompt class handles that exactly once, when the feature lands on
main.