| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 4 | 2 | 0% |
| Commands | 0 | 0 | 4 | 0% |
| Section tags | 2 | 2 | 1 | 40% |
What each file covers
Sections
0 shared · 4 only in A · 2 only in B- − Surface parity (CLI / MCP / share)
- − Knockout surfaces live-resolve — never read the skeleton (the #1 recurring bug)
- − Format opts invariant
- − Regression tests
- + CLAUDE.md
- + Claude Code specifics
Commands
0 shared · 0 only in A · 4 only in B- + pnpm -F @claudinho/mcp build
- + pnpm -r typecheck
- + pnpm lint
- + gh run watch <id> --exit-status
Section tags
2 shared · 2 only in A · 1 only in B- − testing-strategy
- − do-not
- + agent-behaviour
- lint-format
- performance
Line diff
arturogarrido/claudinho · .cursor/rules/surface-parity.mdc
@@ −1 @@
1---
2description: CLI, MCP, and share must pass the same format opts (tz, locale, flags)
3globs: packages/{cli,mcp,core}/**/*
4alwaysApply: false
5---
6
7# Surface parity (CLI / MCP / share)
8
9Any user-visible formatter change must land on **all three surfaces** before merge:
10
11| Surface | Verify |
12|---------|--------|
13| CLI | `packages/cli` command + `packages/cli/test/` |
14| MCP | `toolGet*` in `packages/mcp/src/tools.ts` + `packages/mcp/test/tools.test.ts` |
15| Share | `formatShare*` + `packages/mcp/test/share.test.ts` |
16
17`scripts/release-qa.sh` exercises CLI + share rendering across locales and timezones; MCP arg threading is **not** rendered there — guard it with unit tests.
18
19## Knockout surfaces live-resolve — never read the skeleton (the #1 recurring bug)
20
21The bundled schedule's knockout slots are **resultless placeholders** (codes like `2A`/`2B`, flag `🏳️`). So a team-facing surface that reads the static bundle is **silently** blind to a confirmed knockout tie — no crash, no wrong data, just a stale placeholder. **Knockout live-resolve regressions** shipped repeatedly in different surfaces (root cause: skeleton reads, cache gaps, or both):
22
23- v0.8.2 — R32 pre-draw seeds rendered static, didn't live-resolve.
24- v0.8.6 — bracket third-place slots never read the merged live `match.home/away`.
25- v0.8.7 — `next` / `share next` / MCP next-fixture resolvers read only the static bundle.
26- v0.8.8 — statusline next-match countdown read the static bundle (`🏳️ vs 🏳️` / `⚽ —` in knockouts).
27- v0.8.9 — empty fixtures cache stamped the full 15min TTL at phase boundaries; stale-cache `live · syncing…` still read the skeleton.
28
29**Rule:** every team-facing surface must reach the live overlay (`getBracket` / `getNextFixtureForTeam` — the same `fetchWindow` the bracket uses), fail closed on outage, and attribute the provider only when the overlay served the chosen fixture. **Exception — statusline:** hot path cannot fetch; the cold-path refresher calls `getKnockoutFixtures` and caches to `CacheState.fixtures`; `renderPrompt` merges that slice over the bundle (countdown **and** `live · syncing…`), never leaking 🏳️. **Exception — MCP `fixtures://{date}` resource:** deliberately static ("Static fixture list"; a resource URI carries no tz and gets no live overlay) — do not "fix" it to live-resolve without a maintainer decision; agents use `get_today`/`get_bracket` for resolved pairings.
30
31| Surface | Entry point | Must |
32|---------|-------------|------|
33| CLI `bracket` / `next` | `cmdBracket` / `cmdNext` | call `getBracket` / `getNextFixtureForTeam` |
34| CLI `share bracket` / `share next` | `cmdShare` | same |
35| MCP `get_bracket` / `get_next_fixture` | `toolGetBracket` / `toolGetNextFixture` | same |
36| MCP `get_share_snippet{bracket,next}` | `toolGetShareSnippet` | same |
37| **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. |
38
39**Executable guard:** `packages/{cli,mcp}/test/knockout-surface-coverage.test.ts` pins one fake resolved tie (Mexico vs Ecuador) and asserts every surface renders the real nations, not `🏳️` (statusline from a seeded cache). **`statusline.test.ts` / `refresh.test.ts`** cover syncing + empty-cache TTL. **Adding a team-facing surface? Add it to that test and this table.**
40
41## Format opts invariant
42
43If a core formatter accepts `tz`, `locale`, `flags`, or `date`, **every wrapper** passes them from `CliConfig` / `CommonOpts`.
44
45```typescript
46// ❌ BAD — locale only; tz silently drops to server local
47formatBracketList(view, { footer: false, locale: args.lang });
48
49// ✅ GOOD
50formatBracketList(view, { footer: false, locale: args.lang, tz: args.tz });
51// or reuse fmtOpts(args) where shapes align
52```
53
54## Regression tests
55
56When adding a format option (e.g. `date: true` on `formatKickoff`):
57
58- Add a **core** unit test for the option.
59- Add an **MCP tool test** proving `tz` changes output (e.g. UTC vs `America/Mexico_City` on a cross-midnight kickoff).
60- Update share tests if `formatShareBracket` / compact lines are affected.
61
arturogarrido/claudinho · CLAUDE.md
@@ +1 @@
1# CLAUDE.md
2
3This project uses **AGENTS.md** as the primary agent guide. Read it first:
4
5@AGENTS.md
6
7## Claude Code specifics
8
9- The statusline command must return in **<150ms** and **never** hit the network on the hot path — read from the local micro-cache.
10- Local MCP dev loop:
11 ```bash
12 pnpm -F @claudinho/mcp build
13 claude mcp add claudinho-dev -- node packages/mcp/dist/index.js
14 ```
15- When changing shared types, update `@claudinho/core` and run `pnpm -r typecheck` before committing.
16- Run `pnpm lint` (Biome) before committing; CI gates on it. The setup is lint-only (no formatter) — keep style consistent with the surrounding code.
17- **Before declaring any change "done," run the "Pre-PR self-review" rubric in `AGENTS.md`** — verify external API shapes against a *real* response (fixtures included); apply the change to every surface (CLI text **and** `--json`, MCP `data` **and** text, READMEs); audit against the Hard Constraints (existing code too); do an adversarial failure-mode pass (fail-closed; never cache transient errors); and bound default-on latency. For money/legal/external-API changes, do an independent reviewer pass and self-classify findings **P1/P2/P3**.
18- **After any push to a branch with CI, always watch the run and confirm it's green** (`gh run watch <id> --exit-status`); report the per-job result. Don't consider a push "done" until CI passes.
19
@@ −1 +1 @@
1−---
2−description: CLI, MCP, and share must pass the same format opts (tz, locale, flags)
3−globs: packages/{cli,mcp,core}/**/*
4−alwaysApply: false
5−---
1+# CLAUDE.md
62
7−# Surface parity (CLI / MCP / share)
3+This project uses **AGENTS.md** as the primary agent guide. Read it first:
84
9−Any user-visible formatter change must land on **all three surfaces** before merge:
5+@AGENTS.md
106
11−| Surface | Verify |
12−|---------|--------|
13−| CLI | `packages/cli` command + `packages/cli/test/` |
14−| MCP | `toolGet*` in `packages/mcp/src/tools.ts` + `packages/mcp/test/tools.test.ts` |
15−| Share | `formatShare*` + `packages/mcp/test/share.test.ts` |
7+## Claude Code specifics
168
17−`scripts/release-qa.sh` exercises CLI + share rendering across locales and timezones; MCP arg threading is **not** rendered there — guard it with unit tests.
18−
19−## Knockout surfaces live-resolve — never read the skeleton (the #1 recurring bug)
20−
21−The bundled schedule's knockout slots are **resultless placeholders** (codes like `2A`/`2B`, flag `🏳️`). So a team-facing surface that reads the static bundle is **silently** blind to a confirmed knockout tie — no crash, no wrong data, just a stale placeholder. **Knockout live-resolve regressions** shipped repeatedly in different surfaces (root cause: skeleton reads, cache gaps, or both):
22−
23−- v0.8.2 — R32 pre-draw seeds rendered static, didn't live-resolve.
24−- v0.8.6 — bracket third-place slots never read the merged live `match.home/away`.
25−- v0.8.7 — `next` / `share next` / MCP next-fixture resolvers read only the static bundle.
26−- v0.8.8 — statusline next-match countdown read the static bundle (`🏳️ vs 🏳️` / `⚽ —` in knockouts).
27−- v0.8.9 — empty fixtures cache stamped the full 15min TTL at phase boundaries; stale-cache `live · syncing…` still read the skeleton.
28−
29−**Rule:** every team-facing surface must reach the live overlay (`getBracket` / `getNextFixtureForTeam` — the same `fetchWindow` the bracket uses), fail closed on outage, and attribute the provider only when the overlay served the chosen fixture. **Exception — statusline:** hot path cannot fetch; the cold-path refresher calls `getKnockoutFixtures` and caches to `CacheState.fixtures`; `renderPrompt` merges that slice over the bundle (countdown **and** `live · syncing…`), never leaking 🏳️. **Exception — MCP `fixtures://{date}` resource:** deliberately static ("Static fixture list"; a resource URI carries no tz and gets no live overlay) — do not "fix" it to live-resolve without a maintainer decision; agents use `get_today`/`get_bracket` for resolved pairings.
30−
31−| Surface | Entry point | Must |
32−|---------|-------------|------|
33−| CLI `bracket` / `next` | `cmdBracket` / `cmdNext` | call `getBracket` / `getNextFixtureForTeam` |
34−| CLI `share bracket` / `share next` | `cmdShare` | same |
35−| MCP `get_bracket` / `get_next_fixture` | `toolGetBracket` / `toolGetNextFixture` | same |
36−| MCP `get_share_snippet{bracket,next}` | `toolGetShareSnippet` | same |
37−| **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. |
38−
39−**Executable guard:** `packages/{cli,mcp}/test/knockout-surface-coverage.test.ts` pins one fake resolved tie (Mexico vs Ecuador) and asserts every surface renders the real nations, not `🏳️` (statusline from a seeded cache). **`statusline.test.ts` / `refresh.test.ts`** cover syncing + empty-cache TTL. **Adding a team-facing surface? Add it to that test and this table.**
40−
41−## Format opts invariant
42−
43−If a core formatter accepts `tz`, `locale`, `flags`, or `date`, **every wrapper** passes them from `CliConfig` / `CommonOpts`.
44−
45−```typescript
46−// ❌ BAD — locale only; tz silently drops to server local
47−formatBracketList(view, { footer: false, locale: args.lang });
48−
49−// ✅ GOOD
50−formatBracketList(view, { footer: false, locale: args.lang, tz: args.tz });
51−// or reuse fmtOpts(args) where shapes align
52−```
53−
54−## Regression tests
55−
56−When adding a format option (e.g. `date: true` on `formatKickoff`):
57−
58−- Add a **core** unit test for the option.
59−- Add an **MCP tool test** proving `tz` changes output (e.g. UTC vs `America/Mexico_City` on a cross-midnight kickoff).
60−- Update share tests if `formatShareBracket` / compact lines are affected.
9+- The statusline command must return in **<150ms** and **never** hit the network on the hot path — read from the local micro-cache.
10+- Local MCP dev loop:
11+ ```bash
12+ pnpm -F @claudinho/mcp build
13+ claude mcp add claudinho-dev -- node packages/mcp/dist/index.js
14+ ```
15+- When changing shared types, update `@claudinho/core` and run `pnpm -r typecheck` before committing.
16+- Run `pnpm lint` (Biome) before committing; CI gates on it. The setup is lint-only (no formatter) — keep style consistent with the surrounding code.
17+- **Before declaring any change "done," run the "Pre-PR self-review" rubric in `AGENTS.md`** — verify external API shapes against a *real* response (fixtures included); apply the change to every surface (CLI text **and** `--json`, MCP `data` **and** text, READMEs); audit against the Hard Constraints (existing code too); do an adversarial failure-mode pass (fail-closed; never cache transient errors); and bound default-on latency. For money/legal/external-API changes, do an independent reviewer pass and self-classify findings **P1/P2/P3**.
18+- **After any push to a branch with CI, always watch the run and confirm it's green** (`gh run watch <id> --exit-status`); report the per-job result. Don't consider a push "done" until CI passes.
6119
