Skip to content

Claude Code & AI agents

WaveHouse ships minimal team-wide Claude Code™ configuration. The repo commits only what’s distinctly useful to every contributor; cosmetic and personal choices stay at the user level.

If you’re new to Claude Code itself, the official docs cover the basics. This page is WaveHouse-specific.

  1. Install Claude Code: follow the official install guide. On macOS: brew install --cask claude-code.

  2. Authenticate: log in to your Max subscription — Claude Code prompts you on first run.

  3. Bootstrap the repo: make tools. This installs Go tools, pnpm deps, and also configures git hooks (git config core.hooksPath .githooks). Without this step, the team’s pre-commit / pre-push gates won’t fire.

  4. Optional — worktrunk: install worktrunk for parallel-agent worktree management. The team’s project hooks live in .config/wt.toml.

LayerLives inApplies toPurpose
Git hooks.githooks/ (installed by make tools)Humans + Claude™ uniformlyHard enforcement: make verify on commit; before push, make ci for a code change or make verify for a docs-only one (classifier-gated)
Claude Code agent gate.claude/hooks/agent-bash-gate.sh (PreToolUse Bash) + .claude/settings.json deny rulesAgents onlyCatches accidental violations of Agent PR Discipline: drafts only, no human reviewer adds, a marker (from a review or a logged skip) from every reviewer in scripts/pre-push-reviewers.sh required for the commit each refspec publishes when main doesn’t have it yet, judged in the worktree the push runs in; PR title linted via scripts/lint-pr-title.sh; the gh api REST and GraphQL forms of these, and of a merge, checked too. Plain text checks: only plain forms are caught
Claude Code ergonomic hooks.claude/hooks/gofumpt-on-save.sh + .claude/hooks/markdown-on-save.sh (PostToolUse Edit/Write/MultiEdit), .claude/hooks/review-marker.sh (SubagentStop)Claude onlygofumpt / markdown-on-save: auto-format on file edits (humans get this from their IDE). review-marker: on a reviewer’s VERDICT: ship_it, writes that reviewer’s tmp/<name>-passed-<sha> marker for the commit its REVIEWED: line names
Claude Code skills / agents / commands.claude/skills/, .claude/agents/, .claude/commands/Claude only (when relevant)Workflow guidance and on-demand helpers — not gates

Git hooks are the source of truth for “must pass before merge.” .claude/ layers agent-specific gates and ergonomic hooks on top; it doesn’t substitute for the universal gates.

Two scripts, both committed to the repo:

HookBehavior
pre-commitRuns make verify (the static checks listed in Development → Makefile Targets, ~30s) — blocks on failure. Skipped if make ci or make verify already ran for the current tree state (cached via scripts/ci-marker.sh). A worktree with no Makefile has nothing to verify and commits; a missing or failing scripts/ci-marker.sh is reported and make verify runs.
pre-pushScales the bar to the change set (same classifier CI uses, scripts/classify-paths.sh): a code change requires the make ci marker (tmp/ci-passed-tree-<TREE-sha>, from make ci or make ci-remote); a docs/prose-only push requires only the make verify marker (tmp/verify-passed-tree-<TREE-sha>) — CI classifies a PR by its full file list, so it skips the Go/SDK suites only when the whole PR is docs/prose. A tag of a commit origin/main already contains (a release tag) needs no marker. Blocks if the required marker is absent. Fail-closed: an unclassifiable push falls back to requiring make ci. A pushed commit with no Makefile goes through, having no code to validate; one with a Makefile, pushed from a worktree without scripts/ci-marker.sh, is blocked.

--no-verify is for intentional WIP / draft pushes. Agents should not use it — policy in AGENTS.md §“Agent PR Discipline”, not regex-enforced.

Both markers are tree-keyed so commit-then-push works without a re-run when the tree is unchanged. make ci / make verify skip the marker write when $CI is set (CI runners don’t push). Shared logic lives in scripts/ci-marker.sh.

make tools sets core.hooksPath to the relative .githooks, which git resolves in each worktree: each worktree runs its own branch’s hooks, and one whose branch has no .githooks/ runs none. An absolute core.hooksPath runs one checkout’s hooks in every worktree, which is why the hooks handle a worktree with no code. make test-githooks (part of make verify) commits and pushes through the real hooks from scratch worktrees with and without code.

PathPurpose
.claude/settings.jsonTeam-wide: deny permissions (force-push / git reset —hard / filter-branch / update-ref -d, gh pr merge / ready / approve / request-changes, gh repo/release delete, gh secret delete, gh workflow disable, rm -rf / sudo rm), all four hooks wired, each through a guard that runs its script when it’s there and fails closed when it isn’t (see When a hook script is missing)
.claude/hooks/gofumpt-on-save.shPostToolUse Edit/Write/MultiEdit: auto-formats .go files
.claude/hooks/markdown-on-save.shPostToolUse Edit/Write/MultiEdit: repairs MDX fence structure in .mdx, applies markdownlint --fix (WH001 unwrapping) in .md only, and runs misspell over docs prose in both
.claude/hooks/agent-bash-gate.shPreToolUse Bash: catches accidental Agent PR Discipline violations with plain text checks over the whole command line. Blocks gh pr create / new without --draft, a malformed literal PR title (scripts/lint-pr-title.sh), reviewer or assignee adds, approvals and change requests, and the gh api REST and GraphQL forms of those, of ready-for-review and of a merge. A git push must be a plain git [-C <worktree>] push … on its own (optionally 2>&1 and one | filter), and the commit each refspec publishes needs a marker (from a review or a logged skip) from every reviewer in scripts/pre-push-reviewers.sh when origin/main (local main if there’s no origin/main) doesn’t have it yet; a marker counts from any worktree of the repository. Because it matches text rather than parsing the shell, a command that only mentions one of these (a --body naming a guarded mutation, a commit message containing git push) is blocked too: put long text in a file (--body-file, git commit -F <file>, gh api -F body=@file). Only plain forms are caught; any other form is still against the rules
.claude/hooks/review-marker.shSubagentStop: on a reviewer’s VERDICT: ship_it, writes its tmp/<name>-passed-<sha> marker for the commit named on the report’s REVIEWED: line, into the worktree on that commit, and only if that worktree’s HEAD stayed on it for the whole review (its reflog against the subagent transcript’s first timestamp). The reviewer set comes from scripts/pre-push-reviewers.sh; filters by agent_type in-script. Reads the report from .last_assistant_message, or, when that has no VERDICT: line or says ship_it without REVIEWED:, from the hand-back message in the subagent’s transcript; a final iterate or block stands. An abbreviated sha counts if it names exactly one commit. Logs every decision to tmp/review-marker.log
.claude/hooks/review-gate.test.shTest for the two review hooks: feeds them synthetic events in a scratch repository with sibling worktrees (make test-review-gate, part of make verify)
.claude/hooks/hook-commands.test.shTest for the hook commands in .claude/settings.json: runs each one under sh -c as Claude Code does, with its script present, missing and not executable (make test-hook-commands, part of make verify)
.claude/commands/cover.md/cover [suite] — suite dispatch + coverage threshold analysis
.claude/commands/docs-review.md/docs-review [path|all] — launches the docs-reviewer subagent. No-arg = the gating pre-push docs review; a path/all = advisory
.claude/commands/prepush.md/prepush [all] — the pre-push gate: reads scripts/pre-push-reviewers.sh, runs the reviewers the change needs in parallel (fresh context), skips the rest on the record (scripts/skip-pre-push-review.sh), loops to ship_it. all forces the full set
.claude/agents/pre-push-reviewer.mdpre-push-reviewer subagent — canonical pre-push code review (one of the parallel pre-push reviewers in scripts/pre-push-reviewers.sh); also used for auditing others’ PRs locally
.claude/agents/docs-reviewer.mddocs-reviewer subagent — docs-prose + code↔docs-sync review; mandatory pre-push gate (writes tmp/docs-reviewer-passed-<sha> for the commit on its REVIEWED: line on ship_it, in parallel with the other pre-push reviewers); advisory only for ad-hoc path/all. Scope via scripts/docs-prose.sh
.claude/skills/pr-sync-with-main/SKILL.md“Fix this stale PR” workflow — merge origin/main, never rebase or force-push
.claude/skills/pr-review-locally/SKILL.md“Review PR locally” workflow — wt switch pr:<N> + the relevant reviewers (code, docs, …) in parallel, no PR comments
.claude/skills/pm-triage/SKILL.mdPM-review workflow — triage feedback / backlog / code TODOs and reconcile issue & PR status into well-scoped, tracked Task Board (project #7) issues; invoked as /pm-triage
.claude/settings.local.jsonYour personal overrides — gitignored; put model choice, status line, allow lists, etc. here
.config/wt.tomlWorktrunk project hooks (post-start, pre-merge, pre-remove)

Notably absent: no .mcp.json, no committed status line, no permissions.allow list. See GitHub® access and Permission posture below.

CommandWhat it does
/cover [suite]Renders coverage for a suite (unit / integration / e2e / sdk / all / merge) and surfaces drops below threshold
/docs-review [path|all]Runs the docs-reviewer subagent — accuracy vs code, runnable examples, clarity, completeness, + code↔docs sync. No-arg gates the push (writes the docs marker on ship_it); a path/all is advisory. Complements misspell / markdownlint / links-validator
/prepush [all]The mandatory pre-push self-review — judges which reviewers in scripts/pre-push-reviewers.sh the change needs, runs those in parallel (fresh context), skips the rest on the record (logged), and loops until each reaches ship_it. all forces the full set

To add a command: drop a .md file in .claude/commands/. Filename becomes the slash command. Frontmatter: description and argument-hint; body is the prompt with $ARGUMENTS.

SubagentWhen to use
pre-push-reviewerMandatory before pushing to a PR branch (enforced by .claude/hooks/agent-bash-gate.sh), run in parallel with the other reviewers in scripts/pre-push-reviewers.sh — all must reach ship_it. Also used for auditing someone else’s PR after wt switch pr:<N>. Runs the canonical .github/prompts/pr-review.md workflow against the local branch in fresh context. Fetches PR comments + CI status + linked-issue acceptance criteria when on a PR branch. Returns [MUST]/[SHOULD]/[MAY] findings + parseable REVIEWED: <sha> and VERDICT: ship_it|iterate|block lines that drive the tmp/pre-push-reviewer-passed-<sha> marker.
docs-reviewerMandatory before pushing to a PR branch — runs in parallel with the other pre-push reviewers (all enforced by .claude/hooks/agent-bash-gate.sh). Reviews docs prose (accuracy-vs-code, runnable examples, clarity, completeness) and code↔docs sync (code that changed but whose docs didn’t), using .github/prompts/docs-review.md over the scripts/docs-prose.sh denylist set (Starlight site + governance docs incl. the SDK readme). Default (branch) scope emits REVIEWED: <sha> and VERDICT: ship_it|iterate|block → writes tmp/docs-reviewer-passed-<sha>; a path/all is advisory (no marker). Posts no PR comments, never edits docs. Complements misspell / markdownlint / starlight-links-validator, never duplicates them.

Invoke via the Agent tool with subagent_type: pre-push-reviewer, or via /agents.

To add a subagent: drop a .md file in .claude/agents/. Frontmatter: description (used by main Claude to decide when to delegate), tools, model. Body is the system prompt. To make a reviewer a mandatory pre-push gate (the way pre-push-reviewer and docs-reviewer are), also add its name to scripts/pre-push-reviewers.sh — the single source of truth the hooks and /prepush read, so no hook needs editing. The subagent itself must pin the commit with git -C <worktree> rev-parse HEAD before reading anything, where <worktree> is the worktree its prompt names, else its current directory, and end its report with the parseable REVIEWED: <that sha> and VERDICT: ship_it|iterate|block lines; without both, its ship_it writes no marker (tmp/review-marker.log says why). See AGENTS.md §“Adding a pre-push reviewer”.

Skills load automatically into Claude’s context when conversation patterns match their description.

SkillTriggers on
pr-sync-with-mainWhen a PR shows “out-of-date with base branch”, or a user asks to “fix the PR” / “sync with main”. Documents the merge-not-rebase procedure and the WaveHouse-specific reason long-lived branches need it.
pr-review-locallyWhen a user asks to “review PR ”, “audit PR ”, “look at PR ” — pulls the PR down via wt switch pr:<N> (or gh pr checkout), runs the relevant reviewers (code, docs, …) in parallel in fresh context, surfaces their combined findings without commenting on the PR.
pm-triageWhen a user asks to triage dogfooding feedback, re-prioritize the backlog (P0–P3), sweep code TODOs, or check that work is tracked — runs a PM-style review against the Task Board (project #7) and proposes well-scoped issues. Invoked as /pm-triage.

To add a skill: create .claude/skills/<name>/SKILL.md with frontmatter name + description and the workflow body. Description quality matters — that’s what Claude matches against to load the skill.

Agents (Claude Code etc.) have additional gating beyond what humans face — enforced by .claude/hooks/agent-bash-gate.sh (PreToolUse Bash) + deny rules in .claude/settings.json. Humans keep full git/gh affordances; agents have these extra constraints:

  • Drafts only. gh pr create (or its alias gh pr new) must include --draft. Only humans transition draft → ready (gh pr ready is blocked), approve (gh pr review --approve is blocked), or request changes (gh pr review --request-changes is blocked). The gate also lints the PR title on gh pr create / gh pr new / gh pr edit with a literal title via scripts/lint-pr-title.sh — the same rule as the required CI check’s PR title job, so a malformed title is caught before the PR exists (fail-open when the title is missing or computed).
  • The API forms count too. Through gh api, opening a PR (…/pulls) needs -F draft=true; a review with event=APPROVE or event=REQUEST_CHANGES, GraphQL createPullRequest without draft: true, markPullRequestReadyForReview, requestReviews and a merge (…/pulls/<N>/merge, mergePullRequest, enablePullRequestAutoMerge) are blocked. gh pr merge itself is left to the deny rule in .claude/settings.json. The gate searches the command’s text rather than parsing it, so only plain forms are caught and a command that merely mentions one of these names is blocked too (a false positive); put long text in a file (--body-file, git commit -F <file>, gh api -F body=@file). A form the gate doesn’t catch is still against the rules.
  • No human reviewer assignment. gh pr edit --add-reviewer / --add-assignee, --reviewer / --assignee on gh pr create / gh pr new, and any gh api write to …/requested_reviewers are blocked. GitHub assigns reviewers natively — the required_reviewers ruleset rule requests the @Wave-RF/wavehouse-admins team and the team’s code-review assignment picks the member; humans handle the rest.
  • Bot re-triggers via comments. Agents CAN mention bots in PR comments to re-trigger reviews — @coderabbitai review, etc. This goes through gh pr comment (allowed), not the reviewer API.
  • Pre-push review required on PR branches. Before git push to a branch with commits ahead of main, the agent runs the reviewers in scripts/pre-push-reviewers.sh (today: pre-push-reviewer for code, docs-reviewer for docs prose + code↔docs sync) that the change needs — in fresh context, in parallel — and skips any with nothing to do via scripts/skip-pre-push-review.sh <name> "<reason>" (a logged skip that satisfies the marker, so a docs typo doesn’t pay for a full code review). /prepush does all of this. ship_it requires zero findings at any severity — any [MUST] / [SHOULD] / [MAY] forces iterate; fix and re-invoke (always fresh context) until clean. The push gate requires a marker — from a ship_it or a logged skip — from every listed reviewer for the commit each refspec publishes (its tip, or HEAD with no refspec), judged in the worktree the push runs in, and echoes the skips at push time. A ship_it marks only the commit the reviewer read, so don’t commit in a worktree while its reviewers run.
  • Don’t bypass. --no-verify and hand-writing markers are policy violations, not regex-blocked. An agent that wants to bypass can edit the gate itself — trust the policy in AGENTS.md §“Agent PR Discipline”. Markers come from make ci, the review-marker.sh hook, and scripts/skip-pre-push-review.sh (logged skips); nothing else.
  • PR reviews on others’ PRs stay local. Use the pr-review-locally skill for local-only audits — the reviewers’ findings go to you, not the PR.

Full ruleset and rationale: AGENTS.md §“Agent PR Discipline”.

Each hook in .claude/settings.json names its script under $CLAUDE_PROJECT_DIR, which the Claude Code docs define as “the project root where the session started”, and which stays put when the session enters a worktree. If that checkout is removed while the session runs, or a script goes missing or loses its executable bit, a bare path would make the shell exit 127, and Claude Code treats any exit code but 2 as a non-blocking error: the push gate would stop gating without a word. So each command is a short guard that execs the script when it’s there and executable, unchanged, and otherwise acts by event:

EventScriptWith the script missing
PreToolUse (Bash)agent-bash-gate.shBlocks (exit 2) a command that contains the words git and push; gh and pr with one of create, new, edit, ready, review or merge; or gh and api with pulls or graphql, which covers every call to the pull-request or GraphQL API, reads included. It also blocks every command when there is no jq to read it with. Other commands go through, so the session can still work
SubagentStopreview-marker.shReports it on stderr with exit 1. Exit 2 would keep the subagent running. A reviewer’s ship_it writes no marker, so the push gate blocks for lack of one
PostToolUsegofumpt-on-save.sh, markdown-on-save.shReports it with exit 2, which shows Claude the message; the edit has already happened

Every message names the missing script and says to restart Claude Code from a live checkout. The PreToolUse match is crude on purpose: like the gate, it guards against accidents, not deliberate workarounds, and it errs toward blocking.

We use worktrunk as the recommended worktree manager. It creates real git worktree add directories, so the committed .claude/ config Just Works in every worktree.

Project hooks in .config/wt.toml:

HookCommandWhy
post-startwt step copy-ignored, then make tools (an ordered [[post-start]] pipeline)Seeds the gitignored caches listed in .worktreeinclude (node_modules/, .bin/, build outputs) from the primary checkout, then bootstraps tools, modules, pnpm deps, and git hooks. The install runs after the copy, so it decides the final node_modules layout, not the copy
pre-mergemake verifyFast pre-merge gate (same as pre-commit)
pre-removegit status --shortSurfaces uncommitted work before tearing down

Common workflow:

Terminal window
wt switch -c feat/new-thing # fresh worktree off origin/main
wt switch -x claude -c feat/x # spawn an agent in a new worktree
wt list # status of all worktrees
wt merge # squash + rebase + merge + clean up
wt remove feat/new-thing # tear down (warns on uncommitted work)

User-specific worktrunk config goes in ~/.config/worktrunk/config.toml; the committed .config/wt.toml has team-wide hooks only.

We use gh CLI as the canonical GitHub access path, not a GitHub MCP server. Reasons:

  • gh is already a hard dev requirement
  • Works identically in Claude Code, terminal, and shell scripts
  • No extra auth / approval / npx cold-start

The GitHub MCP server is useful for cross-repo code search and bulk graph queries, but neither is a daily WaveHouse pattern. If you want it, add at user level:

~/.claude.json
// ~/.claude.json — your user-level config
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" }
}
}
}

Then export GITHUB_TOKEN=$(gh auth token) in your shell rc.

Other useful MCP servers (user-level, optional)

Section titled “Other useful MCP servers (user-level, optional)”

None of these are committed at project level — pick what you actually use.

  • Grafana® MCP — for querying Prometheus® / Loki™ / Tempo™ / Pyroscope™ from Claude when debugging observability work. Useful if you touch internal/observability/.
  • ClickHouse® MCP (community) — direct schema introspection + query against your local make dev ClickHouse. Useful for internal/discovery/ and ingest work, but make deps-shell (clickhouse-client REPL) is often enough.

make obs-grafana brings up a local Grafana LGTM stack (see Deployment → Local Observability Stack), so Grafana MCP pointed at it is already the natural choice for trace / log inspection — no heavyweight cluster to stand up first.

.claude/settings.json is structured around the reality that most contributors run Claude Code in bypassPermissions (yolo). In that mode:

  • ✅ permissions.deny still fires — the only programmatic guardrail
  • ❌ permissions.allow / permissions.ask are moot (everything’s already allowed)
  • ✅ Hooks (Claude Code AND git) still fire — the behavioral layer

So the file is deny-only by design. Personal allow / ask lists go in .claude/settings.local.json (gitignored).

defaultMode is intentionally not set in committed config. Each user picks their preferred mode.

The deny list blocks:

BlockedWhy
git push --force, -f, --force-with-leaseForce-pushing is destructive; also loses inline review-comment anchors
git reset --hard origin:*Throws away local work
git filter-branch, git update-ref -dHistory-rewriting / ref destruction
gh pr merge, gh repo delete, gh release deleteIrreversible / shared-state
gh pr ready, gh pr review --approve / -a / --request-changes / -rAgent PR Discipline — draft→ready and review verdicts are human actions
gh workflow disable, gh secret deleteOperational footguns
rm -rf /, rm -rf ~, rm -rf $HOME, sudo rmFilesystem destruction

Not committed at project level. Personal preference — put in .claude/settings.local.json:

.claude/settings.local.json
{
"statusLine": { "type": "command", "command": "~/.config/claude/statusline.sh" },
"outputStyle": "default",
"model": "claude-opus-4-7"
}
  1. Write code (gofumpt-on-save formats Go files as you go; markdown-on-save does the same for .md/.mdx).
  2. git commit → pre-commit hook runs make verify (or skips if make ci already validated this tree). Fix anything that fails.
  3. git push → the pre-push hook blocks until the tree is validated for what changed — make ci for a code change, make verify for a docs/prose-only one (run it, fix, retry). For agents, the agent-bash-gate hook also requires a marker for the pushed commit from every reviewer in scripts/pre-push-reviewers.sh whenever main doesn’t have that commit yet — including the first push, before the PR exists. Run /prepush → it judges which reviewers the change needs, runs those in parallel (on Ship it each marker auto-writes), and skips the rest on the record (skip-pre-push-review.sh writes their markers + logs why) → push succeeds. On Iterate/Block, fix, re-invoke (fresh context each time), repeat.
  4. Open the PR with gh pr create --draft (agents required to use --draft; humans flip to ready when ready).
  5. CI workflows fire on the new HEAD. Address review comments per AGENTS.md §Review Response.

Helpers along the way:

  • Stale PR (“out-of-date with base branch”) → ask Claude to “fix the PR” (loads pr-sync-with-main skill — merges main, doesn’t rebase or force-push).
  • Reviewing someone else’s PR → “review PR 120 locally” / “audit PR 120” (loads pr-review-locally skill — wt switch pr:120 + the relevant reviewers in parallel, no PR comments).
  • Re-trigger a bot reviewer → ask Claude to “ping coderabbit again” — Claude posts the appropriate @<bot> comment on the PR.
  • Coverage check on a specific suite → /cover unit (or integration, e2e, sdk, all).

Memory: Claude Code maintains per-project memory in ~/.claude/projects/<slug>/memory/. AGENTS.md is the SHARED source of truth; memory is for personal observations / preferences that don’t belong in committed config.

The .claude/ layout is intentionally small. Add things when they earn their keep:

  • New slash commands as recurring workflows emerge
  • New subagents for specialized one-shot tasks
  • New skills for context-loaded workflow patterns
  • New Claude Code hooks for edit-time UX gaps (NOT for enforcement — gates go in .githooks/ so they apply to humans too)
  • Adjust .githooks/pre-commit / pre-push if the gates feel wrong

If you build something useful, commit it and update this doc.

  • AGENTS.md — project conventions, architecture, code style, doc-sync rules, SDK-sync rules, branch maintenance. Source of truth for all AI agent work.
  • Claude Code docs — official Claude Code reference.
  • worktrunk.dev — worktree manager.
  • .github/prompts/pr-review.md — the canonical review prompt that pre-push-reviewer runs locally.

Was this page helpful?

Thanks for the feedback!