agentpr/watchpr already propagated command errors to a non-zero exit, but that behaviour had no regression coverage and the root command was not constructible outside main(). watchpr also fired a spurious conflict alert because Gitea computes mergeability asynchronously and can briefly report mergeable=false right after a push. The docs additionally printed the AppRole role_id literal UUID. - Extract newRootCmd() in both cmd/agentpr and cmd/watchpr so main() only runs Execute and exits non-zero on error; add tests asserting Execute returns an error for a bad PR ref / malformed --repo / no args. - Debounce mergeability loss in MeaningfulChange: only alert when mergeable=false persists across two consecutive polls (both prev and cur false, still open); update the table test for one-poll-false (benign), false-persisting (alert), and recovered false->true (benign). - Refer to AGENT_APPROLE_ROLE_ID by env var in README.md/AGENTS.md without printing the literal role_id; keep the code default and env override.
4.2 KiB
AGENTS.md
Project Overview
This repo ships two Gitea-automation CLIs in one RPM (agent-tools). Both act
as the unkin-agent user by minting a scoped Gitea token from Vault, so
actions are attributed to the agent rather than to whoever runs the tool.
agentpr— create pull requests and post PR comments asunkin-agent(fixes the "tea posts as Ben" attribution problem). Subcommands:pr create,pr comment,whoami.watchpr— poll one or more PRs and exit when a tracked PR changes meaningfully: it merges/closes, gets a new non-agent comment, its CI fails, or it loses mergeability. Benign transitions (CI pending→success, the agent's own comments) are ignored.
Both tools are separate main packages under cmd/ and share the
internal/agent package (Vault AppRole login, Gitea REST client, PR-ref
parsing, watch-state comparison).
Structure
cmd/agentpr/main.go # agentpr CLI (pr create / pr comment / whoami)
cmd/watchpr/main.go # watchpr CLI (poll + meaningful-change exit)
internal/agent/ # shared plumbing:
token.go # env config + in-process Gitea-token cache
vault.go # AppRole login + read gitea/creds/unkin-agent
gitea.go # Gitea REST client (PR create/get, comments, status, whoami)
parse.go # owner/repo#N and owner/repo parsing
watch.go # PRState snapshot + MeaningfulChange comparison
go.mod # module git.unkin.net/unkin/agent-tools
Makefile # build / test / lint / completions / rpm / version-bump
packaging/nfpm.yaml # nfpm spec (envsubst-templated) for the RPM (both binaries)
scripts/build-rpm.sh # generates completions + packages the RPM with nfpm
.woodpecker/ # CI: build, test, pre-commit (PR) + release (tag)
dist/ # build output: binaries, completions, RPM (not committed)
Every binary is a separate main package, so make build builds each with its
own -o (a single go build ./... can't emit multiple mains to one file).
Token acquisition (shared)
Both tools call agent.GiteaToken(), which (once per process):
- AppRole login:
POST $VAULT_ADDR/v1/auth/approle/loginwithrole_idonly (nosecret_id) →client_token. GET $VAULT_ADDR/v1/gitea/creds/unkin-agentwithX-Vault-Token→.data.token.
Config via env (all have defaults):
| Variable | Default | Purpose |
|---|---|---|
VAULT_ADDR |
https://vault.service.consul:8200 |
Vault/OpenBao address |
AGENT_APPROLE_ROLE_ID |
built-in default | AppRole role_id (overridable) |
GITEA_URL |
https://git.unkin.net |
Gitea base URL |
AGENT_LOGIN |
unkin-agent |
login whose comments watchpr ignores |
Build
make build # -> dist/agentpr, dist/watchpr (CGO disabled, static)
Requires Go 1.21+. Dependency: github.com/spf13/cobra (CLI).
Packaging (RPM)
make rpm # build both binaries + package into dist/*.rpm via nfpm
scripts/build-rpm.sh generates bash/zsh/fish completions from the built
binaries and bundles them alongside /usr/bin/agentpr and /usr/bin/watchpr.
On a v* tag the release pipeline builds the RPM and PUTs it to the
artifactapi rpm-internal repo, then cuts a Gitea release.
Shell completions
Cobra provides a completion subcommand for each binary
(agentpr completion bash, etc.). The RPM installs them to the standard system
paths (/usr/share/bash-completion/completions/,
/usr/share/zsh/site-functions/, /usr/share/fish/vendor_completions.d/).
Testing
make test # go test -v -race ./...
internal/agent covers PR-ref parsing, the MeaningfulChange table (benign vs
alerting transitions), request-body construction, and the Vault+Gitea client
against httptest servers (fake AppRole login + gitea creds + PR create /
comment / whoami / status). No live Vault/Gitea access is required for tests.
Gotchas
watchprexits 0 with no output changes on--once(just prints state).- The token cache is process-wide (
sync.Once); tests call the unexportedfetchGiteaTokento avoid it. - CI "combined status" comes from
/commits/{sha}/status; an empty head SHA yields an empty state without an API call.