README
ΒΆ
π± Sprout
A map of your codebase for you and your AI agent: where to start reading, what a change could break, and which tests to run.
sprout tour # what this project is, how it's laid out, what to read first
sprout impact # what your uncommitted changes could break, and the tests to run
claude mcp add sprout -- sprout mcp # give Claude Code the same map (other agents: below)
Try it on any GitHub repo, no clone needed: sprout tour github.com/charmbracelet/bubbletea.
No install, with Go: go run github.com/Sprout-DevLabs/sprout@latest tour.
One small binary, standard library only. Reads Go, TypeScript/JavaScript, Python, Rust, Java and Kotlin. π Docs Β· β If Sprout saves you or your agent some digging, a star helps others find it.
Install
| Platform | Command |
|---|---|
| macOS, Linux | brew install sprout-devlabs/tap/sprout |
| Windows | scoop bucket add sprout https://github.com/Sprout-DevLabs/scoop-bucket then scoop install sprout |
| Debian, Ubuntu, Fedora, Alpine, Arch | .deb, .rpm, .apk and .pkg.tar.zst from Releases |
| Any Unix | curl -fsSL https://sprout-devlabs.github.io/sprout-web/install.sh | sh (checks the SHA-256 before installing) |
| Go 1.22+ | go install github.com/Sprout-DevLabs/sprout@latest |
Homebrew and the Linux packages include shell completions and a man page. For other installs, add completions yourself:
sprout --completion zsh > "${fpath[1]}/_sprout" # or bash, fish, powershell
With your coding agent
sprout mcp is a Model Context Protocol
server, so agents get the map, the graph and impact as tools instead of
spending their context on ls -R and grep.
Claude Code, in your project:
claude mcp add sprout -- sprout mcp
Cursor: .cursor/mcp.json in your project, or ~/.cursor/mcp.json for all of them:
{ "mcpServers": { "sprout": { "command": "sprout", "args": ["mcp", "${workspaceFolder}"] } } }
VS Code: .vscode/mcp.json in your project:
{ "servers": { "sprout": { "type": "stdio", "command": "sprout", "args": ["mcp", "${workspaceFolder}"] } } }
Or let Sprout print it with your sprout's full path filled in:
sprout mcp --print-config cursor (or vscode, claude-desktop, claude-code).
Claude Desktop: Settings β Developer β Edit Config, which opens
~/Library/Application Support/Claude/claude_desktop_config.json on macOS or
%APPDATA%\Claude\claude_desktop_config.json on Windows. Desktop apps don't
know your project, and often can't find Homebrew's binaries on their own, so
give both in full (which sprout prints the first):
{ "mcpServers": { "sprout": { "command": "/opt/homebrew/bin/sprout", "args": ["mcp", "/path/to/your/project"] } } }
| Tool | Does |
|---|---|
project_map |
The --ai map, key files included. Agents are told to call it first |
reading_order |
The --entry list |
tree |
A tree of any folder, with optional git status or churn |
diff_tree |
--diff for a revision like main...HEAD |
dependents |
What depends on a file, and why |
deps |
What a file depends on, and why |
impact |
What a change could break, and the tests to run: given files, uncommitted, staged, a range or a commit |
context |
What to know before editing a file: signatures it uses, its users, its tests |
Every tool also takes root, the project's absolute path, for when the
agent is working in a project other than the folder its client has open.
It must be a project folder (one with .git or a manifest like go.mod),
not a hidden one, and inside the folder you gave sprout mcp, if you gave one.
Agents are told when each tool beats searching by hand. The server is read-only. Paths and file arguments are confined to the project, symlinks included, git arguments can't carry options, and it never clones.
If it doesn't work
- No sprout tools show up: restart the client after editing its config.
Run
sprout mcpin a terminal; it should printMCP server on stdio, root β¦and wait. "command not found" in the client's MCP log means it can't see yourPATH: use the full path fromwhich sprout. - The map is of the wrong folder: the server serves the folder you pass
it, else the one your client says is open (MCP roots, followed when it
changes), else the one it started in. In
/or your home folder, whether it started there or the client is open there, it refuses rather than map everything. Start the agent inside the project, pass the project explicitly as in the examples above, or let the agent passroot. When a file isn't found, the error names the folder being served. - Stale results: there are none to worry about. Every call reads the disk again, so files your agent just wrote are in the next answer.
tour: get oriented in one command
sprout tour is --entry with context: the README's opening line, the
ecosystems and languages, the top-level layout, why each file is on the reading
list, and what to run next.
$ sprout tour testdata/tour
Tour of tour
Purpose (README.md excerpt): A small command that prints a greeting.
...
Start here:
1. README.md β project overview (README)
2. cmd/app/main.go β likely entry point (filename convention)
3. internal/greeting/greeting.go β used by 1 file
Next (run from the directory you toured; POSIX shell or PowerShell):
sprout context './cmd/app/main.go'
--limit N (default 8, up to 50) sets how many reading steps and directories
are shown; anything left out is counted. --json prints a stable,
versioned object, documented in docs/tour-json.md. It
works on a folder or on a repository you haven't cloned
(sprout tour github.com/owner/repo). Tour is read-only: it respects ignore
rules, skips symlinks, and never runs the commands it suggests. To map a folder
named tour, write sprout ./tour.
impact: what a change could break
sprout impact traces a change through the graph: every file that depends on
it, directly or through others, and every test that reaches it, with a command
to run them: go test for Go, pytest for Python, and vitest, jest, ava
or mocha for TypeScript and JavaScript when the project uses one. With no
arguments it uses your uncommitted changes;
--staged, --diff main...HEAD and --commit HEAD take them from git, or name
the files. --all lists every affected file and --json is for scripts and CI.
$ sprout impact pkg/controller/garbagecollector/graph_builder.go # in kubernetes
Impact of 1 file
pkg/controller/garbagecollector/graph_builder.go
Affected: 67 files, 8 directly
cmd/kube-controller-manager/app/controllermanager.go uses garbagecollector.GraphBuilder, garbagecollector.NewDependencyGraphBuilder
cmd/kube-controller-manager/app/options/options.go uses garbagecollector.DefaultIgnoredResources
pkg/controller/garbagecollector/dump.go uses beingDeleted
pkg/controller/garbagecollector/garbagecollector.go uses GraphBuilder, NewDependencyGraphBuilder, hasDeleteDependentsFinalizer +2 more
pkg/controller/garbagecollector/graph.go uses beingDeleted
pkg/controller/garbagecollector/patch.go uses monitors
pkg/controller/storageversionmigrator/storageversionmigrator.go uses garbagecollector.GraphBuilder, garbagecollector.Monitor
test/integration/util/util.go uses garbagecollector.DefaultIgnoredResources
β¦ and 59 more files through them (--all lists them)
Tests: 88 files, 67 Go packages
go test ./cmd/kube-controller-manager/app ./cmd/kube-controller-manager/app/options ./pkg/controller/garbagecollector ./pkg/controller/storageversionmigrator ./test/e2e ./test/integration/auth ./test/integration/clustertrustbundles ./test/integration/controllermanager ./test/integration/daemonset ./test/integration/disruption ./test/integration/dra ./test/integration/dra/all
(runs 12 of 67; --all prints the full command)
context: before you edit a file
sprout context FILE is what to know before changing one file, fitted to a
token budget (--budget, default 1500): the signatures it actually uses from
each dependency, the files that use it and why, the tests that reach it, then
what it declares. Agents get it as the context MCP tool.
$ sprout context pkg/controller/garbagecollector/garbagecollector.go --budget 700 # in kubernetes
# pkg/controller/garbagecollector/garbagecollector.go
27 dependencies Β· 5 direct users Β· 60 affected through them Β· 83 tests reach it
## depends on (and what it uses from each)
pkg/controller/controller_ref_manager.go β uses c.GenerateDeleteOwnerRefStrategicMergeBytes
func GenerateDeleteOwnerRefStrategicMergeBytes(dependentUID types.UID, ownerUIDs []types.UID, finalizers ...string) ([]byte, error)
pkg/controller/garbagecollector/errors.go β uses restMappingError
func (r *restMappingError) Error() string
type restMappingError struct
pkg/controller/garbagecollector/graph.go β uses node, objectReference, ownerReferenceCoordinates
type objectReference struct
type node struct
func (n *node) isBeingDeleted() bool
func (n *node) isObserved() bool
β¦
## used by
cmd/kube-controller-manager/app/core.go β uses garbagecollector.GarbageCollector, garbagecollector.NewComposedGarbageCollector
pkg/controller/garbagecollector/dump.go β uses GarbageCollector
pkg/controller/garbagecollector/operations.go β uses GarbageCollector, namespacedOwnerOfClusterScopedObjectErr
pkg/controller/garbagecollector/patch.go β uses GarbageCollector
test/integration/util/util.go β uses garbagecollector.NewGarbageCollector
deps and dependents: what a file touches
sprout deps FILE lists what a file depends on, and sprout dependents FILE what
depends on it, tests included. Each line says why: the import, or for Go, the
names it actually uses. --depth N follows more hops (-1 for all), --no-tests
skips test files, and --json is for scripts.
$ sprout dependents graph.go --depth 2
11 files (6 tests) depend on graph.go
ai.go uses Graph
codegraph.go uses FileID, Graph, GraphFile +2 more
entry.go uses FileID, Graph
query.go uses FileID, Graph
codegraph_test.go test Β· uses Graph
graph_test.go test Β· uses FileID, Graph, GraphFile +1 more
graphdump_test.go test Β· uses FileID
main.go via ai.go Β· uses AIMap
ai_test.go test Β· via ai.go Β· uses estimateTokens
bench_test.go test Β· via codegraph.go Β· uses buildGraph
query_test.go test Β· via query.go Β· uses queryResult
Go is resolved to the file that declares what's used. Other languages are
resolved from their imports: tsconfig paths and workspace packages for
TypeScript, real source roots for Python, and crates, use trees and workspace
crates for Rust. tools/accuracy checks this against each language's own
tooling. To map a folder named deps or dependents, write ./deps.
--ai: context for LLMs
A structure-first map of the project, sized to a token budget (--budget,
default 2000). It contains:
- the README's opening line
- the detected stack and languages
- entry points and the config/CI files
- uncommitted work and the 90-day hotspots
- the layout, opened breadth-first
- the key files: the files the rest of the code uses most, with their function and type signatures
It never includes file bodies. Here's the key-files section for sindresorhus/ky:
## key files (most used first)
source/types/options.ts (used by 12): export type SearchParamsInit; export type SearchParamsOption; β¦
source/core/constants.ts (used by 8): export const supportsRequestStreams; export const supportsAbortController; β¦
source/errors/KyError.ts (used by 7): export class KyError extends Error
source/types/request.ts (used by 5): export type KyRequest<T = unknown>
Signatures come from Go's own parser. For TypeScript/JavaScript, Python, Rust, Java and Kotlin, Sprout reads declarations and imports line by line. Files are ranked by how many other files import or reference them, and tests don't count.
--entry: where to start reading
$ sprout github.com/charmbracelet/bubbletea --entry
Reading order for bubbletea
1. README.md what the project is
2. tutorials/basics/main.go entry point
3. tutorials/commands/main.go entry point
4. tea.go used by 25 files
5. mouse.go used by 7 files
A tree that knows what matters
Inside a git repo, .gitignore decides what's shown. Sprout asks git
itself, so nested ignores, negations and global excludes all work. Outside git,
node_modules, .venv, dist and similar folders are hidden instead. The last
line always says how much was left out.
| Flag | |
|---|---|
-L, --depth N |
Limit depth |
--ignore PATTERNS |
Hide matches, in gitignore syntax: '*.log', 'src/gen/', 'docs/**/*.png', '!keep.log' |
--only PATTERNS |
Show only matching files and prune emptied folders: '*.go', 'web/src/**/*.tsx' |
--changed-within AGE |
Only files modified recently: 30m, 12h, 7d, 2w |
--max-files N |
At most N files per folder, then β¦ 37 more files |
--size |
File sizes and true folder totals, even past --depth |
--sort name|size|time |
Largest or newest first. -r reverses; --dirs-first lists folders first; --si uses powers of 1000 |
-a, --all |
Show hidden and ignored entries |
--hyperlink |
Make names clickable in terminals that support links |
A .sproutignore in the project applies the same patterns on every run.
--diff: a pull request as a tree
$ sprout --diff main...HEAD -L 1
. main...HEAD
βββ .github/ (2 changed) +13 -28
βββ cursed_renderer.go M +67 -17
βββ examples/ (3 changed) +5 -5
βββ tea.go M +45 -20
βββ testdata/ (13 changed) +13 -13
34 files changed, +469 -98
It takes any git revision: main...HEAD, HEAD~3, v1.0..v1.1.
--git and --churn
--git marks M modified, A added, D deleted (shown where the file was),
R renamed, ? untracked and U conflicted, and counts changes on folders.
--churn draws how many commits touched each path. --since '90 days ago'
narrows the window.
Remote repositories
sprout github.com/owner/repo --ai
sprout git@github.com:acme/private.git --churn -L 2
Any git URL, or host/owner/repo shorthand, is cloned into a temporary folder,
mapped, and deleted afterwards. Private repos work through your git
credentials. A cloned repo's own .sproutrc is ignored.
Config
Put default flags in ~/.config/sprout/config (or $SPROUT_CONFIG), or in a
.sproutrc for one project. Use one flag per line:
--depth 3
--hyperlink
--ignore=*.snap,fixtures/
The command line always wins. --no-config skips both files.
Scripting
--json emits the tree, stack and stats, with a schemaVersion. Adding
fields isn't a breaking change. Every other flag applies, so
sprout --diff main...HEAD --json can feed a PR bot directly.
Sprout's deps, dependents, impact, context and tour commands
support --json for machine-readable output with a schemaVersion.
The impact command reports affected files, tests and test commands for
supported languages through testCommands (Python, TypeScript and
JavaScript) and goTestPackages (Go). The context FILE --json command
provides file context, while tour --json returns a structured project
tour. These outputs can be used in scripts, CI workflows and bots.
For example, run Go tests for packages affected by a branch:
sprout impact --diff origin/main...HEAD --json |
jq -r '.goTestPackages[]' |
xargs go test
Example output from sprout dependents graph.go --json, trimmed:
{
"schemaVersion": 1,
"command": "dependents",
"file": "graph.go",
"depth": 1,
"results": [
{
"path": "ai.go",
"depth": 1,
"reason": "uses Graph"
},
{
"path": "codegraph.go",
"depth": 1,
"reason": "uses FileID, Graph, GraphFile +2 more"
}
]
}
Speed
Sprout reads only what the output needs and parses sources in parallel. On Kubernetes (31,412 files, Apple M4, warm cache), from the controlled run in the benchmark log:
| Command | Time |
|---|---|
sprout |
0.23 s |
sprout --json |
0.33 s |
sprout --entry |
0.57 s |
sprout --ai |
0.72 s |
tour costs about what --entry does; impact and context add the tests
to the graph (about half a second more on Kubernetes). bench_test.go
reproduces the numbers, and the log says exactly how they were measured.
Why not repomix, aider or serena?
They're good tools that solve neighbouring problems:
- Repomix, code2prompt pack whole files into one prompt. That's right when the model should read everything and it fits. Sprout gives a ranked map (where to start, which files everything depends on, with signatures) in a fixed token budget, and answers questions about the code instead of shipping all of it.
- Aider's repo map ranks files and
symbols much like
--aidoes, but it lives inside aider. Sprout's map, graph and impact work in any agent over MCP, and in your terminal. - Serena gives agents symbol-level navigation and editing through language servers, precise but heavier: a language server per language. Sprout is one binary with nothing to set up; its graph is file-level, built from imports and references, and adds what a language server doesn't: reading order, change impact with tests to run, git hotspots, and repositories you haven't cloned.
Design principles
- Useful by default.
sproutwith no flags should usually be enough. - Transparent. Anything hidden automatically is counted and can be shown.
- Humans and machines. Color only on terminals (
NO_COLORrespected), plain text in pipes, JSON when asked. - Small. One static binary, standard library only. git is needed only for git features.
Contributing
New to open source? Issues labelled
good first issue
are small, say where to look and how to test, and are a good place to start.
CONTRIBUTING.md walks you through it. Questions and ideas:
Discussions. Bugs:
open an issue. Security
problems: see SECURITY.md.
License
Documentation
ΒΆ
There is no documentation for this package.
Source Files
ΒΆ
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
tools
|
|
|
accuracy/truth/gotruth
command
Command gotruth is the ground truth for Go, at package level: each file's imports come from go/parser, and an import path maps to the directory of the repository module with the longest matching module path, which is how the go command resolves packages when every module is local.
|
Command gotruth is the ground truth for Go, at package level: each file's imports come from go/parser, and an import path maps to the directory of the repository module with the longest matching module path, which is how the go command resolves packages when every module is local. |