Getting started
Install
curl -sSfL https://raw.githubusercontent.com/wagoodman/bonsai/main/install.sh | shThat picks a sensible spot on its own: an existing writable dir on your PATH (like
~/.local/bin) if you have one, otherwise /usr/local/bin, prompting for sudo only when it
has to.
Want a specific location instead? Pass -b DIR:
curl -sSfL https://raw.githubusercontent.com/wagoodman/bonsai/main/install.sh | sh -s -- -b /usr/local/binPin a version by appending a tag (... | sh -s -- v0.1.0), or grab a prebuilt archive from the
releases page.
Your first run
bonsai prune . # rank what's worth pruning in your go moduleIt compiles your target, which is how it gets ground truth instead of guessing from go.mod.
Already have a built binary? Add --binary ./mything to skip the build.
Reading the output
bonsai prune ranks candidates on two axes: the prize (bytes at stake if the module left
the binary) and the effort to realize it. Ranking by prize instead of “what’s easiest to
remove” keeps the big wins from hiding.
biggest win: github.com/hashicorp/go-getter → 17.9 MB at stake, pinned by github.com/anchore/syft (replace or patch); easiest win: github.com/glebarez/sqlite → 3.9 MB now
PRIZE EXCL EFFORT BLOCKER POT GET% IMP-SITES MODULE
17.9 MB 0 B pinnedByDep syft 0 B - 2 github.com/hashicorp/go-getter
3.9 MB 3.9 MB quickWin 4.5 MB 85.7% 4 github.com/glebarez/sqlite
904 kB 0 B coordinated 907 kB 0.0% 23 gorm.io/gorm- PRIZE is the full-graph retained size — bytes gone if the module vanishes by any means.
- EXCL is what you bank by cutting your own imports alone.
- When they differ, EFFORT says why:
quickWin(cut your imports),coordinated(co-prune the sibling targets),pinnedByDep(a dep you don’t control holds it — replace or patch that dep), orcore(too wired in to cut).
The go-getter row is the case worth the reframe: 0 bytes freed by a clean cut, but 17.9 MB at stake behind another dependency.
The one knob worth setting
--controlled is the flag that makes bonsai useful. Without it you get shallow results.By default bonsai assumes the only code you can edit is the module you’re scanning, so the only
refactors it considers are cutting your direct dependencies. But “code you can edit” is usually
bigger than one module: your org’s libraries, a fork you maintain, anything you can send a PR
to. Mark one of those as controlled and bonsai can suggest cutting an import inside that
library — a dependency of a dependency, below anything your go.mod mentions. That’s usually
where the real savings hide.
bonsai prune . --controlled "github.com/yourorg/..."A few more flags:
--lock <pattern>— lock things you’ll never drop so they stop showing up as suggestions.--unlock <module>— the opposite: treat something you own as fair game to drop wholesale.--blame— split each dependency’s fair share of the shared weight so the numbers add up to the real total (the Shapley value).
Next: keep the cut cut
Pruning a dependency once doesn’t keep it gone — a go get re-adds it, a transitive bump
quietly grows the binary. Turn the analysis into a CI gate with bonsai check and a committed
budget. See Configuration for the .bonsai.yaml file.