Guidance for AI coding agents (and humans) working on Trove, a Go CLI
(trove) that manages repositories, pull/merge requests, issues, pipelines
and more across GitHub, GitLab, Bitbucket Cloud and custom forges.
Module: github.com/SurajMazar/trove-cli · Go 1.25 · binary: trove.
make build # ./bin/trove with version metadata
make install # go install into GOBIN / ~/go/bin
make test # go test ./...
make test-race # go test -race ./...
make fmt-check vet # what CI enforces besides tests
go run ./cmd/trove --helpBefore finishing any change: gofmt -l . is empty, go vet ./... passes,
go test -race ./... passes, and go build ./... succeeds. Run
go mod tidy only when imports changed. If the local toolchain is older than
a dependency requires, set GOTOOLCHAIN=local and pin compatible versions
rather than bumping the go directive.
| Path | Role |
|---|---|
cmd/trove |
entry point; wires providers/all and secrets/all into internal/cli |
internal/cli |
Cobra commands; thin: resolve account → check capability → call → render |
internal/app |
application core: config + registry + secrets + account/credential resolution |
internal/forge |
forge provider contract, capability system, registry, forge.Collect pagination; forgetest contract suite |
internal/secrets |
secret provider contract, scheme://key references, resolver; secrettest contract suite |
internal/domain |
provider-neutral models (Repository, PullRequest, Namespace, Pipeline, ...) |
internal/auth |
Credential (self-redacting), Store, RFC 8628 device flow, refresh |
internal/git |
system-git wrapper (clone/fetch/commit/push/...), SSH key selection, remote detection, credential-helper protocol |
internal/tui |
Bubble Tea components (pickers with field search, inputs, spinner, progress, dashboard, pause, full-screen reader) |
internal/output, internal/terminal |
human/JSON/quiet output, tables, error rendering, theme, prompts |
internal/httpx, internal/redact, internal/logging |
retry/rate-limit transport, redaction, slog setup |
internal/services |
bulk clone worker pool, doctor checks |
internal/config |
YAML config: load/migrate/validate/save, dotted get/set |
providers/<name> |
one forge driver per directory; providers/all registers them |
secrets/<name> |
bitwarden, keychain, secretservice; secrets/all registers them |
docs/ |
architecture, providers, authentication, secrets, custom providers |
scripts/demo-video |
reproducible demo video (pty recording → frames → Kokoro voice → ffmpeg); rerun after visible UI changes |
- Forge providers and secret providers are independent systems. Bitwarden/Keychain/Secret Service are secret providers, never forge providers. Neither package tree imports the other.
- Each forge is its own driver. Never assume GitHub behavior applies to
GitLab or Bitbucket (auth, pagination, hierarchy, PR/MR semantics, CI,
SaaS vs self-hosted). No provider-specific API code in
internal/forge,internal/apporinternal/cli. - Capabilities are declared, not emulated. A driver declares exactly
what it implements (
Capabilities()); commands obtain implementations viaforge.As[T](p, cap), which returnserrs.ErrUnsupportedCapability("Provider "x" does not support ..."). Never fake data or return empty results for unsupported features.forge.ValidateCapabilitiesmust report no problems. - Adding a forge = new
providers/<name>package + one line inproviders/all/all.go. No changes to CLI, domain, config, secrets, TUI or git layers should be needed. - No placeholders. No
TODOs, no stub implementations that pretend to work. Verify endpoints and fields against official API docs; never invent them.
- Secrets never go into the config file, logs, errors, JSON output, URLs or
command-line arguments. Config stores
auth.secret_refonly;Saveandconfig setrefuse secret-looking keys. auth.Credentialredacts itself infmt, JSON and slog; useEncode()only when writing to a secret provider.- HTTP debug logs go through
internal/redact(Authorization, cookies, tokens in query strings). Map API errors toerrskinds using providermessagefields, never raw bodies that could echo credentials. - Git authentication: HTTPS via
trove auth git-credential(credential-helper protocol, host-checked); SSH via the account'sssh_keypassed asGIT_SSH_COMMANDwithIdentitiesOnly=yes. Don't embed tokens in remotes.trove gitapplies the same credentials to arbitrary git commands (git.Passthrough, attached to the terminal, exit code preserved viaExitCodeError); destructive pushes are detected bygit.IsDestructivePush. - Destructive operations (delete, force-push, merge, key removal) ask for
confirmation via
confirm(a, ...)and require--yesin scripts. - Environment credentials (
TROVE_TOKEN,TROVE_TOKEN_<ALIAS>) are never persisted.
- Errors: return
*errs.Errorwith a sentinelKind, provider alias, safeMessageand an actionableHint(e.g.trove auth login <alias>). Exit codes come fromerrs.ExitCode. - Context: every network or git operation takes
ctx; cancellation must surface ascontext.Canceled. - Pagination: implement the provider's own mechanism inside a fetch func
and normalize with
forge.Collect(ctx, limit, fetch);Limit <= 0= all. - Concurrency: bounded worker pools only (see
services.Cloner, 4-slot semaphores in multi-provider listing); never unbounded goroutines. - Output: commands render through
a.Out.Result(v, quiet, human). JSON mode writes exactly one JSON document to stdout; progress, spinners, success lines and errors go to stderr. - Interactivity: prompts and TUIs only when
a.IO.Interactive(TTY, not--non-interactive, not JSON). Non-interactive paths fail witherrs.ErrInteractionRequiredand a hint showing the flag to use. - TUI keys:
escgoes back one screen (pickers opened from another screen setEscBack),q/Ctrl+Cquit Trove (tui.ErrQuit); help lines must describe what keys do in the current state. TUIs render to stderr and must fit 80 columns; meaning never relies on color alone (NO_COLOR respected). - Scripted TUI tests: feed one key per read (Bubble Tea v1 groups a burst
of runes into one key message and holds a trailing ESC as a possible escape
sequence); prefer driving models directly with
Update. - Terminology: show the provider's own terms via
Metadata().Terms(Merge Request, Gist, To-Do item, Workspace, Workflow run). - Match the surrounding style; comment non-obvious provider behavior.
- A forge provider: implement
forge.Driver+forge.Providerand only the capability interfaces the API supports; implementforge.Authenticatorandforge.GitAuthenticator. Tests must runforgetest.RunForgeProviderContractTestsagainst anhttptest.Serverfake (serve more than one page of repositories), plus focused tests for request shapes, error mapping (401/403/404/422/429), auth flows and cancellation. Seedocs/custom-providers.md. - A secret provider: implement
secrets.SecretProvider(+Checker,Describer), register it insecrets/all, and runsecrettest.RunSecretProviderContractTestswith an injected fake backend. - A command: add it under
internal/cli, register it inNewRootin the right group, support--json/--quietwhere it returns data, and cover it ininternal/cli/*_test.gousing the mock driver harness (newHarness). UpdateREADME.md(anddocs/when behavior or architecture changes).
- Never call real forge APIs or real secret stores in tests; use
httptest.Server, the mock driver ininternal/cli/cli_test.go, and in-memory secret providers. Real Keychain/Secret Service suites are opt-in (TROVE_KEYCHAIN_INTEGRATION=1,TROVE_SECRETSERVICE_INTEGRATION=1). - Keep tests hermetic: temp dirs for config/cache (
app.Deps.CacheDir),t.Setenv("HOME", ...)for anything under~,GIT_CONFIG_GLOBAL=/dev/nullfor git, local bare repositories instead of network remotes. - Assert that secrets never appear in outputs or error strings.
Tag vX.Y.Z and push the tag; .github/workflows/release.yml runs
GoReleaser (darwin/linux × amd64/arm64, Homebrew cask in
SurajMazar/homebrew-tap, needs the HOMEBREW_TAP_GITHUB_TOKEN secret).