forge: Go reimplementation of the forge CLI (bash-to-Go Phase 1) #145
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/tools!145
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/forge-go"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This adds a Go version of the forge tool next to the bash one. Nothing you run changes yet — a later PR does the actual switch.
It behaves identically to the bash tool: same commands, same output, same exit codes, byte for byte.
The known differences live only in error corners and are all listed below; the biggest: it stops at the first error where bash wrongly kept going (allod/tools#144).
Proof: the bash test suite ported to Go and green, plus a couple hundred automated byte-comparisons against the real tool — all identical.
If it is wrong: revert the PR — nothing depends on it yet.
What this contains
go.mod(forge.anarch.diy/allod/tools, Go 1.24, stdlib only),flake.nix/flake.lock— the dev-only flake from the plan's Phase 0, folded in here since this is its first consumer:nix build .#forge(buildGoModule,vendorHash = null) and ago-checksderivation (gofmt + vet +go test ./...) undernix flake check. Consumers that import this repo withflake = falseare unaffected.cmd/forge/— the CLI: hand-rolled arg parsing transliterated loop-for-loop from the bash source, dispatch, rendering, and all 29 command usage texts captured byte-exactly from the bash tool's own--helpoutput (usage.go).internal/forgeapi/— the HTTP client. The API token stays in-process: set as a header by this process, never in child argv or env, never logged;cmd/forge/andinternal/forgeapi/contain noos/exec(pinned by grep in review and byTestAuthHeaderInProcessOnly).internal/gitremote/— repo inference (git remote get-url origin) and current-branch lookup, the only subprocess use, token-free by construction.tests/forge/*.shmap 1:1 tocmd/forge/*_test.go, with overlaps against the foundation tests noted per file, and the single PTY-only TTY scenario left to the live differential), running againsthttptestservers since the Go binary never execs curl.forgestays in place and continues to ship; it is deleted in the plan's Phase 4 after cutover.Divergences from bash (complete list)
not foundline. The Go port stops cleanly at the first error. Per the plan's known-bug divergence rule this is fixed, tested (// diverges from bash: allod/tools#144markers, including a zero-requests assertion), and tracked in that issue.curl exit Nmessage text is kept verbatim for script compatibility.curl exit 3, no request sent (the reject class was determined byte-by-byte against curl 8.20). curl's{}/[]globbing is not emulated: bash would fan a balanced brace expression into multiple requests; Go percent-encodes and sends one request (pinned by a test as the documented divergence).HOME(bash diesunbound variableeven for--help), or bash arithmetic noise like the extravalue too great for baseline before the real-L 08error — Go prints only the real error or renders the empty value cleanly. The same class covers NUL stripping: bash announceswarning: command substitution: ignored null byte in input(with its own script line number) as it strips; Go strips the same bytes silently, so the wire payload matches and that stderr line is absent.--help) withoutjqandcurlon PATH; the Go binary needs neither, which is the point of the migration, so those two startup errors have no Go counterpart.1e2renders1E+2; Go renders1e2) and accepts laxer spellings into payload construction (+7,1.,.5,nan) where Go emits its invalid-JSON diagnostic; integral IDs beyond int64 compare by float. The one reachable case — digit-only leading-zero integers like001— is normalized identically to jq, pinned by tests.column -talignment is reproduced including display-width (wcwidth) handling for CJK/fullwidth/emoji, byte-verified against util-linux 2.41 on 17 fixture classes; a rare wide codepoint outside the covered ranges could pad one cell differently. On a terminal narrower than the table,columnshrinks columns; the Go port always renders the un-shrunk table (identical whenever output is piped or redirected, which is every scripted use).Risk
Residual risk for this PR: R2. The plan rates Phase 1 R3, but that scrutiny attaches to the cutover PR in profiles/nexus that swaps the deployed wrapper; here the Go code is inert until something references it, the bash tool is untouched, and rollback is deleting new files. What deserves human eyes now: the token-handling paths (in-process only, no subprocess on any API path), the #144 stop-at-first-error ruling, and the curl exit-code classifier — those three choices propagate to every dev VM at cutover.
Validation
nix flake check: gofmt clean,go vetclean, fullgo test ./...green (all ported suites + foundation + divergence pins), package builds withvendorHash = null.--handling): 169/169 byte-identical stdout, stderr, and exit code against the bash tool.-R, auth status, token verify 401 path): 22/22 byte-identical; plus, as development-time evidence (not replayable from this tree), a 40-command sweep across 10 repos during integration, all identical. Mutation behavior is pinned by the ported request-order and JSON-payload tests, and additionally by an owner-authorized live mutation run (24 mutations on clearly-marked FAKE artifacts, alternating binaries, cleaned up after; 25/25 same-artifact dual-binary renders byte-identical — see comments).--argjsonboundary (settled empirically against jq 1.8.1 — jq normalizes001to1), 64-bit-exact comment-ID comparison, first-line-only URL-target parsing, systematic trailing-newline stripping at every command-substitution capture boundary,echo-builtin body rendering inissue view, and a realisattycheck (ioctlTCGETS) fortoken verify. The other 7 findings are the documented divergences above. Both parity harnesses re-ran fully identical after the fixes, and the bash suite (tests/forge/*.sh) still passes untouched.Refs allod/tools#98
Post-PR read-only review pass (gpt-5.6-terra, high reasoning effort; scope: every PR-body claim plus what-does-this-break). Verdict: the change itself held up — it independently re-ran
nix flake check, both parity harnesses (169/169 offline, 22/22 live read-only), and the untouched bash suite (295 assertions, all passing), and confirmed token-safety andflake = falseconsumer isolation statically. All findings are against the PR body's prose, not the code:warning: command substitution: ignored null byte in inputwhen it strips NUL bytes; the Go port strips them silently. The audit-results line also said the NUL findings were "fixed and pinned" without that caveat.Resolution: PR body corrected in place for all three (summary rewritten to defer to the enumerated list, NUL-warning divergence added to the bash-runtime-diagnostics bullet and caveated in the audit line, test-port claim qualified with the PTY exception). No code change: emitting bash's NUL warning would reproduce a bash-runtime diagnostic that names script line numbers, which this PR's divergence policy already excludes, and the PTY branch remains in-process untestable. The reviewer also noted the development-time evidence counts (pre-commit differential sweeps, the 18-finding cross-vendor audit) are not replayable from the committed tree; the body now labels them as development-time evidence distinct from the committed, re-runnable harnesses.
Owner requests, recorded before implementing: (1) the owner summary was not legible at a glance amid the technical detail — the opening five lines are being rewritten in plain speech with the detail moved below a divider, same content; (2) the owner explicitly authorized live mutation parity testing against the real forge using clearly-marked FAKE artifacts that are closed at the end of the test, waiving the dev plan's read-only-validation gate for this run. Results will be posted here when the run completes.
Live mutation parity results (owner-authorized, FAKE artifacts): 24 mutations executed against the real forge, alternating binaries so each ran every mutation verb live — label create/edit/delete, milestone create/edit/delete with a due date, issue create with label+milestone resolution by name, issue edit/comment/labels/milestone-clear, issue close with comment and with reason, pr create from pushed branches, pr edit/comment, pr close with comment and with
-d(remote branch deletion verified gone via ls-remote). After every mutation both binaries rendered the same artifact and were byte-compared with zero normalization: 25/25 identical, 0 failures. This also closes the PR body's stated validation gap — milestone list and view now have live table renders (real milestones existed during the test), byte-identical.Cleanup state: FAKE issues #146/#147 and FAKE PRs #148/#149 remain closed and clearly titled; both test branches deleted; test labels and milestones deleted; the label list is byte-identical to its pre-test snapshot.