1# Crush Development Guide
2
3## Project Overview
4
5Crush is a terminal-based AI coding assistant built in Go by
6[Charm](https://charm.land). It connects to LLMs and gives them tools to read,
7write, and execute code. It supports multiple providers (Anthropic, OpenAI,
8Gemini, Bedrock, Copilot, Hyper, MiniMax, Vercel, and more), integrates with
9LSPs for code intelligence, and supports extensibility via MCP servers and
10agent skills.
11
12The module path is `github.com/charmbracelet/crush`.
13
14## Architecture
15
16```
17main.go CLI entry point (cobra via internal/cmd)
18internal/
19 app/app.go Top-level wiring: DB, config, agents, LSP, MCP, events
20 cmd/ CLI commands (root, run, login, models, stats, sessions)
21 config/
22 config.go Config struct, context file paths, agent definitions
23 load.go crush.json loading and validation
24 provider.go Provider configuration and model resolution
25 agent/
26 agent.go SessionAgent: runs LLM conversations per session
27 coordinator.go Coordinator: manages named agents ("coder", "task")
28 hooked_tool.go Decorator that runs PreToolUse hooks before tool execution
29 prompts.go Loads Go-template system prompts
30 templates/ System prompt templates (coder.md.tpl, task.md.tpl, etc.)
31 tools/ All built-in tools (bash, edit, view, grep, glob, etc.)
32 mcp/ MCP client integration
33 hooks/ Hook engine: runs user shell commands on hook events
34 hooks.go Decision types, aggregation logic, event constants
35 runner.go Parallel hook execution, timeout, dedup
36 input.go Stdin payload builder, env vars, stdout parsing (Crush + Claude Code compat)
37 session/session.go Session CRUD backed by SQLite
38 message/ Message model and content types
39 db/ SQLite via sqlc, with migrations
40 sql/ Raw SQL queries (consumed by sqlc)
41 migrations/ Schema migrations
42 lsp/ LSP client manager, auto-discovery, on-demand startup
43 ui/ Bubble Tea v2 TUI (see internal/ui/AGENTS.md)
44 permission/ Tool permission checking and allow-lists
45 skills/ Skill file discovery and loading
46 shell/ Bash command execution with background job support
47 event/ Telemetry (PostHog)
48 pubsub/ Internal pub/sub for cross-component messaging
49 filetracker/ Tracks files touched per session
50 history/ Prompt history
51```
52
53### Key Dependency Roles
54
55- **`charm.land/fantasy`**: LLM provider abstraction layer. Handles protocol
56 differences between Anthropic, OpenAI, Gemini, etc. Used in `internal/app`
57 and `internal/agent`.
58- **`charm.land/bubbletea/v2`**: TUI framework powering the interactive UI.
59- **`charm.land/lipgloss/v2`**: Terminal styling.
60- **`charm.land/glamour/v2`**: Markdown rendering in the terminal.
61- **`charm.land/catwalk`**: Snapshot/golden-file testing for TUI components.
62- **`sqlc`**: Generates Go code from SQL queries in `internal/db/sql/`.
63
64### Key Patterns
65
66- **Config is a Service**: accessed via `config.Service`, not global state.
67- **Tools are self-documenting**: each tool has a `.go` implementation and a
68 `.md` description file in `internal/agent/tools/`.
69- **System prompts are Go templates**: `internal/agent/templates/*.md.tpl`
70 with runtime data injected.
71- **Context files**: Crush reads AGENTS.md, CRUSH.md, CLAUDE.md, GEMINI.md
72 (and `.local` variants) from the working directory for project-specific
73 instructions.
74- **Persistence**: SQLite + sqlc. All queries live in `internal/db/sql/`,
75 generated code in `internal/db/`. Migrations in `internal/db/migrations/`.
76- **Pub/sub**: `internal/pubsub` for decoupled communication between agent,
77 UI, and services.
78- **Hooks**: User-defined shell commands in `crush.json` that fire before
79 tool execution. The engine (`internal/hooks/`) is independent of fantasy
80 and agent — it takes inputs, runs commands, returns decisions. The
81 `hookedTool` decorator in `internal/agent/hooked_tool.go` wraps tools at
82 the coordinator level. Hooks run before permission checks. See
83 `HOOKS.md` for the user-facing protocol.
84- **CGO disabled**: builds with `CGO_ENABLED=0` and
85 `GOEXPERIMENT=greenteagc`.
86
87## Build/Test/Lint Commands
88
89- **Build**: `go build .` or `go run .`
90- **Test**: `task test` or `go test ./...` (run single test:
91 `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
92- **Update Golden Files**: `go test ./... -update` (regenerates `.golden`
93 files when test output changes)
94 - Update specific package:
95 `go test ./internal/tui/components/core -update` (in this case,
96 we're updating "core")
97- **Lint**: `task lint:fix`
98- **Format**: `task fmt` (`gofumpt -w .`)
99- **Modernize**: `task modernize` (runs `modernize` which makes code
100 simplifications)
101- **Dev**: `task dev` (runs with profiling enabled)
102
103## Code Style Guidelines
104
105- **Imports**: Use `goimports` formatting, group stdlib, external, internal
106 packages.
107- **Formatting**: Use gofumpt (stricter than gofmt), enabled in
108 golangci-lint.
109- **Naming**: Standard Go conventions — PascalCase for exported, camelCase
110 for unexported.
111- **Types**: Prefer explicit types, use type aliases for clarity (e.g.,
112 `type AgentName string`).
113- **Error handling**: Return errors explicitly, use `fmt.Errorf` for
114 wrapping.
115- **Context**: Always pass `context.Context` as first parameter for
116 operations.
117- **Interfaces**: Define interfaces in consuming packages, keep them small
118 and focused.
119- **Structs**: Use struct embedding for composition, group related fields.
120- **Constants**: Use typed constants with iota for enums, group in const
121 blocks.
122- **Testing**: Use testify's `require` package, parallel tests with
123 `t.Parallel()`, `t.SetEnv()` to set environment variables. Always use
124 `t.Tempdir()` when in need of a temporary directory. This directory does
125 not need to be removed.
126- **JSON tags**: Use snake_case for JSON field names.
127- **File permissions**: Use octal notation (0o755, 0o644) for file
128 permissions.
129- **Log messages**: Log messages must start with a capital letter (e.g.,
130 "Failed to save session" not "failed to save session").
131 - This is enforced by `task lint:log` which runs as part of `task lint`.
132- **Comments**: End comments in periods unless comments are at the end of the
133 line.
134
135## Testing with Mock Providers
136
137When writing tests that involve provider configurations, use the mock
138providers to avoid API calls:
139
140```go
141func TestYourFunction(t *testing.T) {
142 // Enable mock providers for testing
143 originalUseMock := config.UseMockProviders
144 config.UseMockProviders = true
145 defer func() {
146 config.UseMockProviders = originalUseMock
147 config.ResetProviders()
148 }()
149
150 // Reset providers to ensure fresh mock data
151 config.ResetProviders()
152
153 // Your test code here - providers will now return mock data
154 providers := config.Providers()
155 // ... test logic
156}
157```
158
159## Formatting
160
161- ALWAYS format any Go code you write.
162 - First, try `gofumpt -w .`.
163 - If `gofumpt` is not available, use `goimports`.
164 - If `goimports` is not available, use `gofmt`.
165 - You can also use `task fmt` to run `gofumpt -w .` on the entire project,
166 as long as `gofumpt` is on the `PATH`.
167
168## Comments
169
170- Comments that live on their own lines should start with capital letters and
171 end with periods. Wrap comments at 78 columns.
172
173## Committing
174
175- ALWAYS use semantic commits (`fix:`, `feat:`, `chore:`, `refactor:`,
176 `docs:`, `sec:`, etc).
177- Try to keep commits to one line, not including your attribution. Only use
178 multi-line commits when additional context is truly necessary.
179
180## Working on the TUI (UI)
181
182Anytime you need to work on the TUI, read `internal/ui/AGENTS.md` before
183starting work.