feat(repo-scope)!: one repo= target on every tool, resolved in one place #685

Open
opened 2026-09-30 00:22:35 +00:00 by hexajon · 1 comment
Owner

This supersedes ADR 0027 (launch scope is the credential boundary). ADR 0019 (foreign primary checkouts are immutable) stays. The PR that builds this adds the superseding ADR.

Problem

I work across two forges every day. madtea names a target repo in three ways, and each tool supports its own subset:

  • no parameter: the repo of the launch directory;
  • owner_repo=: a repo on the launch forge only, because it has no host (#468);
  • dir=: a local checkout, but only on some tools (#485, #638).

So the same repo is named two ways, many tools cannot reach a repo on the other forge, and each tool parses its own target.

I measured this over my own agent sessions: 10,574 madtea calls. 4,204 used dir=, 3,141 used owner_repo=, and 3,101 used no target. One repo got 331 calls by owner_repo= and 234 by dir=. 6 of 7 cross-forge owner_repo= calls returned a 404 from the wrong forge. 16 madt_issues calls with dir= hit the ADR 0027 refusal for an action outside the report carve-out.

Decision

  • No target parameter: the call acts on the launch repo, read from its own origin. This does not change.
  • Every other call names its target with ONE field, repo=, on every tool that acts on a repo. repo= takes a local path (a clone or a linked worktree; madtea reads the forge and owner/name from its origin) or host/owner/name for a forge repo with no checkout, for example git.cheshirecube.cloud/sixfold-space/madtea. owner_repo= and dir= go away.
  • One shared resolver turns a target into the host, the owner/name, and the checkout when there is one. No tool parses its own target. The CLI exposes the same resolver, so a hook resolves a target the same way the tools do.
  • Credentials are the stored forge credentials of the user, looked up by host. The launch-scope credential boundary of ADR 0027 adds friction and no protection: the same user owns every stored credential, and these guards detect accidents, they do not defend against a compromised client (ADR 0019).
  • The foreign-primary rule of ADR 0019 stays: madtea never changes a foreign primary checkout, and work on a foreign repo goes through a linked worktree.

#468, #485, and #638 each describe one part of this problem.

Acceptance

  • Every tool that acts on a repo takes repo=, and no other target field.
  • One resolver, with tests for: no target, a local clone, a linked worktree, and host/owner/name on each of two forges.
  • A call to the second forge authenticates with the stored credential for that host.
  • A new ADR supersedes ADR 0027, and the Status line of each points to the other.
  • madt_help repo-scope documents the one form.
  • The release notes mark the change as breaking.
This supersedes ADR 0027 (launch scope is the credential boundary). ADR 0019 (foreign primary checkouts are immutable) stays. The PR that builds this adds the superseding ADR. ## Problem I work across two forges every day. madtea names a target repo in three ways, and each tool supports its own subset: - no parameter: the repo of the launch directory; - `owner_repo=`: a repo on the launch forge only, because it has no host (#468); - `dir=`: a local checkout, but only on some tools (#485, #638). So the same repo is named two ways, many tools cannot reach a repo on the other forge, and each tool parses its own target. I measured this over my own agent sessions: 10,574 madtea calls. 4,204 used `dir=`, 3,141 used `owner_repo=`, and 3,101 used no target. One repo got 331 calls by `owner_repo=` and 234 by `dir=`. 6 of 7 cross-forge `owner_repo=` calls returned a 404 from the wrong forge. 16 `madt_issues` calls with `dir=` hit the ADR 0027 refusal for an action outside the report carve-out. ## Decision - No target parameter: the call acts on the launch repo, read from its own origin. This does not change. - Every other call names its target with ONE field, `repo=`, on every tool that acts on a repo. `repo=` takes a local path (a clone or a linked worktree; madtea reads the forge and owner/name from its origin) or `host/owner/name` for a forge repo with no checkout, for example `git.cheshirecube.cloud/sixfold-space/madtea`. `owner_repo=` and `dir=` go away. - One shared resolver turns a target into the host, the owner/name, and the checkout when there is one. No tool parses its own target. The CLI exposes the same resolver, so a hook resolves a target the same way the tools do. - Credentials are the stored forge credentials of the user, looked up by host. The launch-scope credential boundary of ADR 0027 adds friction and no protection: the same user owns every stored credential, and these guards detect accidents, they do not defend against a compromised client (ADR 0019). - The foreign-primary rule of ADR 0019 stays: madtea never changes a foreign primary checkout, and work on a foreign repo goes through a linked worktree. ## Related #468, #485, and #638 each describe one part of this problem. ## Acceptance - Every tool that acts on a repo takes `repo=`, and no other target field. - One resolver, with tests for: no target, a local clone, a linked worktree, and `host/owner/name` on each of two forges. - A call to the second forge authenticates with the stored credential for that host. - A new ADR supersedes ADR 0027, and the Status line of each points to the other. - `madt_help repo-scope` documents the one form. - The release notes mark the change as breaking.
Author
Owner

One more forge-bound path for the list above, which the body does not mention: madt_api_call. The raw passthrough takes no repo or dir target (APICallInput in internal/mcp/tools_api.go) and resolves the launch forge's own credentials (config.GetCredentials), so a call to a repo on the other forge returns a 404 from the launch forge. I confirmed this live: a GET of an issue on the other forge came back 404 from the launch forge.

repo= is specified for "every tool that acts on a repo", but madt_api_call acts on an endpoint, not a repo, so it falls outside that wording. Worth deciding whether the escape hatch also takes a host selector, so a raw call can reach the other forge, or stays launch-forge-only by design. Relates to #668.

One more forge-bound path for the list above, which the body does not mention: `madt_api_call`. The raw passthrough takes no repo or dir target (`APICallInput` in `internal/mcp/tools_api.go`) and resolves the launch forge's own credentials (`config.GetCredentials`), so a call to a repo on the other forge returns a 404 from the launch forge. I confirmed this live: a GET of an issue on the other forge came back 404 from the launch forge. `repo=` is specified for "every tool that acts on a repo", but `madt_api_call` acts on an endpoint, not a repo, so it falls outside that wording. Worth deciding whether the escape hatch also takes a host selector, so a raw call can reach the other forge, or stays launch-forge-only by design. Relates to #668.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#685
No description provided.