05594113a2
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.
104 lines
4.2 KiB
Markdown
104 lines
4.2 KiB
Markdown
# 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` | 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
|
|
|
|
```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.
|