feat(mcp): extract a DirScope embeddable — make dir= as modular as RepoScope, with an enforced tool matrix #62

Closed
opened 2026-07-14 20:40:07 +00:00 by hexajon · 0 comments
hexajon commented 2026-07-14 20:40:07 +00:00 (Migrated from codeberg.org)

Problem

owner_repo is already a reusable modular bit: RepoScope (internal/mcp/helpers.go:31) is embedded in ~25 tool input structs, with cwd-mismatch guarding centralized in repoguard.go and per-target forge/credential resolution landed by ADR 0025.

dir= is only half-modular. The machinery is shared — resolveLocalRepoDir (internal/mcp/dirguard.go:69), the foreign-repo guards (internal/mcp/foreignrepo.go: IsForeignRepoDir, IsForeignPrimaryCheckoutDir), and config.GetConfigForDir (ADR 0025 T5) — but there is no embeddable counterpart to RepoScope. Every dir-taking tool declares its own ad-hoc Dir string field (tools_auth.go:29, the localgit/worktrees inputs, orchestrate's pull shape at tools_workflow.go:22), and tools that never declared the field simply don't have it. That is exactly how the #14 gap happened: orchestrate's reconcile shape has no dir=/owner_repo=, so the merge-failure rail is unfollowable for foreign-repo PRs.

Ask

  1. DirScope embeddable, symmetric with RepoScope: the schema fragment (dir param + doc string) plus ONE shared resolve-and-guard entry point that returns the resolved dir, its foreign/primary/worktree classification, and the dir-resolved config — so a tool opts in with an embed line and a helper call, not a re-implementation.
  2. Tool-matrix audit, done once: classify every MCP tool — local-tree-touching → DirScope; remote-API → RepoScope; both where the operation spans layers (finish, orchestrate, pull/push). Per-tool semantics stay a deliberate decision, not blanket: some tools must document or refuse a scope rather than silently accept it (e.g. madt_search already documents that owner_repo does not filter it).
  3. Parity-test enforcement (internal/parity/ culture): the audited matrix is pinned by a test, so a new tool that touches the local tree without embedding DirScope (or takes an undeclared scope) fails CI rather than shipping the next #14-style gap.
  4. Migrate existing ad-hoc Dir string fields onto the embeddable in the same change (no schema-visible rename — the JSON param stays dir).

Acceptance

  • DirScope exists and every current dir-taking tool embeds it; grep shows no ad-hoc Dir string fields left in tool input structs.
  • The scope matrix is recorded (doc table or the test itself) and enforced by a parity test in the gate.
  • No user-visible schema change for existing tools (param name, optionality, and semantics unchanged); conformance stays green.
  • #14 (orchestrate reconcile dir=/owner_repo) becomes an embed-and-wire change on top of this.

Refs #14, #18. Groundwork landed by ADR 0025 (#30).

## Problem `owner_repo` is already a reusable modular bit: `RepoScope` (internal/mcp/helpers.go:31) is embedded in ~25 tool input structs, with cwd-mismatch guarding centralized in repoguard.go and per-target forge/credential resolution landed by ADR 0025. `dir=` is only half-modular. The machinery is shared — `resolveLocalRepoDir` (internal/mcp/dirguard.go:69), the foreign-repo guards (internal/mcp/foreignrepo.go: `IsForeignRepoDir`, `IsForeignPrimaryCheckoutDir`), and `config.GetConfigForDir` (ADR 0025 T5) — but there is no embeddable counterpart to `RepoScope`. Every dir-taking tool declares its own ad-hoc `Dir string` field (tools_auth.go:29, the localgit/worktrees inputs, orchestrate's pull shape at tools_workflow.go:22), and tools that never declared the field simply don't have it. That is exactly how the #14 gap happened: orchestrate's reconcile shape has no `dir=`/`owner_repo=`, so the merge-failure rail is unfollowable for foreign-repo PRs. ## Ask 1. **`DirScope` embeddable**, symmetric with `RepoScope`: the schema fragment (`dir` param + doc string) plus ONE shared resolve-and-guard entry point that returns the resolved dir, its foreign/primary/worktree classification, and the dir-resolved config — so a tool opts in with an embed line and a helper call, not a re-implementation. 2. **Tool-matrix audit, done once**: classify every MCP tool — local-tree-touching → `DirScope`; remote-API → `RepoScope`; both where the operation spans layers (finish, orchestrate, pull/push). Per-tool semantics stay a deliberate decision, not blanket: some tools must document or refuse a scope rather than silently accept it (e.g. `madt_search` already documents that `owner_repo` does not filter it). 3. **Parity-test enforcement** (internal/parity/ culture): the audited matrix is pinned by a test, so a new tool that touches the local tree without embedding `DirScope` (or takes an undeclared scope) fails CI rather than shipping the next #14-style gap. 4. Migrate existing ad-hoc `Dir string` fields onto the embeddable in the same change (no schema-visible rename — the JSON param stays `dir`). ## Acceptance - `DirScope` exists and every current dir-taking tool embeds it; grep shows no ad-hoc `Dir string` fields left in tool input structs. - The scope matrix is recorded (doc table or the test itself) and enforced by a parity test in the gate. - No user-visible schema change for existing tools (param name, optionality, and semantics unchanged); conformance stays green. - #14 (orchestrate reconcile dir=/owner_repo) becomes an embed-and-wire change on top of this. Refs #14, #18. Groundwork landed by ADR 0025 (#30).
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#62
No description provided.