- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo | ||
| cmd/forge-agent | ||
| dev | ||
| docs | ||
| internal | ||
| secrets | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| .sops.yaml | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| future_work.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
forge-agent
A safe, agent-native CLI and MCP interface for Git forges. It turns non-transactional forge APIs into explicit, revision-aware, verifiable state transitions. The forge is the sole source of truth: no database, cache, daemon, state file, or telemetry.
Works with Forgejo and Gitea through their shared /api/v1. Support is claimed only for the
versions in Compatibility, never because an API looks similar. GitHub is not targeted.
Install
From source (Go 1.27): go run ./dev build (writes bin/forge-agent) or go run ./dev install. Release archives for
linux, macOS and Windows on amd64 and arm64 are built with go run ./dev release; they are reproducible (the same
commit gives byte-identical archives) and come with a SHA256SUMS file, so you can check what you download:
shasum -a 256 -c SHA256SUMS --ignore-missing. forge-agent --version reports the version, commit and date.
Verify a release
Releases are published as a Forgejo release with SHA256SUMS and its detached GPG signature SHA256SUMS.asc.
The signature proves who published the checksums; the checksums prove the archives are intact.
gpg --import docs/release-key.asc # the release public key, in this repository
gpg --fingerprint 56FAE1568DE3CCBBDFFF9EA715AF3C4C2B55C86C # must match the line below
gpg --verify SHA256SUMS.asc SHA256SUMS # "Good signature from forge-agent releases"
shasum -a 256 -c SHA256SUMS --ignore-missing
Release key fingerprint: 56FA E156 8DE3 CCBB DFFF 9EA7 15AF 3C4C 2B55 C86C. Releases are signed with a signing
subkey of this key; if gpg reports the key as not certified, that only means you have not marked it trusted yourself.
The archives are also reproducible: go run ./dev release on the tagged commit gives the same checksums.
Usage
export FORGE_URL=https://codeberg.org FORGE_TOKEN=... # FORGEJO_* also accepted
forge-agent issue get owner/repo#42 # → issue + revision
forge-agent issue update owner/repo#42 --expected-revision r1:… \
--state closed --add-label done --remove-assignee alice [--dry-run]
forge-agent issue create owner/repo --title "…" [--body …] [--label X]… [--assignee U]… [--milestone M]
forge-agent issue list owner/repo [--state open|closed|all] [--label X]… [--milestone M] [--query Q] [--limit 20] [--page N]
forge-agent issue comment owner/repo#42 --body "…"
forge-agent issue context owner/repo#42 # issue + comments, blockers, linked PRs, activity
forge-agent project context owner/repo # counts, open milestones, newest open issues, blockers
forge-agent pr context owner/repo#50 # PR + CI status, reviews, changed files, referenced issues
forge-agent git context [path] # local checkout → branch issue, open PR, CI status
forge-agent work next owner/repo [--label X] [--unassigned] [--unblocked] # candidate issues + facts; never a ranking
forge-agent doctor [owner/repo] # provider + version (tested?), token owner, repository access
forge-agent --audit issue update … # one JSON audit line per call on stderr (or FORGE_AUDIT=1)
forge-agent mcp # MCP over stdio
MCP tools: get_issue, update_issue, create_issue, list_issues, comment_issue, issue_context, project_context, pr_context, git_context, next_work. Both transports share one service and
emit the same compact JSON. Errors are structured and carry a recovery hint.
Semantics
read → revision check → plan → dry-run | apply → verify → result.
Scalars: set / clear (only milestone is clearable; set.body="" clears the body).
Collections: add / remove, never replaced (create_issue takes explicit initial values). Valid no-ops succeed with zero writes.
Results report state: verified | unchanged | partially_applied | unknown.
NETWORK_ERROR after a write means state may have changed: read before mutating again.
Mutations are never retried automatically. list_issues returns bounded, compact pages (default 20, max 50) with more/next_page.
issue_context, project_context, pr_context and git_context are bounded semantic snapshots (see docs/adr/0006 and 0007): every section reports its total, and a section that cannot be read is listed under unavailable.
next_work lists candidate issues with facts (assignees, milestone due date, open blockers) in the forge's own order and never ranks them.
comment_issue marks comments as agent-written (an invisible marker, shown as by_agent in context). Audit output never contains content (see docs/adr/0008).
Copy docs/AGENTS.md into your project's AGENTS.md for static usage rules.
create_issue and comment_issue need no revision (see docs/adr/0005); if their outcome is unknown, check before retrying.
Compatibility
Every row is the full integration suite run against a disposable, version-pinned container
(go run ./dev integration gitea|forgejo|codeberg). forge-agent doctor [owner/repo] reports the
provider and whether it is in this table.
| Provider | Version tested | Stand-in | Claim |
|---|---|---|---|
| Forgejo | 16.0.x releases | codeberg.org/forgejo/forgejo:16.0.3 |
tested |
| Gitea | 28.0.x releases | gitea/gitea:28.0.0 |
tested |
| Codeberg | development snapshots of Forgejo 16.0 | forgejo-experimental/forgejo@sha256:4c23554a… |
the suite passes on a development build of that family; not a tested release (doctor reports tested: false) |
A new minor version, a pre-release or a development build is untested until the suite has been run on it.
Differences found so far are handled inside the client, never leaked to callers: Gitea removes a label
only by id (Forgejo also by name), so removal reads the issue's attached labels and deletes by id on both.
The suite needs docker only for these targets, and each container is removed when its run ends (pass or fail;
-keep keeps it for debugging). Any server given through FORGE_TEST_URL, FORGE_TEST_TOKEN and
FORGE_TEST_REPO works with go run ./dev integration.
Limits (documented, not hidden)
- The revision is a client-side check; a change between the check and the write is not preventable.
- Assignee changes use full-list PATCH merged onto a fresh read; a race inside that window can lose an update.
- Dry-run cannot detect provider-authoritative failures (e.g. a nonexistent label, open-dependency 412s); these are listed under
unchecked.
Development
Every task is plain Go: go run ./dev help lists them. go run ./dev test and go run ./dev fuzz are what CI runs
(go run ./dev ci). Integration tests: go run ./dev integration all (docker: Gitea, Forgejo and a Codeberg-like Forgejo
development build) or go run ./dev integration against a server you provide; CI has no docker, so this is a manual
gate before every release. Agent evaluation (go run ./dev eval): fixtures, tasks and baselines in test/agent_eval/.
Releases are built, signed and published by go run ./dev release, sign and publish (a v* tag runs them in CI).
A pull request states whether it changed the golden files (the external contract) and whether that is additive or
breaking. See CONTRIBUTING.md and docs/adr/.