@thebattlearena/corner 0.10.1-beta.80 → 0.10.1-beta.82

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 CHANGED
@@ -439,7 +439,7 @@ accept|reject --why "<lo que dijo el humano>"`.
439
439
 
440
440
  **Revisión humana por bloques (PR Review Lab)**: cada punto del plan técnico es un bloque (`B<n>` es
441
441
  el punto n). `judges-card.mjs plan <pr> estimate <n> --minutes <m> [--priority 1-4]` le pone estimado y
442
- prioridad. Al pasar a «en curso» se anotan el inicio y el autor (el `user.email` de git del worktree),
442
+ prioridad (sin ellos un bloque no arranca; también van en el mismo comando: `plan <pr> <n> "en curso" --minutes <m> --priority <p>`). Al pasar a «en curso» se anotan el inicio y el autor (el `user.email` de git del worktree),
443
443
  y al pasar a «espera al juez» el fin y el rango (`--range A..B` lo fija a mano); todo va en
444
444
  `plan.jsonl`, que es lo que lee el modo «Pelea del ring» de PR Review Lab. Cuando `humanReviewEvery`
445
445
  bloques (2 por default, `null` lo apaga) esperan al juez, suena la campana en PR Review Lab y
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thebattlearena/corner",
3
- "version": "0.10.1-beta.80",
3
+ "version": "0.10.1-beta.82",
4
4
  "description": "Corner: tu esquina de Battle Arena en Claude Code (el plugin `battlearena`). Hooks de tracking, objetivo del turno, plan de batalla /battlearena:conquer, Ringside (el panel de tu día), el ring y la radio. Zero-dependency.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -610,10 +610,25 @@ function plan([pr, target, ...rest]) {
610
610
  const [state] = rest;
611
611
  if (!target || !state)
612
612
  throw new Error(
613
- 'usage: plan <pr> <n|title> <pendiente|espera al juez|en curso|hecho> [--ref <sha|round-bell>] [--range A..B] | estimate <n> --minutes <m> [--priority 1-4] | add … | list',
613
+ 'usage: plan <pr> <n|title> <pendiente|espera al juez|en curso|hecho> [--ref <sha|round-bell>] [--range A..B] [--minutes <m> --priority <1-4>] | estimate <n> --minutes <m> [--priority 1-4] | add … | list',
614
614
  );
615
+ const { config } = loadConfig(projectDir(), {
616
+ contract: false,
617
+ venue: readState(dir).venue ?? DEFAULT_VENUE,
618
+ });
619
+ const inline = [value('--minutes'), value('--priority')].some(
620
+ (given) => given != null,
621
+ );
615
622
  const changed = setPlanState(dir, target, state, value('--ref'), undefined, {
616
623
  range: value('--range'),
624
+ estimate: inline
625
+ ? estimateFacts({
626
+ priority: value('--priority'),
627
+ minutes: value('--minutes'),
628
+ })
629
+ : null,
630
+ // Under the human review by blocks a block starts with its estimate and priority.
631
+ requireEstimate: Boolean(config.humanReviewEvery),
617
632
  });
618
633
  console.log(
619
634
  `${changed.n}. ${changed.title}: ${changed.from} → ${changed.to} at ${localStamp(new Date())}${changed.facts.range && changed.to === 'espera al juez' ? ` · range ${changed.facts.range}` : ''}`,
@@ -70,6 +70,21 @@ export function factsForState(previous, to, { at, head, email, range }) {
70
70
  return {};
71
71
  }
72
72
 
73
+ /**
74
+ * Why a block may not start: under the human review by blocks, a block enters «en curso» only with
75
+ * its estimate and priority (PR Review Lab orders by one and calibrates the other against the real
76
+ * time per person). Null when it may. PURE.
77
+ */
78
+ export function startRefusal(facts, to, n) {
79
+ if (to !== 'en curso') return null;
80
+ const missing = [
81
+ facts.estimateMin == null && '--minutes <m>',
82
+ facts.priority == null && '--priority <1-4>',
83
+ ].filter(Boolean);
84
+ if (!missing.length) return null;
85
+ return `B${n} has no ${facts.estimateMin == null ? 'estimate' : 'priority'} yet: a block starts with its estimate and priority (the human review by blocks). Add ${missing.join(' ')} to this command, or run plan <pr> estimate ${n} ${missing.join(' ')} first`;
86
+ }
87
+
73
88
  /** Checks an estimate: priority 1–4 and whole minutes. PURE. */
74
89
  export function estimateFacts({ priority, minutes }) {
75
90
  const facts = {};
@@ -12,7 +12,12 @@ import { join } from 'node:path';
12
12
 
13
13
  import { readState } from './config.mjs';
14
14
  import { scriptJson } from './contract-endpoints.mjs';
15
- import { authorEmail, blockFacts, factsForState } from './review-blocks.mjs';
15
+ import {
16
+ authorEmail,
17
+ blockFacts,
18
+ factsForState,
19
+ startRefusal,
20
+ } from './review-blocks.mjs';
16
21
  import {
17
22
  parsePlan,
18
23
  parsePlanLog,
@@ -34,7 +39,7 @@ export function setPlanState(
34
39
  state,
35
40
  ref = null,
36
41
  at = new Date().toISOString(),
37
- { range = null, estimate = null } = {},
42
+ { range = null, estimate = null, requireEstimate = false } = {},
38
43
  ) {
39
44
  const file = join(dir, PLAN);
40
45
  const markdown = read(file);
@@ -58,6 +63,11 @@ export function setPlanState(
58
63
  range,
59
64
  })),
60
65
  };
66
+ const refusal =
67
+ requireEstimate && next !== item.state
68
+ ? startRefusal(facts, next, item.n)
69
+ : null;
70
+ if (refusal) throw new Error(refusal);
61
71
  writeFileSync(file, withPlanState(markdown, item, next));
62
72
  appendFileSync(
63
73
  join(dir, PLAN_LOG),
@@ -2,6 +2,7 @@
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 wrestler (Sonnet, effort high by default) writes in its own worktree; you (effort high) review each commit range with read-only cornermen against a numbered rulebook, every brief generated by `brief.mjs` with the rules embedded, 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
4
  argument-hint: "start <pr> | start --from-sparring <slug> | resume [<pr>] | close"
5
+ effort: high
5
6
  ---
6
7
 
7
8
  # referee
@@ -212,7 +213,8 @@ way out, and it refuses without a KO. Then the bout runs as always, with three i
212
213
  `brief.mjs` briefs no wrestler. That is what a compaction or a new session resumes from.
213
214
  **Human review by blocks**: each point of the technical plan is a block. Give each its estimate
214
215
  and priority when the plan is made (`judges-card.mjs plan <pr> estimate <n> --minutes <m>
215
- --priority <1-4>`), move it to «en curso» when its wrestler starts and to «espera al juez» when
216
+ --priority <1-4>`; the script refuses to start a block without both, or pass them with the
217
+ start: `plan <pr> <n> "en curso" --minutes <m> --priority <p>`), move it to «en curso» when its wrestler starts and to «espera al juez» when
216
218
  its review passes (that stamps the author, the times and the range PR Review Lab reviews). Every
217
219
  `humanReviewEvery` blocks waiting, brief.mjs stops: the human reviews that cut in PR Review Lab
218
220
  (bout mode) and signs a verdict with a Tape. The next brief applies it: a pass closes the
@@ -1,7 +1,8 @@
1
1
  ---
2
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 (Sonnet at effort high by default) 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'
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 (Sonnet at effort high by default) 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 bell <slug>`, `/ring:sparring mask <slug> [kept|lost]`, `/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> | bell <slug> | mask <slug> [kept|lost] | list'
5
+ effort: high
5
6
  ---
6
7
 
7
8
  # sparring
@@ -365,10 +366,12 @@ said «sin equipo». The team attacks with a storm of Tapes until **the bell**.
365
366
  The judge's original notes stay in the db. The PDF goes to the judge for validation, and
366
367
  nothing reaches the arbiter until the judge approves it.
367
368
 
368
- 5. **At the bell**: `node "$RING/scripts/sparring.mjs" mark <slug> bell --note "<Tapes · accepted
369
- blows>"`. Late Tapes go to the referee bout as notes, not to the scoreboard.
370
- 6. **The mask.** The scoreboard counts only the accepted blows; the team's Tapes are evidence, not
371
- votes. The judge looks at it and decides (never you, never a count, never the server):
369
+ 5. **At the bell** (`/ring:sparring bell <slug>`, §5): `node "$RING/scripts/sparring.mjs" mark <slug>
370
+ bell --note "<Tapes · accepted blows>"`. Late Tapes go to the referee bout as notes, not to the
371
+ scoreboard.
372
+ 6. **The mask** (`/ring:sparring mask <slug> [kept|lost]`, §5). The scoreboard counts only the
373
+ accepted blows; the team's Tapes are evidence, not votes. The judge looks at it and decides
374
+ (never you, never a count, never the server):
372
375
  - **mask kept** (`mark <slug> mask-kept --note "<scoreboard>"`): the team found little; the rudo
373
376
  reaches the referee as a champion, with a short first package;
374
377
  - **mask lost** (`mark <slug> mask-lost --note "<scoreboard>"`): badly hurt; the accepted
@@ -391,7 +394,37 @@ catalog is its first rebound when the mask was lost, a short first package when
391
394
  The card and the PR body always carry the fixed warning: the credentials it used are assumed
392
395
  rotated. Nobody follows it up.
393
396
 
394
- ## 5 · `list`
397
+ ## 5 · `bell <slug>` and `mask <slug> [kept|lost]`: the close of the second fall
398
+
399
+ The judge closes the defense from here, without remembering script names. Both read
400
+ `~/.claude/ring/sparrings/<slug>/sparring.json` first and refuse, saying why, when the last verdict
401
+ is not `ko`, or when the moment is already marked (its milestone is there: say when, and stop).
402
+
403
+ **`bell <slug>`**: the defense is over.
404
+
405
+ 1. Refuse unless `defense` was marked (or `no-team`: then there is no bell, say so and go to the
406
+ mask).
407
+ 2. The scoreboard: the Tapes the team recorded and the blows of the triage (`fallos` with
408
+ `ArtifactData`): how many apply, do not apply and are still pending. A pending blow is not
409
+ counted yet: tell the judge which ones and ask whether to ring anyway.
410
+ 3. `node "$RING/scripts/sparring.mjs" mark <slug> bell --note "<n> Tapes · <m> golpes · <a>
411
+ aplican · <d> no aplican"`, refresh the chronicle (`/ring:play-by-play --sparring <slug>`) and
412
+ show the scoreboard. Then ask for the mask (below) in the same message.
413
+
414
+ **`mask <slug> [kept|lost]`**: the judge's call on how the rudo came out of the defense.
415
+
416
+ 1. Refuse unless `bell` (or `no-team`) is marked.
417
+ 2. Without `kept|lost`: show the scoreboard and the accepted blows by criticality, explain the two
418
+ outcomes in one line each, and ask with AskUserQuestion. The judge decides; never you, never a
419
+ count:
420
+ - **kept**: the team found little; the rudo reaches the referee as a champion and the bout's
421
+ first package is short;
422
+ - **lost**: badly hurt; the whole accepted catalog is the referee's first rebound.
423
+ 3. `node "$RING/scripts/sparring.mjs" mark <slug> mask-kept|mask-lost --note "<scoreboard>"`,
424
+ refresh the chronicle, and give the next step: in a session in each repo of the sparring,
425
+ `/ring:referee start --from-sparring <slug>` (the exact `cd … && claude "…"` line per repo).
426
+
427
+ ## 6 · `list`
395
428
 
396
429
  `node "$RING/scripts/sparring.mjs" list`: every sparring on this machine with its repos and PRs,
397
430
  verdict and age.