Why madtea

Other forge CLIs wrap the API. madtea wraps the workflow — and it is built on the assumption that an AI agent with shell access may be driving it.

This is the punch list: what madtea does that gh, tea, and raw API calls don’t. Every claim below is verified against the source tree; where a comparison against gh or tea is asserted, it is backed by the CLI comparison.

1. It has an issue graph. Nobody else does.

gh and tea have zero dependency commands — this whole category is a capability gap, not an ergonomics win.

  • madtea issue rank — ranks the open backlog (repo or whole org, one call) into keystones / free leaves / blocked, by what unblocks the most downstream. “What next?” and “what can run in parallel?” become one command. Merges native Gitea dependencies with body-convention parsing (Depends on, owner/repo#N), so it works cross-repo where Gitea’s own dependencies can’t. --emit-graph returns the full nodes/edges/cycles graph as JSON.
  • madtea issue deps add — wire chains (103 -> 102 -> 101) and epic fan-ins in one call. Every edge is existence- and cycle-checked before anything writes — a bad edge at position 50 applies nothing. No half-wired graph, ever.
  • madtea issue epic status / tick — cross-repo tracking epics that keep themselves true: status rolls up children into done / keystones / blocked / missing from references in the epic body; tick flips only the drifted checkboxes, diff-only with optimistic locking, so a concurrent hand edit is never clobbered.
  • madtea issue import — file an entire roadmap (milestones + dependency-ordered issues) from one JSON spec, transactionally. Schema, milestone refs, labels, and dependency cycles are all validated against the live repo before the first write; progress checkpoints to a spec-bound sidecar so --resume continues after a crash without duplicating anything.
  • Dependency-safe auto-closemadtea finish closes branch-named issues (issue-42-...) only when they have no open blockers; anything that looks like a parent epic gets referenced (Refs #N), not closed. Fail-closed: an API error means “don’t close”, never “close anyway” — and every per-issue decision is previewed before any write.

2. One call where every other tool makes you chain five.

  • madtea finish — commit + push + PR + merge + back to the default branch as one resumable, step-ledgered operation. Died after opening the PR? Re-run it — it resumes the open PR instead of 409ing or duplicating. Resolves the default branch from the forge API, not your stale local origin/HEAD, and can finish a branch sitting in a linked worktree without checking it out. It also watches for two footguns most tools ignore: after the merge lands it checks the default branch for a protection rule (exact-name or glob) and warns loudly when the merge went in unguarded — naming the madtea repo protect command that fixes it — and it warns before a first push that would hijack an unborn default branch. Both are best-effort warnings that never fail a successful finish.
  • madtea orchestrate — merge a batch of PRs, run your verify command, and on failure bisect across the merge commits to isolate exactly which PR broke it — then open and auto-merge the revert PR itself. An optional strict overlap pre-check refuses to start when PRs touch the same paths. “Merge the stack and hope CI catches it” becomes a self-healing command.
  • madtea prune — cleans merged local branches and the orphaned merged remote branches no local checkout remembers (merged via the web UI or by someone else). Dry-run by default; with --delete-branches it also removes squash/cherry-merged branches — only after re-verifying each branch’s content already lives in the default branch; anything unverifiable is skipped and reported, and branches held by worktrees are never touched.
  • madtea pull — upstream renamed mastermain? Detects it from ref state (not localized git stderr), confirms the new default with the forge API, repairs the local branch, and completes the pull — but only when local HEAD would fast-forward. Can’t repair safely? It touches nothing and prints the exact commands to run by hand.
  • Merge retry that reads the response body — distinguishes Gitea’s transient “try again later” merge lock from a genuinely stale branch, both of which arrive as the same HTTP 405. Retry blind and you loop forever; rebase blind and you rewrite a healthy branch. madtea encodes the difference so you don’t have to know it.

3. Your token never touches the transcript.

  • Every authenticated git operation (push, pull, fetch, clone, finish) routes the token through a throwaway GIT_ASKPASS script — mode 0700, deleted after the call, signal-handled. The token never appears in argv, in remote URLs, in ps output, in shell history, or in an AI agent’s conversation log. This is what makes madtea safe to drive from bash.
  • madtea auth login stores tokens in the OS keychain, falling back to GPG-encrypted pass, then to systemd-creds (TPM-sealed when available) — so even a headless server gets a secure tier. Plaintext git config (mode 0600) is a last resort you must ask for with --insecure-storage.
  • madtea secret-exec — resolves the token through the same secure path and injects it into one child process’s environment and nowhere else (madtea secret-exec GITEA_TOKEN -- <command>). The audited replacement for export GITEA_TOKEN=$(cat ~/.token).
  • The Claude Code plugin ships seven non-overridable PreToolUse hooks (ADR 0009) that stop the agent itself from running token-leaking commands: raw remote git against credential-embedding URLs, credential-pattern git config reads, any read of .git-credentials regardless of the reading program. Born from a real leaked-token incident. See safety hooks.

4. The destructive path doesn’t exist.

  • reset --hard, checkout -- <files>, stash drop, branch -D: not flagged, not confirmed — removed. Each refusal names the exact escape-hatch command a human can run at a terminal. The mistake class “agent nukes uncommitted work” is structurally impossible.
  • Anchored body edits — no whole-body replace existsmadtea issue body patch swaps one exact text span and is rejected, not forced when the anchor has drifted or isn’t unique; task-toggle flips one checkbox and leaves every other byte. Both run under an optimistic lock (HTTP 412 on a stale read), and comments — which Gitea gives no version token — are guarded by an --expected-body check that is required on the MCP surface. An agent told to “update the status” structurally cannot paste its summary over your repro steps. See safe body edits and ADR 0002.
  • Branch delete is merged-only (git branch -d), always — and on refusal it probes merge status to tell you whether the branch was squash-merged (safe to discard by hand) or genuinely divergent work.
  • Worktree removal has no force override on any madtea surface (neither the madtea worktree remove flag nor a madt_worktrees parameter): a dirty or locked worktree is refused with the data at risk named, because discarding uncommitted work or overriding a deliberate lock is unrecoverable. A user who genuinely needs it runs raw git worktree remove --force in their own shell — madtea never sudo rm -rfs or force-discards on an agent’s behalf. See ADR 0029.
  • The persistent MCP server fails closed on wrong-repo calls: when the caller’s repo and the server’s working directory disagree in a multi-repo workspace, it refuses and names both — built after a real merged-a-PR-into-the-wrong-repo bug. See wrong-repo protection.
  • Panics in the MCP server log the full message and goroutine stack through the lifecycle logger before exit — a background server death is diagnosable instead of silent.

5. An MCP surface designed for LLM callers — not a CLI shoved over stdio.

  • ~50 consolidated, action-dispatched tools (one per resource noun) instead of hundreds of per-verb ones. One manifest drives runtime validation, tool descriptions, help topics, and generated docs — the schema an agent sees cannot quietly diverge from what the server executes.
  • Actions are real JSON-Schema enums with a description per value — the model sees every valid verb directly in the tool schema, and an invalid one fails validation before dispatch instead of after a wasted round-trip.
  • Shape-tolerant inputs441, "441", ["441","442"], and "441,442" all normalize; batch-first calls take many IDs with per-item results. Unknown arguments are rejected with “did you mean numbers?” instead of Go silently dropping the field and erroring confusingly downstream.
  • Gated domains — admin, wiki, packages, ActivityPub, and other low-traffic tool groups load on demand via madt_enable. An agent that never touches them never pays their schema’s token cost.
  • Every MCP client gets the same guardrails — safety-critical facts live in the server instructions and tool descriptions themselves (ADR 0001), proven by a CI clean-room test that drives a credential-less server through real tasks. Claude Code, Codex, Antigravity, Cursor, Zed: identical guarantees.
  • Self-maintaining helpmadt_help resolves generated reference, noun aliases, and hand-written topics; ask for an action verb and it names the owning tool instead of dead-ending. The same reference is exposed as MCP resources (madtea://reference/{noun}).
  • First-class client installmadtea serve exposes everything over stdio; madtea install wires the client (--mcp-client for Codex and Antigravity), and madtea mcp-config prints the right config snippet for Claude Code, Codex (TOML), or Antigravity, auto-detecting which is installed.
  • madtea api passthrough with self-updating tips — gh-style --jq, --template, --paginate, and {owner}/{repo} expansion for any endpoint not yet first-classed — and when a typed command exists, the response tips you back to it, with a CI parity guard so the tips can’t rot.

6. Docs and naming that cannot drift — enforced, not promised.

  • Zero relearning — every command and flag matches gh’s name verbatim, else tea’s, with zero aliases (naming conventions). If you know gh pr create --title, you know madtea. A CI parity test walks the CLI tree and the MCP registry and fails the build if the two surfaces diverge.
  • Drift guards as tests — the dispatch guard fails CI if any manifest action lacks a live handler (or vice versa); the description guard requires each action’s gotcha keywords to stay present in the agent-facing text.
  • One generator, one pass, three outputs — the human reference docs, the MCP reference, and the runtime help agents query all derive from one introspection of the live command tree. CI byte-compares regenerated output against what’s committed; stale docs can’t merge.
  • Forgejo-vs-Gitea divergence is a typed error — madtea probes the server flavour and wraps confirmed-unsupported endpoints in a distinct, explained error instead of an opaque 404 you’re supposed to decode from tribal knowledge — backed by an endpoint table in the API compatibility doc verified against live containers of both.

Canonical source: docs/WHY.md in the madtea repo.