Design decisions
The durable, cross-cutting decisions that constrain the design.
| Doc | What it covers |
|---|---|
| 0001-mcp-universal-source-of-truth.md | Agent conventions live in the MCP layer so they reach every client. |
| 0002-safe-inline-body-updates.md | Why there’s no wholesale-replace path for bodies, and how anchored edits are guarded. |
| 0003-side-effect-confirmation.md | Every side-effecting command confirms through one structural, enforced seam. |
| 0004-terminal-closure-signal.md | A 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.md | A 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.md | finish 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.md | A 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.md | A 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.md | The 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.md | A 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.md | linux/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.md | Releases 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.md | Credentials 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.md | CLI 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.md | The 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.md | CLI/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.md | Target 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.md | internal/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.md | A 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.md | Identifier 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.md | The 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.md | Under 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.md | Every 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.md | A 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.md | The 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.md | Hook 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.md | The 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.md | Semver 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.md | No 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.md | A 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.md | Recoverable-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.md | Canonical 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.md | Reverses 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.
- ADR 0001 - Agent conventions live in the MCP layer madtea ships as both a CLI binary and a Model Context Protocol (MCP) server, and as a Claude Code plugin that bundles skills and PreToolUse hooks around that same binary.
- ADR 0002 - Safe inline body updates madtea lets an agent or a human edit issue bodies, pull-request bodies, and comment bodies.
- ADR 0003 - Side-effect confirmation is structural A command that performs a side-effect must tell the caller what it did — otherwise the caller re-runs work that already happened.
- ADR 0004 - A terminal-closure signal on completed results A side-effecting command already confirms what it did (ADR-0003): finish names its merge and pull, a close says “Closed issue #42”.
- ADR 0005 - Proactive parameter-name schema clarity Agents reach for a “natural” argument name that is not the tool’s canonical parameter, the JSON unmarshaler silently drops it, and a wasted round-trip follows. Recurring, real instances:
- ADR 0006 - finish refuses the default branch madtea finish completes a feature branch: it commits, pushes, opens a PR, merges it, and returns to the default branch.
- ADR 0007 - Structural fail-closed in multi-repo workspaces Mutating local-git MCP calls fail closed when the server cwd is a structurally detected multi-repo workspace and no caller-repo signal is present, closing a dead-zone that signal-only guards could…
- ADR 0008 - The operation registry is the source of record madtea ships as both a CLI binary and an MCP server, and today maintains two independently-authored help corpora:
- ADR 0009 - Agent-facing guards are non-overridable madtea’s Claude Code plugin ships four PreToolUse Bash hooks (check-git-{commit,local,cli,remote}.sh).
- ADR 0010 - Tag-driven releases, one blessed install Every release left a trail on the default branch: a docs(changelog): backfill [Unreleased] PR, a chore: prepare release PR, and their merge commits. Two coupled causes:
- ADR 0011 - Cross-platform support is first-class madtea ships binaries for linux, macOS, and windows (GoReleaser builds linux_{amd64,arm64}, darwin_{amd64,arm64}, windows_amd64).
- ADR 0012 - Release signing and trust-on-first-use updates madtea update downloads a release and replaces the running binary.
- ADR 0013 - Secure-backend-preferred credential storage madtea authenticates to a forge with a token; where and how it is stored sets the blast radius of a leak.
- ADR 0014 - CLI and MCP are thin surfaces over one service layer madtea ships two distinct user-facing surfaces: the Cobra CLI (internal/cmd) and the MCP server (internal/mcp).
- ADR 0015 - One MCP tool per resource noun, action-dispatched madtea’s MCP server must expose the full Forgejo/Gitea API surface to an agent.
- ADR 0016 - Names match gh/tea verbatim madtea exposes two surfaces — the Cobra CLI (madtea …) and the MCP server (madt_ tools) — and every operation appears on both.
- ADR 0017 - Target the standard Gitea API v1 madtea targets the standard Gitea API v1 endpoints that Forgejo maintains for compatibility, working with both platforms without modification.
- ADR 0018 - internal/diag is the sole diagnostics emitter madtea ships as both a CLI binary and an MCP server (ADR 0014). The two surfaces have different output channels:
- ADR 0019 - Foreign primary checkouts are immutable ADR 0007 (structural fail-closed, multi-repo) introduced dir= targeting: a madtea agent can name any other on-disk git repository as the target of a mutating local-git tool (madt_branch_create, madt_commit, madt_push, …).
- ADR 0020 - Identifier arity is a hard boundary The FlexIDs lineage made identifier value shapes liberal: multiple input forms for a number list are normalized to []string.
- ADR 0021 - Pure-read steering is advisory madtea’s Claude Code plugin ships a local-git steering hook (hooks/scripts/check-git-local.sh) that intercepts the agent’s Bash tool and redirects raw git forms to the madt_* MCP tool that does the same job.
- ADR 0022 - Hermetic test guard A handler test called issuesDevelop without sandboxing.
- ADR 0023 - Shipped strings are self-contained madtea ships strings to users in three compiled-in forms: hook steering text (the plugin’s PreToolUse scripts), error/refusal returns from the CLI and the madt_* tools, and MCP tool/parameter descriptions.
- ADR 0024 - Commit file sets are declared, never swept madt_commit and madt_finish (and madt_add) once offered a blanket-staging mode — all=true on the MCP surface, -A/–all on the CLI — that ran the equivalent of git add -A before committing: stage…
- ADR 0025 - Origin-first forge resolution and host-keyed credentials madtea resolves a repository’s path (owner/name) from its origin remote, but resolves the API server from the single madtea.url config value.
- ADR 0026 - Guard texts are terminal or forwarding madtea steers agents away from raw, credential-unsafe or shared-state-unsafe git through two families of guards: the Claude Code plugin’s PreToolUse hooks (they deny/advise on the Bash tool) and the…
- ADR 0027 - Launch scope is the credential boundary dir= began as a repo selector for local-git tools (ADR 0007) and was extended to any git repo on disk, with ADR 0019 clamping foreign primaries to the worktree→finish contribution flow.
- ADR 0028 - Semver tiers within 0.x Within 0.x, breaking changes bump MINOR and all other releasable types bump PATCH; the cadence gate counts releasable commits regardless of tier.
- ADR 0029 - No surface carries a work-destroying override madtea’s destructive-operation surfaces grew case by case, and by mid-2026 three different safety models coexisted with no ADR pinning any of them:
- ADR 0030 - git-mirrored commands match git's argument shape ADR 0016 pins CLI subcommand, flag, and MCP action names to gh then tea verbatim, with one refinement for git-mirrored commands: madtea’s thin wrappers over local git commands (branch, tag, checkout,…
- ADR 0031 - Recoverable guards refuse by default, override per call ADR 0029 pinned the rule for unrecoverable acts: they are omitted from every madtea surface.
- ADR 0032 - Public home and GitHub distribution edge The canonical source home is cheshirecube; GitHub is the public distribution edge for everything a user’s machine fetches directly; the import path is the vanity domain madtea.lol.
- ADR 0033 - Distribution homes to cheshirecube; GitHub deferred ADR 0032 split two roles.