<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Design decisions on madtea</title><link>https://madtea.lol/docs/adr/</link><description>Recent content in Design decisions on madtea</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://madtea.lol/docs/adr/index.xml" rel="self" type="application/rss+xml"/><item><title>ADR 0015 - One MCP tool per resource noun, action-dispatched</title><link>https://madtea.lol/docs/adr/0015-consolidated-action-dispatched-tools/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0015-consolidated-action-dispatched-tools/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1693 OperationSpec registry, 1695 action enum, 1646 call-time git-repo check)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea&amp;rsquo;s MCP server must expose the full Forgejo/Gitea API surface to an
agent. Naively mapped, this is upwards of 200 discrete operations across a
dozen resource domains. Two competing pressures shape the design:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;An agent&amp;rsquo;s tool-picker is a search space.&lt;/strong&gt; Every additional tool the
model must discriminate among adds latency and increases mis-selection
probability. A 200-tool surface is impractical.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A single opaque &amp;ldquo;call anything&amp;rdquo; tool loses discoverability.&lt;/strong&gt; Without
structure, an agent cannot learn what operations are available without
reading extensive prose; it cannot self-correct from a bad choice; and the
schema cannot carry per-operation constraints.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The existing &lt;code&gt;madt_api_call&lt;/code&gt; passthrough tool fills the second role when
needed, but it carries no first-class operation semantics — it is the escape
hatch, not the primary surface.&lt;/p&gt;</description></item><item><title>ADR 0013 - Secure-backend-preferred credential storage</title><link>https://madtea.lol/docs/adr/0013-credential-storage-model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0013-credential-storage-model/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1726, 1753, 1765; 1718 / ADR 0009)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea authenticates to a forge with a token; where and how it is stored sets
the blast radius of a leak. Several incidents shaped the model: plaintext tokens
written even under a secure backend, a token exposed by
&lt;code&gt;git config --get-regexp&lt;/code&gt;, and a token read from the wrong
git-config scope so the wrong ciphertext was decrypted.&lt;/p&gt;</description></item><item><title>ADR 0011 - Cross-platform support is first-class</title><link>https://madtea.lol/docs/adr/0011-cross-platform-first-class/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0011-cross-platform-first-class/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1759, 1731 Linux-assumption regressions); release/install overhaul epic&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea ships binaries for linux, macOS, and windows (GoReleaser builds
&lt;code&gt;linux_{amd64,arm64}&lt;/code&gt;, &lt;code&gt;darwin_{amd64,arm64}&lt;/code&gt;, &lt;code&gt;windows_amd64&lt;/code&gt;). Yet the code
carried Unix-only assumptions that surfaced only as Windows bugs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;internal/safeio&lt;/code&gt; fsynced the parent directory after a rename — legal on POSIX,
unsupported on Windows (&lt;code&gt;FlushFileBuffers&lt;/code&gt; on a directory handle errors), so
every credentialed write failed on Windows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal/selfreplace&lt;/code&gt; relied on &lt;code&gt;os.Rename&lt;/code&gt; overwriting an existing
destination — POSIX-atomic, but Windows refuses, and a locked running &lt;code&gt;.old&lt;/code&gt;
image blocked the self-update.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;madtea install&lt;/code&gt; hardcoded &lt;code&gt;/usr/local/bin&lt;/code&gt; + &lt;code&gt;sudo cp&lt;/code&gt;/&lt;code&gt;chmod&lt;/code&gt; — meaningless
on Windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each was caught late (by a user on Windows, or by a manual &lt;code&gt;GOOS=windows&lt;/code&gt; build
during review) because CI only builds and tests on linux (&lt;code&gt;golang:1.26-alpine&lt;/code&gt;).
The implicit default was &amp;ldquo;Unix,&amp;rdquo; and nothing structurally enforced otherwise.&lt;/p&gt;</description></item><item><title>ADR 0024 - Commit file sets are declared, never swept</title><link>https://madtea.lol/docs/adr/0024-declared-files-staging-contract/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0024-declared-files-staging-contract/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-10&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1981)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;madt_commit&lt;/code&gt; and &lt;code&gt;madt_finish&lt;/code&gt; (and &lt;code&gt;madt_add&lt;/code&gt;) once offered a blanket-staging
mode — &lt;code&gt;all=true&lt;/code&gt; on the MCP surface, &lt;code&gt;-A&lt;/code&gt;/&lt;code&gt;--all&lt;/code&gt; on the CLI — that ran the
equivalent of &lt;code&gt;git add -A&lt;/code&gt; before committing: stage every modified, deleted, and
untracked path in the worktree, then commit the lot. It sat beside the declared
form (&lt;code&gt;files=[...]&lt;/code&gt; on MCP, &lt;code&gt;--files&lt;/code&gt; on CLI), which stages and commits only the
named paths.&lt;/p&gt;</description></item><item><title>ADR 0018 - internal/diag is the sole diagnostics emitter</title><link>https://madtea.lol/docs/adr/0018-diagnostics-via-internal-diag/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0018-diagnostics-via-internal-diag/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1528 diagnostics visibility epic, 1549 migration, 1550, 1548)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea ships as both a CLI binary and an MCP server (ADR 0014). The two
surfaces have different output channels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLI&lt;/strong&gt;: user-facing warnings, notices, and errors go to &lt;code&gt;os.Stderr&lt;/code&gt;.
ANSI color is appropriate when a TTY is detected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP tool call&lt;/strong&gt;: there is no stderr the agent can see. Diagnostics must
be captured and delivered &lt;strong&gt;inside the tool result&lt;/strong&gt; — a severity-prefixed
block that the agent reads as part of the response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before the diagnostics visibility work, every warning or notice in the codebase was written as a raw
&lt;code&gt;fmt.Fprintf(os.Stderr, &amp;quot;Warning: ...&amp;quot;)&lt;/code&gt; call. This worked on the CLI surface
but was invisible on the MCP surface: an agent calling &lt;code&gt;madt_issues&lt;/code&gt; that
triggered a credential-fallback warning or a pagination-truncation notice would
get the main response with no indication anything was unusual, because the
warning had gone to the server&amp;rsquo;s stderr log and not into the tool result.&lt;/p&gt;</description></item><item><title>ADR 0006 - finish refuses the default branch</title><link>https://madtea.lol/docs/adr/0006-finish-refuses-default-branch/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0006-finish-refuses-default-branch/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Amended: 2026-07-17 by ADR 0031 — &lt;code&gt;commit&lt;/code&gt; joins &lt;code&gt;finish&lt;/code&gt; in refusing the
default branch (per-call &lt;code&gt;--allow-default-branch&lt;/code&gt; override), closing the
branch-commit-finish loop at both ends.&lt;/li&gt;
&lt;li&gt;Date: 2026-06-24&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1621 formalizes a rule locked on 1601)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;madtea finish&lt;/code&gt; completes a &lt;em&gt;feature&lt;/em&gt; branch: it commits, pushes, opens a PR,
merges it, and returns to the default branch. Running it &lt;em&gt;from&lt;/em&gt; the default
branch is a category error — there is no feature branch to merge, and the only
coherent interpretations (open a PR from the default onto itself; push the
default with no review) are exactly the unguarded paths finish exists to avoid.&lt;/p&gt;</description></item><item><title>ADR 0019 - Foreign primary checkouts are immutable</title><link>https://madtea.lol/docs/adr/0019-foreign-primary-checkouts-immutable/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0019-foreign-primary-checkouts-immutable/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-03&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1912)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;ADR 0007 (structural fail-closed, multi-repo) introduced &lt;code&gt;dir=&lt;/code&gt; targeting: a
madtea agent can name any other on-disk git repository as the target of a
mutating local-git tool (&lt;code&gt;madt_branch_create&lt;/code&gt;, &lt;code&gt;madt_commit&lt;/code&gt;, &lt;code&gt;madt_push&lt;/code&gt;,
…). ADR 0009 (non-overridable guards) established that the agent-facing safety
hooks are structurally enforced with no opt-out.&lt;/p&gt;
&lt;p&gt;The cross-repo intent contract (ADR 0007 amendment) extended &lt;code&gt;dir=&lt;/code&gt; to &lt;em&gt;any&lt;/em&gt; repository on
disk — not just siblings sharing a workspace root — and built the worktree flow
as the sanctioned mechanism for agents contributing to a foreign repo:&lt;/p&gt;</description></item><item><title>ADR 0016 - Names match gh/tea verbatim</title><link>https://madtea.lol/docs/adr/0016-gh-tea-vocabulary-parity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0016-gh-tea-vocabulary-parity/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1225 batch rename to parity, 1572 stale CLAUDE.md vs deliberate bareword, 1593 no-negative-steers, 1604, 1605 param-name ergonomics)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea exposes two surfaces — the Cobra CLI (&lt;code&gt;madtea …&lt;/code&gt;) and the MCP server
(&lt;code&gt;madt_&amp;lt;noun&amp;gt;&lt;/code&gt; tools) — and every operation appears on both. Without a
deliberate naming rule, each surface accumulates its own vocabulary as
contributors pick names that feel natural or internally consistent. Over time
this creates two problems:&lt;/p&gt;</description></item><item><title>ADR 0030 - git-mirrored commands match git's argument shape</title><link>https://madtea.lol/docs/adr/0030-git-mirrored-argument-shape/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0030-git-mirrored-argument-shape/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-17&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/171"&gt;#171&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;ADR 0016 pins CLI subcommand, flag, and MCP action &lt;em&gt;names&lt;/em&gt; to &lt;code&gt;gh&lt;/code&gt; then &lt;code&gt;tea&lt;/code&gt;
verbatim, with one refinement for git-mirrored commands: madtea&amp;rsquo;s thin wrappers
over &lt;strong&gt;local git&lt;/strong&gt; commands (&lt;code&gt;branch&lt;/code&gt;, &lt;code&gt;tag&lt;/code&gt;, &lt;code&gt;checkout&lt;/code&gt;, &lt;code&gt;stash&lt;/code&gt;, &lt;code&gt;worktree&lt;/code&gt;, …)
match &lt;code&gt;git&lt;/code&gt;&amp;rsquo;s vocabulary, not gh/tea&amp;rsquo;s, because they &lt;em&gt;are&lt;/em&gt; git — an agent or
human reaching for them arrives with &lt;code&gt;git&lt;/code&gt;&amp;rsquo;s muscle memory. That refinement is
recorded in &lt;a href="https://codeberg.org/sixfold_space/madtea/src/commit/fab96f47b347af4edbf28328921b96e40fad0a523bd8dc1f72f48ab75a78a71c/docs/contributing/naming-conventions.md"&gt;naming-conventions.md&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Names are not the only muscle memory a git-mirrored command inherits. The
argument &lt;strong&gt;shape&lt;/strong&gt; — which operands are positionals versus flags, and what a
positional &lt;em&gt;means&lt;/em&gt; — is just as load-bearing, and ADR 0016 does not speak to
it. &lt;code&gt;madtea worktree add&lt;/code&gt; diverged here: it took the branch as a positional
(&lt;code&gt;add &amp;lt;path&amp;gt; [branch]&lt;/code&gt;) and had the service layer silently decide the meaning
— an existing branch was checked out, a missing one was created (&lt;code&gt;git worktree add -b&lt;/code&gt;). Raw git&amp;rsquo;s signature is &lt;code&gt;git worktree add &amp;lt;path&amp;gt; [&amp;lt;commit-ish&amp;gt;]&lt;/code&gt;: the
positional after the path is a commit-ish to &lt;strong&gt;check out&lt;/strong&gt; (an existing ref,
never a silent create), and creating a new branch is the explicit &lt;code&gt;-b &amp;lt;branch&amp;gt;&lt;/code&gt;. madtea&amp;rsquo;s &lt;code&gt;worktree add&lt;/code&gt; had no &lt;code&gt;-b&lt;/code&gt; at all.&lt;/p&gt;</description></item><item><title>ADR 0022 - Hermetic test guard</title><link>https://madtea.lol/docs/adr/0022-hermetic-test-guard/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0022-hermetic-test-guard/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-07&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (2034)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;A handler test called &lt;code&gt;issuesDevelop&lt;/code&gt; without sandboxing. Because
&lt;code&gt;serviceissue.Develop&lt;/code&gt; operates on the process working directory&amp;rsquo;s git repo and
resolves credentials from the ambient environment, the test — run from the real
madtea checkout — resolved real issue #42 through the &lt;strong&gt;live&lt;/strong&gt; forge API, created
and checked out a branch, and &lt;strong&gt;pushed it to origin&lt;/strong&gt;. It did this silently,
through a full green gate, twice (origin gained
&lt;code&gt;issue-42-phase-3-git-commands-migration&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>ADR 0020 - Identifier arity is a hard boundary</title><link>https://madtea.lol/docs/adr/0020-identifier-arity-boundary/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0020-identifier-arity-boundary/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-07&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (2029 / PR 2035; builds on ADR 0005 and the FlexIDs lineage 881, 884, 1327)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;The FlexIDs lineage made identifier &lt;strong&gt;value shapes&lt;/strong&gt; liberal: multiple input forms for a number list are normalized to &lt;code&gt;[]string&lt;/code&gt;. For example, &lt;code&gt;numbers&lt;/code&gt; accepts &lt;code&gt;441&lt;/code&gt;, &lt;code&gt;&amp;quot;441&amp;quot;&lt;/code&gt;, &lt;code&gt;[441]&lt;/code&gt;, &lt;code&gt;[&amp;quot;441&amp;quot;]&lt;/code&gt;, and &lt;code&gt;&amp;quot;441, 442&amp;quot;&lt;/code&gt; interchangeably. ADR 0005 codified the complementary rule for &lt;strong&gt;names&lt;/strong&gt;:
parameter names are strict (gh/tea/API-anchored), with only a bounded
did-you-mean. The identifier arity sweep extended shape liberality across operation arity in one
direction: every single-target action accepts a single-element &lt;code&gt;numbers=[N]&lt;/code&gt;
as equivalent to &lt;code&gt;number=N&lt;/code&gt; (&lt;code&gt;resolveOneNumber&lt;/code&gt;), because that input&amp;rsquo;s
meaning is unambiguous.&lt;/p&gt;</description></item><item><title>ADR 0027 - Launch scope is the credential boundary</title><link>https://madtea.lol/docs/adr/0027-launch-scope-credential-boundary/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0027-launch-scope-credential-boundary/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted (owner ruling 2026-07-15; enforcement tracked in &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/90"&gt;#90&lt;/a&gt;, issue-filing carve-out implementation in &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/59"&gt;#59&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Date: 2026-07-15&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/90"&gt;#90&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Amended 2026-07-17 (&lt;a href="https://codeberg.org/sixfold_space/madtea/issues/155"&gt;#155&lt;/a&gt;): a third enumerated read-only carve-out — the
whoami-scoped local-resolution report (see &amp;ldquo;Carve-out 3&amp;rdquo; below).&lt;/li&gt;
&lt;li&gt;Amended 2026-07-19 (&lt;a href="https://codeberg.org/sixfold_space/madtea/issues/263"&gt;#263&lt;/a&gt;): the residual-risk paragraph in Consequences
restated honestly - the boundary is host-scoped by credential, not
clone-scoped (the on-disk clone is fabricable friction, not authorization).&lt;/li&gt;
&lt;li&gt;Amended 2026-07-19 (&lt;a href="https://codeberg.org/sixfold_space/madtea/issues/264"&gt;#264&lt;/a&gt;): carve-out 2&amp;rsquo;s repo-prefix is now enforced
structurally client-side - the dir= report client is confined by
gitea.WithRepoScope, which refuses any request outside the target repo&amp;rsquo;s own
subtree, not just by convention of which methods the carve-out handlers call.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dir=&lt;/code&gt; 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. Separately, ADR 0025 made credential
resolution host-keyed and per-checkout: a clone of a Codeberg repo can carry
working Codeberg credentials while the session&amp;rsquo;s launch checkout is authed to
a different forge — or to nothing at all.&lt;/p&gt;</description></item><item><title>ADR 0014 - CLI and MCP are thin surfaces over one service layer</title><link>https://madtea.lol/docs/adr/0014-layered-dual-surface/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0014-layered-dual-surface/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1492 SoC audit, 1493 client-construction dedup, 1495 assignee set-merge dedup)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea ships two distinct user-facing surfaces: the Cobra CLI (&lt;code&gt;internal/cmd&lt;/code&gt;)
and the MCP server (&lt;code&gt;internal/mcp&lt;/code&gt;). Before the SoC audit, both
surfaces contained business logic, API client construction calls, and
result-assembly code that was either duplicated or subtly divergent. Concrete
examples found in the audit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gitea.NewClientWithConfig&lt;/code&gt; was called directly in individual MCP tool files,
duplicating the exact same construction logic the CLI used.&lt;/li&gt;
&lt;li&gt;Assignee set-merge (build a &lt;code&gt;map[string]struct{}&lt;/code&gt;, loop-add, loop-delete,
reconcile with an assignee identifier) was copy-pasted into multiple locations
across both &lt;code&gt;internal/mcp&lt;/code&gt; and &lt;code&gt;internal/cmd&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither of these duplications was caught by any test; behavior could drift
between surfaces silently. The immediate risk is that a change to business
logic is applied to one surface and not the other, making the CLI and MCP
behave differently for the same operation.&lt;/p&gt;</description></item><item><title>ADR 0001 - Agent conventions live in the MCP layer</title><link>https://madtea.lol/docs/adr/0001-mcp-universal-source-of-truth/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0001-mcp-universal-source-of-truth/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-02&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1316 agent self-sufficiency, 1317 &amp;ldquo;decide first&amp;rdquo; sub-issue)&lt;/li&gt;
&lt;li&gt;Amended: 2026-07-02 — the Tier taxonomy below is forked by ADR 0008;
see &amp;ldquo;ADR 0008 update&amp;rdquo; at the end of this document for what changed and what
didn&amp;rsquo;t.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;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. The plugin manifest advertises support for
&amp;ldquo;Claude Code, OpenAI Codex CLI, and Google Gemini CLI&amp;rdquo;, and the README
documents install paths for those plus Cursor, Windsurf, Cline, Zed, and
&amp;ldquo;any MCP client&amp;rdquo;.&lt;/p&gt;</description></item><item><title>ADR 0009 - Agent-facing guards are non-overridable</title><link>https://madtea.lol/docs/adr/0009-non-overridable-agent-guards/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0009-non-overridable-agent-guards/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Amended: 2026-07-07 by ADR 0021 — pure-read local-git steering demoted to non-blocking advisory; blocking guards (mutation, credential-exposure, shared-state) are unchanged.&lt;/li&gt;
&lt;li&gt;Date: 2026-06-28&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1721)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea&amp;rsquo;s Claude Code plugin ships four &lt;code&gt;PreToolUse&lt;/code&gt; Bash hooks
(&lt;code&gt;check-git-{commit,local,cli,remote}.sh&lt;/code&gt;). They intercept the agent&amp;rsquo;s Bash
tool and either steer raw &lt;code&gt;git&lt;/code&gt; / &lt;code&gt;madtea&lt;/code&gt; CLI calls to the credential-safe
&lt;code&gt;madt_*&lt;/code&gt; tools, or hard-block the raw remote ops that would leak a credential
token embedded in a remote URL.&lt;/p&gt;</description></item><item><title>ADR 0008 - The operation registry is the source of record</title><link>https://madtea.lol/docs/adr/0008-operation-registry-source-of-record/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0008-operation-registry-source-of-record/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-28&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1693)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea ships as both a CLI binary and an MCP server, and today maintains &lt;strong&gt;two independently-authored help corpora&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLI help&lt;/strong&gt; — Cobra &lt;code&gt;Short&lt;/code&gt;/&lt;code&gt;Long&lt;/code&gt;/flag-usage strings, hand-written per
command in &lt;code&gt;internal/cmd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP help&lt;/strong&gt; — &lt;code&gt;ActionSpec.Summary&lt;/code&gt; and jsonschema param tags, authored in
&lt;code&gt;internal/mcp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The parity test (&lt;code&gt;internal/parity&lt;/code&gt;) keeps &lt;em&gt;names&lt;/em&gt; aligned across surfaces but
never description text, so the two corpora drift freely. Concrete divergences
today: &lt;code&gt;pr merge&lt;/code&gt; exposes four boolean flags (&lt;code&gt;--squash&lt;/code&gt;, &lt;code&gt;--rebase&lt;/code&gt;,
&lt;code&gt;--rebase-merge&lt;/code&gt;, &lt;code&gt;--ff-only&lt;/code&gt;) on the CLI versus a single &lt;code&gt;strategy&lt;/code&gt; enum on
MCP; the CLI flag &lt;code&gt;--admin&lt;/code&gt; appears as &lt;code&gt;force_merge&lt;/code&gt; in MCP; &lt;code&gt;issues create&lt;/code&gt;
is described as &amp;ldquo;Create a new issue.&amp;rdquo; on the CLI and &amp;ldquo;Create an issue.&amp;rdquo; in
MCP. MCP summaries are also deliberately richer — a drift guard mandates that
footgun keywords appear inline — but that policy runs against MCP alone and
cannot protect the CLI description.&lt;/p&gt;</description></item><item><title>ADR 0025 - Origin-first forge resolution and host-keyed credentials</title><link>https://madtea.lol/docs/adr/0025-origin-first-forge-resolution/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0025-origin-first-forge-resolution/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-12&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/30"&gt;epic #30&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Amends: &lt;a href="https://madtea.lol/docs/adr/0013-credential-storage-model/"&gt;0013&lt;/a&gt; (credential keying),
&lt;a href="https://madtea.lol/docs/adr/0007-structural-fail-closed-multi-repo/"&gt;0007&lt;/a&gt; (extends the structural
workspace detection to forge selection)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea resolves a repository&amp;rsquo;s &lt;em&gt;path&lt;/em&gt; (&lt;code&gt;owner/name&lt;/code&gt;) from its &lt;code&gt;origin&lt;/code&gt;
remote, but resolves the &lt;em&gt;API server&lt;/em&gt; from the single &lt;code&gt;madtea.url&lt;/code&gt; config
value. The two can disagree, and when they do madtea silently queries the
wrong forge.&lt;/p&gt;
&lt;p&gt;Observed failure (2026-07-12): a workspace holds clones from two forges —
a self-hosted Gitea (the configured &lt;code&gt;madtea.url&lt;/code&gt;) and Codeberg. In a clone
whose &lt;code&gt;origin&lt;/code&gt; is &lt;code&gt;codeberg.org/&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>ADR 0005 - Proactive parameter-name schema clarity</title><link>https://madtea.lol/docs/adr/0005-parameter-name-ergonomics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0005-parameter-name-ergonomics/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-24&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1604, 1605; builds on 1577 bounded did-you-mean, 1593 no negative steers)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;Agents reach for a &amp;ldquo;natural&amp;rdquo; argument name that is not the tool&amp;rsquo;s canonical
parameter, the JSON unmarshaler silently drops it, and a wasted round-trip
follows. Recurring, real instances:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;madt_finish&lt;/code&gt; is keyed by &lt;code&gt;title&lt;/code&gt;; an agent passed &lt;code&gt;commit_message&lt;/code&gt; (and
&lt;code&gt;message&lt;/code&gt;) — &amp;ldquo;the commit message&amp;rdquo; is the reflex word for a commit subject.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;madt_search&lt;/code&gt; is keyed by &lt;code&gt;q&lt;/code&gt;; an agent passed &lt;code&gt;query&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each time one of these surfaces, the reflex is to reach for the &lt;strong&gt;reactive&lt;/strong&gt;
side — extend the unknown-argument did-you-mean with another synonym, a
compound/&lt;code&gt;_&lt;/code&gt;-token splitter, or a broad alias table — until &amp;ldquo;no guess ever
fails.&amp;rdquo; That instinct keeps getting re-explored from scratch, and it pulls
toward exactly the failure mode we want to avoid: a never-fail matcher that is
confidently wrong (&lt;code&gt;user_message → body?&lt;/code&gt;, &lt;code&gt;review_id → id&lt;/code&gt;). This ADR records
the decision so a future &amp;ldquo;wrong parameter name&amp;rdquo; bug resolves by reference.&lt;/p&gt;</description></item><item><title>ADR 0021 - Pure-read steering is advisory</title><link>https://madtea.lol/docs/adr/0021-read-steering-is-advisory/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0021-read-steering-is-advisory/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-07&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (2044, 2047)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea&amp;rsquo;s Claude Code plugin ships a local-git steering hook
(&lt;code&gt;hooks/scripts/check-git-local.sh&lt;/code&gt;) that intercepts the agent&amp;rsquo;s Bash tool and
redirects raw &lt;code&gt;git&lt;/code&gt; forms to the &lt;code&gt;madt_*&lt;/code&gt; MCP tool that does the same job. Initially it did this by &lt;strong&gt;hard deny&lt;/strong&gt; — &lt;code&gt;permissionDecision: &amp;quot;deny&amp;quot;&lt;/code&gt;, no
agent-reachable opt-out (ADR 0009).&lt;/p&gt;
&lt;p&gt;Over time the hard-deny set grew to cover a large read surface: &lt;code&gt;git status&lt;/code&gt;,
&lt;code&gt;git branch&lt;/code&gt; read/list, &lt;code&gt;git rev-parse HEAD&lt;/code&gt;, &lt;code&gt;git log&lt;/code&gt; (every read form),
&lt;code&gt;git worktree list&lt;/code&gt;, &lt;code&gt;git ls-tree&lt;/code&gt;, &lt;code&gt;git show &amp;lt;ref&amp;gt;:&amp;lt;path&amp;gt;&lt;/code&gt;, &lt;code&gt;git grep &amp;lt;pattern&amp;gt; &amp;lt;ref&amp;gt;&lt;/code&gt;, &lt;code&gt;git tag -l&lt;/code&gt;, &lt;code&gt;git diff &amp;lt;ref&amp;gt;&lt;/code&gt;, &lt;code&gt;git ls-files --deleted&lt;/code&gt;,
and &lt;code&gt;git sparse-checkout list&lt;/code&gt;. The read-steering audit classified every hard-blocked
git form and found &lt;strong&gt;twelve pure reads&lt;/strong&gt; blocked &lt;em&gt;purely for MCP-tool steering&lt;/em&gt;
— they cannot mutate the object DB, refs, working tree, index, config, or
stashes, and never contact a remote. Their block carried no mutation,
credential, or shared-state rationale; the only reason was economics (steer the
agent onto the one-call structured &lt;code&gt;madt_*&lt;/code&gt; tool).&lt;/p&gt;</description></item><item><title>ADR 0031 - Recoverable guards refuse by default, override per call</title><link>https://madtea.lol/docs/adr/0031-recoverable-guard-tier/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0031-recoverable-guard-tier/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-17&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/222"&gt;#222&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;ADR 0029 pinned the rule for unrecoverable acts: they are omitted from every
madtea surface. It deliberately said nothing about a second class this repo
keeps meeting: acts that destroy nothing but are almost always workflow
wreckage. The motivating incident (2026-07-17): a process with a confused
working directory committed a feature snapshot directly onto the local default
branch. Nothing was lost - the commit was branched off and the ref reset by the
user - but the damage cost real recovery work: a diverged local main, a broken
local build, and untangling by hand. Remote branch protection never entered the
picture; the commit was never pushed, and protection fires at push time anyway,
after work has stacked on the wrong base. Not every repo configures protection
at all.&lt;/p&gt;</description></item><item><title>ADR 0010 - Tag-driven releases, one blessed install</title><link>https://madtea.lol/docs/adr/0010-release-is-a-tag-and-one-blessed-install/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0010-release-is-a-tag-and-one-blessed-install/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1711 release-mess report); release/install overhaul epic filed on acceptance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;Every release left a trail on the default branch: a &lt;code&gt;docs(changelog): backfill [Unreleased]&lt;/code&gt; PR, a &lt;code&gt;chore: prepare release&lt;/code&gt; PR, and their merge commits.
Two coupled causes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The changelog was hand-maintained.&lt;/strong&gt; &lt;code&gt;CHANGELOG.md&lt;/code&gt;&amp;rsquo;s &lt;code&gt;[Unreleased]&lt;/code&gt; section
was updated per-PR by hand, so the discipline lapsed and a backfill scramble
preceded every cut (and &lt;code&gt;release.sh&lt;/code&gt; &lt;em&gt;refuses&lt;/em&gt; on an empty &lt;code&gt;[Unreleased]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;release.sh&lt;/code&gt; committed to a protected branch.&lt;/strong&gt; It bumped
&lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt;, promoted &lt;code&gt;[Unreleased] → [version]&lt;/code&gt;, and
regenerated docs — all commits, so on a branch-protected &lt;code&gt;main&lt;/code&gt; they went
through their own release PR.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But the commit-to-&lt;code&gt;main&lt;/code&gt; step is unnecessary:&lt;/p&gt;</description></item><item><title>ADR 0012 - Release signing and trust-on-first-use updates</title><link>https://madtea.lol/docs/adr/0012-release-signing-and-tofu-updates/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0012-release-signing-and-tofu-updates/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1266 release signing key); release/install overhaul epic (ADR 0010)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;madtea update&lt;/code&gt; downloads a release and replaces the running binary. Without
integrity verification, a tampered release or a man-in-the-middle on the download
runs arbitrary code as the user. madtea depends on no third-party update service
and publishes to a self-hosted forge, so the trust model must be self-contained
in the binary plus the release artifacts.&lt;/p&gt;</description></item><item><title>ADR 0002 - Safe inline body updates</title><link>https://madtea.lol/docs/adr/0002-safe-inline-body-updates/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0002-safe-inline-body-updates/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-03&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1349 RFC / brainstorm; supersedes 1348 CLI body-edit divergence / checklist toggle)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea lets an agent or a human edit issue bodies, pull-request bodies, and comment bodies. The motivating failure is &lt;em&gt;clobbering&lt;/em&gt;: an agent regenerates a tracking issue&amp;rsquo;s body from a stale read and silently discards everything that was there — closed sub-issue checkboxes, hand-written notes, a progress counter. The blunt fix that shipped earlier blocks issue-body edits on the MCP (agent) surface entirely and tells the agent to use a comment instead. That stopped the clobbering, but it also forbade every &lt;em&gt;legitimate&lt;/em&gt; inline update (tick a box, fix a typo, append a section, sync a checklist), so tracking-issue bodies rot while the truth lives in comments — exactly the state on several affected tracking issues at the time.&lt;/p&gt;</description></item><item><title>ADR 0028 - Semver tiers within 0.x</title><link>https://madtea.lol/docs/adr/0028-semver-tiers-within-0x/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0028-semver-tiers-within-0x/</guid><description>&lt;p&gt;Within 0.x, breaking changes bump MINOR and all other releasable types bump PATCH; the cadence gate counts releasable commits regardless of tier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Status: Accepted (owner ruling 2026-07-17); Amended 2026-07-17 (&lt;a href="https://codeberg.org/sixfold_space/madtea/issues/244"&gt;#244&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Date: 2026-07-17&lt;/li&gt;
&lt;li&gt;Tracking:&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="amendment-2026-07-17-244"&gt;Amendment (2026-07-17, &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/244"&gt;#244&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Two changes to the original decision:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;0.x MINOR tier narrowed to breaking-only.&lt;/strong&gt; The original text said
&amp;ldquo;breaking or feat bumps MINOR&amp;rdquo;. The amended rule is: only a breaking
change (&lt;code&gt;type!:&lt;/code&gt; / &lt;code&gt;BREAKING CHANGE&lt;/code&gt;) bumps MINOR within 0.x; &lt;code&gt;feat:&lt;/code&gt;
and every other releasable type (fix/ux/perf) bump PATCH. Post-1.0
mapping is unchanged. The cadence score weights are NOT affected - a
feat still scores 3 pts while releasing as a patch.&lt;/p&gt;</description></item><item><title>ADR 0023 - Shipped strings are self-contained</title><link>https://madtea.lol/docs/adr/0023-shipped-strings-self-contained/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0023-shipped-strings-self-contained/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-10&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (2130)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea ships strings to users in three compiled-in forms: hook steering text
(the plugin&amp;rsquo;s PreToolUse scripts), error/refusal returns from the CLI and the
&lt;code&gt;madt_*&lt;/code&gt; tools, and MCP tool/parameter descriptions. The house habit — natural
while the project was internal-only — has been to justify a rule by citing the
governance artifact behind it: &amp;ldquo;ADR 0009&amp;rdquo;, &lt;code&gt;&amp;quot;the removed all=true, #1964&amp;quot;&lt;/code&gt;,
&lt;code&gt;&amp;quot;see #1862&amp;quot;&lt;/code&gt;. Those citations resolve only inside the org: they point at ADR
files in this repo and issues on an internal forge.&lt;/p&gt;</description></item><item><title>ADR 0003 - Side-effect confirmation is structural</title><link>https://madtea.lol/docs/adr/0003-side-effect-confirmation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0003-side-effect-confirmation/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-03&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1370 epic, 1371 this ADR; implementation 1372-1381)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;A command that performs a side-effect must tell the caller &lt;strong&gt;what it did&lt;/strong&gt; — otherwise the caller re-runs work that already happened. The concrete failure that prompted this: &lt;code&gt;madtea finish&lt;/code&gt; ends by running &lt;code&gt;git checkout &amp;lt;default&amp;gt; &amp;amp;&amp;amp; git pull&lt;/code&gt; (&lt;code&gt;returnToDefaultBranch&lt;/code&gt;), but its output says only &lt;em&gt;&amp;ldquo;Returned to &amp;lt;default&amp;gt;&amp;rdquo;&lt;/em&gt; — never that it pulled, or to which commit. A caller can&amp;rsquo;t tell the pull happened and re-runs &lt;code&gt;madtea pull&lt;/code&gt; (an observed redundancy).&lt;/p&gt;</description></item><item><title>ADR 0017 - Target the standard Gitea API v1</title><link>https://madtea.lol/docs/adr/0017-standard-forge-api-target/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0017-standard-forge-api-target/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-01&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/src/commit/fab96f47b347af4edbf28328921b96e40fad0a523bd8dc1f72f48ab75a78a71c/docs/contributing/forgejo-15-actions-probe.md"&gt;forgejo-15-actions-probe.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea targets the standard Gitea API v1 endpoints that Forgejo maintains for compatibility, working with both platforms without modification. In practice, the two platforms are close but not identical. Forgejo is a soft fork of Gitea that
maintains compatibility with the Gitea API v1 for the vast majority of endpoints
but has not implemented some Actions-family endpoints that Gitea ships (verified
empirically in &lt;a href="https://codeberg.org/sixfold_space/madtea/src/commit/fab96f47b347af4edbf28328921b96e40fad0a523bd8dc1f72f48ab75a78a71c/docs/contributing/forgejo-15-actions-probe.md"&gt;forgejo-15-actions-probe.md&lt;/a&gt; against Forgejo 15).&lt;/p&gt;</description></item><item><title>ADR 0029 - No surface carries a work-destroying override</title><link>https://madtea.lol/docs/adr/0029-strict-omission-destruction-model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0029-strict-omission-destruction-model/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-17&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/206"&gt;#206&lt;/a&gt; (epic), &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/207"&gt;#207&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea&amp;rsquo;s destructive-operation surfaces grew case by case, and by mid-2026
three different safety models coexisted with no ADR pinning any of them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Omission&lt;/strong&gt; - force-delete of a local branch exists on NO madtea surface.
The MCP &lt;code&gt;madt_branch_delete&lt;/code&gt; tool has no force parameter, and the CLI
&lt;code&gt;madtea branch -d&lt;/code&gt; refuses &lt;code&gt;-D&lt;/code&gt;/&lt;code&gt;--force-delete&lt;/code&gt;, redirecting to raw
&lt;code&gt;git branch -D&lt;/code&gt; in the user&amp;rsquo;s own shell (&lt;code&gt;internal/cmd/git/branch.go&lt;/code&gt;).
The plugin hooks then steer agents off raw git. Two independent layers,
neither depending on the other behaving.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Machine-proof destruction&lt;/strong&gt; - &lt;code&gt;madtea prune&lt;/code&gt; deletes a ref only after
content-verifying it is contained in the default branch, and &lt;code&gt;madtea push --force&lt;/code&gt; rides &lt;code&gt;--force-with-lease&lt;/code&gt;. Refs die; work provably does not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consent-gated presence&lt;/strong&gt; - &lt;code&gt;madt_worktrees remove force=&lt;/code&gt; (&lt;a href="https://codeberg.org/sixfold_space/madtea/issues/168"&gt;#168&lt;/a&gt;) and the
&lt;code&gt;madt_delete&lt;/code&gt; tool (repo/issue/release/tag/wiki page) DO exist on the agent
surface, guarded by an elicitation dialog.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Model 3 leaked twice, and both leaks are structural, not incidental:&lt;/p&gt;</description></item><item><title>ADR 0007 - Structural fail-closed in multi-repo workspaces</title><link>https://madtea.lol/docs/adr/0007-structural-fail-closed-multi-repo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0007-structural-fail-closed-multi-repo/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-25&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1631)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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 not cover.&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;The wrong-repo guard (&lt;code&gt;internal/mcp/repoguard.go&lt;/code&gt;) fails
closed only when it has a caller-repo signal that &lt;em&gt;differs&lt;/em&gt; from the repo the
server resolved from its launch-fixed cwd. In our actual deployment that signal
effectively never exists, so the block path has — outside tests — never fired:&lt;/p&gt;</description></item><item><title>ADR 0004 - A terminal-closure signal on completed results</title><link>https://madtea.lol/docs/adr/0004-terminal-closure-signal/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0004-terminal-closure-signal/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-06-23&lt;/li&gt;
&lt;li&gt;Tracking: internal pre-Codeberg tracker (1597)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;A side-effecting command already confirms &lt;em&gt;what it did&lt;/em&gt; (ADR-0003): finish
names its merge and pull, a close says &amp;ldquo;Closed issue #42&amp;rdquo;. That cured the
caller re-running a step the tool had already performed — but it did not cure a
second, subtler redundancy: after a result that &lt;strong&gt;ends a unit of work&lt;/strong&gt;, an
agent re-verifies the &lt;em&gt;outcome&lt;/em&gt;. The concrete failure that prompted this:
&lt;code&gt;madt_finish&lt;/code&gt; reported a clean merge, and the agent then ran &lt;code&gt;go build&lt;/code&gt; to
re-confirm the merged tree compiled — work the finish had already validated and
landed. The same instinct fires after a release publish (re-fetch the release),
an issue close (re-get the issue), a branch delete (re-list branches).&lt;/p&gt;</description></item><item><title>ADR 0026 - Guard texts are terminal or forwarding</title><link>https://madtea.lol/docs/adr/0026-terminal-forwarding-guards/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://madtea.lol/docs/adr/0026-terminal-forwarding-guards/</guid><description>&lt;ul&gt;
&lt;li&gt;Status: Accepted&lt;/li&gt;
&lt;li&gt;Date: 2026-07-14&lt;/li&gt;
&lt;li&gt;Tracking: &lt;a href="https://codeberg.org/sixfold_space/madtea/issues/15"&gt;#15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;madtea steers agents away from raw, credential-unsafe or shared-state-unsafe
git through two families of guards: the Claude Code plugin&amp;rsquo;s PreToolUse hooks
(they deny/advise on the Bash tool) and the &lt;code&gt;madt_*&lt;/code&gt; tool refusals (they reject
an unsafe call and teach the safe one). Each guard was written well on its own.
The failure mode is at the &lt;em&gt;seams&lt;/em&gt; between them.&lt;/p&gt;
&lt;p&gt;A live case study (an agent in a foreign-repo worktree, mid-merge-conflict):&lt;/p&gt;</description></item></channel></rss>