surface-parity · git:20261013.2d5c100 · 2026-10-13 · sha256 5208a46de361b1d2
surface-parity git:20261013.2d5c100A
Immutable. This exact content never changes and is served at /api/v1/blob/5208a46de361b1d2.
---
description: CLI, MCP, and share must pass the same format opts (tz, locale, flags)
globs: packages/{cli,mcp,core,plugin}/**/*
alwaysApply: false
---
# Surface parity (CLI / MCP / share)
Any user-visible formatter change must land on **all three surfaces** before merge:
| Surface | Verify |
|---------|--------|
| CLI | `packages/cli` command + `packages/cli/test/` |
| MCP | `toolGet*` in `packages/mcp/src/tools.ts` + `packages/mcp/test/tools.test.ts` |
| Share | `formatShare*` + `packages/mcp/test/share.test.ts` |
One stated exception: the match timeline (core `timelineRows`) is CLI `match` (its text and `--json`'s `lines`) and MCP
`get_match` only; a share card prints none.
## Knockout surfaces live-resolve — never read the skeleton (the #1 recurring bug)
**Rule:** every team-facing surface must reach the live overlay (`getBracket` / `getNextFixtureForTeam` — on the bundled competition, the same `fetchWindow` the bracket uses; off it `next` reads discovery's schedule ahead and `bracket` makes no request), fail closed on outage, and attribute the provider only when the overlay served the chosen fixture.
| Surface | Entry point | Must |
|---------|-------------|------|
| CLI `bracket` / `next` | `cmdBracket` / `cmdNext` | call `getBracket` / `getNextFixtureForTeam` (a saved pin: `nextFixtureForPin`, which on the bundle IS `getNextFixtureForTeam` by code) |
| CLI `share bracket` / `share next` | `cmdShare` | same |
| MCP `get_bracket` / `get_next_fixture` | `toolGetBracket` / `toolGetNextFixture` | same (no `team`: the server's `CLAUDINHO_TEAM`, else the pin through `nextFixtureForPin`) |
| MCP `get_share_snippet{bracket,next}` | `toolGetShareSnippet` | same |
| **statusline** | `renderPrompt` + `runRefresh` | hot path, NO network — indirect: refresher caches `getKnockoutFixtures` → `CacheState.fixtures`; `renderPrompt` merges them, **failing closed to `⚽ —`** when the cache lacks the pairing; unresolved placeholders dropped in syncing (never 🏳️). Empty successful fetch uses a **short TTL** (~60s); provider errors keep prior cache. **`cmdPrompt` and `cmdHook`** both spawn fixtures refresh in knockout phase. |
| **`ambient --json`** | `cmdAmbient` → `ambientView` | the statusline's cache through the same reader: its `line` and `next` come from the resolved pairings and fail closed the same way (`⚽ —`, `next: null`); hot path, NO network, the same refresher trigger as `cmdPrompt`. |
| **Claude Code plugin's band and `/claudinho`** | `packages/plugin/hooks/register.tsx` (`ui.render` on `AbovePrompt`; `command.run` on `claudinho`) | the band renders `ambient --json`'s `line`; `/claudinho` relays its `line`, then its `attribution` and `disclaimer` from a view; both read no skeleton (no cache, no bundle: the CLI's object only), so they fail closed where `ambient --json` does; its coverage is the kit (`packages/plugin/hooks/*.test.tsx`, `claude plugin test`), not the two coverage tests. |
**Adding a team-facing surface? Add it to BOTH coverage tests and this table.**
## Format opts invariant
If a core formatter accepts `tz`, `locale`, `flags`, or `date`, **every wrapper** passes them from `CliConfig` / `CommonOpts`; a formatter that renders relative to now (a countdown) takes the request's ONE clock (`ctx.now` / `args.now`, else the adapter's read clock) as a required parameter, never a `new Date()` of its own.
The rest is `AGENTS.md` "Conventions" (canonical) and the guides that hold what this rule stated that no bullet does: `guides/conventions/surfaces.md` (the flair slot, what `release:qa` renders, the format opts example, the regression tests), `guides/conventions/selection.md` (the mode line and the pinned team), `guides/conventions/verdicts.md` (verdict forwarding), `guides/conventions/bundle-and-bracket.md` (the knockout live-resolve history and its exceptions) and `guides/conventions/stage-and-teams.md` (clubs and stages).