Add agentws worktree-management binary
agentws manages per-branch git worktrees for the unkin-agent user: it clones repos into the source root (~/src/prodenv/<repo>) so branches are visible in Ben's main checkout, and creates isolated worktrees under the worktree root (~/.cache/agentws/<repo>__<branch>). - New internal/agent/git.go: small, testable git helpers shelling out to the git binary (clone/fetch/worktree add/remove/list/prune, branch + config ops, porcelain parsing, path sanitizing). No go-git dependency. - New cmd/agentws: new / list / rm / clean / token / credential subcommands. Auth uses an ephemeral git credential helper (agentws credential get) so the ~1h Gitea token is never persisted in a remote URL or config; per-worktree config keeps the shared checkout's identity untouched. - Wire agentws into Makefile, scripts/build-rpm.sh, packaging/nfpm.yaml (binary + bash/zsh/fish completions), .woodpecker/release.yaml (cross-compile + assets) and .gitignore. - Tests: table tests for parsing/sanitizing/dir-naming, a real temp-git repo for the worktree lifecycle, and hermetic cmd tests (bad input + credential-helper host guard) that never touch the network. - Document agentws in README.md and AGENTS.md.
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
## 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
|
||||
This repo ships several Gitea-automation CLIs in one RPM (`agent-tools`). They
|
||||
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`
|
||||
@@ -13,25 +13,32 @@ actions are attributed to the agent rather than to whoever runs the tool.
|
||||
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.
|
||||
- **`agentws`** — manage per-branch git worktrees for `unkin-agent`. Clones
|
||||
repos into the source root (`~/src/prodenv/<repo>`), creates worktrees under
|
||||
the worktree root (`~/.cache/agentws/<repo>__<branch>`), and authenticates
|
||||
clone/fetch/push via an ephemeral credential helper. Subcommands: `new`,
|
||||
`list`, `rm`, `clean`, `token`, `credential`.
|
||||
|
||||
Both tools are separate `main` packages under `cmd/` and share the
|
||||
All 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).
|
||||
parsing, watch-state comparison, git worktree helpers).
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
cmd/agentpr/main.go # agentpr CLI (pr create / pr comment / whoami)
|
||||
cmd/watchpr/main.go # watchpr CLI (poll + meaningful-change exit)
|
||||
cmd/agentws/main.go # agentws CLI (new / list / rm / clean / token / credential)
|
||||
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
|
||||
git.go # git worktree/clone/fetch helpers (os/exec, no go-git)
|
||||
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)
|
||||
packaging/nfpm.yaml # nfpm spec (envsubst-templated) for the RPM (all 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)
|
||||
@@ -42,7 +49,7 @@ 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):
|
||||
All 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`.
|
||||
@@ -55,12 +62,30 @@ Config via env (all have defaults):
|
||||
| `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 |
|
||||
| `AGENT_LOGIN` | `unkin-agent` | login whose comments watchpr ignores; agentws git identity |
|
||||
| `AGENTWS_SRC_ROOT` | `~/src/prodenv` | agentws source-of-truth checkout root |
|
||||
| `AGENTWS_ROOT` | `~/.cache/agentws` | agentws worktree root |
|
||||
| `AGENTWS_OWNER` | `unkin` | Gitea org that owns agentws-managed repos |
|
||||
|
||||
### agentws git auth (ephemeral credential helper)
|
||||
|
||||
Gitea tokens are ~1h ephemeral, so `agentws` never bakes one into a remote URL
|
||||
or config. `agentws token` prints a fresh token; `agentws credential get`
|
||||
implements the git credential protocol (reads the key=value request on stdin,
|
||||
and for the configured Gitea host only emits `username=unkin-agent` +
|
||||
`password=<fresh token>`). `agentws new` wires this per worktree — it enables
|
||||
`extensions.worktreeConfig` on the repo once, then writes `user.name`,
|
||||
`user.email` and `credential.helper = !<agentws> credential` to the
|
||||
**per-worktree** config so the shared checkout's identity/config is untouched.
|
||||
Clone/fetch pass the same helper transiently via `-c credential.helper=...`.
|
||||
Worktrees are created FROM `~/src/prodenv/<repo>` (`git worktree add`) so agent
|
||||
branches are visible in Ben's main checkout; `rm`/`clean` fetch there afterwards
|
||||
to keep the default branch current.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
make build # -> dist/agentpr, dist/watchpr (CGO disabled, static)
|
||||
make build # -> dist/agentpr, dist/watchpr, dist/agentws (CGO disabled, static)
|
||||
```
|
||||
|
||||
Requires Go 1.21+. Dependency: `github.com/spf13/cobra` (CLI).
|
||||
@@ -68,11 +93,12 @@ Requires Go 1.21+. Dependency: `github.com/spf13/cobra` (CLI).
|
||||
## Packaging (RPM)
|
||||
|
||||
```bash
|
||||
make rpm # build both binaries + package into dist/*.rpm via nfpm
|
||||
make rpm # build all 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`.
|
||||
binaries and bundles them alongside `/usr/bin/agentpr`, `/usr/bin/watchpr` and
|
||||
`/usr/bin/agentws`.
|
||||
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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user