原始内容
name: bruhs description: End-to-end development lifecycle for AI coding agents — scaffold projects, plan and build features, create Linear tickets, open and review PRs, clean up after merge, audit codebases, and improve UI polish/motion quality. Works with any agent that reads CLAUDE.md or AGENTS.md. Use when starting a project, implementing a feature, shipping code, addressing PR reviews, merging, auditing code quality, designing or reviewing UI polish, or reviewing animations.
bruhs
Index
|commands|spawn,claim,cook,verify,yeet,walk,land,peep,dip,slop,doodle,deepen,drill,recap
|practices|type-driven-design,architecture-deepening,testing-infrastructure,source-ground-truth,_common,pr-review,typescript-react,typescript-hono,python,python-fastapi,effect-ts,effect-,rust,rust-,ui-design,design-engineering
|config|CLAUDE.md + AGENTS.md (bruhs:state and bruhs:rules blocks)
Commands Quick Reference
|spawn|Create project or add to monorepo|commands/spawn.md |claim|Initialize config for existing project|commands/claim.md |cook|Plan + Build feature end-to-end|commands/cook.md |verify|Prove a claim with falsifiable evidence (VERIFIED/NOT VERIFIED/INCONCLUSIVE)|commands/verify.md |yeet|Ship: Linear ticket → Branch → Commit → PR|commands/yeet.md |walk|Generate reviewer walkthrough (markdown or interactive HTML canvas)|commands/walk.md |land|Watch PR checks and iterate on CI failures until green|commands/land.md |peep|Address PR review comments and merge|commands/peep.md |dip|Clean up after merge, switch to base branch|commands/dip.md |slop|Deep codebase analysis, AI slop cleanup (--quick for branch-diff only)|commands/slop.md |doodle|Visualize architecture as tldraw diagrams (PRs, modules, deps, compare, map, freeform)|commands/doodle.md |deepen|Find shallow modules, propose architectural deepenings (explore → candidates → grill)|commands/deepen.md |drill|Find missing/weak test-infra layers, propose adoptions (explore → candidates → grill)|commands/drill.md |recap|Status update from git log over a time window (bugfix/tech-debt/net-new)|commands/recap.md
Lifecycle Map
Commands compose into an end-to-end loop. Typical flow:
spawn / claim → project setup
↓
cook <feature> → plan + build
↓
verify <claim> → prove the behavior changed (recommended for fixes)
↓
slop --quick → clean up branch-diff slop before shipping
↓
yeet → Linear ticket + branch + commit + PR
↓
walk --post → reviewer walkthrough comment (large PRs)
↓
land → watch CI to green
↓
peep → address review comments (compose with land via --land)
↓
dip → post-merge cleanup
Out-of-band, any time:
slop— deep codebase auditdoodle/deepen/drill— structural / test-infra investigationrecap— status update for standup or retro
Invocation
/bruhs→ Interactive menu (AskUserQuestion)/bruhs:<command>→ Direct to command/bruhs:cook <feature>or/bruhs:cook TICKET-123→ With argument/bruhs:verify [claim | PR#] [--keep-artifacts]→ Falsifiable verification/bruhs:walk [PR#] [--canvas|--post|--commit-body]→ Reviewer walkthrough/bruhs:land [PR#] [--no-fix|--max-iterations N]→ Watch CI to green/bruhs:peep [PR# | TICKET] [--land|--resolve-conflicts]→ Address review comments/bruhs:slop [path] [--quick|--fix|--report] [--severity ...]→ Codebase analysis/bruhs:doodle <mode> [args] [--out|--gist|--commit|--pr-comment|--format|--depth]→ Render diagram (modes: pr, module, deps, dependents, compare, map, freeform)/bruhs:deepen [path | module-name] [--no-explore]→ Find shallow modules, propose deepenings/bruhs:drill [path | layer-name] [--no-explore]→ Find missing/weak test-infra layers, propose adoptions/bruhs:recap [window] [--all-authors|--linear|--branch <name>]→ Status update from git log
Type-Driven Design
A function's type signature should tell you everything about what it does.
Priority Hierarchy (fix in this order)
- Missing/wrong type signatures - Types ARE documentation
- Hidden side effects - Signature lies about behavior
- any types - Type system disabled
- ! or as on external data - Compiler trust violated
- Fast-path violations -
awaitin loops, N+1 queries, sync-in-async, per-request client construction (see Performance below) - Other implementation issues - Secondary to type correctness
Checklist
Signatures:
- Explicit return types on public functions
- No any - use unknown + validation
- No ! - handle null explicitly
- No as for external data - validate instead
- Errors in return type, not thrown silently
- readonly parameters signal no mutation
- Discriminated unions for state (not multiple booleans)
Errors:
- Errors visible in return type (Result<T,E> or union)
- Typed errors, not strings
- Handle at call site, not deferred
- No empty catch blocks
Immutability:
- Prefer const over let
- Don't mutate parameters
- Return new objects instead of mutating
Patterns
// ❌ Signature hides truth
function getUser(id: string): User // might throw, might be null
// ✅ Signature tells full story
function getUser(id: string): Promise<Result<User, NotFoundError | NetworkError>>
// ❌ Multiple booleans = impossible states
type State = { isLoading: boolean; isError: boolean; data: User | null }
// ✅ Discriminated union = only valid states
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error }
For full patterns → practices/type-driven-design.md
Architectural Deepening
A deep module hides a lot of behaviour behind a small interface. A shallow module's interface is nearly as complex as its implementation.
Use the /bruhs:deepen command to find shallow modules and propose deepenings. Distinct from /bruhs:slop (line-level audit) — this is structural.
Vocabulary (use these terms exactly)
| Term | Meaning |
|---|---|
| Module | Anything with an interface and an implementation (function, class, package, slice) |
| Interface | Everything a caller must know — types, invariants, ordering, errors, configuration |
| Depth | Behaviour-per-unit-of-interface. Deep = lots of behaviour behind a small interface |
| Seam | A place where you can alter behaviour without editing in that place (Feathers) |
| Adapter | A concrete thing that satisfies an interface at a seam |
| Leverage | Caller-side benefit of depth — capability per unit of interface learned |
| Locality | Maintainer-side benefit — change/bugs/tests concentrate in one place |
Core principles
- Depth is a property of the interface, not the implementation. Internal seams are fine; don't expose them.
- The deletion test. Imagine deleting the module. If complexity vanishes → pass-through, merge it. If it scatters across N callers → the module earns its keep.
- The interface is the test surface. If you want to test past it, the module is the wrong shape.
- One adapter means a hypothetical seam. Two adapters means a real one. Don't add a port for a single implementation.
When to invoke /bruhs:deepen
- A codebase feels like it bounces between many small files for one operation
- Tests feel like they're testing the wrong thing (mocks everywhere, brittle to refactors)
- Callers must learn implementation details (transport, encoding, storage) to use a "domain" module
- Same invariant repeats across N call sites
- During
/bruhs:slopyou keep flagging "architecture" — promote to/bruhs:deepenfor structural treatment
For full glossary, dependency categories, seam discipline, design-it-twice mechanics → practices/architecture-deepening.md
Testing Infrastructure
A test suite is a layered safety net. Each layer catches a different bug class. A missing layer isn't covered by the others — it's just where bugs ship from.
Use the /bruhs:drill command to find missing or weak layers and propose adoption plans. Companion to /bruhs:deepen: deepen modules, drill tests.
The 8 layers
| # | Layer | Catches |
|---|---|---|
| 1 | Acceptance specs | Intent drift — no executable definition of "done" |
| 2 | Unit tests | Regressions in module behaviour at the interface |
| 3 | Coverage gate | Untested code landing (measured on changed lines per PR, not total) |
| 4 | Mutation runner | Tests that exist but assert nothing meaningful — the kill ratio is the only number that proves coverage is doing work |
| 5 | Complexity composite | High-complexity / low-coverage hotspots (the worst-of-both — Savoia's "CRAP") |
| 6 | Architecture checker | Layer / dependency rule violations — encodes ADRs so they're enforced, not just documented |
| 7 | Test-code linter | Rot in test files themselves; AI-generated test code becoming a second untyped codebase |
| 8 | CI orchestrator | Discipline collapse — runs every layer as hard gates. The 8th is load-bearing |
Core principles
- Hard gate, advisory, or ratchet — pick one, defend it. Indefinite advisory is decoration. Ratchet ("no worse than baseline") is honest about legacy debt and locks in improvement.
- The orchestrator is load-bearing. Layers 1–7 without a real CI gate are theatre.
continue-on-error: trueon a gated layer is advisory regardless of what the config claims. - Replace, don't layer. Old advisory checks become waste once a hard gate exists. Old shallow-module tests become waste once the deeper module's interface is tested. Delete; don't preserve out of guilt.
- The "what would slip through" test. Don't propose adopting a layer unless you can name a concrete bug class it catches in this codebase.
When to invoke /bruhs:drill
- Tests pass but bugs still ship — the suite isn't catching what it claims to catch
- Coverage is high but feels meaningless (no mutation kill ratio measured)
- ADRs exist but get violated; "clean architecture" decisions erode unenforced
- AI-generated test files are accumulating without review
- CI is green; the build still feels unreliable — likely an advisory orchestrator
- During
/bruhs:slopyou keep flagging "no test for X" — promote to/bruhs:drillfor safety-net treatment
For full layer specs, diagnostics, gating posture, rejected framings → practices/testing-infrastructure.md
Common Rules
Naming
| Type | Convention | Example |
|---|---|---|
| Components | PascalCase | UserCard.tsx |
| Hooks | camelCase + use |
useAuth.ts |
| Utilities | camelCase | formatDate.ts |
| Constants | SCREAMING_SNAKE | MAX_RETRIES |
| Booleans | is/has/can/should | isLoading, hasPermission |
| Functions | verb + noun | getUser, createOrder |
| Handlers | handle/on prefix | handleSubmit, onUserClick |
Code Organization
- Functions do ONE thing
- 20-30 lines max per function
- 200-300 lines max per file
- If you need comments to separate sections → extract functions
Error Handling
- Specific messages:
User "${id}" not foundnotSomething went wrong - Validate at boundaries, trust internal code
- Never swallow errors (empty catch blocks)
Git
<type>: <description>
Fixes TICKET-123
Types: feat|fix|refactor|chore|docs|test
Branch: <type>/<ticket-id>-<short-description>
Comments
- ✅ WHY (business logic, workarounds, non-obvious decisions)
- ❌ WHAT (code already says what it does)
- ❌ Commented-out code
- ❌ TODO without ticket reference
External Searches
Always include current year in WebSearch queries for fresh results.
For full guidelines → practices/_common.md
Performance (Fast-Path-By-Default)
Pick the fast path by default. You don't need a benchmark to avoid an anti-pattern.
Philosophy
- Correctness first, but equivalent-correctness patterns are not equivalent. When two patterns solve the same problem, pick the faster one — even without a measurement.
- Obvious perf wins don't require benchmarks. N+1 queries,
awaitin a loop, sync I/O on the event loop, per-request client construction — these are anti-patterns, not "premature optimization." - Measure when the tradeoff is real (readability cost, complexity cost, risk of regression). Don't measure to justify avoiding an anti-pattern.
- Measure at boundaries, not inside — p50/p95/p99 at ingress/egress; internal timers lie about contention.
Universal Defaults
- Batch at boundaries. One round-trip of N beats N round-trips (DB, HTTP, IPC, GPU, syscalls).
- Stream, don't buffer. Start bytes moving before you have them all — SSR, JSON, file I/O, LLM tokens.
- Don't block the hot loop. Event loop, render thread, request handler — push CPU off, I/O async, never sync-in-async.
- Colocate data with consumer. Query next to its route, fetch next to its RSC, cache next to its reader.
- Built-ins beat rewrites.
Array.prototype,itertools,std::collections, platformfetch. - Bound concurrency on user input.
p-limit,asyncio.Semaphore,buffer_unordered(n)— unboundedPromise.allover user data is a DoS. - Singleton clients. Never
new Client()per request — it defeats connection pooling.
LLM-Common Traps (flag these in review)
awaitin aforloop over independent items →Promise.all/gather/join!.map().filter().reduce()chains over large arrays → one pass or lazy iterator- N+1 ORM access in "clean" code → eager/join loading
useMemo/useCallbacksprinkled without a measured re-render problemJSON.parse(JSON.stringify(x))for deep clone →structuredCloneor targeted copy- Unbounded
Promise.all/ recursion over user input → clamp concurrency - Sync hashing/crypto in request path → worker / threadpool
- Per-request client construction (
new PrismaClient,httpx.AsyncClient(),reqwest::Client::new()) → singleton - Full-body logging in hot paths → structured log fields
- Parsing config / reading env per request → load once at boot
Per-stack performance patterns → practices/<stack>.md (Performance section in each)
Source as Ground Truth
Code is the best ground truth over docs.
Whenever the work touches a third-party package, repo, or dependency, resolve its real behavior from the installed source, not from training memory or docs that may have drifted. Docs orient; the source decides. When they disagree, the version in the lockfile wins.
Use opensrc to pull a package's source on demand:
npm install -g opensrc
rg "parse" $(opensrc path zod) # search the real implementation
cat $(opensrc path zod)/src/types.ts # read a specific file
find $(opensrc path pypi:requests) -name "*.py" # non-npm via pypi: prefix
Reach for it before citing an API you're unsure of, when docs are thin or contradict behavior, when an error points into a dependency, or when a review finding hinges on what a library actually does. Reading source is not running it — execute untrusted / AI-generated code in a sandbox, never your host (see Config → stack sandboxing).
For full guidance (version-matching, context7-vs-source, fallbacks) → practices/source-ground-truth.md
Command Details
Each command's full workflow lives in its own file. Read the specific file when executing that command:
| Command | File |
|---|---|
| cook | commands/cook.md |
| yeet | commands/yeet.md |
| peep | commands/peep.md |
| dip | commands/dip.md |
| spawn | commands/spawn.md |
| claim | commands/claim.md |
| slop | commands/slop.md |
| doodle | commands/doodle.md |
| deepen | commands/deepen.md |
| drill | commands/drill.md |
Interactive Menu
When /bruhs is invoked without arguments, present the command list via AskUserQuestion and route to the selected command's file. Options: spawn, claim, cook, yeet, peep, doodle, dip, deepen, drill. slop is intentionally excluded from the menu — invoke directly with /bruhs:slop.
Config Reference
Project state lives in two marker-bounded blocks inside CLAUDE.md and AGENTS.md (mirrored). The model reads them every session — no separate config file to keep in sync. Hand-written rules live outside the blocks and are never touched.
Block 1 — bruhs:state (auto-maintained JSON)
<!-- bruhs:state:begin v1 -->
<!-- AUTO-MAINTAINED BY /bruhs. Edits inside this block will be overwritten. -->
### Project State (managed by /bruhs)
```json
{
"integrations": {
"linear": {
"mcpServer": "linear-<workspace>",
"team": "<team-id>",
"teamName": "Team Name",
"project": "<project-id>",
"projectName": "Project Name",
"labels": { "feat": "Feature", "fix": "Bug", "chore": "Chore", "refactor": "Improvement" }
}
},
"tooling": {
"mcps": ["linear", "notion", "context7"],
"skills": ["superpowers", "shadcn", "vercel-react-best-practices"]
},
"stack": {
"structure": "single|monorepo",
"framework": "nextjs|tanstack-start|astro|hono|...",
"styling": ["tailwind", "shadcn"],
"database": ["drizzle-postgres", "convex", "..."],
"auth": "better-auth|clerk|...",
"gpu": ["modal", "runpod", "lambda"]
}
}
### Block 2 — `bruhs:rules` (stack-derived behavioral rules)
```markdown
<!-- bruhs:rules:begin v1 -->
<!-- AUTO-MAINTAINED BY /bruhs. Stack-specific rules derived from detected stack. -->
### Stack-Specific Rules (managed by /bruhs)
#### Convex
- Convex actions that import the `ai` SDK MUST live in a file whose first line is `'use node'`.
- ...
<!-- bruhs:rules:end -->
Rules are derived from the detected stack via scripts/derive_stack_rules.py — short, high-signal, and only stack-specific. Universal behavioral rules (the kind that map to your failure modes) should be hand-written by you, outside the bruhs blocks.
Scripts
| Script | Purpose |
|---|---|
scripts/sync_bruhs_block.py |
Atomically write a bruhs:state or bruhs:rules block into CLAUDE.md + AGENTS.md. |
scripts/read_bruhs_block.py |
Read the JSON state (or rules markdown) back out. Falls back to legacy .claude/bruhs.json for --kind state during the transition. |
scripts/derive_stack_rules.py |
Given state JSON on stdin, emit the markdown rules body to pipe into sync_bruhs_block.py --kind rules. |
Migration from .claude/bruhs.json
Legacy .claude/bruhs.json is still read by read_bruhs_block.py as a fallback. /bruhs:claim detects the legacy file and offers to port it into the marker blocks, then prompts before deleting the old file.
Linear MCP
Tool format: mcp__<mcpServer>__linear_<method>
Common tools:
| Tool | Purpose |
|---|---|
linear_get_teams |
List teams (includes labels) |
linear_get_user |
Current user (for assigneeId) |
linear_create_issue |
Create ticket |
linear_edit_issue |
Update status |
linear_get_issue |
Fetch ticket by ID |
Multi-workspace: Each project's bruhs:state block points to its Linear workspace via mcpServer.
Example:
// Load the tool
ToolSearch(`select:mcp__${config.integrations.linear.mcpServer}__linear_create_issue`)
// Create issue
const issue = call(`mcp__linear-sonner__linear_create_issue`, {
title: "Add leaderboard",
teamId: config.integrations.linear.team,
projectId: config.integrations.linear.project,
assigneeId: user.viewer.id,
labelIds: [labelId]
})
// Use Linear's generated branch name
const branchName = issue.gitBranchName // "sonner-140-add-leaderboard"