No toolchain required -- it's a single static binary:
curl -L https://github.com/ha1tch/gorepoman/releases/latest/download/repoman-linux-amd64 -o repoman
chmod +x repoman
Swap linux-amd64 for the platform in use -- see the README for the full
table (Linux/macOS/Windows/FreeBSD/OpenBSD/NetBSD/DragonFly, amd64 and
arm64). Building from source instead requires Go 1.25 or later (see
go.mod) and no third-party dependencies: go build -o repoman ./cmd/repoman.
A handful of optional external tools unlock better validation for
specific file types but nothing requires them -- see doctor below.
doctor firstrepoman doctor
This is an environment diagnostic, not a pass/fail test. It reports the Go
version this binary was compiled with, the current platform, and which of
four optional external tools this environment has — gofmt, bash, node, PyYAML — each with what it
specifically enables:
[OK] Go 1.27.0 (>= 1.25 (this project's go.mod floor), fine)
[OK] Platform: Ubuntu 24.04.4 LTS
[OK] gofmt (/usr/local/go/bin/gofmt) -- usage: gofmt [flags] [path ...]
enables: real gofmt -e syntax validation for .go substitutions
[OK] bash (/usr/bin/bash) -- GNU bash, version 5.2.21(1)-release ...
enables: real bash -n syntax validation for .sh/.bash substitutions
[OK] node (/usr/bin/node) -- v22.22.2
enables: real node --check syntax validation for .js/.mjs/.cjs ...
[OK] pyyaml -- 6.0.3
enables: real yaml.safe_load validation for .yaml/.yml substitutions
(pyyaml above is a real dependency of this Go binary too, not a leftover
-- YAML validation shells out to python3 -c "import yaml..." under the
hood, the same as the .py and JSON validators shell out to their own
respective checkers.)
None of these are required. Every optional tool has a documented fallback
— a heuristic check, or an honest "not independently verified" — when it's
absent, and selftest passes cleanly either way. doctor exists so that's
a visible, informed choice rather than a silent one: you'll know from the
first run whether an edit to a .go file is getting real gofmt
validation or the heuristic path. --quiet drops the per-tool detail and
just confirms the Go/platform baseline.
selftest — the actual acceptance gaterepoman selftest
This is the gate that matters. It prints doctor's environment summary
first (informational only — an absent optional tool never fails this gate,
that's a supported operating mode, not a defect), then exercises every
tool against a synthetic repository it builds in a temp directory: ed's
own embedded selftest, roles, syncver, register, guards, wave
tracking, relcore, and a final section targeting specific real bugs found
in this project's own history rather than speculative edge cases.
Concretely: a genuinely missing optional toolchain component (a full go
binary, needed for gomod's real-problem detection specifically — gofmt
itself is checked unconditionally regardless of what's really installed)
doesn't turn the gate red. It shows up as selftest: all N checks green
(M deferred — optional toolchain missing), still exit 0, with exactly
what to install and a reminder to re-run for full coverage. Deferred is
not a weaker kind of pass — those specific checks genuinely couldn't run
their scenario without the tool, so they're skipped rather than either
faked or allowed to block a gate gorepoman's whole premise says shouldn't
need a toolchain to clear in the first place.
Do not trust an installation whose selftest fails. A red gate means
something in this environment doesn't match what the tools assume — fall
back to plain, careful manual editing rather than trusting ed/
str_replace_extended in that state, and flag it before doing anything
else.
If you're checking the result by piping through tail/head and then
reading $?, that $? is not selftest's exit code. This has
independently confused more than one session, so it's worth being
explicit rather than assuming it's obvious: in cmd | tail -N, the
shell's $? reports tail's exit status, not cmd's -- and tail
almost always exits 0 regardless of what it read. A build that actually
failed (real exit 1) can show $? as 0 under exactly this pattern,
which is precisely backwards from what it looks like it's telling you.
Three ways to check the real result instead:
# option 1: capture to a file, THEN check $?, THEN look at the tail
repoman selftest > /tmp/out.txt 2>&1; echo "exit: $?"; tail -20 /tmp/out.txt
# option 2: bash's own array of each pipeline stage's exit code
repoman selftest 2>&1 | tail -20; echo "real exit: ${PIPESTATUS[0]}"
# option 3: pipefail makes $? reflect the pipeline's failing stage
set -o pipefail
repoman selftest 2>&1 | tail -20; echo "exit: $?"
The last printed line is also deliberately unambiguous either way --
selftest: all N checks green on a real pass, SELFTEST FAILED -- do
not trust this build on a real failure -- specifically so the plain
text survives even a small tail -N regardless of which of the above
you use. But text surviving a truncation and a shell correctly
reporting an exit code are two different guarantees; check the actual
result, don't infer it from either alone.
strreplace's own selftest (repoman strreplace selftest) and ed's
own (repoman ed selftest) can each also be run standalone — selftest
already calls both as part of its own run, but running one in isolation
is useful when you're specifically debugging that tool rather than the
whole suite.
-h/--help: live docs, with two ways to skip themEvery command's -h/--help prints its own embedded usage text first,
unconditionally, then attempts a live fetch of the matching chapter from
this project's own docs site -- an addition, never a replacement, and
one that fails completely silently (nothing printed, no hang) if the
network isn't reachable. The embedded text alone is always enough to use
the command; the live fetch is a bonus when it's available.
Two ways to skip the fetch, for two different situations:
REPOMAN_NO_WEB_HELP=1 -- a standing decision for a session: "I
already know this tool, stop fetching." Set once, applies to every
-h call for the rest of that session.--brief -- a per-call override, for the opposite case: the variable
isn't set, but this specific -h call shouldn't wait on a fetch or
print a full chapter. Works in either position (repoman ed -h --brief
or repoman ed --brief -h) -- deliberately, since remembering a
specific argument order for a flag meant to reduce friction would just
be a different kind of friction.Both are named in the embedded help text itself, every time, so neither needs to be already known to be discovered.
.repoman.jsonA repository opts in by having a .repoman.json file at its root. An empty
object is entirely valid:
{}
Every configuration key has a documented default (see
repoman-090-configuration.md for the full reference), so an empty {}
means "everything at its default" — docs/TRACKING.md for the register,
T- prefixed ids, no staged-wave tracking configured, and so on. You only
add keys for the things you actually want to diverge from default: a
different id prefix, wave tracking paths, version-sync targets, a release
manifest.
The tools work perfectly well with no .repoman.json at all for anything
that doesn't need project-specific state — ed and roles in particular
need nothing beyond a target path. The config file matters once you start
using register, guards, syncver, relcore, or the wave-tracking
tools, since those read and write to paths this file controls.
repoman-040-editing.md — ed and strreplace, the precise, journaled
editing tools, with worked examples.repoman-050-roles.md — the syntactic-role classifier that makes mass
substitution safe, and its per-language notes and known limits.repoman-060-register-and-guards.md — tracking open work and keeping
dormant tests honest.repoman-070-releases.md and repoman-080-waves.md — version sync,
resumable releases, and staged-work tracking.repoman-090-configuration.md — every .repoman.json key, in one place.