Imported from meimingqi222/crush-fork (
AGENTS.md). Install upstream withnpx skills add meimingqi222/crush-fork. Copyright stays with the author.
Crush Development Guide
Project Overview
Crush is a terminal-based AI coding assistant built in Go by Charm. It connects to LLMs and gives them tools to read, write, and execute code. It supports multiple providers (Anthropic, OpenAI, Gemini, Bedrock, Copilot, Hyper, MiniMax, Vercel, and more), integrates with LSPs for code intelligence, and supports extensibility via MCP servers and agent skills.
The module path is github.com/charmbracelet/crush.
Architecture
main.go CLI entry point (cobra via internal/cmd)
fantasy/ Local fork of charm.land/fantasy (go.mod replace);
the LLM framework code is edited HERE, in-repo
internal/
app/app.go Top-level wiring: DB, config, agents, LSP, MCP, events
cmd/ CLI commands (root, run, login, models, stats, sessions)
config/
config.go Config struct, context file paths, agent definitions
load.go crush.json loading and validation
provider.go Provider configuration and model resolution
agent/
agent.go SessionAgent: runs LLM conversations per session
coordinator.go Coordinator: manages named agents ("coder", "task")
prompts.go Loads Go-template system prompts
templates/ System prompt templates (coder.md.tpl, explore.md.tpl, etc.)
tools/ All built-in tools (bash, edit, view, grep, glob, etc.)
mcp/ MCP client integration
session/session.go Session CRUD backed by SQLite
message/ Message model and content types
db/ SQLite via sqlc, with migrations
sql/ Raw SQL queries (consumed by sqlc)
migrations/ Schema migrations
lsp/ LSP client manager, auto-discovery, on-demand startup
memory/ Persistent memory engine (recall, retention)
plugin/ Plugin system (chat transform hooks, etc.)
ui/ Bubble Tea v2 TUI (see internal/ui/AGENTS.md)
permission/ Tool permission checking and allow-lists
skills/ Skill file discovery and loading
shell/ Bash command execution with background job support
event/ Telemetry (PostHog)
pubsub/ Internal pub/sub for cross-component messaging
filetracker/ Tracks files touched per session
history/ Prompt history
Key Dependency Roles
charm.land/fantasy: LLM provider abstraction layer AND the agent step loop (tool execution, message accumulation). Handles protocol differences between Anthropic, OpenAI, Gemini, etc. Replaced to the local./fantasydirectory via go.modreplace— changes to the framework are made in this repo. Seedocs/pitfalls/fantasy-dual-message-state.mdbefore touching message flow.charm.land/bubbletea/v2: TUI framework powering the interactive UI.charm.land/lipgloss/v2: Terminal styling.charm.land/glamour/v2: Markdown rendering in the terminal.charm.land/catwalk: Provider/model catalog (embedded data + fetched at runtime); consumed byinternal/config/catwalk.go.sqlc: Generates Go code from SQL queries ininternal/db/sql/.
Key Patterns
- Config is injected, not global:
config.Init(workingDir, dataDir, debug)(called ininternal/cmd/root.go) returns a*config.ConfigStorethat is passed explicitly to components. - Tools are self-documenting: each tool has a
.goimplementation and a.mddescription file ininternal/agent/tools/. - System prompts are Go templates:
internal/agent/templates/*.md.tplwith runtime data injected. - Context files: Crush reads AGENTS.md, CRUSH.md, CLAUDE.md, GEMINI.md
(and
.localvariants) from the working directory for project-specific instructions. - Persistence: SQLite + sqlc. All queries live in
internal/db/sql/, generated code ininternal/db/. Migrations ininternal/db/migrations/. - Pub/sub:
internal/pubsubfor decoupled communication between agent, UI, and services. - CGO disabled: builds with
CGO_ENABLED=0andGOEXPERIMENT=greenteagc.
Build/Test/Lint Commands
- Build:
go build .orgo run . - Test: use the cheapest tier that can disprove your change. Do NOT run the full suite after every edit — see "Test Tiers" below.
- Update Golden Files:
go test ./... -update(regenerates.goldenfiles when test output changes; e.g.go test ./internal/ui/diffview -update) - Lint:
task lint:fix - Format:
task fmt(gofumpt -w .) - Modernize:
task modernize(runsmodernizewhich makes code simplifications) - Dev:
task dev(runs with profiling enabled)
Test Tiers
task test is go test -race -failfast ./... over the whole repo. It is the
pre-commit gate, not the edit loop. Two multipliers make it expensive:
-racecosts roughly 4x —internal/agentalone is ~12s plain, ~47s raced.-count=1discards Go's test cache. Measured oninternal/config: 9.0s cold, 0.35s cached, 4.5s with-count=1. On an unchanged package-count=1buys nothing and costs ~13x.
Escalate only as far as the change requires:
| Tier | Command | When |
|---|---|---|
| 1 | task t -- ./internal/agent -run TestFoo |
While iterating. Seconds. |
| 2 | task test:pkg -- ./internal/agent/... |
Change is complete in one area. Cached, so unchanged packages are free. |
| 3 | task test:race -- ./internal/agent -run TestRegistry |
Only when the change touches goroutines, locks, queues, or shared mutable state. Keep it scoped. |
| 4 | task test |
Once, before committing. |
Rules:
- Never add
-count=1to make a run "more thorough". It only disables caching. Use it when you specifically need to defeat a cached result. - Do not add
-racereflexively. A pure function or a text-projection change cannot have a data race. - Diagnose a suspected flake with one comparison, not N reruns.
git stash && task test:race -- <pkg> -run TestX && git stash popsettles "did I introduce this?" in a single run. Repeated identical runs mostly burn time. - Run a single test with
go test ./internal/agent -run TestApplyTruncatedToolResults.
Code Style Guidelines
Standard Go conventions apply and are not restated here. Project-specific rules:
- Formatting: ALWAYS format Go code you write. Use gofumpt (stricter than
gofmt;
task fmtrunsgofumpt -w .); fall back togoimports, thengofmt, if gofumpt is unavailable. - Testing: Use testify's
requirepackage, parallel tests witht.Parallel(),t.Setenv()for environment variables, andt.TempDir()for temporary directories (no cleanup needed). - JSON tags: Use snake_case for JSON field names.
- Log messages: Must start with a capital letter (e.g., "Failed to save
session", not "failed to save session"). Enforced by
task lint:log(part oftask lint). - Comments: Own-line comments start with a capital letter and end with a period; wrap at 78 columns. End-of-line comments need no period.
Testing with Mock Providers
Tests that need provider configurations inject mock clients rather than
hitting the network — see mockCatwalkClient / mockHyperClient and
TestProviders_Integration_WithMockClients in
internal/config/provider_test.go for the pattern.
Committing
- ALWAYS use semantic commits (
fix:,feat:,chore:,refactor:,docs:,sec:, etc). - Try to keep commits to one line, not including your attribution. Only use multi-line commits when additional context is truly necessary.
Creating Pull Requests
- Default target: When asked to create a PR, always create it in the fork
repository (
meimingqi222/crush-fork), NOT the upstream repository (charmbracelet/crush). - PRs should be from a feature branch to
mainwithin the fork. - Use
gh pr create --repo meimingqi222/crush-forkto ensure the PR is created in the correct repository.
Working on the TUI (UI)
Anytime you need to work on the TUI, read internal/ui/AGENTS.md before
starting work.
Code Review Checklist
When reviewing code changes, check for pitfalls documented in docs/pitfalls/:
- Scan the changed files for patterns matching known issues
- Verify API contracts match expected data formats (raw vs encoded)
- Cross-reference with symptoms described in each pitfall document
- For internal prompt display, read
docs/pitfalls/internal-prompt-display-leakage.md - For TUI dialog cursor/layout changes, read
docs/pitfalls/tui-dialog-cursor-coordinates.md