SCM Projects Wrapper Tool Flow-Through Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Implementation subagents run sonnet; the merge subagent runs opus. Develop in a worktree off
main; build/deploy frommainonly. Steps use checkbox (- [ ]) syntax.
Goal
Make the three new SCM-projects MCP tools (propose_issue, review_verdict, pr_outcome) reach agents running inside the tatara-claude-code-wrapper, and prove they do. The wrapper does NOT enumerate MCP tools: RegisterTataraMCP (internal/bootstrap/mcp_register.go:8) runs tatara mcp-config <workspace>, which writes a .mcp.json entry {"command":"tatara","args":["mcp"]}. The tatara mcp subcommand serves every tool returned by OperatorTools(). So the new tools flow in automatically the moment the baked tatara binary is rebuilt from the SCM-projects cli release. The wrapper's only real work is (T1) re-pin the baked tatara-cli version, (T2) add an integration test that runs the rebuilt tatara mcp binary and asserts tools/list advertises the three new names so the flow-through can never silently regress, and (T3) bump the image + chart appVersion with MEMORY/ROADMAP notes.
Architecture
wrapper boot (internal/bootstrap)
RegisterTataraMCP(workspace, run) -- UNCHANGED: run("tatara","mcp-config",workspace)
|
v
tatara mcp-config writes /workspace/.mcp.json:
{"mcpServers":{"tatara":{"command":"tatara","args":["mcp"]}}}
|
v
claude launches `tatara mcp` (stdio MCP server)
|
v
tatara mcp serves OperatorTools() -- 12 tools after SCM-projects cli release
(9 existing + propose_issue + review_verdict + pr_outcome)
The wrapper baked the tatara binary at TATARA_CLI_VERSION=0.4.0 (MEMORY 2026-06-07). The SCM-projects work adds 3 operator tools to the cli and ships them in cli 0.5.0. This plan re-pins the wrapper to 0.5.0 (Makefile + Dockerfile) and locks the flow-through with a binary-level integration test.
Tech Stack
Go 1.25 (go.mod go 1.25.0), stdlib log/slog (JSON), testify/require, os/exec for the binary integration test, encoding/json for MCP JSON-RPC framing. Docker buildx (multi-stage; COPY --from=tatara-cli). Helm chart charts/tatara-claude-code-wrapper. No new Go module dependencies: tatara-cli is a baked binary, NOT a go.mod require.
File Structure¶
| File | Create/Modify | Responsibility |
|---|---|---|
internal/bootstrap/mcp_register.go | Modify (comment only) | Document that tool flow-through is binary-driven, not enumerated; no logic change. |
internal/bootstrap/mcp_flowthrough_test.go | Create | Integration test: run the baked tatara mcp binary, drive an MCP initialize + tools/list exchange over stdio, assert the response advertises propose_issue, review_verdict, pr_outcome. Skips cleanly when the binary is absent (local dev) but runs in CI where the image stage provides it. |
Dockerfile | Modify (ARG TATARA_CLI_VERSION=latest -> 0.5.0) | Pin the baked tatara-cli image tag to the SCM-projects cli release. |
Makefile | Modify (TATARA_CLI_VERSION ?= latest -> 0.5.0) | Default the build-arg to the pinned cli release so make and CI bake the same version. |
charts/tatara-claude-code-wrapper/Chart.yaml | Modify (version + appVersion) | Chart 0.1.7 -> 0.1.8, appVersion 0.1.6 -> 0.1.7 for the re-pinned image. |
MEMORY.md | Modify (append) | Dated entry: tools flow through via tatara mcp, not enumeration; cli re-pinned to 0.5.0. |
ROADMAP.md | Modify (mark done / re-scope) | Note SCM-projects tool flow-through shipped. |
Task 1: Re-pin the baked tatara-cli to the SCM-projects release (0.5.0)¶
The SCM-projects sequencing (docs/superpowers/specs/2026-06-09-scm-projects-pr-reactions-design.md step 2-3) ships the 3 new operator tools in tatara-cli; current baked version is 0.4.0 (MEMORY 2026-06-07), the SCM-projects release is 0.5.0. tatara-cli is a baked binary (Dockerfile stage 2 FROM harbor.../tatara-cli:${TATARA_CLI_VERSION}), NOT a go.mod require, so this is a Dockerfile + Makefile pin only -- no go.mod change.
Files: - Modify: Dockerfile (line ARG TATARA_CLI_VERSION=latest) - Modify: Makefile (line TATARA_CLI_VERSION ?= latest)
- Step 1: Pin the Dockerfile ARG. Edit
Dockerfile. Change the build arg default fromlatestto the pinned release so even an un-overriddendocker buildbakes the correct cli:
(Replace the existing ARG TATARA_CLI_VERSION=latest near the top of the file. The stage-2 FROM harbor.szymonrichert.pl/containers/tatara-cli:${TATARA_CLI_VERSION} AS tatara-cli already consumes it; do not touch the FROM line.)
- Step 2: Pin the Makefile default. Edit
Makefile. Change:
(Replace TATARA_CLI_VERSION ?= latest. The docker buildx build ... --build-arg TATARA_CLI_VERSION=$(TATARA_CLI_VERSION) line already passes it through; do not touch that line. ?= keeps it overridable for a manual override build.)
- Step 3: Verify the pin is consistent. Run:
Expected: both files show 0.5.0 (Dockerfile ARG TATARA_CLI_VERSION=0.5.0, Makefile TATARA_CLI_VERSION ?= 0.5.0). No latest remaining for this arg.
- Step 4: Commit the pin.
git add Dockerfile Makefile && git commit -m "chore(image): pin baked tatara-cli to 0.5.0 (SCM-projects tools)"
Task 2: Lock the tool flow-through with a binary integration test¶
RegisterTataraMCP does not enumerate tools, so a unit test against it (mcp_register_test.go) cannot prove the new tools reach agents. The only thing that proves flow-through is the baked tatara mcp binary advertising the names. Add an integration test that locates tatara on PATH (or /usr/local/bin/tatara, where the Dockerfile copies it), runs tatara mcp, performs the MCP stdio handshake (initialize then tools/list), and asserts the three names appear. The test skips when the binary is absent so local go test stays green; CI builds the image stage that provides the binary and runs it there.
Files: - Create: internal/bootstrap/mcp_flowthrough_test.go - Modify: internal/bootstrap/mcp_register.go (doc comment only, Step 5) - Test path: internal/bootstrap/mcp_flowthrough_test.go
- Step 1: Write the failing flow-through test. Create
internal/bootstrap/mcp_flowthrough_test.gowith the full integration test. It locates the binary, spawnstatara mcp, sends a newline-delimited JSON-RPCinitializethentools/list, reads the responses, and asserts the three tool names are advertised:
package bootstrap_test
import (
"bufio"
"context"
"encoding/json"
"io"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// tataraBinary returns the path to the baked tatara CLI, preferring the
// Dockerfile install location, falling back to PATH. Empty string => not found.
func tataraBinary() string {
const baked = "/usr/local/bin/tatara"
if _, err := exec.LookPath(baked); err == nil {
return baked
}
if p, err := exec.LookPath("tatara"); err == nil {
return p
}
if abs, err := filepath.Abs(baked); err == nil {
if _, err := exec.LookPath(abs); err == nil {
return abs
}
}
return ""
}
type rpcReq struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params any `json:"params,omitempty"`
}
type toolsListResult struct {
Result struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
} `json:"result"`
}
// TestTataraMCP_AdvertisesScmProjectTools proves the baked tatara CLI's `mcp`
// server advertises the SCM-projects tools that the wrapper relies on flowing
// through automatically (RegisterTataraMCP runs `tatara mcp-config`, which wires
// `tatara mcp`; the wrapper never enumerates tools, so this is the only guard
// against a silent regression when the baked cli version is bumped).
func TestTataraMCP_AdvertisesScmProjectTools(t *testing.T) {
bin := tataraBinary()
if bin == "" {
t.Skip("tatara binary not found; runs in the image stage / CI where it is baked")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, bin, "mcp")
stdin, err := cmd.StdinPipe()
require.NoError(t, err)
stdout, err := cmd.StdoutPipe()
require.NoError(t, err)
require.NoError(t, cmd.Start())
defer func() {
_ = stdin.Close()
_ = cmd.Wait()
}()
send := func(r rpcReq) {
b, err := json.Marshal(r)
require.NoError(t, err)
_, err = stdin.Write(append(b, '\n'))
require.NoError(t, err)
}
send(rpcReq{JSONRPC: "2.0", ID: 1, Method: "initialize", Params: map[string]any{
"protocolVersion": "2024-11-05",
"capabilities": map[string]any{},
"clientInfo": map[string]any{"name": "wrapper-flowthrough-test", "version": "1"},
}})
send(rpcReq{JSONRPC: "2.0", ID: 2, Method: "tools/list"})
names := collectToolNames(t, stdout)
for _, want := range []string{"propose_issue", "review_verdict", "pr_outcome"} {
require.Containsf(t, names, want, "tatara mcp must advertise %q; got %v", want, names)
}
}
// collectToolNames reads newline-delimited JSON-RPC responses until it sees the
// tools/list result (the one carrying a non-empty tools array), returning the
// advertised tool names.
func collectToolNames(t *testing.T, r io.Reader) []string {
t.Helper()
sc := bufio.NewScanner(r)
sc.Buffer(make([]byte, 0, 64*1024), 1<<20)
for sc.Scan() {
line := sc.Bytes()
if len(line) == 0 {
continue
}
var res toolsListResult
if err := json.Unmarshal(line, &res); err != nil {
continue
}
if len(res.Result.Tools) == 0 {
continue
}
names := make([]string, 0, len(res.Result.Tools))
for _, tl := range res.Result.Tools {
names = append(names, tl.Name)
}
return names
}
require.NoError(t, sc.Err())
t.Fatal("tatara mcp produced no tools/list result")
return nil
}
- Step 2: Run the test, expect SKIP locally / FAIL semantics in CI. Run:
Expected locally (no baked binary on a dev box): --- SKIP: TestTataraMCP_AdvertisesScmProjectTools (tatara binary not found ...). This confirms the test compiles, the skip guard fires, and the suite stays green. In the image stage (where /usr/local/bin/tatara is the 0.4.0 binary, pre-rebuild) the same test would FAIL with tatara mcp must advertise "propose_issue"; got [...9 names...] -- that is the failing-state proof the test is meaningful. After Task 1 re-pins to 0.5.0 and the image is rebuilt, it PASSES.
- Step 3: Verify against a locally-built SCM-projects cli (optional but recommended before merge). If a
0.5.0(or HEAD-of-SCM-projects)tatarabinary is available, put it onPATHand re-run to see a real PASS, not just a skip:
go build -o /tmp/ttbin/tatara ../tatara-cli/cmd/tatara && PATH="/tmp/ttbin:$PATH" go test ./internal/bootstrap/ -run TestTataraMCP_AdvertisesScmProjectTools -v
Expected: --- PASS: TestTataraMCP_AdvertisesScmProjectTools. If it FAILs with the 9-tool list, the cli release has not landed the 3 tools yet -- block the wrapper merge until cli 0.5.0 is published (sequencing: operator -> cli -> wrapper).
- Step 4: Run the full bootstrap suite to confirm no regression. Run:
Expected: all existing tests PASS (TestRegisterTataraMCP_RunsMcpConfig, TestRegisterTataraMCP_PropagatesError, bootstrap/enforce/namespace/claudejson tests) and the new test SKIPs (or PASSes with a baked binary). No FAILs.
- Step 5: Document the flow-through in
mcp_register.go(comment only). Editinternal/bootstrap/mcp_register.go. Replace the existing doc comment so the next reader knows tools are NOT enumerated here and where the guard lives. Change:
// RegisterTataraMCP merges the tatara MCP server into the workspace .mcp.json
// via the tatara CLI's own mcp-config command.
func RegisterTataraMCP(workspace string, run CmdRunner) error {
return run("tatara", "mcp-config", workspace)
}
to:
// RegisterTataraMCP wires the tatara MCP server into the workspace .mcp.json
// via the tatara CLI's own mcp-config command. This writes a single entry
// {"command":"tatara","args":["mcp"]}; the set of MCP tools agents see is
// whatever `tatara mcp` serves (OperatorTools()), NOT enumerated here. New
// operator tools therefore flow through automatically once the baked tatara
// binary is rebuilt; mcp_flowthrough_test.go guards that against regression.
func RegisterTataraMCP(workspace string, run CmdRunner) error {
return run("tatara", "mcp-config", workspace)
}
- Step 6: gofmt + vet + lint clean. Run:
gofmt -l internal/bootstrap/ && go vet ./internal/bootstrap/ && golangci-lint run ./internal/bootstrap/...
Expected: gofmt -l prints nothing (no unformatted files), go vet and golangci-lint exit 0 with no findings.
- Step 7: Commit the flow-through guard.
git add internal/bootstrap/mcp_flowthrough_test.go internal/bootstrap/mcp_register.go && git commit -m "test(bootstrap): assert tatara mcp advertises propose_issue/review_verdict/pr_outcome"
Task 3: Bump chart appVersion + chart version; MEMORY/ROADMAP notes¶
The re-pinned baked cli is a new image, so the chart appVersion (image tag) and version (chart revision) both move. Record the decision in MEMORY (so the next agent knows tool flow-through is binary-driven and the cli is pinned at 0.5.0) and update ROADMAP.
Files: - Modify: charts/tatara-claude-code-wrapper/Chart.yaml (version: 0.1.7 -> 0.1.8, appVersion: "0.1.6" -> "0.1.7") - Modify: MEMORY.md (append dated entry) - Modify: ROADMAP.md (note shipped)
- Step 1: Bump the chart. Edit
charts/tatara-claude-code-wrapper/Chart.yaml. Change:
to:
- Step 2: helm lint clean. Run:
Expected: 1 chart(s) linted, 0 chart(s) failed. No errors about version/appVersion format.
- Step 3: Append the MEMORY entry. Edit
MEMORY.md. Add under the dated-decisions section:
- 2026-06-09 - **SCM-projects MCP tools flow through, not enumerated.** RegisterTataraMCP only runs `tatara mcp-config`, which wires `{command:tatara,args:[mcp]}`; agents see whatever `tatara mcp` serves (OperatorTools()). propose_issue/review_verdict/pr_outcome arrived for free once the baked cli was re-pinned 0.4.0 -> 0.5.0 (Dockerfile ARG + Makefile default). mcp_flowthrough_test.go runs the binary's MCP tools/list and asserts the 3 names so a future cli pin can't silently drop them. Chart 0.1.8 / appVersion 0.1.7.
- Step 4: Update ROADMAP. Edit
ROADMAP.md. Remove (or strike) any pending "SCM-projects tools reach agents" item if present; otherwise add a shipped note at the top of the relevant section:
- SHIPPED 2026-06-09 - SCM-projects MCP tools (propose_issue/review_verdict/pr_outcome) reach agents via the re-pinned baked tatara-cli 0.5.0; flow-through asserted by mcp_flowthrough_test.go.
- Step 5: Commit the bump + notes.
git add charts/tatara-claude-code-wrapper/Chart.yaml MEMORY.md ROADMAP.md && git commit -m "chore(chart): bump 0.1.8/appVersion 0.1.7 for re-pinned cli 0.5.0; MEMORY/ROADMAP"
Verification before completion¶
Before claiming done, run and confirm (per superpowers:verification-before-completion):
go test ./internal/bootstrap/ -v-- all PASS, new test SKIPs locally (or PASSes with a baked binary on PATH).- The real flow-through proof (the load-bearing check): build the SCM-projects cli binary and run the wrapper test against it -- Expected:
go build -o /tmp/ttbin/tatara ../tatara-cli/cmd/tatara && PATH="/tmp/ttbin:$PATH" go test ./internal/bootstrap/ -run TestTataraMCP_AdvertisesScmProjectTools -v--- PASS, advertised names includepropose_issue,review_verdict,pr_outcome. If only 9 tools show, the cli release has not landed -- do NOT merge the wrapper; the operator -> cli -> wrapper sequence is not satisfied. grep -n TATARA_CLI_VERSION Dockerfile Makefile-- both0.5.0, nolatest.gofmt -l internal/bootstrap/empty;go vet ./...andgolangci-lint run ./...clean.helm lint charts/tatara-claude-code-wrapper-- 0 failed.pre-commit run --all-files-- all hooks pass.
Then request code review (superpowers:requesting-code-review), apply critical/high fixes, merge the worktree back to main, clean up the worktree, and build/push the image from main with make build TATARA_CLI_VERSION=0.5.0 (the pin is the default, so a bare make build also bakes 0.5.0). Build only after cli 0.5.0 is published to Harbor, since stage 2 COPY --from=tatara-cli pulls it.