AGENTS.md
AGENTS.mdAGENTS.mdroot
Quality
84/100
Scores the file, not the repository.Length
4,154 words
17 headings · 2 code blocksRepository
27
— · pushed 0 days agoLast changed
yesterday
First indexed 2 days ago.1# AGENTS.md — Claudinho23Guidance for AI coding agents working **in this repository**. (Standard [AGENTS.md](https://agents.md); Claude Code reads it via `CLAUDE.md`.)45## What this is67Claudinho surfaces the 2026 men's football tournament in developer environments: a **CLI**, a **statusline** (Claude Code **and Cursor CLI** — `init claude`/`init cursor` one-step setup), an **MCP server** (also a **Cursor Marketplace plugin** + cursor.directory listing), a **score-aware hook** (Claude Code `UserPromptSubmit`), live scores/fixtures and **cumulative group standings** (from the provider's standings feed), a **knockout bracket**, read-only **prediction-market signals** (Polymarket odds — informational only), and **shareable terminal snippets** (`claudinho share` — copy-pasteable match **and standings** cards), and a **fuzzy team resolver** (`get_team` / `claudinho team` — a nation name or code → FIFA code + flag + group; the only **offline** MCP tool, `openWorldHint:false`; agents call it to resolve a user's team name into the code the other tools need). **Planned:** a desktop **notifier**, a precomputed **AI pundit**, and a small edge **gateway** that polls a data feed once for everyone.89## Stack1011- TypeScript, Node ≥ 20, ES modules12- pnpm workspaces (monorepo) · tsup (build) · vitest (test) · Biome (lint)13- MCP: `@modelcontextprotocol/sdk`14- Gateway (_planned_, not yet built): Cloudflare Workers + KV + D1 (`services/gateway`)1516## Layout1718| Path | Package | Role |19|---|---|---|20| `packages/core` | `@claudinho/core` | domain model, provider adapters, normalize, tz, emoji flags, i18n, static schedule, bracket, validators |21| `packages/cli` | `@claudinho/cli` | the `claudinho` binary (CLI + statusline + hook + cache/refresher) |22| `packages/mcp` | `@claudinho/mcp` | stdio MCP server |23| `.cursor-plugin/plugin.json` + `mcp.json` (repo root) | — | Cursor Marketplace **plugin** — wraps `@claudinho/mcp` for cursor.com/marketplace. Plugin version is decoupled from npm (`npx -y @claudinho/mcp` = latest); guarded by `packages/mcp/test/cursor-plugin.test.ts`. |24| `packages/core/src/data/schedule.2026.json` | — | static fixtures, bundled into clients (regenerate via `pnpm -F @claudinho/core gen:schedule`) |25| `packages/core/src/markets` | — | prediction-market sidecar: `MarketSignal` model, `isReliableMarketSignal` gate, copy bank, `PolymarketProvider` (read-only public data; event slugs auto-derived per fixture), `mapping.2026.json` (slug overrides only, ships empty) |26| `packages/core/src/share` | — | shareable-snippet formatters: `formatShareSnippet` (match cards) and `formatShareTable` (group-standings cards); disclaimer non-optional, no ANSI, English-only copy in v1 (except `share bracket`, localized) |27| `.cursor/rules/*.mdc` | — | Cursor rules — the public, contributor-facing engineering guardrails (release discipline, surface parity, bracket/schedule invariants) |28| `packages/notifier` | `@claudinho/notifier` | _planned_ — `claudinho watch` daemon |29| `services/gateway` | — | _planned_ — edge API + SSE + cron |3031## Commands3233- `pnpm install` — install deps34- `pnpm build` / `pnpm test` / `pnpm typecheck` / `pnpm lint` — across all packages (`lint` = Biome)35- `pnpm -F @claudinho/core test` — operate on a single package36- `pnpm release:qa` — pre-tag surface renderer (see "Release readiness")3738**Bumping `@biomejs/biome`?** Nothing to do — `biome.json`'s `$schema` points at39`./node_modules/@biomejs/biome/configuration_schema.json`, so it resolves to whatever version is40installed and is correct by construction after any bump. This replaced a version-pinned41`https://biomejs.dev/schemas/<version>/schema.json` URL that drifted on **six** consecutive bumps42(Dependabot rewrites only the root `package.json`, never `biome.json`). Each drift made Biome emit a43*"configuration schema version does not match the CLI version"* diagnostic on every lint run — three44of them (2.5.0 / 2.5.1 / 2.5.3) landed on `main` that way, since an info-level diagnostic fails45nothing. Only from the guard test onward did drift become a red build, which is what CI then caught.46`packages/core/test/biome-schema.test.ts` now guards the *shape*: it fails if anyone reintroduces a47pinned URL, or if a Biome upgrade moves the bundled schema file. (If you ever do edit `biome.json`,48hand-edit it — `biome migrate` reformats the whole config to tabs.)4950## Releasing5152Releases ship via `.github/workflows/publish.yml` on a `v*` tag, using npm **trusted53publishing** (OIDC) — **no `NPM_TOKEN`**. A trusted publisher is configured on npm for all54three packages (`@claudinho/core` · `cli` · `mcp`), all published **with provenance**.5556To cut a release:57581. Bump the version in the **three** `package.json` files (`packages/{cli,mcp,core}/package.json`).59 The cli `--version` and MCP `serverInfo.version` are injected from package.json at build time60 (tsup `define` → `process.env.CLAUDINHO_VERSION`), so there are no source constants to touch.61 **Also bump `packages/mcp/mcpb/manifest.json`** — the `.mcpb` desktop-extension manifest carries62 its own `version`; a vitest guard (`packages/mcp/test/manifest.test.ts`) fails if it drifts.632. `pnpm -r build && pnpm -r typecheck && pnpm -r test && pnpm lint` (CI re-runs these). **For any64 user-facing change, also run `pnpm release:qa`** and eyeball every surface against the live feed65 before tagging (see "Release readiness").663. Commit, then `git tag vX.Y.Z && git push origin vX.Y.Z`. The workflow gates (build/test/lint +67 tag==version), then `pnpm -r publish --provenance` ships all three via OIDC (versions already on68 npm are skipped) and auto-creates the GitHub Release (`gh release create --generate-notes`).6970**MCP-affecting releases** (anything that changes a tool's shape or description) also bump71`packages/mcp/server.json` and re-publish to the MCP Registry — from the repo root, pass the path72(`mcp-publisher publish packages/mcp/server.json`), since `mcp-publisher` defaults to `./server.json`73in the cwd and ours isn't at the root.7475**Adding or changing an MCP tool:** every tool declares an `outputSchema` and returns76`structuredContent` (`packages/mcp/src/server.ts`). A **new** tool must be added to77`OUTPUT_SCHEMAS` *and* to `packages/mcp/test/output-schema.test.ts` (which parses each handler's78`data` — healthy **and** degraded — against its schema). The output schemas are **hand-mirrored**79from the `@claudinho/core` types and kept permissive (`.passthrough()` on nested objects); the80`.strict()` top-level guard test is the safety net that catches schema/handler drift, so keep it81green. `build:mcpb` injects these schemas into the Smithery-scored `.mcpb` manifest.8283**Smithery (`.mcpb`) re-publish** — optional, and only when you want the Smithery listing to84track a new version: `pnpm -F @claudinho/mcp build:mcpb` (stages `mcpb/manifest.json` + the tsup85server + its external deps, smoke-tests it, then `mcpb pack` → `packages/mcp/dist/claudinho-<v>.mcpb`),86then `smithery mcp publish <that .mcpb> -n arturogarrido/claudinho`. The bundle pins the server at87that version (it runs the bundled code, not `npx`-latest), so the listing is a snapshot until you88re-publish.8990**Lessons (learned the hard way):**91- npm deprecated classic "Automation" tokens — use **trusted publishing**, not a token.92- A brand-new package can't have a trusted publisher pre-configured; its **first** publish must be93 a manual `pnpm -F @claudinho/<pkg> publish --access public` (enter OTP), then add its trusted94 publisher for later releases.95- Account 2FA set to "Authorization and writes" forces an OTP that CI can't supply; trusted96 publishing (OIDC) sidesteps it entirely.9798## Commit attribution (all agents)99100This repo is worked on by multiple AI coding agents. Commits produced with one are101**co-authored by the agent and the model** that wrote them, so `git log` (and the GitHub102contributors view) shows which agent — and which model — did the work. Add a trailer in the103**last paragraph** of the commit message, and credit the same in the PR body:104105```106Co-Authored-By: <Agent> (<Model>) <agent-no-reply-email>107```108109Use the model **actually in use**, not a hardcoded one. Examples, one per agent (Claude Code110uses Anthropic's no-reply address; Cursor and Codex follow the same pattern with their own):111112```113Co-Authored-By: Claude Code (Opus 4.8) <noreply@anthropic.com>114Co-Authored-By: Cursor (Composer 2.5) <...>115Co-Authored-By: Codex (GPT-5) <noreply@openai.com>116```117118## Reviewing PRs (all agents)119120- For PR reviews, first verify the local checkout matches the PR head before running gates:121 `gh pr view <n> --json headRefOid,headRefName` and `git rev-parse HEAD`. If they differ,122 check out or fast-forward the PR branch before reviewing.123- Review-only tasks are read-only unless the user explicitly asks for fixes. Lead with findings,124 classify them P1/P2/P3, and include tight file/line references. If there are no findings, say125 that plainly and list the checks run plus any residual risk.126- Do not treat a previous review, memory, or local branch name as current truth. Re-check the PR127 SHA, merge state, and CI status at the end of the review.128129## Codex / GPT specifics130131- Codex has no separate sidecar guide in this repo; `AGENTS.md` is its source of truth. Follow132 the shared sections here: "Reviewing PRs", "Pre-PR self-review", "Release readiness", and133 "Commit attribution".134- If Codex makes a commit, use the actual GPT model in the `Co-Authored-By` trailer; the example135 above is illustrative, not a hardcoded model name.136137## Conventions138139- Shared domain types live in `@claudinho/core` — never duplicate them.140- Every data vendor implements the `ProviderAdapter` interface — keep providers swappable. An adapter **FETCHES**; it must not interpret. Turning a payload into a domain type happens in `packages/core/src/trust/` and nowhere else (`packages/core/src/trust/espn.ts` is the model), because feed strings reach terminals, share cards, and Claude's context via the hook. Cache readers call the **same** constructors: when the live and cache paths had separate rules, every fix landed on one of them and left the other open — the asymmetry behind most of the security findings in #96. `core/test/trust-parity.test.ts` asserts the two agree. `ProviderAdapter` keeps its plain-array contract. Record-level refusal is local: malformed, duplicate, or truncated records are omitted while readable siblings remain usable and attributed to the provider. A transport/JSON failure, an unreadable envelope, or a non-empty provider list with no usable records reaches the domain's degraded fallback. Never turn one refused record into a batch-wide outage.141- **Standings expected scope and static fallback compatibility are separate contracts.** `expectedStandingsGroups` says which tables must exist for a provider result to be complete; it never authorizes bundled teams. `standingsFallbackGroups` explicitly says which groups may use the bundled roster. The default World Cup adapter advertises A–L for both. A custom-competition adapter may declare its expected letters but leaves bundled fallback unset, so an outage or omitted expected table returns empty + degraded and can never inject World Cup teams. After a successful open-scope fetch, an absent group means "no such group." An all-groups read uses one fallback verdict because `StandingsResult` cannot honestly represent mixed live and static provenance.142- **Text has ROLES, not one universal cleaner.** A human label is prose (no controls, no format characters, no emoji — bounded by display columns AND code points); an identifier is checked against an exact grammar; a timestamp is re-emitted canonically; a flag is **generated** from the nation, never read from a payload or a cache file. That last one is load-bearing: while flags travelled through the text filter, the filter needed an emoji carve-out, and a carve-out without its own grammar is a covert channel (TAG characters, variation selectors and ZWJ each rode through it in turn — a `🏴` plus 42 tag characters is one 2-column glyph spelling a full instruction sentence).143- **A rejection says which KIND it is.** `ParseResult` distinguishes `valid` / `definitive-none` / `malformed` / `ambiguous` / `unresolved`. For per-item market resolution, `valid`, `definitive-none` and `ambiguous` are stable conclusions and may be cached for a TTL; `malformed` and `unresolved` must not become definitive negative market results. A successfully fetched provider batch is different: share its readable prefix for the coalescing TTL even when a sibling row was malformed, because refetching identical bytes cannot heal that row and must not starve valid siblings.144- **Market completeness reaches the renderer.** `cachedMarketSignals` / `marketSignalsFor` return `{ signals, complete }`; default-on annotations, dedicated market commands/tools, share snippets, and JSON/structured output must retain that verdict. A complete empty batch may render "no signal". An incomplete batch renders an explicit unavailable/incomplete notice and carries `marketComplete:false` or `complete:false`; never collapse it to an empty `Map`.145- **Bound the WORK, not just the output.** Collections are sliced before the per-record work, never after (`takeBounded`), and a surface reports `total`/`shown`/`truncated` from ONE `BoundedList` rather than recomputing counts per call site.146- Static data (schedule, groups, flags) ships bundled in clients; only **live state** hits the network.147- **Standings come from the provider's standings feed, NOT computed from a match window.** The bundled schedule is a resultless skeleton, and clients only fetch a ±1-day live window — so deriving a table from those matches yields a *wrong, partial* table mid-tournament (this was a real bug: groups not playing that day read all-zeros). `table`/`get_standings`/`standings://` go through core `getStandings` → optional `adapter.fetchStandings()` (authoritative cumulative table from ESPN's standings endpoint — the SAME endpoint `fetchGroupMap` already hits, so no new egress). It **fails closed** with no attribution: the default World Cup scope may show a static roster-at-zero, while a competition with no compatible bundled roster returns empty + degraded. `computeStandings` (match-derived) remains for the compatible static fallback only.148- **`CLAUDINHO_COMPETITION` is a deliberate keeper — do not remove it.** It points the live fetch at another ESPN competition (e.g. `fifa.friendly`) and is woven through both the live fetch and the **hot-path cache key** (the cache is competition-keyed). It looks dormant during the World Cup but is load-bearing — it's the seam for following other tournaments.149- **The bundled schedule is a resultless skeleton.** No scores/status, no confirmed nations in knockout slots. `sanitizeBundledFixture` restores topology placeholders; `gen:schedule` **fails loud** if any knockout fixture carries a real nation flag. Advancement comes only from the live overlay — clients never invent it from static JSON.150- **The live fixture's pairing wins over the static topology's winner-refs.** `buildBracketView`/`resolveSlot` resolve a knockout slot from the ESPN fixture ESPN actually serves for that match (`liveParticipant`), and only fall back to projecting a winner from the bundled `winner`/`loser` topology refs when that fixture is **absent from the merged set** (degraded feed). This is deliberate: the bundled winner-ref indices (parsed from ESPN's placeholder slot labels at generation time) do **not** reliably correspond to ESPN's actual R32→R16 feeder assignment, so projecting from them rendered **wrong R16 pairings** (v0.8.16 P1: "Paraguay vs Mexico" instead of the real ties). The topology is now structure/labels + a degraded-only fallback; the pairing is ESPN's. Guarded by the `P1 GUARD` case in `bracket-resolve.test.ts` (feeder ref disagrees with the live fixture → live wins).151- **Knockout/team-facing surfaces MUST live-resolve — never read the skeleton.** Because the bundle's knockout slots are 🏳️ placeholders (above), *every* team-facing surface must reach the live overlay (`getBracket` / `getNextFixtureForTeam`) to show real nations; a surface that reads the static bundle is **silently** blind to a confirmed tie (no crash, just a stale placeholder). This is Claudinho's most recurring bug: the *same* root cause shipped as v0.8.2 (R32 seeds), v0.8.6 (third-place slots), and v0.8.7 (next fixture) — three hotfixes, three different surfaces. **The surface list (keep in sync):** CLI `bracket` · `next` · `share bracket` · `share next`; MCP `get_bracket` · `get_next_fixture` · `get_share_snippet{bracket,next}`. The **statusline** can't fetch on the hot path (<150ms, cache-only), so it live-resolves *indirectly*: the cold-path refresher caches resolved knockout fixtures (`getKnockoutFixtures` → `CacheState.fixtures`) and the statusline reads them, **failing closed to `⚽ —`** (never a 🏳️ placeholder leak) when the cache lacks the pairing (v0.8.8; v0.8.9 adds empty-cache short TTL at phase boundaries and drops unresolved matchups from `live · syncing…`). **`cmdPrompt` and `cmdHook`** both spawn fixtures refresh in knockout phase. **Executable guard:** `packages/{cli,mcp}/test/knockout-surface-coverage.test.ts` pins one fake resolved tie (MEX vs ECU) and asserts every surface renders the real nations (the statusline from a seeded cache). **Add any new team-facing surface to that test** (and to `.cursor/rules/surface-parity.mdc`). **Deliberate exception — the MCP `fixtures://{date}` resource:** it is labeled "Static fixture list" and serves the bundled skeleton by design (a resource URI carries no timezone and gets no live overlay, so knockout slots stay 🏳️ placeholders there); agents needing live-resolved pairings use `get_today`/`get_bracket`. It is the only team-facing surface allowed to read the skeleton directly.152- Market signals use a separate `MarketProvider` interface (not `ProviderAdapter`). The Polymarket event slug is **derived per fixture** (`fifwc-{home}-{away}-{date}`), so most matches resolve with no mapping; `mapping.2026.json` holds slug **overrides only** (ships empty) and validation **fails closed**. Market-facing copy is English-only in v1 (the approved legal copy bank lives in `core/src/markets/format.ts`). Two derivation quirks (both fail-closed, both `release:qa`-tripwired): Polymarket slugs by the **host-local date** (try the UTC date + prior day — `deriveEventSlugs`), and it abbreviates some nations differently from their FIFA code (`POLYMARKET_TOKEN` alias table, e.g. `NED→nld`, `COD→cdr` — used by both slug derivation and outcome-market matching). Candidate-slug fetches honor the enrichment **deadline between candidates** (not just between fixtures), so alias fan-out (up to 8 slugs) never blocks default-on rendering.153- **The statusline and hook are English-only by design** — a deliberate carve-out from the four-locale rule. Both are single-line, latency-bound ambient surfaces whose few fixed tokens ("live · syncing…", "in 2d 4h", the hook's context label) stay EN; the interactive commands localize, the two ambient surfaces don't.154- **Shareable snippets** (`claudinho share`, the MCP `get_share_snippet` tool, `core/src/share`) are pure, deterministic **plain-text** artifacts (no ANSI — they get pasted): English-only copy in v1 **except `share bracket`** (localized en/es/pt/fr via `ShareBracketOptions.locale`; the non-affiliation disclaimer + hashtag stay EN as fixed strings), market lines reuse the approved copy bank verbatim (`marketBlock`/`marketLine`, never hand-composed), and the non-affiliation disclaimer is **non-optional** (only the hashtag and install cue are toggleable). They use the same reliable market gate as `today`/`match` and are **never** on the statusline/hook hot path. **`share table <GROUP>`** produces a standings card — facts + emoji flags only, **no market line**; a degraded (roster-only) card carries an explicit not-live notice so it can't paste as real.155- **Star CTAs are human-interactive-only.** The npm→GitHub conversion nudges — `claudinho star`, the every-Nth dimmed footer on `today`/`live`/`next`/`table`/`bracket`/`match`/`team`, the post-`init` line, and the README callouts — live ONLY on interactive human surfaces. They are **never** on the hot path (statusline `prompt` / `hook`), **never** in `--json` or piped output (`process.stdout.isTTY`-gated, with a `CLAUDINHO_NO_STAR` opt-out), and **never** in MCP tool output/descriptions (that's agent context — a "star us" there wastes tokens and can leak to end users). The footer counter (`packages/cli/src/starNudge.ts`) is best-effort and never throws: a CTA must not break, slow, or pollute a command.156157## Hard constraints (legal — do not violate)158159- **Facts + emoji flags only.** Never add team crests, kits, player photos/likenesses, broadcast footage, or FIFA/Anthropic logos or wordmarks.160- Keep the dual disclaimer — *"Not affiliated with FIFA or Anthropic"* — on user-facing surfaces.161- Attribute data providers; respect their rate limits.162- **Prediction-market data is read-only and informational.** Public market data only — no wallet/auth/CLOB/trading endpoints, no outbound market links (`url` stays `null`). Never frame odds as betting/trading advice (no "bet/wager/value/edge/lock"); keep the *"informational only"* caveat and attribute the provider (Polymarket). Market signals are a **sidecar** — never embedded in `Match`, and never read on the statusline/hook hot path (a regression test enforces this).163164## Pre-PR self-review (run before declaring a change "done")165166This is the lens an external reviewer uses — apply it yourself first. For changes167touching money/legal/external APIs, also run an **independent adversarial pass**168(e.g. a reviewer subagent with fresh eyes on the diff) and self-classify any169findings **P1/P2/P3**.1701711. **Verify external contracts against ground truth.** For any new API/integration,172 fetch a *real* response and confirm the parser **and the test fixtures** match it.173 Never ship against an assumed payload shape — green tests built on a wrong fixture174 prove nothing.1752. **Apply the change to *every* surface.** Enumerate them: CLI (text **and** `--json`),176 MCP (structured `data` **and** text), statusline/hook, share, and the READMEs. A behavior177 that lands on 3 of 4 surfaces is a bug — and "every surface" means each surface's *args*178 (tz/locale/flags) are threaded, not just that the surface exists.1793. **Audit the whole touched area against the Hard Constraints — including pre-existing180 code, not just the diff** (e.g. "attribute data providers" applies to *every*181 provider, not only the one you added).1824. **Adversarial failure-mode pass per new code path:** empty / missing / malformed183 input; transient vs. permanent error (never cache a transient failure as a real184 "no result"); duplicate / ambiguous data; timeout / deadline / concurrency. Default185 to **fail-closed**.1865. **State the worst-case latency/cost of any default-on path** under realistic load187 (e.g. "N sequential fetches × T timeout") and bound it (deadline + cache).1886. **Sync the meta in the same change:** READMEs, the Cursor rules (`.cursor/rules/`), MCP189 tool descriptions, and release guards (`publish.yml`, pinned tool versions). Flag any190 claim that went stale.191192## Change discipline (the failures that cost #97 twelve rounds)193194Nothing here is new — it is the rules above, made unskippable. Each line is a195mistake repeated at least twice in one PR, several of them *after* being written196down.197198**Changing a shared rule**199200- **Put the rule where every path reaches it, then delete the other copy.** Not "add201 the check at the site the report mentioned". #97 fixed the knockout winner rule in202 the ESPN parser while the cache path had none — inside the PR whose whole thesis is203 that one value must not have two readers.204- **Grep for siblings before calling a class closed.** Every single-instance fix in205 #97 had two or three: `updatedAt` had four other timestamps, the statusline had the206 hook, `parseCachedMatches` had both ESPN constructors.207208**Tests**209210- **Make it fail before trusting it.** Revert the rule; green means it pins nothing.211 About a third of #97's tests first passed for the wrong reason.212- **Pin the CALL, not just the function.** Delete the call site and confirm red. Two213 #97 tests pinned a helper and stayed green when its only caller was removed — the214 second written one round after that lesson was recorded.215- **Never assert wall-clock time.** Three timing tests failed under load or on CI, and216 each "fix" was a new constant. If the property has an observable consequence, assert217 that: put a valid item just past the cap and prove it is never reached.218- **Escape invisible characters in fixtures.** Written literally they are lost in219 transit, and the test then passes on plain ASCII while claiming otherwise.220221**Before saying it is done**222223- **Run the gates AND read the output.** #97 pushed a lint error to a repo that gates224 on lint, and separately broke a `release:qa` tripwire — both times the output was225 produced and not read.226- **Diff real-feed output against the base branch**, key order included, for anything227 claiming to be a refactor.228- **Wait for CI on the SHA you pushed.** `gh run watch` on a queued run returns229 success; check `headSha` matches.230231## Definition of Done (per user-facing feature, not per PR)232233The Pre-PR rubric above is per *change*. A feature that spans several PRs also needs a234**feature-level** acceptance gate — the bracket feature became a core release plus four235reactive dot-releases because each gap (ambiguous dates, missing host-nation flags, a236dropped `tz` on MCP `get_bracket`) was found by *using* the feature after it was already237live. Before implementing a user-facing feature, write 3–5 acceptance criteria **from the238user's point of view** and don't call it done until each holds on a real terminal.239**Put them in the PR description before the first commit, together with an explicit240statement of what the change does NOT cover.** PR #97 skipped this and took twelve241review rounds: with no written finish line every round ended at "I fixed what was242reported" and the next round moved it, and with no stated boundary, findings that were243equally true of `main` arrived as blockers instead of as issues. The criteria:244245- The output is **unambiguous** to read (e.g. "which calendar day is this match?" across a 3-week span).246- Every entity renders **consistently with the rest of the product** (host nations show flags like every other team; no static/placeholder leaks; the resultless invariant holds).247- It behaves across **all timezones, all four locales** (en/es/pt/fr), and **every surface** (CLI text + `--json`, MCP `data` + text, share) — not just en/local/CLI.248- It **fails closed** (degraded feed → honest TBD/notice, never an invented or stale fact).249250Scope these up front so the feature ships whole, not in dot-release pieces.251252## Release readiness — run `scripts/release-qa.sh` before tagging253254"Test it on a real terminal first" is executable: **`scripts/release-qa.sh`** (`pnpm release:qa`)255renders *every* user-facing surface against the **live feed, across two timezones and all four256locales**, and ends with tripwires for known regression classes (bracket shows a calendar date,257`tz` is actually threaded, disclaimers intact; it SKIPs rather than fails on a degraded/unreachable258feed, so a network blip never blocks a release). Build, run it, **read the output**, then tag. It259does not replace the eyeball — its job is to put every surface in front of you so nothing ships260unseen. (It covers CLI/share rendering; MCP arg-threading is guarded by261`packages/mcp/test/tools.test.ts` — keep that green too.)262263## Release cadence — batch, don't dot-release per fix264265Two kinds of release, and only one is urgent:266267- **Hotfix-now** — live data *correctness* only (a wrong score/standing *during* a match,268 or a feed outage rendering as authoritative). Ship immediately.269- **Everything else** — UX, polish, cosmetics, follow-on sub-features — **batch** onto the270 working branch and release as a single bump. Stack review fixes for the same feature into the271 same PR before merge.272273Every release carries real toil (a multi-file version bump, and for MCP-affecting changes an MCP274Registry re-publish). Fewer, fuller releases cut that directly. When unsure, accumulate.275276## Don't277278- Don't put API keys in client packages — keys live **only** in the gateway.279- Don't block the statusline hot path on the network — read from the local cache (<150ms).280
Also in arturogarrido/claudinho
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| arturogarrido/claudinho.cursor/rules/bundle-bracket-pr.mdc · 27 | Cursor rules | buildtesttesting-strategygit+1 | 79/100 | 2 days ago | |
| arturogarrido/claudinho.cursor/rules/change-discipline.mdc · 27 | Cursor rules | no sections | 30/100 | yesterday | |
| arturogarrido/claudinho.cursor/rules/release-discipline.mdc · 27 | Cursor rules | lint-formatgitperformancedeployment+1 | 63/100 | 2 days ago | |
| arturogarrido/claudinho.cursor/rules/surface-parity.mdc · 27 | Cursor rules | lint-formattesting-strategyperformancedo-not | 68/100 | 2 days ago | |
| arturogarrido/claudinho.cursor/rules/trust-boundary.mdc · 27 | Cursor rules | do-not | 58/100 | yesterday | |
| arturogarrido/claudinhoCLAUDE.md · 27 | CLAUDE.md | lint-formatperformanceagent-behaviour | 72/100 | 2 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| wpscanteam/wpscanAGENTS.md · 9.7k | AGENTS.md | setupbuildteststyle+6 | 100/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | 3 days ago |
