@thebattlearena/corner 0.8.0 → 0.9.1-beta.70
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.
- package/README.md +38 -5
- package/fonts/battle-arena-icons/BattleArenaIcons.ttf +0 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/icons.generated.ts +2 -0
- package/plugin/hooks/pane.tsx +16 -2
- package/plugin/hooks/register.tsx +62 -33
- package/plugin/hooks/ring-io.ts +59 -1
- package/plugin/hooks/ring-pane.tsx +125 -35
- package/plugin/hooks/ring.ts +73 -38
- package/plugin/hooks/sparring.ts +139 -0
- package/plugin/icons/rudo-amber-2x1.png +0 -0
- package/plugin/icons/rudo-amber-4x2.png +0 -0
- package/plugin/icons/rudo-amber-5x2.png +0 -0
- package/plugin/icons/rudo-amber-6x2.png +0 -0
- package/plugin/icons/rudo-grey-2x1.png +0 -0
- package/plugin/icons/rudo-grey-4x2.png +0 -0
- package/plugin/icons/rudo-grey-5x2.png +0 -0
- package/plugin/icons/rudo-grey-6x2.png +0 -0
- package/plugin/icons/rudo-pink-2x1.png +0 -0
- package/plugin/icons/rudo-pink-4x2.png +0 -0
- package/plugin/icons/rudo-pink-5x2.png +0 -0
- package/plugin/icons/rudo-pink-6x2.png +0 -0
- package/plugin/icons/rudo-pink-dim-2x1.png +0 -0
- package/plugin/icons/rudo-pink-dim-4x2.png +0 -0
- package/plugin/icons/rudo-pink-dim-5x2.png +0 -0
- package/plugin/icons/rudo-pink-dim-6x2.png +0 -0
- package/plugin/icons/rudo-red-2x1.png +0 -0
- package/plugin/icons/rudo-red-4x2.png +0 -0
- package/plugin/icons/rudo-red-5x2.png +0 -0
- package/plugin/icons/rudo-red-6x2.png +0 -0
- package/plugin/tests/ring-pane.test.tsx +154 -0
- package/plugin/tests/ring.test.ts +13 -4
- package/plugin/tests/sparring.test.ts +204 -0
- package/plugin/types/index.d.ts +25 -1
- package/ring/.claude-plugin/plugin.json +1 -1
- package/ring/agents/rudo.md +95 -0
- package/ring/scripts/chronicle.mjs +24 -0
- package/ring/scripts/config.mjs +47 -1
- package/ring/scripts/fight.mjs +238 -0
- package/ring/scripts/judges-card.mjs +53 -9
- package/ring/scripts/live-hook.mjs +42 -15
- package/ring/scripts/live.mjs +96 -1
- package/ring/scripts/mask-guard.mjs +184 -6
- package/ring/scripts/monitors.mjs +2 -1
- package/ring/scripts/play-by-play.mjs +39 -9
- package/ring/scripts/round-bell.mjs +10 -6
- package/ring/scripts/sparring-chronicle.mjs +470 -0
- package/ring/scripts/sparring.mjs +1161 -0
- package/ring/skills/play-by-play/SKILL.md +49 -3
- package/ring/skills/play-by-play/templates/chronicle.html +7 -0
- package/ring/skills/referee/SKILL.md +46 -2
- package/ring/skills/sparring/SKILL.md +408 -0
- package/ring/skills/sparring/references/host-signal.md +86 -0
- package/ring/skills/sparring/references/lessons.md +50 -0
- package/ring/skills/sparring/templates/pr-body.md +34 -0
- package/ring/skills/sparring/templates/rudo-brief.md +47 -0
- package/ring/skills/sparring/templates/sparring-card.md +33 -0
- package/ring/skills/weigh-in/SKILL.md +21 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: play-by-play
|
|
3
|
-
description: Narrates a ring bout as a Battle Arena sports chronicle (rounds, blows, fouls, bells) with the real time of each event, and publishes it as a private Artifact page that keeps one stable link for the life of the bout. Invoke with `/ring:play-by-play <pr>`, "write the chronicle of the bout", "update the play-by-play".
|
|
4
|
-
argument-hint: "<pr>"
|
|
3
|
+
description: Narrates a ring bout as a Battle Arena sports chronicle (rounds, blows, fouls, bells) with the real time of each event, and publishes it as a private Artifact page that keeps one stable link for the life of the bout. A fight that starts at a sparring is one chronicle from the sparring's opening, two out of three falls. Invoke with `/ring:play-by-play <pr>`, `/ring:play-by-play --sparring <slug>`, "write the chronicle of the bout", "update the play-by-play".
|
|
4
|
+
argument-hint: "<pr> | --sparring <slug>"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# play-by-play
|
|
@@ -25,6 +25,37 @@ re-request closes it), `events` (type, real time `at`, `local` HH:MM, title, det
|
|
|
25
25
|
`falta`, `juez`, `arbitro`, `bateria`, `campana`. The classification rules are at the top of
|
|
26
26
|
`$RING/scripts/chronicle.mjs`.
|
|
27
27
|
|
|
28
|
+
### A fight that starts at a sparring
|
|
29
|
+
|
|
30
|
+
The chronicle starts at the sparring, so its material never ends up scattered:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node "$RING/scripts/play-by-play.mjs" --sparring <slug> --tz <the human's time zone> [--extra <json|file>] [--no-gh]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It prints the same shape plus `falls`, `catalogs`, `bouts` and the `page` to write. The fight is
|
|
37
|
+
told in three falls, and every event carries its `fall`, its `round` and its `day` (a fight spans
|
|
38
|
+
days):
|
|
39
|
+
|
|
40
|
+
1. **First fall, the rudo against the judge** (rounds `s1`, `s2`…): from `open` to the KO, one
|
|
41
|
+
round per verdict of `history[]` (a rematch closes one and opens the next). The judge's Tapes
|
|
42
|
+
are attacks at the time the catalog writes next to them; each round's catalog
|
|
43
|
+
(`blows-round-<n>.md`) is in `catalogs`, every D/B/F/Q blow with its Tape minute, to narrate
|
|
44
|
+
them by minute of the Tape, never by an invented clock time.
|
|
45
|
+
2. **Second fall, the defense of the técnicos** (round `defense`): from the KO (or the first
|
|
46
|
+
milestone of the defense, if it came first) through the vestidor (the host gates), the
|
|
47
|
+
defense opened to the team, its Tapes, the bell and whether the rudo keeps or loses the mask.
|
|
48
|
+
With «sin equipo» (`no-team`) there is no second fall: that fall comes `skipped` and the
|
|
49
|
+
third starts at the judge's call; tell it in one line, never as an empty fall.
|
|
50
|
+
3. **Third fall, the referee**: from the hand-off, every bout opened `--from-sparring <slug>`,
|
|
51
|
+
each with its own rounds (`<pr>/<n>`, as a bout's chronicle has them).
|
|
52
|
+
|
|
53
|
+
The milestones exist only if the sparring's referee marked them when they happened
|
|
54
|
+
(`sparring.mjs vestidor <slug>`, `sparring.mjs mark <slug> defense|bell|mask-kept|mask-lost|no-team`); `judges-card open
|
|
55
|
+
--from-sparring` marks the hand-off. A moment nobody marked is not in the JSON: say it as a gap,
|
|
56
|
+
or bring its time through `--extra` when someone provided it.
|
|
57
|
+
`/ring:play-by-play <pr>` on a bout opened from a sparring prints the same whole fight.
|
|
58
|
+
|
|
28
59
|
Times the records do not hold (a battery run the host logs elsewhere, a screenshot the human
|
|
29
60
|
sent) enter only through `--extra` (an array of `{ at, type, title, detail, ref }`, inline or as
|
|
30
61
|
the path of a JSON file), and they stay marked `provided`.
|
|
@@ -45,7 +76,9 @@ dark mode, no external scripts, only the Google Fonts stylesheet). Keep its stru
|
|
|
45
76
|
- masthead with the live status, headline with the PR number, a two-sentence dek, the byline;
|
|
46
77
|
- the fight card (champion vs challenger) and the crew line;
|
|
47
78
|
- the tally (duration, Copilot findings, commits, fouls) and the legend of the eight tags;
|
|
48
|
-
- one section per round, each beat with its time in the margin and its source in `.ref`;
|
|
79
|
+
- one section per round, each beat with its time in the margin and its source in `.ref`; in a
|
|
80
|
+
fight from a sparring, the rounds go under a header per fall (`h3.fall`), the card is the rudo
|
|
81
|
+
against the judge, and the first beat of each day says the day;
|
|
49
82
|
- exactly two pull quotes in the whole chronicle;
|
|
50
83
|
- the judges' card table and the sources footer.
|
|
51
84
|
|
|
@@ -62,6 +95,15 @@ link (a second, different link is refused), and every time it stamps `playByPlay
|
|
|
62
95
|
|
|
63
96
|
Then offer, once, to put the link in the PR body; do it only with the human's yes.
|
|
64
97
|
|
|
98
|
+
**A fight from a sparring has one page and one link.** Write it to
|
|
99
|
+
`<sparring dir>/play-by-play.html` (the JSON's `page`), always that path, whether it is refreshed
|
|
100
|
+
from the sparring or from one of its bouts. If `sparring.json` already has `playByPlayUrl`,
|
|
101
|
+
publish with `url` set to it. After every publish from the sparring, run
|
|
102
|
+
`node "$RING/scripts/sparring.mjs" play-by-play-url <slug> <url>`; from a bout,
|
|
103
|
+
`judges-card.mjs play-by-play-url <pr> <url>` as always, which refuses any link but the fight's and
|
|
104
|
+
also stamps the sparring. The sparring keeps its first link for the whole fight, through every
|
|
105
|
+
bout of every repo.
|
|
106
|
+
|
|
65
107
|
## Rules
|
|
66
108
|
|
|
67
109
|
- **No invented times.** A gap is said as a gap: the beat goes where it must have happened, with
|
|
@@ -81,3 +123,7 @@ Then offer, once, to put the link in the PR body; do it only with the human's ye
|
|
|
81
123
|
|
|
82
124
|
The referee and title-defense refresh it after each round-bell and at each bell (the Copilot
|
|
83
125
|
re-request); `close` publishes the final edition, with the status "Final".
|
|
126
|
+
|
|
127
|
+
In a sparring, the referee of the sparring publishes it first at `open` and refreshes it after
|
|
128
|
+
each verdict, after the vestidor, when the defense opens and at the bell. Those refreshes report;
|
|
129
|
+
like every chronicle, they decide nothing about the sparring.
|
|
@@ -58,6 +58,7 @@ h1 em{font-style:normal;color:var(--red)}
|
|
|
58
58
|
blockquote{margin:22px 0 22px 74px;padding:0 0 0 18px;border-left:4px solid var(--gold);font-size:21px;line-height:1.45;font-style:italic;max-width:56ch}
|
|
59
59
|
@media (max-width:560px){blockquote{margin-left:0}.beat{grid-template-columns:minmax(0,1fr);gap:2px}}
|
|
60
60
|
.ref{display:block;font-family:var(--f-mono);font-size:12px;color:var(--muted);margin-top:4px;overflow-wrap:anywhere}
|
|
61
|
+
h3.fall{font-family:var(--f-label);font-weight:700;font-size:15px;letter-spacing:.1em;text-transform:uppercase;color:var(--red);margin:56px 0 -28px;border-top:3px double var(--ink);padding-top:10px}
|
|
61
62
|
h3.section{font-family:var(--f-display);font-weight:400;font-size:24px;text-transform:uppercase;margin:52px 0 10px;border-top:3px solid var(--ink);padding-top:12px}
|
|
62
63
|
.tbl{overflow-x:auto;background:var(--paper);border:1px solid var(--rule)}
|
|
63
64
|
table{border-collapse:collapse;width:100%;min-width:600px;font-family:var(--f-label);font-size:15.5px}
|
|
@@ -78,6 +79,10 @@ footer{margin-top:44px;font-family:var(--f-label);font-size:14px;color:var(--mut
|
|
|
78
79
|
t-bateria, t-campana; a foul beat also gets class "falta";
|
|
79
80
|
- .ref under each beat says where the time came from (decision id, sha, review, run);
|
|
80
81
|
- exactly two blockquotes (pull quotes) in the whole chronicle;
|
|
82
|
+
- a fight from a sparring (--sparring): one <h3 class="fall"> before the first round of each
|
|
83
|
+
fall of the JSON (first fall: the rudo against the judge; second: the defense of the técnicos;
|
|
84
|
+
third: the referee), the card is the rudo against the judge, and the first beat of each day
|
|
85
|
+
says the day (the event's `day`) in its prose;
|
|
81
86
|
- no scripts; only the Google Fonts stylesheet above loads from outside.
|
|
82
87
|
-->
|
|
83
88
|
<div class="wrap">
|
|
@@ -105,6 +110,8 @@ footer{margin-top:44px;font-family:var(--f-label);font-size:14px;color:var(--mut
|
|
|
105
110
|
<span class="tag t-reclamo">{{Reclamo de Copilot}}</span><span class="tag t-dado">{{Golpe dado}}</span><span class="tag t-recibido">{{Golpe recibido}}</span><span class="tag t-falta">{{Falta}}</span><span class="tag t-arbitro">{{Árbitro}}</span><span class="tag t-juez">{{Juez}}</span><span class="tag t-bateria">{{Batería}}</span><span class="tag t-campana">{{Campana}}</span>
|
|
106
111
|
</div>
|
|
107
112
|
|
|
113
|
+
<!-- In a fight from a sparring, before the first round of each fall:
|
|
114
|
+
<h3 class="fall">{{e.g. "Primera caída · el rudo contra el juez"}}</h3> -->
|
|
108
115
|
<!-- One section per round of the JSON; round 0 is the preview ("La previa"). -->
|
|
109
116
|
<section class="round">
|
|
110
117
|
<h2>{{ROUND NAME, e.g. "Round 1 · siete golpes de entrada"}}</h2>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: referee
|
|
3
3
|
description: Orchestrates a refactor or a large delivery on a backend, frontend or mobile PR as its referee, at the venue the PR belongs to. One long-lived wrestler (effort medium) writes in its own worktree; you (effort high) review each commit range with read-only cornermen against a numbered rulebook, send one rebound package at a time, open small do-not-merge round-bell PRs so the human reviews early, decide design doubts yourself from the rulebook and precedents (escalating only a short hard list), and keep a durable judges card so any new session can resume. Invoke with `/ring:referee start <pr>`, `/ring:referee resume [<pr>]`, `/ring:referee close`, "referee the refactor of #N", "resume the bout".
|
|
4
|
-
argument-hint: "start <pr> | resume [<pr>] | close"
|
|
4
|
+
argument-hint: "start <pr> | start --from-sparring <slug> | resume [<pr>] | close"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# referee
|
|
@@ -79,6 +79,50 @@ list reaches the human.
|
|
|
79
79
|
|
|
80
80
|
## 1 · `start <pr>`
|
|
81
81
|
|
|
82
|
+
**`--from-sparring <slug>`**: the bout cleans the deliverable of a sparring the judge called KO
|
|
83
|
+
(`/ring:sparring`). Before step 1, read `~/.claude/ring/sparrings/<slug>/card.md`
|
|
84
|
+
(`node "$RING/scripts/sparring.mjs" list` names them) and refuse unless its verdict is `ko`. A
|
|
85
|
+
sparring that spanned several repos gets one bout per repo; this one covers this session's repo.
|
|
86
|
+
**The `<pr>` is not needed**: unless the sparring was already promoted (its `sparring.json` names a
|
|
87
|
+
`mainevent` for this repo), run `node "$RING/scripts/sparring.mjs" promote <slug> --repo
|
|
88
|
+
<owner/repo>`. It creates `release/<slug>` from the repo's base, `mainevent/<slug>` from the
|
|
89
|
+
sparring tip, and opens the deliverable PR mainevent → release as a draft; that PR is the bout's
|
|
90
|
+
`<pr>` and `release/<slug>` its **stack base**. The branches:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
main
|
|
94
|
+
└─ release/<slug> reaches main in one PR at the end (the release exception of the line ceiling)
|
|
95
|
+
└─ mainevent/<slug> PR → release; the wrestler cleans everything here, no line ceiling
|
|
96
|
+
└─ ring/<pr>/round-<n> round-bells, do-not-merge as always: the human reads each concept
|
|
97
|
+
sparring/<slug> do-not-merge, never merged: the evidence of what the KO saw
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Never push `sparring/*` to another branch by hand (mask-guard refuses it): `promote` is the only
|
|
101
|
+
way out, and it refuses without a KO. Then the bout runs as always, with three inputs from the card:
|
|
102
|
+
|
|
103
|
+
- **What the judge validated is the behavior that must not change**: write it as numbered
|
|
104
|
+
invariants for the concept-mode cornerman, and add characterization tests first (T rules).
|
|
105
|
+
- **The rudo's shortcuts for this repo are the first rebound package**, blockers first.
|
|
106
|
+
- **The contract the sparring drafted goes through §2b** like any other: nothing of it is approved
|
|
107
|
+
because the sparring used it. Its credentials (by name) are a hard-list item: taking a sandbox
|
|
108
|
+
key's variable to production is the human's.
|
|
109
|
+
- **Open it with `--from-sparring <slug>`** (step 5): `judges-card.mjs open` refuses a sparring
|
|
110
|
+
that is not a KO, records `fromSparring` and marks the hand-off on the sparring. The bout is then
|
|
111
|
+
the third fall of the sparring's chronicle: one page and one link for the whole fight
|
|
112
|
+
(`/ring:play-by-play`).
|
|
113
|
+
- **That contract is shown to the judge first, without the judge asking for it.** Right after
|
|
114
|
+
opening the bout (step 5) and before the first wrestler brief: list every file under
|
|
115
|
+
`config.apiSpec` and `config.dbModels` that changes between the stack base and the PR head
|
|
116
|
+
(`git diff --name-only <base>...<head> -- <those paths>`), have a cornerman review it (§2b
|
|
117
|
+
step 3), draw the diagram of every migration (§2b step 4), serve `contract-preview.mjs`, and
|
|
118
|
+
escalate it all as **one** contract escalation: the preview URL, the spec diff, each migration
|
|
119
|
+
with its backfill, and your recommendation. Until `approve-contract` records the judge's yes for
|
|
120
|
+
exactly those files (already committed, so the hashed content is the one at the PR head), the
|
|
121
|
+
whole contract zone is frozen: the wrestler cleans only what does not touch those files, and no
|
|
122
|
+
rebound changes them. Starting the cleanup before that escalation is a foul; log it in the
|
|
123
|
+
judges card.
|
|
124
|
+
|
|
125
|
+
|
|
82
126
|
1. `gh pr view <pr> --json number,title,url,headRefName,baseRefName,files`. The **stack base** is
|
|
83
127
|
`baseRefName` unless the human names another one; ask if the PR is part of a stack.
|
|
84
128
|
2. **Venue.** `gh pr diff <pr> --name-only | node "$RING/scripts/config.mjs" venue`, or
|
|
@@ -93,7 +137,7 @@ list reaches the human.
|
|
|
93
137
|
`git worktree add --detach <abs path> origin/<headRefName>` (after `git fetch origin`). Never
|
|
94
138
|
launch the wrestler with `isolation: "worktree"`: that creates another tree on another branch.
|
|
95
139
|
5. Open the bout: `node "$RING/scripts/judges-card.mjs" open --pr <pr> --worktree <abs path>
|
|
96
|
-
--branch <headRefName> --base <stack base> --venue <venue
|
|
140
|
+
--branch <headRefName> --base <stack base> --venue <venue> [--from-sparring <slug>]`. This activates `mask-guard` for this repo.
|
|
97
141
|
It refuses to open when `.claude/ring.json` leaves `apiSpec` or `dbModels` empty while the repo
|
|
98
142
|
has specs or migrations: fix the config (paths or globs; `null` means "detect") rather than
|
|
99
143
|
passing `--allow-empty-contract`, which is only for a bout that truly has no contract. Then
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sparring
|
|
3
|
+
description: Opens a sparring, a quick and deliberately low-quality draft (one PR per repo it touches, back and front included) that only proves whether a feature is worth building, before anyone pays for building it well. A rudo per repo (effort medium) writes it inside a timebox in its own worktree; the draft PR (labels sparring + do-not-merge) is the event the host project's CI catches to deploy a preview; credentials travel by name only. The judge calls KO, rematch or discard, in two out of three falls: a KO passes the host gates (vestidor), faces the team's Tapes until a bell (unless «sin equipo»), keeps or loses the mask, and then hands each branch to a ring bout. Invoke with `/ring:sparring <idea | Linear ticket>`, `/ring:sparring preview <slug>`, `/ring:sparring verdict <slug>`, `/ring:sparring list`, "make a quick draft of this to try it", "spar this feature first".
|
|
4
|
+
argument-hint: '<idea | ticket> | preview <slug> | verdict <slug> | vestidor <slug> | list'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# sparring
|
|
8
|
+
|
|
9
|
+
A sparring answers one question, "is this worth building?", with evidence. The code is
|
|
10
|
+
disposable in quality, not in behavior: if the judge calls a KO, a ring bout refactors the same
|
|
11
|
+
branch and keeps what the judge validated. You are the referee of the sparring; the rudo writes.
|
|
12
|
+
|
|
13
|
+
**The judge sees the whole experience, working, never an isolated or superficial piece.** A
|
|
14
|
+
sparring shows the full screen or flow the feature lives in (the list, the detail, the filters and
|
|
15
|
+
the new piece, as the design draws them), and **every control on it works**: search, filters, tabs,
|
|
16
|
+
loaders, pagination or infinite scroll, actions and their errors. The data comes from the back,
|
|
17
|
+
built quick and dirty for the sparring; a mock is only for what cannot be had yet (an external
|
|
18
|
+
provider, data no environment has). A widget on an empty page, or a screen of buttons that do
|
|
19
|
+
nothing, leaves the judge without what they need to call the verdict. The code is thrown away, so
|
|
20
|
+
cutting scope or depth saves nothing that matters: the speed comes from the quality you skip.
|
|
21
|
+
|
|
22
|
+
## Pace
|
|
23
|
+
|
|
24
|
+
A sparring is careless code that puts working first, so it is fast or it is wrong. The judge
|
|
25
|
+
clicks a first working version of the whole screen **within 90 minutes** of the interview; the
|
|
26
|
+
rest of the timebox goes to the controls that are still dead. Where time goes, and what you cut:
|
|
27
|
+
|
|
28
|
+
- **The interview is one message with your proposed answers**; the judge answers with "yes" or a
|
|
29
|
+
correction. Everything that does not change the hypothesis, the proof, the scope or a hard stop,
|
|
30
|
+
you decide without asking, then and while the rudos work.
|
|
31
|
+
- **Contract first, in the first 15 minutes.** With several repos, the producer writes the contract
|
|
32
|
+
(routes, shapes, error codes) to `<sparring dir>/contrato.md` before implementing, and every rudo
|
|
33
|
+
starts at once against it. The consumer never waits for the producer's code.
|
|
34
|
+
- **Ceremony the rudo skips:** migrations in the repo (tables come from a SQL in `tools/`), OpenAPI
|
|
35
|
+
and generated types until the last push, typecheck after every edit (only before each push),
|
|
36
|
+
tests, wiki, DTOs, CQRS, ports, feature flags. Each one goes on the shortcuts list.
|
|
37
|
+
- **Data is seeded locally first**, without asking: synthetic rows, enough to look like the design.
|
|
38
|
+
A shared database only when the judge asks for it.
|
|
39
|
+
- **Report early, not only at the end:** each rudo reports its first clickable version and then
|
|
40
|
+
every hour, so a wrong direction is caught at minute 60, not at hour 5.
|
|
41
|
+
|
|
42
|
+
## 0 · Every time you are invoked
|
|
43
|
+
|
|
44
|
+
1. **Paths.** `RING` is the plugin root: two directories above this skill's base directory
|
|
45
|
+
(`${CLAUDE_PLUGIN_ROOT}` when it is expanded). Script: `$RING/scripts/sparring.mjs`. Templates:
|
|
46
|
+
`$RING/skills/sparring/templates/`. The host contract:
|
|
47
|
+
`$RING/skills/sparring/references/host-signal.md`.
|
|
48
|
+
2. **The repos are the sparring's, not the session's.** A sparring may target repos other than the
|
|
49
|
+
checkout this session runs in, and may span several (a back and its front). Every script call
|
|
50
|
+
names its repo by worktree; never rely on the session's directory.
|
|
51
|
+
3. **Config, per repo.** `node "$RING/scripts/config.mjs" show` run from inside each repo's
|
|
52
|
+
worktree: read `config.sparring` (label, `previewEnvironment`, `previewTimeoutMin`,
|
|
53
|
+
`staleDays`) and `config.gates.typecheck`. A repo without `previewEnvironment` has no preview:
|
|
54
|
+
say it once and plan for a local test.
|
|
55
|
+
4. **State** lives in `~/.claude/ring/sparrings/<slug>/` (`sparring.json`, `card.md`, `tools/`),
|
|
56
|
+
one directory per sparring whatever repos it spans. It is not a bout: no lookout, no cornermen,
|
|
57
|
+
no round-bells. A sparring recorded per repo by an older version is merged into one the first time a command reads it; its `tools/` and notes move along, with a symlink left at the old path.
|
|
58
|
+
5. **Lessons.** Read `$RING/skills/sparring/references/lessons.md`: what earlier judges asked for
|
|
59
|
+
when they called rematch or discard. Plan with them, and paste the ones that apply into every
|
|
60
|
+
rudo brief.
|
|
61
|
+
6. **Chronicle.** The fight's chronicle starts at `open`, not at the referee bout: publish it then
|
|
62
|
+
(`/ring:play-by-play --sparring <slug>`) and refresh it after each verdict, after the vestidor,
|
|
63
|
+
when the defense opens, at the bell and at the mask, marking each of those moments first (§4:
|
|
64
|
+
`sparring.mjs vestidor <slug>` stamps the vestidor; `sparring.mjs mark <slug>
|
|
65
|
+
defense|bell|mask-kept|mask-lost|no-team` the rest).
|
|
66
|
+
|
|
67
|
+
## 1 · `/ring:sparring <idea | ticket>`
|
|
68
|
+
|
|
69
|
+
1. **Read the input first.** When the idea comes with a plan, a ticket or an artifact, check what
|
|
70
|
+
of it already exists in the code before asking anything: that decides the scope question.
|
|
71
|
+
2. **Short interview** (one message, numbered, answerable in one line each). Skip what the idea or
|
|
72
|
+
the ticket already says:
|
|
73
|
+
- **Hypothesis:** "if X exists, the team / customer does Y".
|
|
74
|
+
- **Proof of value:** what the judge has to _see_ to say yes. Observable and unambiguous; "it
|
|
75
|
+
looks good" is not one.
|
|
76
|
+
- **The full screen the judge will see:** which screen or flow, complete, the feature lives in,
|
|
77
|
+
and what of it already exists. What is missing (the screen the idea stands on, an endpoint, a
|
|
78
|
+
table) is built quick and dirty or mocked so the whole experience is there; never a
|
|
79
|
+
placeholder. Ask only what stays out on purpose.
|
|
80
|
+
- **Repos:** which ones it touches (back, front, both) and their base branches (default: each
|
|
81
|
+
repo's default branch). All of them belong to this one sparring.
|
|
82
|
+
- **Designs:** a `.pen` frame, a Figma link or images the rudo has to follow, if any.
|
|
83
|
+
- **Timebox:** 3 h at most. If it does not fit, split it and spar the riskiest part first.
|
|
84
|
+
- **Credentials:** which provider lab it needs and the NAMES of the variables
|
|
85
|
+
(`KAPSO_API_KEY`). Never ask for, accept or repeat a value; if the human pastes one, do not
|
|
86
|
+
echo it and tell them to rotate it.
|
|
87
|
+
- **Real sends to the judge:** whether the proof needs a message, call or email that really
|
|
88
|
+
leaves through the provider (§1.8). Only to the judge's own test destinations.
|
|
89
|
+
3. **Hard stops before starting** (escalate, do not open): a production credential, real money,
|
|
90
|
+
messages or calls to customers, customer data or PII in fixtures, or a destructive schema
|
|
91
|
+
change. Those break rule F or the additive-only rule. The one exception is §1.8: real sends to
|
|
92
|
+
the judge's own allowlisted destinations, with their explicit yes.
|
|
93
|
+
4. **A worktree per repo**, outside every main checkout, e.g. a sibling `<repo>-sparring-<slug>`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git -C <repo checkout> fetch origin
|
|
97
|
+
git -C <repo checkout> worktree add -b sparring/<slug> <abs path> origin/<base>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
5. **Open it** with the first repo, then add the others. Credential names are validated: anything
|
|
101
|
+
that is not `UPPER_SNAKE_CASE` is refused, so a value never lands in the state.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
node "$RING/scripts/sparring.mjs" open <slug> --worktree <abs path> --base <base> \
|
|
105
|
+
--hypothesis "<…>" --proof "<…>" [--credentials NAME,NAME]
|
|
106
|
+
node "$RING/scripts/sparring.mjs" add-repo <slug> --worktree <abs path of the other repo> --base <base>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
6. **Credentials, locally:** give the judge the path `<worktree>/.env.sparring` of each repo that
|
|
110
|
+
needs them and the names to fill. They fill it; you never read it back. For a host's preview,
|
|
111
|
+
give the judge the command to load each missing secret into that host's store
|
|
112
|
+
(`gh secret set <NAME> --env <previewEnvironment>`); loading secrets is theirs.
|
|
113
|
+
7. **Launch a rudo per repo**, all in the background, each with `templates/rudo-brief.md` filled
|
|
114
|
+
for its repo (scope, designs, the other repos and what they exchange):
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Agent{subagent_type: "ring:rudo", effort: "medium", run_in_background: true,
|
|
118
|
+
description: "Rudo: <slug> <repo>", prompt: <the brief>}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Never `isolation: "worktree"`. When one repo produces what another consumes (the back's SDK or
|
|
122
|
+
endpoint for the front), say so in both briefs: the producer goes first and leaves the hand-off
|
|
123
|
+
in `<sparring dir>/tools/` (§2).
|
|
124
|
+
|
|
125
|
+
8. **Real sends to the judge** (a message, call or email that really leaves through the provider),
|
|
126
|
+
only when the proof needs it and the judge said yes. The destinations are **the judge's own and
|
|
127
|
+
change with every judge**: they are never asked for in chat, never written in code, a fixture,
|
|
128
|
+
the card, a commit or a log. The judge fills them in `<worktree>/.env.sparring`, one variable
|
|
129
|
+
per kind, `SPARRING_ALLOWED_<KIND>` (`SPARRING_ALLOWED_PHONES`, `SPARRING_ALLOWED_EMAILS`,
|
|
130
|
+
`SPARRING_ALLOWED_CHAT_IDS`…), comma-separated. The code that sends:
|
|
131
|
+
- **closed by default:** an empty or missing variable sends nothing;
|
|
132
|
+
- sends only when the destination is on the list, normalized the same way on both sides (for
|
|
133
|
+
phones, digits only);
|
|
134
|
+
- records every other attempt without sending it (`{ sent: false, reason: "not_allowlisted" }`),
|
|
135
|
+
so the screen still works with seeded destinations;
|
|
136
|
+
- logs whether it sent, never the destination.
|
|
137
|
+
|
|
138
|
+
The provider's credentials go by name like any other (§1.6); a production key is still a hard
|
|
139
|
+
stop unless the judge explicitly accepts it for these sends, and then the allowlist is the only
|
|
140
|
+
guard, so say so. The variables are listed in the card by name, never their values. The
|
|
141
|
+
allowlist is per sparring and per judge: a later judge fills their own.
|
|
142
|
+
|
|
143
|
+
## 2 · While the rudos work
|
|
144
|
+
|
|
145
|
+
- Their doubts come to you: decide them for speed (the fastest way to show the whole experience)
|
|
146
|
+
and only take to the human what changes the hypothesis, the proof, the scope or the hard stops.
|
|
147
|
+
- Check each report against the hard stops, not against style: a secret in a diff
|
|
148
|
+
(`git -C <wt> diff <base>...HEAD`), a non-additive migration, a real provider endpoint outside
|
|
149
|
+
sandbox mode. Any of those goes back to that rudo before anything else.
|
|
150
|
+
- **Hand-off between repos.** Nothing is published to a registry for a sparring. The producer
|
|
151
|
+
leaves what the consumer needs in `<sparring dir>/tools/` (an SDK as an `npm pack` tarball, a
|
|
152
|
+
fixture, a mock) and reports its path, the port it serves on and one `curl` that proves it; you
|
|
153
|
+
forward that to the consumer's rudo, which installs it from there.
|
|
154
|
+
- **Seeding a shared database** (dev, staging) when the proof needs data that does not exist yet,
|
|
155
|
+
because the feature is new: only with the judge's explicit yes. The rudo prepares it, never runs
|
|
156
|
+
it: synthetic rows only, each one marked with the sparring's slug, values that can reach nobody
|
|
157
|
+
(phones with an unassigned prefix, `example.invalid` emails), a dry run that prints what it
|
|
158
|
+
would write, and a cleanup that deletes only the marked rows, ready **before** seeding. Write the
|
|
159
|
+
seed and the cleanup commands under "Seed" in the card's notes (below the notes marker, which every rewrite keeps). **The judge runs the seed**, with `!` in
|
|
160
|
+
their session; the referee and the rudo never write to a shared database, and a write the
|
|
161
|
+
session's permissions block is never retried another way. Cleaning up is pending until the
|
|
162
|
+
verdict.
|
|
163
|
+
- At the end of the timebox each rudo stops. A second timebox is the human's call (§4 rematch).
|
|
164
|
+
|
|
165
|
+
## 3 · The PRs and the preview
|
|
166
|
+
|
|
167
|
+
1. When a rudo pushed something worth trying, open its repo's PR (`--repo` can be left out only
|
|
168
|
+
when the sparring has one repo). Each PR body links the other repos of the sparring:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
node "$RING/scripts/sparring.mjs" pr <slug> --repo <owner/repo> --title "<feature>" \
|
|
172
|
+
--how-to-test "<steps from the rudo>" --shortcuts "<the rudo's list>"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
It opens the draft with the sparring and do-not-merge labels (created in that repo when it
|
|
176
|
+
lacks them) and the credentials block (names only) and warning. That PR is the event the
|
|
177
|
+
repo's CI catches.
|
|
178
|
+
|
|
179
|
+
2. Previews: `node "$RING/scripts/sparring.mjs" preview <slug> --wait` with
|
|
180
|
+
`run_in_background: true` looks at every repo (or one, with `--repo`), each bounded by its own
|
|
181
|
+
`previewTimeoutMin`. Give the judge a link only when it printed one; otherwise say exactly what
|
|
182
|
+
it printed (no preview configured, pending, failure, timed out). Never say "ready to try"
|
|
183
|
+
without the link in hand.
|
|
184
|
+
3. **Local test** for the repos without a preview: one runbook for the judge, in order. The
|
|
185
|
+
producer first (how to start it, its port, `.env.sparring`, the `curl` that proves it), then
|
|
186
|
+
each consumer pointed at it (the env var with the producer's URL, how to install the hand-off
|
|
187
|
+
from `tools/`, how to start it, the URL to open). Ports must not collide with what the judge
|
|
188
|
+
already runs; ask when unsure. Locally, the producer accepts requests from any
|
|
189
|
+
`http://localhost:<port>` origin (CORS), never a single fixed port; that is a shortcut to list.
|
|
190
|
+
4. **Before announcing it, click everything.** A browser pass (Playwright or the like) over each
|
|
191
|
+
screen at the sizes the design draws, clicking every visible control and listing the ones that
|
|
192
|
+
do nothing, have no loader or fail silently. A dead control goes back to its rudo; the
|
|
193
|
+
sparring is not announced until the list is empty or the judge accepted what is left.
|
|
194
|
+
5. Tell the judge, in one short message: **the exact URL of each screen to open** (with its path,
|
|
195
|
+
not just the host), **the user, workspace or account to enter with**, what to look at first,
|
|
196
|
+
the links or the runbook, the hypothesis, the proof of value to check, and the credentials
|
|
197
|
+
warning. A sparring with a front is never announced without the screen's URL.
|
|
198
|
+
|
|
199
|
+
## 4 · `verdict <slug>`: two out of three falls
|
|
200
|
+
|
|
201
|
+
The fight has three falls, in this order: the rudo against the judge (verdicts), the defense of the
|
|
202
|
+
técnicos (the team's Tapes, after the vestidor) and the técnico (the referee bout). Mark each
|
|
203
|
+
moment when it happens (`sparring.mjs mark`, §0.6) so the chronicle has its real time.
|
|
204
|
+
|
|
205
|
+
### First fall · the rudo against the judge
|
|
206
|
+
|
|
207
|
+
#### The judge attacks with a Tape
|
|
208
|
+
|
|
209
|
+
When the rudos deliver, the judge attacks: they record their review as a Tape session
|
|
210
|
+
(`battlearena.dev/t/<id>`), voice over their own clicks, and hand it to the battle so it knows every
|
|
211
|
+
blow it took. That Tape is the evidence of the verdict and the brief of the next round.
|
|
212
|
+
|
|
213
|
+
1. **Read it yourself**, with the Tape tools (`qa_session_overview`, `qa_session_timeline` with
|
|
214
|
+
speech, `qa_session_errors`, `qa_session_frame` for what was on screen). Never work from a
|
|
215
|
+
summary someone else wrote.
|
|
216
|
+
2. **One voice: you, the referee of the sparring, write the catalog of blows.** Nobody else
|
|
217
|
+
(another session, a monitor, a rudo) sends their own version to the rudos; if one reaches you,
|
|
218
|
+
reconcile it into yours before anything goes out. Two catalogs of one verdict make the rudos
|
|
219
|
+
fight different fights.
|
|
220
|
+
3. **The catalog** goes to `<sparring dir>/blows-round-<n>.md`, each blow with the Tape minute and
|
|
221
|
+
the judge's words when they matter:
|
|
222
|
+
- **Decisions** of the judge (D1, D2…): product calls the rudos apply, never reopen.
|
|
223
|
+
- **Bugs** (B1…): what broke, how to reproduce it.
|
|
224
|
+
- **Missing** (F1…): what the judge expected to find.
|
|
225
|
+
- **Questions for the judge** (Q1…): design doubts the rudos answer with a proposal, not a
|
|
226
|
+
question back.
|
|
227
|
+
- **Noise**: what failed on screen but is not the sparring's (a third-party widget, an
|
|
228
|
+
unrelated 401); listed so nobody fixes it.
|
|
229
|
+
|
|
230
|
+
Then split it **by repo**: each rudo gets only its own blows, plus the decisions that touch it.
|
|
231
|
+
|
|
232
|
+
4. Record the verdict with the Tape as evidence (`--note "Tape <url> (<date time>): D1…; …"`) and
|
|
233
|
+
link the catalog in the card's notes. The card keeps every round (earlier verdicts stay under
|
|
234
|
+
"Earlier rounds").
|
|
235
|
+
|
|
236
|
+
#### The call
|
|
237
|
+
|
|
238
|
+
Ask the judge for the call and the evidence (a Tape session, screenshots, what they saw), then
|
|
239
|
+
`node "$RING/scripts/sparring.mjs" verdict <slug> <ko|rematch|discard> --note "<why · evidence>"`.
|
|
240
|
+
|
|
241
|
+
- **rematch:** adjust and try again inside a new timebox; send the rudo what changes.
|
|
242
|
+
- **discard:** list the PRs for the human to close (`gh pr close <n> --delete-branch` is theirs).
|
|
243
|
+
The card keeps why: a discarded idea is a result too.
|
|
244
|
+
- **ko:** it is worth building, and the first fall is over. It does **not** go to the referee yet:
|
|
245
|
+
ask the judge, in the same message, whether the team defends it (default) or it goes on «sin
|
|
246
|
+
equipo». With «sin equipo», `node "$RING/scripts/sparring.mjs" mark <slug> no-team` and jump to
|
|
247
|
+
the third fall: no vestidor, no defense. Otherwise, the vestidor.
|
|
248
|
+
|
|
249
|
+
After a **rematch** or a **discard**, distill the judge's reason into a rule that holds for the
|
|
250
|
+
next sparring and append it to `references/lessons.md` (rule, date, slug), unless a lesson already
|
|
251
|
+
says it. That is how one judge's feedback reaches every later sparring, not only this one.
|
|
252
|
+
|
|
253
|
+
### The vestidor (after the KO, before the team)
|
|
254
|
+
|
|
255
|
+
Until the KO the sparring stays local, fast and ugly; nothing of what follows is prepared earlier,
|
|
256
|
+
so the rudo neither worries about it nor spends time on it. Now it has to look serious, whatever
|
|
257
|
+
the repo: **every repo of the sparring passes its host's own gates**, so each PR could be deployed
|
|
258
|
+
if needed.
|
|
259
|
+
|
|
260
|
+
1. `node "$RING/scripts/sparring.mjs" vestidor <slug>` (refused unless the last verdict is `ko`).
|
|
261
|
+
In each repo's worktree it runs that repo's top-level gates from `.claude/ring.json` (or
|
|
262
|
+
autodetected): `typecheck`, `lint` and `build` when configured, one at a time through the
|
|
263
|
+
machine lock; `test` never (it takes paths; the referee bout runs the tests). It prints each
|
|
264
|
+
gate with its exit code, keeps the logs in `<sparring dir>/vestidor/`, and stamps the
|
|
265
|
+
`vestidor` milestone only when all of them passed; otherwise it stamps nothing and exits 1.
|
|
266
|
+
2. A red gate goes back to its rudo. A lint rule that only blocks the build may be switched off on
|
|
267
|
+
the sparring branch, listed as a shortcut. **Typecheck is never switched off.** Run the vestidor
|
|
268
|
+
again until it passes.
|
|
269
|
+
3. **Now, and only now, the sharing prep**, as one step, expecting all of it to bite at once:
|
|
270
|
+
- the schema in additive migrations (or loaded into the shared database), not only a `tools/` SQL;
|
|
271
|
+
- any SDK or hand-off the front needs, vendored on the branch when it is not published;
|
|
272
|
+
- identity per judge from their session, never one shared operator;
|
|
273
|
+
- a seed of the shared environment with enough data to look like the local run (§2: the judge
|
|
274
|
+
runs it). Real data only with the judge's own yes, given in your session, for that environment;
|
|
275
|
+
- the allowlist of real sends stays closed for everyone but the judge.
|
|
276
|
+
4. **Deploy through the host's own mechanism** (its preview or sandbox workflow), never one of the
|
|
277
|
+
ring's. Click everything with a logged-in session before sharing the URLs.
|
|
278
|
+
5. **A short guide for the team:** the URLs with their path, how to log in, what to try first,
|
|
279
|
+
the hypothesis, how to record a Tape and where to send it (ask the judge where).
|
|
280
|
+
|
|
281
|
+
### Second fall · the defense of the técnicos
|
|
282
|
+
|
|
283
|
+
The judge opens the arena to the team («¡a la defensa de los técnicos!»), mandatory unless they
|
|
284
|
+
said «sin equipo». The team attacks with a storm of Tapes until **the bell**.
|
|
285
|
+
|
|
286
|
+
1. **The bell.** The judge sets the time limit when opening the defense:
|
|
287
|
+
`node "$RING/scripts/sparring.mjs" mark <slug> defense --note "<hours>h"`. Battle Arena will keep
|
|
288
|
+
it (the ring API, draft PR #779: `POST /v1/ring/bouts/:id/stage` with `defenseHours`); until
|
|
289
|
+
that is merged and this plugin calls it, the milestone's note is the only record of the limit:
|
|
290
|
+
tell the judge so.
|
|
291
|
+
2. **The rudo is on defense now**, correcting under fire what the judge accepts as "apply now". Its
|
|
292
|
+
fear shows only in the live feed, the Ringside and the chronicle, never in a commit or a PR.
|
|
293
|
+
3. **Every Tape goes through the same funnel**: read it yourself (first fall, step 1) and fold it
|
|
294
|
+
into one catalog of blows for the defense (`blows-round-<n>.md`, the next round, with the
|
|
295
|
+
judge who gave each blow). Duplicates merge and conflicts between judges go to the main judge as
|
|
296
|
+
a question; the rudos never get one catalog per teammate.
|
|
297
|
+
4. **Triage before any score.** Nothing the team said reaches a rudo, and nothing counts on the
|
|
298
|
+
scoreboard, until the main judge has triaged it. Analyze every point of every Tape (none is
|
|
299
|
+
omitted, not even the ones that look off-topic or are duplicates) and propose, ranked by
|
|
300
|
+
criticality (blocker: unusable or wrong data · high: breaks or confuses a main flow · medium ·
|
|
301
|
+
low: cosmetic or nice-to-have), a recommendation for each: apply now, apply in the referee bout,
|
|
302
|
+
ask the judge, or out of scope. A recommendation is never a decision: **only the judge discards**.
|
|
303
|
+
Points you would drop stay in the list, marked, with the reason, for the judge to rule on. What
|
|
304
|
+
the judge accepts becomes the defense's catalog of blows; the rest is recorded with the judge's
|
|
305
|
+
ruling, so it is not lost. Triage Tapes as they arrive; at the bell every point is ruled.
|
|
306
|
+
**The triage page is where the judge rules.** Publish it as a private Artifact, in the
|
|
307
|
+
play-by-play style (one beat per blow, grouped by criticality as rounds, the Tape's start time in
|
|
308
|
+
the margin). Each blow carries:
|
|
309
|
+
- who gave it;
|
|
310
|
+
- its source: every cited minute is a ▶ link to the Battle Arena library's QA view at that
|
|
311
|
+
second (`/labs/biblioteca?sesion=<recording id>&vista=qa&t=<seconds>`);
|
|
312
|
+
- its recommendation;
|
|
313
|
+
- the judge's ruling.
|
|
314
|
+
|
|
315
|
+
The ruling lives in the page's `db`, in collection `fallos`, one document per blow ID. Only the
|
|
316
|
+
judge writes it (rule `{path:"fallos", read:"view", write:"admin"}`); everyone the page is shared
|
|
317
|
+
with reads it. A ruling is one of:
|
|
318
|
+
- **Aplica**, with the judge's observation **mandatory** (what to apply and how);
|
|
319
|
+
- **No aplica**, with an **optional** observation.
|
|
320
|
+
|
|
321
|
+
Then the judge marks **Aplicado al Rudo** once the rudo's correction is in, and the page stamps
|
|
322
|
+
the time.
|
|
323
|
+
|
|
324
|
+
You read `fallos` with `ArtifactData`: only `aplica` blows, each with its observation, go to the
|
|
325
|
+
rudos. You tell the judge when one is corrected so they can mark it applied. Never write a
|
|
326
|
+
ruling yourself.
|
|
327
|
+
|
|
328
|
+
**The judge to the técnicos.** The page closes with one card per teammate who got in the ring:
|
|
329
|
+
- their Tapes;
|
|
330
|
+
- their blows;
|
|
331
|
+
- how many apply, do not apply, or are still pending. These numbers are computed live from
|
|
332
|
+
`fallos`.
|
|
333
|
+
|
|
334
|
+
On each card, the judge leaves their feedback to that person: which blows counted, what was
|
|
335
|
+
missing, and how to attack better next time. It goes in collection `feedback`, one document per
|
|
336
|
+
person, and only the judge writes it (`{path:"feedback", read:"view", write:"admin"}`). Nothing
|
|
337
|
+
moves to the rudos until the judge says the triage is complete.
|
|
338
|
+
|
|
339
|
+
**The fight report.** When the judge says the review is complete, read `fallos` and `feedback`
|
|
340
|
+
and build a PDF report in the Lotería style of the host (its paper, ink, picado and card
|
|
341
|
+
tokens). Render it as HTML and print it with headless Chrome. Look at the pages before handing
|
|
342
|
+
it over. The report has four parts:
|
|
343
|
+
- **The poster:** who got in the ring, the tally (Tapes, blows, apply, do not apply), a table by
|
|
344
|
+
criticality, and the blows that did not apply with the judge's reason.
|
|
345
|
+
- **One card per técnico:** Tapes, blows, apply and do not apply. The judge's feedback is
|
|
346
|
+
polished into «Lo mejor» and «Para la próxima», faithful to what the judge wrote and without
|
|
347
|
+
typos. Say which lines you added where the judge's note had nothing for that part.
|
|
348
|
+
- **Next steps by workstream:** product decisions, research before design, design rounds in the
|
|
349
|
+
host's design tool (with the number of proposals asked for), and direct fixes to the Rudo.
|
|
350
|
+
Each blow that applies is one row: ID, topic, and a short action you write from the judge's
|
|
351
|
+
observation. A blow that only refers to another one names it. Check that every blow that
|
|
352
|
+
applies appears at least once.
|
|
353
|
+
- **The close of the second fall:** hand off to the arbiter, the bell, the mask, and the third
|
|
354
|
+
fall.
|
|
355
|
+
|
|
356
|
+
The judge's original notes stay in the db. The PDF goes to the judge for validation, and
|
|
357
|
+
nothing reaches the arbiter until the judge approves it.
|
|
358
|
+
|
|
359
|
+
5. **At the bell**: `node "$RING/scripts/sparring.mjs" mark <slug> bell --note "<Tapes · accepted
|
|
360
|
+
blows>"`. Late Tapes go to the referee bout as notes, not to the scoreboard.
|
|
361
|
+
6. **The mask.** The scoreboard counts only the accepted blows; the team's Tapes are evidence, not
|
|
362
|
+
votes. The judge looks at it and decides (never you, never a count, never the server):
|
|
363
|
+
- **mask kept** (`mark <slug> mask-kept --note "<scoreboard>"`): the team found little; the rudo
|
|
364
|
+
reaches the referee as a champion, with a short first package;
|
|
365
|
+
- **mask lost** (`mark <slug> mask-lost --note "<scoreboard>"`): badly hurt; the accepted
|
|
366
|
+
catalog becomes the referee's first rebound.
|
|
367
|
+
|
|
368
|
+
### Third fall · the técnico
|
|
369
|
+
|
|
370
|
+
The next step is `/ring:referee start --from-sparring <slug>`, one bout per repo (a session in each
|
|
371
|
+
repo). It promotes the sparring: `sparring.mjs promote <slug>` (refused unless the last verdict is
|
|
372
|
+
`ko`) creates `release/<slug>` from the repo's base, `mainevent/<slug>` from the sparring tip, and
|
|
373
|
+
opens the deliverable PR mainevent → release as a draft; nobody creates a branch by hand, and
|
|
374
|
+
pushing `sparring/*` elsewhere stays blocked. The sparring and its PR stay do-not-merge, as the
|
|
375
|
+
evidence of what the KO saw; the bout cleans `mainevent/<slug>` and shows the judge the sparring's
|
|
376
|
+
contract before any cleanup. `judges-card.mjs open
|
|
377
|
+
--from-sparring` refuses a sparring that is not a KO, warns when neither `vestidor` nor `no-team`
|
|
378
|
+
was marked, and stamps the `referee` milestone itself (`mark <slug> referee` only when a bout opens
|
|
379
|
+
some other way). The behavior the judge validated is the invariant the bout keeps; the defense's
|
|
380
|
+
catalog is its first rebound when the mask was lost, a short first package when it was kept.
|
|
381
|
+
|
|
382
|
+
The card and the PR body always carry the fixed warning: the credentials it used are assumed
|
|
383
|
+
rotated. Nobody follows it up.
|
|
384
|
+
|
|
385
|
+
## 5 · `list`
|
|
386
|
+
|
|
387
|
+
`node "$RING/scripts/sparring.mjs" list`: every sparring on this machine with its repos and PRs,
|
|
388
|
+
verdict and age.
|
|
389
|
+
Those over `staleDays` without a verdict show `STALE`: bring them to the judge for a call.
|
|
390
|
+
|
|
391
|
+
## Hard guards
|
|
392
|
+
|
|
393
|
+
`mask-guard` enforces these on every session, sparring or bout or neither:
|
|
394
|
+
|
|
395
|
+
- `.env.sparring` is never committed;
|
|
396
|
+
- a `sparring/*` branch is never merged (`git merge sparring/…`) nor pushed to any branch that is
|
|
397
|
+
not itself `sparring/*`;
|
|
398
|
+
- a PR from `sparring/*` is created only as a draft with the do-not-merge label, and never taken out
|
|
399
|
+
of draft (`gh pr ready`) or merged (`gh pr merge`).
|
|
400
|
+
|
|
401
|
+
And these are yours:
|
|
402
|
+
|
|
403
|
+
- No credential value in any message, file, PR, commit or log. Names only.
|
|
404
|
+
- Sandbox or test mode of every provider; production keys are an escalation, never a shortcut.
|
|
405
|
+
- A real send only to the judge's own allowlisted destinations (§1.8), closed by default; never a
|
|
406
|
+
destination in a file, a commit or a log.
|
|
407
|
+
- No files in a checkout the sparring does not own; helpers go in `<sparring dir>/tools/`.
|
|
408
|
+
- Every PR is a draft; closing, merging and base changes are the human's.
|