Files
mopac-pmo/archive/REPORT-20260829-1600-gitea-cli.md

5.0 KiB

REPORT — mopac-gitea-go (mgit): Gitea Go client + CLI delivered

Task: TASK-20260829-1600 — Gitea Go client (Redmine #508) Delivered: 2026-08-29 16:00 turn Repo: https://git.knownelement.com/ukrrs/mopac-gitea-go (created via the Gitea API with the tea token; public, AGPLv3) Clone: ~/projects/meta/MOPAC/gitea-go (origin main, 9 commits, pushed) Status: v0 complete, green, pushed. Ready for harness PR-flow cutover.

No Redmine writes in this turn (PMO owns the ticket, per directive).

What landed

Stdlib-only Go module git.knownelement.com/ukrrs/mopac-gitea-go: library package gitea (Client + sentinel errors — importable by the harness for the turn PR flow, no shell parsing), CLI mgit (internal/cli + cmd/mgit), stateful fake Gitea (internal/fakegitea), 0600-disciplined config loader (internal/config). ALL dev work ran inside the digest-pinned golang:1.26-bookworm builder via ./dev.sh/Makefile (host never runs a toolchain). TDD red/green at every layer; atomic conventional commits pushed to origin main.

Commit Content
1e47f5b Seed: AGPLv3 LICENSE (first commit)
eb49c62 Dev harness (digest-pinned Docker builder)
43bc581 Config: GITEA_* env + 0600 env-file loading
7c11987 gitea client core, fake server, repo endpoints
c251cd6 Branch list + commit-status endpoints
5b35e8c Pull-request create/list/get/merge endpoints
9b08589 mgit CLI (flags accepted after positionals)
5feac3c End-to-end smoke vs containerized fake
55dbc9a README to docs standard

Command surface

Command Effect Key flags
mgit repo create -n NAME create repo (own account or org) --owner ORG, --desc, --private, --auto-init, --default-branch
mgit repo list list repos --owner OWNER
mgit repo show OWNER/REPO one repo
mgit branch list OWNER/REPO branches + tip shas
mgit status create OWNER/REPO REF report commit status --state success|pending|error|failure, --context, --description, --target-url
mgit status list OWNER/REPO REF combined + latest per check
mgit pr create OWNER/REPO open PR (head may be owner:branch) --title, --body FILE|-, --base, --head
mgit pr list OWNER/REPO list PRs --state open|closed|all
mgit pr show OWNER/REPO NUMBER one PR with body
mgit pr merge OWNER/REPO NUMBER merge open PR --do merge|rebase|rebase-merge|squash|fast-forward

Auth is token-scoped via env GITEA_URL/GITEA_KEY (or a 0600 --config env file; MGIT_CONFIG/~/.config/mgit/env also honored). -o json on every command. Exit codes 0/1/2; API errors are one parseable stderr line (mgit: gitea: not found: http 404). Flags parse before OR after positionals.

Test results

  • Unit suite: 71 passing tests (39 functions + 32 table-driven subtests), 0 failures/skips, across three packages (library round-trips against the fake, error mapping 401/403/404/409/422/5xx -> sentinels with "http NNN", unreachable, malformed, redaction, config 0600 discipline, CLI surface incl. usage tables and stdin body).
  • The fake Gitea implements real semantics the tests pin: latest-status-per-context folding, worst-of combined status, short-sha (>= 7 char) resolution, merge-style validation, 409 on duplicate open PRs and re-merges, branch-advance on merge.
  • Token redaction: the fake DELIBERATELY echoes the presented token in every error body; library test + CLI test + the smoke redaction pass prove the token appears nowhere else (it lives only in the Authorization: token header).

Smoke (end-to-end, ./dev.sh smoke)

Builds mgit + fakegitea in the digest-pinned builder, boots the fake in a container on 127.0.0.1:8602, drives the real binary from the host through a 0600 env file: repo create (private, auto-init) -> list -> list -o json (parsed) -> show; branch list; status pending -> success flip -> combined "success (1 checks)"; PR create (body from file) -> list -> show -> merge squash -> closed list still shows it, open list empty; failure paths: missing repo (exit 2, one-line http 404 stderr), wrong token (http 401, no token material), loose env file (exit 1, "insecure mode"); final redaction scan over every captured out/err. Result: smoke: OK. Only the exact container ID the script spawns is ever removed.

Notes for the harness

  • PR flow for turns = branch list -> status create on head sha -> pr create -> pr merge, one static binary, JSON everywhere.
  • One bootstrapping fix during the turn: the smoke fake initially used per-boot go run (cold compile inside the container raced the readiness window); it now builds bin/fakegitea alongside the CLI in the same builder step and boots the compiled binary.
  • A stale go run ./smoke/fakeserver debug container (harness repo's, not this repo's) was removed by exact ID match during cleanup; no broad process or container operations were used at any point.