Design decisions

The durable, cross-cutting decisions that constrain the design.

DocWhat it covers
0001-mcp-universal-source-of-truth.mdAgent conventions live in the MCP layer so they reach every client.
0002-safe-inline-body-updates.mdWhy there’s no wholesale-replace path for bodies, and how anchored edits are guarded.
0003-side-effect-confirmation.mdEvery side-effecting command confirms through one structural, enforced seam.
0004-terminal-closure-signal.mdA result that ends a unit of work carries a positive “settled, no need to re-verify” marker; a recorded-and-immutable mid-flow result (a commit) carries a distinct “recorded, no need to re-check” one.
0005-parameter-name-ergonomics.mdA wrong parameter-name guess is fixed proactively at the schema description (gh/tea-anchored), not by extending the reactive matcher.
0006-finish-refuses-default-branch.mdfinish always refuses to run from the resolved default branch (whatever it is), with one exception: an empty repo, where it treats the operation as the initial push.
0007-structural-fail-closed-multi-repo.mdA no-owner_repo/no-dir mutating local-git MCP call fails closed in a structurally detected multi-repo workspace, with no env/roots signal required. Amended: dir=’s workspace-roots confinement is removed (any git repo on disk); a FOREIGN dir= target carries its own tool-specific intent contract.
0008-operation-registry-source-of-record.mdA surface-neutral OperationSpec registry is the single source of record for all operation help; CLI, MCP schemas, and Resources/Prompts all render from it, replacing two independently-authored corpora.
0009-non-overridable-agent-guards.mdThe agent-facing steering/safety hooks are non-overridable — no agent-reachable git-config opt-out — since the hooks only ever constrain the agent (humans bypass PreToolUse); the empty-repo initial commit is the one structural exemption.
0010-release-is-a-tag-and-one-blessed-install.mdA release is a git tag cut by one local command with zero commits to the default branch (generated notes, local signing, downstream channel bumps in their own repos); install is package-manager-first (Homebrew/Scoop) with a single cross-platform madtea install fallback.
0011-cross-platform-first-class.mdlinux/macOS/windows are first-class; OS-specific behavior lives behind //go:build splits (real + no-op pair), and GOOS-per-target go build is the CI cross-platform gate.
0012-release-signing-and-tofu-updates.mdReleases are GPG-signed; madtea update verifies against the key embedded in the running binary (trust-on-first-use); key rotation ships a new release.
0013-credential-storage-model.mdCredentials prefer an OS secure backend, are read/written at a consistent git-config scope, and are never placed on argv (handed to git via GIT_ASKPASS). Amended #424: madtea never custodies a signing key - git delegates to the OS/agent/hardware.
0014-layered-dual-surface.mdCLI and MCP are thin, co-equal surfaces over one internal/service layer; dependencies point downward with a single types-only format→service edge, CI-locked by internal/parity/{layering,duplication}.go.
0015-consolidated-action-dispatched-tools.mdThe MCP surface is ~one tool per resource noun, action-dispatched via a registry-derived enum; low-traffic domains load on demand via madt_enable.
0016-gh-tea-vocabulary-parity.mdCLI/MCP names match gh then tea verbatim; no invented vocabulary, one canonical name per operation on both surfaces, enforced by internal/parity/parity.go.
0017-standard-forge-api-target.mdTarget the standard Gitea API v1 behind one gitea.Client; no per-platform branching in core logic — Forgejo endpoint gaps are surfaced as typed ErrUnsupportedOnForgejo errors, not routed around.
0018-diagnostics-via-internal-diag.mdinternal/diag is the sole diagnostics emitter; a per-request context sink routes warnings into the MCP tool result and to CLI stderr, CI-locked by diag_drift.
0019-foreign-primary-checkouts-immutable.mdA foreign repo’s PRIMARY checkout is immutable to madtea’s mutating verbs — its HEAD and working tree are shared state; linked worktrees are the sanctioned writable surface for cross-repo work. Amended #418 (forge-API writes inherit the rule, keyed on server-side forge state) and #416 (an operator-added root is in-scope).
0020-identifier-arity-boundary.mdIdentifier arity is a hard boundary: shapes are liberal within a parameter’s declared arity, never across it — a singular never comma-splits into a batch, a single-element plural is accepted everywhere, and multi-element on a single-target action errors instead of partially applying.
0021-read-steering-is-advisory.mdThe hooks hard-block only load-bearing policy (state mutation, credential exposure, shared-state); pure-read local-git steering is demoted to a non-blocking additionalContext advisory — the madt_* surface is a projection of git that can never cover its full read surface, so a read block wedges agents and trains hook-evasion. Amends ADR 0009 for reads; deny guards unchanged.
0022-hermetic-test-guard.mdUnder go test (keyed on testing.Testing()), two structural guards make it impossible to resolve real ambient credentials or mutate a git repo outside the test’s temp area; the allowance keys on test state (temp-scoped git config / a repo root under the OS temp dir), never a settable switch — the rejected MADTEA_TEST_ALLOW_REAL_CONFIG opt-in follows ADR 0009’s no-agent-reachable-escape line.
0023-shipped-strings-self-contained.mdEvery string shipped with the binary (steering, errors, refusals, tool descriptions) states its rule self-contained — no ADR numbers, internal issue numbers, or internal repo/host names, which don’t travel with the binary; dynamic caller context and non-shipped text (docs, ADRs, commits) are exempt.
0024-declared-files-staging-contract.mdA commit’s file set must be declared — files=[...]/--files is the only stage-and-commit path; all=true/-A/--all blanket staging is removed everywhere (commit/finish/add, both surfaces), and no_commit completes already-committed work without staging. Supersedes the staging sections of ADRs 0007 and 0019.
0025-origin-first-forge-resolution.mdThe API forge is derived from the repo’s origin host (env > per-repo override > origin > no-remote global fallback); credentials are host-keyed with one account per forge; repo-less calls resolve a session forge from the launch dir or fail closed on ambiguity. Amends 0013’s credential keying.
0026-terminal-forwarding-guards.mdHook denies and tool refusals form one DAG that terminates in executable commands: every steer is terminal (hands back a runnable call) or forwarding (points at a guard whose own refusal continues the chain) — no two guards point at each other, compound denies say nothing ran, and no refusal recommends a raw form the hooks block.
0027-launch-scope-credential-boundary.mdThe launch scope is the credential scope; dir= selects a repo, never a token. Two enumerated crossings only — contribute (the ADR 0019 worktree→finish flow) and report (issue filing against a foreign clone’s own origin) — each bound to the target checkout’s own remote; the raw API passthrough never resolves foreign credentials. Amended #416: scope is the operator’s - launch, or a runtime-added root.
0028-semver-tiers-within-0x.mdSemver tiers apply within 0.x (breaking-only bumps minor; feat and all other releasable commits bump patch; major waits for a deliberate 1.0) against a declared public API; no launch-gated version freeze. Amended #244: daily-cap gate (24h between releases at hard threshold).
0029-strict-omission-destruction-model.mdNo madtea surface carries a work-destroying override; destruction is offered only when machine-proven safe (content-verified prune, force-with-lease, merged-only branch delete), refusals name non-madtea tools (raw git, forge web UI), and elicitation guards only reversible-but-notable actions, retiring the per-client-degrading consent gate.
0030-git-mirrored-argument-shape.mdA CLI command that mirrors a local git command anchors its argument SHAPE (flag vs positional, and each positional’s meaning) to git’s own signature, not only its names — so worktree add’s positional is a plain checkout (-b creates), while the named-parameter MCP surface keeps its create-or-checkout convenience. Extends ADR 0016 from names to shape.
0031-recoverable-guard-tier.mdRecoverable-but-wrong-workflow acts (a commit landing on the default branch) are refused by default at the service seam, overridable only per call via --allow-<refused-state> / allow_<refused_state>= - never sticky config; structural carve-outs (empty-repo bootstrap) stay keyed on git state; severity decides the tier (unrecoverable stays omitted per 0029).
0032-public-home-and-github-distribution-edge.mdCanonical source home is cheshirecube; GitHub is the public distribution edge for every channel a user’s machine fetches directly (releases, curl installer, self-updater, Homebrew tap, Scoop bucket, plugin marketplace, go-get source); the import path is the vanity domain madtea.lol, decoupling the module name from the forge. Amended 2026-08-16: SHA-256 canonical cannot push-mirror to SHA-1 GitHub, so the go-get source is a synced SHA-1 copy, not a mirror. Refines the install story of 0010 and the self-updater source of 0012. Superseded by 0033 (distribution edge).
0033-cheshirecube-distribution-home.mdReverses 0032’s distribution edge: distribution homes to cheshirecube and GitHub is fully deferred. Releases, curl installer, self-updater, Homebrew tap, Scoop bucket, and plugin marketplace all live under cheshirecube (GoReleaser Gitea provider, no GitHub token). The vanity path madtea.lol and cheshirecube source home from 0032 stand; go install madtea.lol/cmd/madtea@latest resolves directly from cheshirecube, with a TEMPORARY GOPRIVATE=madtea.lol note while the forge is IP-whitelist-gated (dropped at full-public). Self-updater trust model (0012) unchanged except its fetch target; ADR 0017 preserved (no GitHub REST path); GitHub SHA-1 mirror (#386) parked as a fallback, not closed.

Part of the madtea documentation tree.