@thebattlearena/corner 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +38 -5
  2. package/fonts/battle-arena-icons/BattleArenaIcons.ttf +0 -0
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/hooks/icons.generated.ts +2 -0
  6. package/plugin/hooks/pane.tsx +16 -2
  7. package/plugin/hooks/register.tsx +62 -33
  8. package/plugin/hooks/ring-io.ts +59 -1
  9. package/plugin/hooks/ring-pane.tsx +125 -35
  10. package/plugin/hooks/ring.ts +73 -38
  11. package/plugin/hooks/sparring.ts +139 -0
  12. package/plugin/icons/rudo-amber-2x1.png +0 -0
  13. package/plugin/icons/rudo-amber-4x2.png +0 -0
  14. package/plugin/icons/rudo-amber-5x2.png +0 -0
  15. package/plugin/icons/rudo-amber-6x2.png +0 -0
  16. package/plugin/icons/rudo-grey-2x1.png +0 -0
  17. package/plugin/icons/rudo-grey-4x2.png +0 -0
  18. package/plugin/icons/rudo-grey-5x2.png +0 -0
  19. package/plugin/icons/rudo-grey-6x2.png +0 -0
  20. package/plugin/icons/rudo-pink-2x1.png +0 -0
  21. package/plugin/icons/rudo-pink-4x2.png +0 -0
  22. package/plugin/icons/rudo-pink-5x2.png +0 -0
  23. package/plugin/icons/rudo-pink-6x2.png +0 -0
  24. package/plugin/icons/rudo-pink-dim-2x1.png +0 -0
  25. package/plugin/icons/rudo-pink-dim-4x2.png +0 -0
  26. package/plugin/icons/rudo-pink-dim-5x2.png +0 -0
  27. package/plugin/icons/rudo-pink-dim-6x2.png +0 -0
  28. package/plugin/icons/rudo-red-2x1.png +0 -0
  29. package/plugin/icons/rudo-red-4x2.png +0 -0
  30. package/plugin/icons/rudo-red-5x2.png +0 -0
  31. package/plugin/icons/rudo-red-6x2.png +0 -0
  32. package/plugin/tests/ring-pane.test.tsx +154 -0
  33. package/plugin/tests/ring.test.ts +13 -4
  34. package/plugin/tests/sparring.test.ts +204 -0
  35. package/plugin/types/index.d.ts +25 -1
  36. package/ring/.claude-plugin/plugin.json +1 -1
  37. package/ring/agents/rudo.md +95 -0
  38. package/ring/scripts/chronicle.mjs +24 -0
  39. package/ring/scripts/config.mjs +47 -1
  40. package/ring/scripts/fight.mjs +238 -0
  41. package/ring/scripts/judges-card.mjs +53 -9
  42. package/ring/scripts/live-hook.mjs +42 -15
  43. package/ring/scripts/live.mjs +96 -1
  44. package/ring/scripts/mask-guard.mjs +184 -6
  45. package/ring/scripts/monitors.mjs +2 -1
  46. package/ring/scripts/play-by-play.mjs +39 -9
  47. package/ring/scripts/round-bell.mjs +10 -6
  48. package/ring/scripts/sparring-chronicle.mjs +470 -0
  49. package/ring/scripts/sparring.mjs +1161 -0
  50. package/ring/skills/play-by-play/SKILL.md +49 -3
  51. package/ring/skills/play-by-play/templates/chronicle.html +7 -0
  52. package/ring/skills/referee/SKILL.md +46 -2
  53. package/ring/skills/sparring/SKILL.md +408 -0
  54. package/ring/skills/sparring/references/host-signal.md +86 -0
  55. package/ring/skills/sparring/references/lessons.md +50 -0
  56. package/ring/skills/sparring/templates/pr-body.md +34 -0
  57. package/ring/skills/sparring/templates/rudo-brief.md +47 -0
  58. package/ring/skills/sparring/templates/sparring-card.md +33 -0
  59. package/ring/skills/weigh-in/SKILL.md +21 -3
@@ -0,0 +1,86 @@
1
+ # The sparring signal: what a host project can hook into
2
+
3
+ The ring never deploys anything. A sparring PR is the event, and each host project decides what
4
+ to do with it. This page is the contract between the two.
5
+
6
+ ## 1. The event: the draft PR
7
+
8
+ `/ring:sparring` opens a **draft** PR from `sparring/<slug>` with two labels: `sparring` (the host
9
+ can rename it with `sparring.label` in `.claude/ring.json`) and `do-not-merge`. GitHub then emits:
10
+
11
+ - `pull_request: labeled` when the PR is opened with its labels;
12
+ - `pull_request: synchronize` every time the rudo pushes a new commit.
13
+
14
+ A host that wants a preview listens to both in its CI:
15
+
16
+ ```yaml
17
+ # .github/workflows/sparring-preview.yml (in the host repo, never in the ring)
18
+ on:
19
+ pull_request:
20
+ types: [labeled, synchronize]
21
+ jobs:
22
+ preview:
23
+ if: contains(github.event.pull_request.labels.*.name, 'sparring')
24
+ environment: sparring-preview # protected: the judge approves the deploy (§3)
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ # … whatever the project does: a hidden Amplify branch, a preview API, a local stack …
29
+ ```
30
+
31
+ A host without a preview does nothing: the referee says so and the judge tests it locally.
32
+
33
+ A sparring that spans several repos (a back and its front) opens one PR per repo, each from its
34
+ own `sparring/<slug>` branch and each body linking the others. Every repo's CI reacts to its own
35
+ PR and reads its own `.claude/ring.json`: a back may deploy a preview while its front does not.
36
+
37
+ ## 2. The answer: a GitHub deployment
38
+
39
+ The ring reads the preview back from the **GitHub Deployments API**, for the exact tip of the
40
+ sparring branch, in the environment named by `sparring.previewEnvironment`:
41
+
42
+ ```json
43
+ "sparring": {
44
+ "label": "sparring",
45
+ "previewEnvironment": "sparring-preview",
46
+ "previewTimeoutMin": 20,
47
+ "staleDays": 7
48
+ }
49
+ ```
50
+
51
+ Amplify, Vercel and Netlify already publish deployments with an `environment_url`. A custom
52
+ workflow publishes one by running in a GitHub `environment` with a `url`, or with the Deployments
53
+ API. `node "$RING/scripts/sparring.mjs" preview <slug> --wait` polls every 30 s, stops on a
54
+ terminal state (`success`, `failure`, `error`, `inactive`) and never waits past
55
+ `previewTimeoutMin`. Only a `success` gives the judge a link; anything else is reported as it is.
56
+
57
+ ## 3. Credentials: names in the PR, values in the host
58
+
59
+ A sparring against a provider's lab (a sandbox of Kapso, Stripe, an LLM vendor…) needs that
60
+ provider's keys. **No value ever goes through the ring**: not the PR, the commits, the card, nor
61
+ the session log. The PR body declares only the names, in a block the host's CI can parse:
62
+
63
+ ```
64
+ <!-- sparring:credentials
65
+ KAPSO_API_KEY
66
+ KAPSO_WEBHOOK_SECRET
67
+ -->
68
+ ```
69
+
70
+ - The host resolves each name from its own secret store (secrets of the GitHub environment, SSM,
71
+ Vault…) and injects them into its preview.
72
+ - A secret that does not exist yet is loaded **by the judge**, never by the referee or the rudo;
73
+ the referee gives the exact command (`gh secret set KAPSO_API_KEY --env sparring-preview`).
74
+ - **Sandbox or test mode only.** A production credential breaks rule F (no real money, messages,
75
+ calls or customer events); the referee stops and escalates.
76
+ - **Recommended:** protect the `sparring-preview` environment with the judge as required
77
+ reviewer. Then the secrets are released only when the judge approves that deployment, and a
78
+ sparring can never take the keys on its own.
79
+ - The credentials a sparring declares are **assumed rotated** once it ends: the card and the PR
80
+ carry a fixed warning and nobody follows it up.
81
+
82
+ ## 4. The database
83
+
84
+ A sparring changes the schema **additively only** (new tables or columns, English names) and never
85
+ against production. If the host's preview has a database, it must not be the production one: the
86
+ ring cannot check it, so it is the host's responsibility.
@@ -0,0 +1,50 @@
1
+ # Lessons from verdicts
2
+
3
+ What a judge said when calling `rematch` or `discard`, distilled into a rule that holds for the
4
+ next sparring. The referee reads them in §0, pastes the ones that apply into each rudo brief, and
5
+ appends a line here whenever a verdict leaves a new lesson (§4). A lesson is written as a rule, not
6
+ an anecdote, with the date and the sparring that left it.
7
+
8
+ - **Working, not superficial.** «El prototipo no tiene que quedar superficial, tiene que ser
9
+ funcional, el código fuera pero funcional». A metric on an empty page lets the judge decide
10
+ nothing: build the whole screen of the design, and every control does something.
11
+ _(2026-10-28, tira-morada-metricas)_
12
+ - **The data comes from the back, not from front mocks.** The judge wants to see the services
13
+ resolve the information. A front mock only covers what no environment can give yet.
14
+ _(2026-10-28, tira-morada-metricas)_
15
+ - **No data? Seed it.** A new feature has no workspace with activity: do not hunt for one, inject
16
+ synthetic rows and prove the endpoints resolve them. Locally first, without asking; a shared
17
+ database only when the judge asks, and the judge runs it with `!`.
18
+ _(2026-10-28, tira-morada-metricas)_
19
+ - **Careless code is fast code.** A sparring in its fifth hour is paying for quality nobody asked
20
+ for: see «Pace» in the SKILL. _(2026-10-28, tira-morada-metricas)_
21
+ - **A real send goes only to the judge's own destinations.** When the proof needs a message that
22
+ really leaves (Responder enviando de verdad), the judge fills their own test destinations in
23
+ `SPARRING_ALLOWED_<KIND>`; every judge brings theirs. Closed by default, the rest recorded as
24
+ `not_allowlisted`, and no destination ever written in a file. _(2026-10-28, tira-morada-metricas)_
25
+ - **Choosing pieces is not approving the composition.** «Pero no hiciste las propuestas en pen»: when
26
+ the judge picks pieces out of several proposals, how they sit together goes through the design
27
+ tool (2–4 compositions) before any code. _(2026-10-29, tira-morada-metricas)_
28
+ - **Keep a list of rejected patterns and check everything against it.** After ~15 rejected
29
+ proposals the same patterns kept coming back (a side bar on the left, a right column, a solid
30
+ pill on the current word). The referee keeps the list in the blows file, every brief carries it,
31
+ and it applies to what is already implemented too: one rejected bar was still in the code.
32
+ _(2026-10-29, tira-morada-metricas)_
33
+ - **After two rejected rounds, start from named real references.** «Letras de Spotify», «Otter»,
34
+ «Notas de voz»: a proposal anchored to a product the judge knows converges faster than one
35
+ invented from scratch. _(2026-10-29, tira-morada-metricas)_
36
+ - **One voice to the judge.** Options and choices go through the referee only; when two sessions
37
+ presented the same options, the judge answered a third one and the choice arrived secondhand.
38
+ _(2026-10-29, tira-morada-metricas)_
39
+ - **Measure data as present only when it is non-empty.** `tool_calls` exists as a key in 98 % of
40
+ the calls but has an action in 19 %; a design built on the 98 % promised a fill it could not
41
+ keep. _(2026-10-29, tira-morada-metricas)_
42
+ - **The click pass reloads and slows down.** Bugs the judge saw (a quote lost on reload, a deep
43
+ link outside the first page, loaders that flash in 5 ms) only show with a reload and a simulated
44
+ delay (`?delay=`); a click pass without them reports green. _(2026-10-29, tira-morada-metricas)_
45
+ - **A partial screenshot is not a diagnosis.** «Todos los turnos marcan 0:00» came from a capture
46
+ showing only the first three turns, which really are in the first second. Before calling a bug,
47
+ read the data behind the screen (the API response) and the part of the screen that is not in the
48
+ capture. _(2026-10-29, tira-morada-metricas)_
49
+ - **A question that blocks goes first, alone.** «El centrado del player no me necesitas no?, por que ese aun no lo veo implementado»: the question had been repeated at the end of five long status messages and the judge never saw it. When the next step waits on the judge, open the message with the question and its options, and leave the status for after. _(2026-10-30, tira-morada-metricas)_
50
+ - **Sharing prep waits for «la defensa de los técnicos».** Migrations, the host build (a full typecheck and `next build`, not only `next dev`), a vendored SDK, identity per judge and a seed of the shared environment are done ONLY when the judge hints the team will attack it, never in advance. When that happens, expect all five to bite at once: this sparring had its tables only in a tools/ SQL, an SDK that existed only as a local tarball, one shared operator for every judge, a template DB without the workspace, and a type error only `next build` caught. _(2026-10-30, tira-morada-metricas)_
@@ -0,0 +1,34 @@
1
+ **Sparring draft: it proves whether this is worth building. Never merge it.** The code is
2
+ deliberately quick and dirty; if the judge calls it a KO, it passes the host gates, faces the
3
+ team's review and then a ring bout refactors this branch
4
+ (`/ring:referee start <pr> --from-sparring {{slug}}`).
5
+
6
+ ## Hypothesis
7
+
8
+ {{hypothesis}}
9
+
10
+ ## The other repos of this sparring
11
+
12
+ {{siblings}}
13
+
14
+ ## Proof of value
15
+
16
+ What has to be seen to say "yes, this is worth it":
17
+
18
+ {{proof}}
19
+
20
+ ## How to test it
21
+
22
+ {{howToTest}}
23
+
24
+ ## Shortcuts the rudo took
25
+
26
+ The debt a KO turns into the first rebound package of the bout.
27
+
28
+ {{shortcuts}}
29
+
30
+ ## Credentials
31
+
32
+ {{credentials}}
33
+
34
+ {{credentialsBlock}}
@@ -0,0 +1,47 @@
1
+ <!-- The referee fills every <…> and sends this as the prompt of
2
+ Agent{subagent_type: "ring:rudo", effort: "medium", run_in_background: true}.
3
+ Never `isolation: "worktree"`: the worktree already exists. -->
4
+
5
+ Sparring `<slug>`: <the feature, in 3 lines>. Your repo: `<owner/repo>`, branch `sparring/<slug>`,
6
+ base `<base>`.
7
+ Other repos of this sparring: <`owner/repo` — its rudo, and what you exchange with it | none>.
8
+
9
+ Worktree: `<absolute worktree path>`. Your cwd resets: every git command is `git -C <worktree> …`.
10
+ Ring scripts: `<absolute ring path>/scripts`. Sparring dir: `<sparring dir>`; a helper you need goes
11
+ in `<sparring dir>/tools/`, never in the repo.
12
+
13
+ Hypothesis: <if X exists, the team / customer does Y>.
14
+ Proof of value (what the judge has to see): <observable, unambiguous>.
15
+ Full screen the judge sees, working (every visible control does something, data from the back): <the screen or flow, complete, as the design draws it; what exists
16
+ already, and what you build quick and dirty or mock so nothing is a placeholder; what stays out on
17
+ purpose>.
18
+ Judge enters as: <user / workspace / account>.
19
+ Seed: <none | what data the proof needs; you prepare it in tools/ (marked rows, unreachable values,
20
+ dry run, cleanup) and the judge runs it>.
21
+ Designs: <paths of .pen frames or images | none>. A `.pen` file is read only through the pencil MCP
22
+ tools, never with Read or Grep.
23
+ Timebox: <≤3> h from now (<end time>). When it runs out, stop and report what you have.
24
+ Pace: a first clickable version of the whole screen within 90 minutes (<time>); report it, then
25
+ every hour. Skip the ceremony: no migration in the repo (tables from a SQL in `tools/`), OpenAPI
26
+ and generated types only before the last push, typecheck only before each push, no tests, wiki,
27
+ DTOs, CQRS, ports or flags. List each skip as a shortcut.
28
+ Blows from the judge for your repo this round (from `<sparring dir>/blows-round-<n>.md`, with the
29
+ Tape minute; decisions are applied, never reopened): <none | the list>.
30
+ Lessons from earlier verdicts that apply here: <lines from references/lessons.md | none>.
31
+
32
+ Credentials (by NAME, never a value): <NAME, NAME | none>. Locally they live in
33
+ `<worktree>/.env.sparring`, which the judge fills and you never commit, print or echo.
34
+ Real sends: <none | which channel really sends (reply, call, email) and the variable with the
35
+ judge's own destinations: `SPARRING_ALLOWED_<KIND>`, closed by default, the rest `not_allowlisted`>.
36
+
37
+ Gate: typecheck only: `<command>`, through one-at-a-time.
38
+ Database: additive only (new tables or columns, English names), and never against production.
39
+ forbidAiTrailers is <true|false>.
40
+
41
+ Hand-off between repos (when there are other repos): the producer leaves what the consumer needs in
42
+ `<sparring dir>/tools/` (an SDK as `npm pack` tarball, a fixture, a mock) and reports its path, the
43
+ port it serves on and one `curl` that proves it; the consumer installs it from there, never from a
44
+ registry the sparring published to.
45
+
46
+ Push only to `sparring/<slug>`: `git -C <worktree> push origin HEAD:sparring/<slug>`. Then report;
47
+ the referee opens the PR. Do NOT open ready PRs, merge, change bases, or push anywhere else.
@@ -0,0 +1,33 @@
1
+ # Sparring card · {{slug}}
2
+
3
+ Opened {{openedAt}}. One hypothesis and one verdict for every repo below.
4
+
5
+ {{repos}}
6
+
7
+ ## Hypothesis
8
+
9
+ {{hypothesis}}
10
+
11
+ ## Proof of value
12
+
13
+ {{proof}}
14
+
15
+ ## Shortcuts the rudo took
16
+
17
+ {{shortcuts}}
18
+
19
+ ## Credentials
20
+
21
+ {{credentials}}
22
+
23
+ ## Verdict
24
+
25
+ {{verdict}}
26
+
27
+ ### Earlier rounds
28
+
29
+ {{earlierRounds}}
30
+
31
+ <!-- ko: the vestidor (sparring.mjs vestidor {{slug}}), the team's defense until the bell unless «sin equipo»,
32
+ the mask, then a ring bout per repo refactors its branch (/ring:referee start <pr> --from-sparring {{slug}}).
33
+ rematch: adjust and test again, inside the timebox. discard: close it; why stays written here. -->
@@ -19,10 +19,14 @@ autodetected gates and safe defaults; the file exists so the human decides them
19
19
  node "$RING/scripts/config.mjs" detect
20
20
  ```
21
21
 
22
- It prints `gates` (typecheck, test, lint), `detectedFrom` (where each one came from), `missing`
23
- (what it could not detect) and `existingHostConfig`. If a host config already exists and the user
22
+ It prints `gates` (typecheck, test, lint, and `build` when the repo has one), `detectedFrom` (where
23
+ each one came from), `missing` (what it could not detect) and `existingHostConfig`. If a host config already exists and the user
24
24
  did not pass `--refresh`, show it and ask whether to rewrite it before going on.
25
25
 
26
+ `build` is optional and never in `missing`: it comes from `package.json` `scripts.build`, a Makefile
27
+ `build` target or `go build ./...`. Only the sparring's vestidor runs it (the host's real build after
28
+ a KO); bouts and `mask-guard` ignore it. Without one, write `null` or leave it out.
29
+
26
30
  Test commands take `{paths}`: the wrestler runs only the tests it touched, never the whole suite.
27
31
  A test gate without `{paths}` (for example `make test`) runs everything; say so and ask for a
28
32
  by-path variant.
@@ -96,6 +100,14 @@ as "1. b" (AskUserQuestion only when the host repo's instructions require it), a
96
100
 
97
101
  Do not ask about what you detected: show it and let the human correct it if they want.
98
102
 
103
+ 10. **Sparring preview** (optional, one question): does this repo's CI deploy a preview for a
104
+ sparring PR (a draft from `sparring/<slug>` with the `sparring` label)? (a) no (default:
105
+ `previewEnvironment` stays null and the judge tests sparrings locally); (b) yes, in the GitHub
106
+ deployment environment `<name>`. Recommend protecting that environment with the judge as
107
+ required reviewer, so sparring secrets are released only on their approval, and remind them
108
+ the preview must never use the production database. The host contract is
109
+ `$RING/skills/sparring/references/host-signal.md`.
110
+
99
111
  ## 3. Write
100
112
 
101
113
  ```json
@@ -103,7 +115,8 @@ Do not ask about what you detected: show it and let the human correct it if they
103
115
  "gates": {
104
116
  "typecheck": "<command>",
105
117
  "test": "<command with {paths}>",
106
- "lint": "<command or null>"
118
+ "lint": "<command or null>",
119
+ "build": "<command or null>"
107
120
  },
108
121
  "validateCommand": null,
109
122
  "reproduceCommand": null,
@@ -118,6 +131,7 @@ Do not ask about what you detected: show it and let the human correct it if they
118
131
  "reviewEvery": { "commits": 3, "lines": 200 },
119
132
  "backupBranch": "ring/{pr}/respaldo",
120
133
  "findingsGate": { "open": 0, "unshown": 3 },
134
+ "sparring": { "previewEnvironment": "sparring-preview", "previewTimeoutMin": 20 },
121
135
  "venues": {
122
136
  "backend": { "paths": ["apps/api/**", "packages/**"] },
123
137
  "frontend": {
@@ -194,6 +208,10 @@ Do not ask about what you detected: show it and let the human correct it if they
194
208
  ranges inside it are still open, and a push also waits while more than `unshown` findings the
195
209
  referee discarded or ticketed were never shown to the human. Title-defense and close always need
196
210
  a fresh full audit (`findings.mjs audit <pr> --full`), whatever this says.
211
+ - `sparring` is optional (default `{ "label": "sparring", "previewEnvironment": null,
212
+ "previewTimeoutMin": 20, "staleDays": 7 }`): the label of sparring PRs, the GitHub deployment
213
+ environment the host's CI publishes their preview to, how long `sparring.mjs preview --wait`
214
+ waits, and after how many days a sparring without a verdict shows as stale.
197
215
  - `protectedBranches`: **while a bout is active, `mask-guard` blocks pushes to these branches**,
198
216
  including a bare `git push` from one of them. Tell the human both blocks exist when you show the
199
217
  config.