# 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 as `unkin-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): 1. AppRole login: `POST $VAULT_ADDR/v1/auth/approle/login` with `role_id` only (no `secret_id`) → `client_token`. 2. `GET $VAULT_ADDR/v1/gitea/creds/unkin-agent` with `X-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` | `ababbcd3-9c77-5c6a-be2d-287fce9214a6` | AppRole role_id | | `GITEA_URL` | `https://git.unkin.net` | Gitea base URL | | `AGENT_LOGIN` | `unkin-agent` | login whose comments watchpr ignores | ## Build ```bash make build # -> dist/agentpr, dist/watchpr (CGO disabled, static) ``` Requires Go 1.21+. Dependency: `github.com/spf13/cobra` (CLI). ## Packaging (RPM) ```bash 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 `PUT`s 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 ```bash 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 - `watchpr` exits 0 with no output changes on `--once` (just prints state). - The token cache is process-wide (`sync.Once`); tests call the unexported `fetchGiteaToken` to avoid it. - CI "combined status" comes from `/commits/{sha}/status`; an empty head SHA yields an empty state without an API call.