AI-native code database for Go. A defn is a definition — the atomic unit of code. Navigate, edit, and understand Go code by structure instead of by file.
AI agents spend most of their time finding code, not changing it. On gin-gonic/gin:
Reading a function — without defn:
grep "Render" → 269 results across dozens of files
Read context.go (1,489 lines) → scroll to find the right Render
There are 20+ definitions named "Render" — which one?
4+ tool calls, ~8K tokens of file content.
Reading a function — with defn:
code(op: "read", name: "Render")
One call. defn disambiguates by blast radius — returns (*Context).Render, the one with the most callers.
Editing — without defn:
Read context.go → find function → craft str_replace with enough
surrounding context to be unique → hope it matches exactly one location
Editing — with defn:
code(op: "edit", name: "Render", new_body: "func (c *Context) Render...")
This extends to harder queries. We asked Claude "blast radius of Render" on gin via claude -p:
Without defn — 9 tool calls, 144K input tokens, 45s
Claude used 9 Grep/Read calls to find callers of Render in context.go. Found 19 of 21 callers. Couldn't compute transitive callers or test coverage — would require reading every test file and tracing call chains manually.
With defn — 2 tool calls, 51K input tokens, 12s
Claude called code(op:"impact", name:"Context.Render"). defn parsed the receiver, found (*Context).Render, and returned all 21 callers, 134 transitive callers, and 111 covering tests in one response.
65% fewer tokens, 75% faster, more complete answer. Measured via claude -p on gin-gonic/gin. Results vary by run.
go install github.com/justinstimatze/defn/cmd/defn@latest
go install golang.org/x/tools/cmd/goimports@latest
cd your-go-project
defn init . --server # recommended: starts Dolt server for multi-agent
defn init . # or: embedded mode, no server neededTo remove everything: defn clean
Requires Go 1.25+, CGO, and goimports. Binary is ~140MB due to embedded Dolt engine.
macOS: install ICU first: brew install icu4c then build with:
export CGO_CFLAGS="-I$(brew --prefix icu4c)/include"
export CGO_LDFLAGS="-L$(brew --prefix icu4c)/lib"
go install github.com/justinstimatze/defn/cmd/defn@latestdefn init parses your Go source, stores definitions and references in Dolt, and configures MCP for Claude Code and OpenAI Codex.
The database and files are kept in sync. Edits via defn update the database and emit files. File edits are auto-detected and re-ingested. Either can recover the other: defn init rebuilds the database from files; defn emit recreates files from the database. The .defn/ directory is gitignored — rebuild on clone with defn init.
defn init . creates a .defn/ directory with an embedded database. No server needed. Good for single-user workflows.
defn init . --server starts a Dolt server automatically:
- Installs to
.defn-server/, binds to127.0.0.1:3307 - Creates a
defnuser with a cryptographically random password (no root access) - Configures MCP with the authenticated DSN
- Requires Dolt installed
Manage the server: defn server start, defn server stop, defn server status.
Why server mode:
- Multiple agents work concurrently (each session has its own branch)
- Standard MySQL clients for debugging (
mysql -h 127.0.0.1 -P 3307) defn push/defn pullto DoltHub for cloud hosting
After defn init, start Claude Code or Codex and ask:
"What's the blast radius of changing the Render function?"
"Which functions have no test coverage?"
"Find all functions that handle authentication"
Analysis of SWE-bench agent trajectories (via adit-code) across 1,840 files and 49 repos found that file size is the strongest predictor of agent cost. The common operations defn replaces:
| Operation | With files | With defn |
|---|---|---|
| Find a function | grep → read file → scroll | code(op:"read", name:"X") |
| Edit a function | read file → find it → str_replace | code(op:"edit", name:"X", body:...) |
| Understand callers | grep → disambiguate → read each | code(op:"impact", name:"X") |
| Run affected tests | go test ./... (everything) |
code(op:"test", name:"X") |
| Rename across codebase | grep → edit each file | code(op:"rename", old_name:"X", new_name:"Y") |
Name-based ops accept file:line paths and Receiver.Method syntax.
One MCP tool — code — with an op field. Single tool schema in context instead of 17 separate tool definitions, following the Dynamic Context Loading pattern.
code(op: "read", name: "Render")
code(op: "impact", name: "Render")
code(op: "edit", name: "Render", new_body: "func ...")
code(op: "search", pattern: "%Auth%")
Operations:
| Op | What it does | Key params |
|---|---|---|
read |
Full source of a definition | name or file:line |
search |
Find by name pattern (%) or body text | pattern |
impact |
Blast radius, callers, test coverage | name |
explain |
Signature + callers + callees + tests | name |
similar |
Find definitions with similar signatures | name |
untested |
Definitions without test coverage | — |
edit |
Replace body, auto-emit + build | name, new_body |
create |
Create (infers name/kind from body) | body, optional module |
delete |
Remove + clean up references | name |
rename |
Rename + update callers (string replacement) | old_name, new_name |
move |
Move to another module | name, module |
test |
Run only affected tests | name |
apply |
Batch operations | operations |
diff |
Uncommitted changes | — |
history |
Commit history for a definition | name |
find |
Definition at a file:line | file, line |
sync |
Re-ingest after file edits | — |
query |
Read-only SQL | sql |
Write operations auto-emit files and auto-resolve references. The MCP server watches for external file changes and auto-reingests, so editing via file tools stays in sync. Set DEFN_LEGACY=1 to disable auto-emit.
Configuration via defn.toml (optional):
db = ".defn" # or a MySQL DSN for server mode
port = "3307" # server port
build-timeout = "30s"
test-timeout = "60s"Each function, method, type, and constant is a row. References between them are rows. Test coverage is derived from the reference graph.
-- Who calls Render and has no tests?
SELECT d.name FROM definitions d
JOIN `references` r ON r.from_def = d.id
WHERE r.to_def = (SELECT id FROM definitions WHERE name = 'Render' AND receiver = '*Context')
AND d.test = FALSE
AND NOT EXISTS (
SELECT 1 FROM `references` r2
JOIN definitions t ON t.id = r2.from_def AND t.test = TRUE
WHERE r2.to_def = d.id
)Dolt gives git semantics on SQL data. Branch, merge, diff, commit — natively, on definitions.
defn branch feature
defn checkout feature
# make changes
defn commit "add auth middleware"
defn checkout main
defn merge featureMulti-agent workflow (server mode):
# Each agent gets its own branch on the shared server:
defn branch agent-1 && defn checkout agent-1
# ... agent 1 works ...
defn commit "add auth middleware"
defn branch agent-2 && defn checkout agent-2
# ... agent 2 works ...
defn commit "refactor handlers"
# Merge both back
defn checkout main
defn merge agent-1
defn merge agent-2In server mode, each MySQL session has its own branch context via CALL DOLT_CHECKOUT — multiple agents work concurrently on the same Dolt server without interference. In embedded mode, defn worktree <name> copies the database to a separate directory.
For remote collaboration (e.g. pushing to DoltHub): defn push origin main.
Dolt handles row-level merge. Semantic conflicts (e.g. one agent renames a function, another adds a caller) require manual resolution.
- Inline comments between statements are lost on round-trip. Doc comments on functions/types and package doc comments are preserved. Comments within expressions are preserved. But standalone comments like
// Step 2: do Xare dropped bygo/astre-printing. Use doc comments instead. - Go only. The type-checked reference graph requires
go/types. Other languages would need their own type checkers. renameop is string replacement, not AST transformation. May affect comments/strings containing the name.- Emit produces one file per package. Multi-file package layouts (foo.go, bar.go) are collapsed to a single file. Original file structure is not preserved.
applyop is not atomic. Partial failures leave the database in a mixed state. Check the response for per-operation errors.
defn can ingest, emit, and rebuild itself:
defn ingest . && defn emit /tmp/out
cd /tmp/out && go build ./cmd/defn/Verified on go-chi/chi, gorilla/mux, gin-gonic/gin, BurntSushi/toml.
| Project | Lines | Defs | Refs | Init time | DB size |
|---|---|---|---|---|---|
| chi | 10K | 370 | 704 | 11s | 15M |
| gin | 24K | 1,580 | 3,829 | 48s | 81M |
| hugo | 218K | 10,221 | 22,209 | 7min | 714M |
Tested up to ~200K lines. Projects with cgo imports (moby) or Go 1.26+ (kubernetes) are not yet supported. Init is a one-time cost — incremental resolve after edits is much faster.
gopls answers "who calls X?" too. But it can't compose callers + transitives + test coverage into one answer. It doesn't persist across sessions. It doesn't version definitions. It needs file:line:col — defn just needs a name. And it can't edit, rename, or delete.
MIT. Built on Dolt (Apache 2.0) and adit-code research. See INFLUENCES.md.