@thebattlearena/corner 0.9.1-beta.70 → 0.10.1-beta.78
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 +46 -26
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/ring/agents/cornerman.md +32 -6
- package/ring/agents/rudo.md +2 -1
- package/ring/agents/wrestler.md +19 -4
- package/ring/hooks/hooks.json +42 -0
- package/ring/scripts/bout-stats-render.mjs +366 -0
- package/ring/scripts/bout-stats.mjs +360 -0
- package/ring/scripts/brief-build.mjs +130 -0
- package/ring/scripts/brief-guard.mjs +154 -0
- package/ring/scripts/brief.mjs +272 -0
- package/ring/scripts/compact-hook.mjs +110 -0
- package/ring/scripts/config.mjs +25 -1
- package/ring/scripts/contract-endpoints.mjs +163 -0
- package/ring/scripts/contract-preview.mjs +34 -14
- package/ring/scripts/fight.mjs +27 -0
- package/ring/scripts/findings.mjs +82 -3
- package/ring/scripts/format-gate.mjs +723 -0
- package/ring/scripts/format-worker.mjs +120 -0
- package/ring/scripts/front-routes.mjs +345 -0
- package/ring/scripts/judges-card.mjs +324 -3
- package/ring/scripts/lookout.mjs +22 -4
- package/ring/scripts/mask-guard.mjs +34 -1
- package/ring/scripts/page-embed.mjs +112 -0
- package/ring/scripts/play-by-play.mjs +155 -7
- package/ring/scripts/preparation-collect.mjs +177 -0
- package/ring/scripts/preparation.mjs +547 -0
- package/ring/scripts/round-bell.mjs +9 -2
- package/ring/scripts/rule-ids.mjs +124 -0
- package/ring/scripts/tech-plan-log.mjs +81 -0
- package/ring/scripts/tech-plan.mjs +191 -0
- package/ring/scripts/verdicts-log.mjs +75 -0
- package/ring/scripts/verdicts.mjs +205 -0
- package/ring/skills/play-by-play/SKILL.md +235 -33
- package/ring/skills/play-by-play/templates/blocks.css +196 -0
- package/ring/skills/play-by-play/templates/chronicle.html +358 -55
- package/ring/skills/play-by-play/templates/endpoints-view.js +1153 -0
- package/ring/skills/play-by-play/templates/interview-form.js +516 -0
- package/ring/skills/play-by-play/templates/now-panel.js +719 -0
- package/ring/skills/referee/SKILL.md +181 -56
- package/ring/skills/referee/references/api-diagram.md +56 -0
- package/ring/skills/referee/references/precedents.md +11 -0
- package/ring/skills/referee/references/rulebook.md +97 -6
- package/ring/skills/referee/templates/api-diagram.html +115 -0
- package/ring/skills/referee/templates/cornerman-batch.md +17 -11
- package/ring/skills/referee/templates/cornerman-concept.md +28 -30
- package/ring/skills/referee/templates/cornerman-range.md +16 -11
- package/ring/skills/referee/templates/escalation.md +7 -1
- package/ring/skills/referee/templates/round-bell-body.md +4 -0
- package/ring/skills/referee/templates/wrestler-brief.md +28 -41
- package/ring/skills/referee/templates/wrestler-cutoff.md +4 -0
- package/ring/skills/referee/templates/wrestler-resume.md +4 -0
- package/ring/skills/sparring/SKILL.md +13 -4
- package/ring/skills/sparring/templates/contrato.md +29 -0
- package/ring/skills/sparring/templates/rudo-brief.md +7 -0
- package/ring/skills/title-defense/SKILL.md +2 -2
- package/ring/skills/weigh-in/SKILL.md +6 -0
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
// When a database model changes, the referee draws the change as an entity-relationship diagram
|
|
18
18
|
// (references/db-diagram.md) in <bout dir>/db-diagram.html; the page shows it above the model
|
|
19
19
|
// diff, which folds into a "Code diff" disclosure, and warns when a model file changed after it.
|
|
20
|
+
// The endpoints get theirs the same way (references/api-diagram.md, <bout dir>/api-diagram.html):
|
|
21
|
+
// shown above "What changes", whose diff folds under it, and stale when a spec file changed after.
|
|
20
22
|
//
|
|
21
23
|
// The diff runs in two places from one module (spec-diff.mjs): the page parses both specs (YAML
|
|
22
24
|
// via js-yaml) and computes operations, schemas and error responses; the server computes the same
|
|
@@ -129,7 +131,7 @@ function worktreeFiles(worktree) {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
/** The spec roots the config names: its plain paths, and the OpenAPI roots its globs match. */
|
|
132
|
-
function specRoots(worktree, config) {
|
|
134
|
+
export function specRoots(worktree, config) {
|
|
133
135
|
const files = worktreeFiles(worktree);
|
|
134
136
|
const head = (path) => {
|
|
135
137
|
try {
|
|
@@ -241,6 +243,9 @@ function dbDiff(worktree, ref, config) {
|
|
|
241
243
|
/** The referee's diagram of the model change, drawn per references/db-diagram.md. */
|
|
242
244
|
export const DIAGRAM_FILE = 'db-diagram.html';
|
|
243
245
|
|
|
246
|
+
/** The referee's diagram of the endpoints, drawn per references/api-diagram.md. */
|
|
247
|
+
export const API_DIAGRAM_FILE = 'api-diagram.html';
|
|
248
|
+
|
|
244
249
|
/**
|
|
245
250
|
* When a changed model file was last written, in ms: its mtime, or for a deleted file the time of
|
|
246
251
|
* the commit that deleted it. A deletion not committed yet has no date: `Infinity`, so it always
|
|
@@ -263,12 +268,12 @@ function writtenAt(worktree, path) {
|
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
/**
|
|
266
|
-
*
|
|
267
|
-
* changed
|
|
268
|
-
* below.
|
|
271
|
+
* A diagram of the change (the models by default, or `API_DIAGRAM_FILE`), or `null` when the
|
|
272
|
+
* referee has not drawn it: `stale` lists the changed files written (or deleted) after it, so the
|
|
273
|
+
* judge knows it may not match the diff below.
|
|
269
274
|
*/
|
|
270
|
-
export function diagramState(dir, worktree, files) {
|
|
271
|
-
const diagram = join(dir,
|
|
275
|
+
export function diagramState(dir, worktree, files, file = DIAGRAM_FILE) {
|
|
276
|
+
const diagram = join(dir, file);
|
|
272
277
|
if (!existsSync(diagram)) return null;
|
|
273
278
|
const drawnAt = statSync(diagram).mtimeMs;
|
|
274
279
|
const stale = files
|
|
@@ -319,7 +324,7 @@ export function page(specs, first = 0) {
|
|
|
319
324
|
pre { overflow-x: auto; border: 1px solid var(--line); border-radius: 6px; padding: 8px; font-size: 12px; }
|
|
320
325
|
pre span { display: block; white-space: pre; }
|
|
321
326
|
#swagger { background: #fff; border-radius: 6px; }
|
|
322
|
-
|
|
327
|
+
.diagram { width: 100%; min-height: 320px; border: 1px solid var(--line); border-radius: 6px; background: #fff; }
|
|
323
328
|
.warn { background: var(--chg); border-radius: 6px; padding: 8px; }
|
|
324
329
|
details { margin-top: 12px; } summary { cursor: pointer; }
|
|
325
330
|
</style></head>
|
|
@@ -364,7 +369,14 @@ export function page(specs, first = 0) {
|
|
|
364
369
|
html += list('Spec files changed', specFiles.files.map((f) => f.status + ' ' + f.path), '') +
|
|
365
370
|
(specFiles.diff ? '<details><summary>Spec text diff</summary><pre>' + specLines + '</pre></details>' : '');
|
|
366
371
|
}
|
|
367
|
-
|
|
372
|
+
const apiStale = data.apiDiagram && data.apiDiagram.stale.length
|
|
373
|
+
? '<p class="warn">Changed after the diagram was drawn: ' + esc(data.apiDiagram.stale.join(', ')) + '. Ask the referee to redraw it.</p>'
|
|
374
|
+
: '';
|
|
375
|
+
const changes = html || 'No API changes against the base.';
|
|
376
|
+
document.getElementById('changes').innerHTML = data.apiDiagram
|
|
377
|
+
? apiStale + '<iframe class="diagram" src="/api-diagram" sandbox="allow-same-origin" title="Endpoints of the contract"></iframe>' +
|
|
378
|
+
'<details><summary>Code diff</summary>' + changes + '</details>'
|
|
379
|
+
: changes;
|
|
368
380
|
const db = data.db;
|
|
369
381
|
const lines = db.diff.split('\\n').map((l) => '<span class="' + (l.startsWith('+') ? 'add' : l.startsWith('-') ? 'del' : '') + '">' + esc(l) + '</span>').join('');
|
|
370
382
|
const code = list('Files', db.files.map((f) => f.status + ' ' + f.path), '') +
|
|
@@ -374,13 +386,13 @@ export function page(specs, first = 0) {
|
|
|
374
386
|
? '<p class="warn">Changed after the diagram was drawn: ' + esc(db.diagram.stale.join(', ')) + '. Ask the referee to redraw it.</p>'
|
|
375
387
|
: '';
|
|
376
388
|
const drawn = db.diagram
|
|
377
|
-
? stale + '<iframe
|
|
389
|
+
? stale + '<iframe class="diagram" src="/db-diagram" sandbox="allow-same-origin" title="Database model change"></iframe>' +
|
|
378
390
|
'<details><summary>Code diff</summary>' + code + '</details>'
|
|
379
391
|
: code;
|
|
380
392
|
const dbBox = document.getElementById('db');
|
|
381
393
|
dbBox.innerHTML = db.files.length ? drawn : 'No database-model changes against the base.';
|
|
382
|
-
const frame
|
|
383
|
-
|
|
394
|
+
for (const frame of document.querySelectorAll('iframe.diagram'))
|
|
395
|
+
frame.addEventListener('load', () => { frame.style.height = frame.contentDocument.documentElement.scrollHeight + 'px'; });
|
|
384
396
|
</script>
|
|
385
397
|
</body></html>`;
|
|
386
398
|
}
|
|
@@ -447,8 +459,11 @@ export function handler({ dir, worktree, base, config }) {
|
|
|
447
459
|
: send(200, contentType(path), text);
|
|
448
460
|
}
|
|
449
461
|
if (url.pathname === '/favicon.ico') return send(204, 'text/plain', '');
|
|
450
|
-
if (url.pathname === '/db-diagram') {
|
|
451
|
-
const diagram = join(
|
|
462
|
+
if (url.pathname === '/db-diagram' || url.pathname === '/api-diagram') {
|
|
463
|
+
const diagram = join(
|
|
464
|
+
dir,
|
|
465
|
+
url.pathname === '/db-diagram' ? DIAGRAM_FILE : API_DIAGRAM_FILE,
|
|
466
|
+
);
|
|
452
467
|
return existsSync(diagram)
|
|
453
468
|
? send(200, 'text/html; charset=utf-8', readFileSync(diagram))
|
|
454
469
|
: send(404, 'text/plain', 'not found');
|
|
@@ -467,6 +482,7 @@ export function handler({ dir, worktree, base, config }) {
|
|
|
467
482
|
: send(200, contentType(spec), text);
|
|
468
483
|
}
|
|
469
484
|
if (url.pathname === '/diff.json') {
|
|
485
|
+
const specFiles = specFilesDiff(worktree, ref, config);
|
|
470
486
|
const body = {
|
|
471
487
|
spec,
|
|
472
488
|
specs,
|
|
@@ -476,7 +492,11 @@ export function handler({ dir, worktree, base, config }) {
|
|
|
476
492
|
? serverSpecDiff(spec, atBase(spec), proposed(spec))
|
|
477
493
|
: null,
|
|
478
494
|
db: withDiagram(dbDiff(worktree, ref, config)),
|
|
479
|
-
specFiles
|
|
495
|
+
specFiles,
|
|
496
|
+
// Like the database's: no diagram without a spec change, so an old one never shows.
|
|
497
|
+
apiDiagram: specFiles.files.length
|
|
498
|
+
? diagramState(dir, worktree, specFiles.files, API_DIAGRAM_FILE)
|
|
499
|
+
: null,
|
|
480
500
|
};
|
|
481
501
|
return send(
|
|
482
502
|
200,
|
package/ring/scripts/fight.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { join } from 'node:path';
|
|
|
11
11
|
import { commitEvents, localDay, localTime, score } from './chronicle.mjs';
|
|
12
12
|
import { repoBouts, slugFromRemote } from './config.mjs';
|
|
13
13
|
import { LIVE_LOG, SPARRING_FILE, sparringsHome } from './live.mjs';
|
|
14
|
+
import { sparringFold } from './preparation.mjs';
|
|
14
15
|
import {
|
|
15
16
|
dedupe,
|
|
16
17
|
feedEvents,
|
|
@@ -86,6 +87,29 @@ function option(argv, name) {
|
|
|
86
87
|
return index === -1 ? null : argv[index + 1];
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* The bout's weigh-in with what `sparring.mjs promote` made for its repo: the deliverable PR
|
|
92
|
+
* (mainevent → release) and the sparring's draft and worktree it came from. PURE.
|
|
93
|
+
*/
|
|
94
|
+
export function withPromotion(weighIn, state, repo) {
|
|
95
|
+
const entry = (state.repos ?? []).find((item) => item.repo === repo);
|
|
96
|
+
if (!entry) return weighIn;
|
|
97
|
+
return {
|
|
98
|
+
...weighIn,
|
|
99
|
+
promotion: {
|
|
100
|
+
...weighIn.promotion,
|
|
101
|
+
repo,
|
|
102
|
+
mainevent: entry.mainevent ?? null,
|
|
103
|
+
sparring: {
|
|
104
|
+
branch: entry.branch,
|
|
105
|
+
pr: entry.pr ?? null,
|
|
106
|
+
url: entry.url ?? null,
|
|
107
|
+
worktree: entry.worktree,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
89
113
|
/** Every bout opened `--from-sparring` this sparring, in the sparring's repos. */
|
|
90
114
|
export function boutsOfSparring(state, env = process.env) {
|
|
91
115
|
return (state.repos ?? []).flatMap((entry) =>
|
|
@@ -220,12 +244,15 @@ export function collectFight(slug, argv, { collectBout, readExtra }) {
|
|
|
220
244
|
proof: state.proof,
|
|
221
245
|
verdict: state.verdict ?? null,
|
|
222
246
|
repos: (state.repos ?? []).map(({ repo, pr, url }) => ({ repo, pr, url })),
|
|
247
|
+
fold: sparringFold(state, bouts.length),
|
|
223
248
|
bouts: bouts.map((bout) => ({
|
|
224
249
|
repo: bout.repo,
|
|
225
250
|
pr: bout.pr,
|
|
226
251
|
boutDir: bout.boutDir,
|
|
227
252
|
opened: bout.opened,
|
|
228
253
|
copilotOnHead: bout.copilotOnHead,
|
|
254
|
+
weighIn: withPromotion(bout.weighIn, state, bout.repo),
|
|
255
|
+
interview: bout.interview,
|
|
229
256
|
})),
|
|
230
257
|
falls: sliced.falls,
|
|
231
258
|
rounds: sliced.rounds,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// The ledger of every cornerman finding of a bout, and its audit. Zero-dependency.
|
|
3
3
|
//
|
|
4
|
-
// node findings.mjs add <pr> --source range|concept|batch|final|copilot|human [--range <from>..<to>]
|
|
4
|
+
// node findings.mjs add <pr> --source range|concept|batch|final|copilot|human|contract [--range <from>..<to>]
|
|
5
|
+
// (contract: the judge's «se mejora» on an endpoint, from judges-card page-verdicts)
|
|
5
6
|
// (<text> | --file <report>) one finding, or one per line of a report
|
|
7
|
+
// [--rule <id>] the referee's own rule for the lines that cite none (logged as theirs)
|
|
8
|
+
// A reviewer's finding (range, concept, batch, final, human) cites a rule of the venue
|
|
9
|
+
// in its second field (`<file:line> — R11 — …`); X0 when none covers it.
|
|
6
10
|
// node findings.mjs clean <pr> --range <from>..<to> the cornerman found nothing in that range
|
|
7
11
|
// node findings.mjs applied <pr> F<n>[,F<m>…] <sha> the wrestler applied it in that commit
|
|
8
12
|
// node findings.mjs discard <pr> F<n>[,…] --id D<k> --reason "<why>"
|
|
@@ -38,8 +42,16 @@ import {
|
|
|
38
42
|
repoSlug,
|
|
39
43
|
} from './config.mjs';
|
|
40
44
|
import { record } from './live.mjs';
|
|
45
|
+
import {
|
|
46
|
+
citedRules,
|
|
47
|
+
loadRules,
|
|
48
|
+
NO_RULE,
|
|
49
|
+
uncitedFindings,
|
|
50
|
+
} from './rule-ids.mjs';
|
|
41
51
|
|
|
42
52
|
export const LEDGER = 'findings.json';
|
|
53
|
+
/** Sources whose findings come from a review against the rulebook, so each one cites a rule. */
|
|
54
|
+
export const CITED_SOURCES = ['range', 'concept', 'batch', 'final', 'human'];
|
|
43
55
|
export const SOURCES = [
|
|
44
56
|
'range',
|
|
45
57
|
'concept',
|
|
@@ -47,6 +59,7 @@ export const SOURCES = [
|
|
|
47
59
|
'final',
|
|
48
60
|
'copilot',
|
|
49
61
|
'human',
|
|
62
|
+
'contract',
|
|
50
63
|
];
|
|
51
64
|
/** States that need nothing more from the referee before a round-bell or a push. */
|
|
52
65
|
const SETTLED = ['applied', 'discarded', 'ticket', 'escalated'];
|
|
@@ -377,19 +390,85 @@ function scorecard(dir, ledger, label) {
|
|
|
377
390
|
});
|
|
378
391
|
}
|
|
379
392
|
|
|
393
|
+
/**
|
|
394
|
+
* The rule each finding line answers to: the one it cites, or the referee's `--rule` for a line
|
|
395
|
+
* that cites none. Throws listing every line without a valid one. PURE.
|
|
396
|
+
*/
|
|
397
|
+
export function ruledLines(texts, ids, rule) {
|
|
398
|
+
if (rule && rule !== NO_RULE && !ids.has(rule))
|
|
399
|
+
throw new Error(`--rule ${rule} is not a rule of this venue`);
|
|
400
|
+
const lines = texts.map((text) => ({
|
|
401
|
+
text,
|
|
402
|
+
by: citedRules(text) || !rule ? null : 'referee',
|
|
403
|
+
}));
|
|
404
|
+
const pending = lines.filter((line) => !line.by).map((line) => line.text);
|
|
405
|
+
const wrong = uncitedFindings(pending, ids);
|
|
406
|
+
if (wrong.length)
|
|
407
|
+
throw new Error(
|
|
408
|
+
`every finding cites a rule of the venue in its second field (\`<file:line> — R11 — …\`), or X0 when none covers it; rebound the report to the cornerman, or take the line as yours with --rule <id>:\n${wrong.map(({ line, reason }) => ` ${line} (${reason})`).join('\n')}`,
|
|
409
|
+
);
|
|
410
|
+
return lines.map(({ text, by }) => (by ? { text, by, rule } : { text }));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** The findings no rule covers, as proposals for the rulebook in the judges card. */
|
|
414
|
+
function proposeRules(dir, findings) {
|
|
415
|
+
const card = join(dir, 'judges-card.md');
|
|
416
|
+
const uncovered = findings.filter(
|
|
417
|
+
(finding) =>
|
|
418
|
+
finding.rule === NO_RULE || citedRules(finding.text)?.includes(NO_RULE),
|
|
419
|
+
);
|
|
420
|
+
if (!uncovered.length || !existsSync(card)) return;
|
|
421
|
+
const text = uncovered.reduce(
|
|
422
|
+
(current, finding) =>
|
|
423
|
+
withSectionLine(
|
|
424
|
+
current,
|
|
425
|
+
'For the rulebook / precedents',
|
|
426
|
+
`- ${finding.id} (X0, no rule covers it): ${finding.text} — which rule is missing?`,
|
|
427
|
+
),
|
|
428
|
+
readFileSync(card, 'utf8'),
|
|
429
|
+
);
|
|
430
|
+
writeFileSync(card, text);
|
|
431
|
+
}
|
|
432
|
+
|
|
380
433
|
function add([pr, ...argv]) {
|
|
381
434
|
const { values, rest } = flags(argv);
|
|
382
435
|
const { dir, state } = bout(pr);
|
|
383
436
|
const texts = values.file
|
|
384
437
|
? reportLines(readFileSync(values.file, 'utf8'))
|
|
385
438
|
: [rest.join(' ').trim()].filter(Boolean);
|
|
439
|
+
const cited = CITED_SOURCES.includes(values.source);
|
|
440
|
+
const ids = cited
|
|
441
|
+
? loadRules(
|
|
442
|
+
loadConfig(projectDir(), {
|
|
443
|
+
contract: false,
|
|
444
|
+
venue: state.venue ?? 'backend',
|
|
445
|
+
}).config,
|
|
446
|
+
).ids
|
|
447
|
+
: null;
|
|
448
|
+
const lines = cited
|
|
449
|
+
? ruledLines(texts, ids, values.rule)
|
|
450
|
+
: texts.map((text) => ({ text }));
|
|
386
451
|
const { ledger, added } = withFindings(readLedger(dir), {
|
|
387
452
|
source: values.source,
|
|
388
453
|
...rangeOf(state, values.range),
|
|
389
|
-
texts,
|
|
454
|
+
texts: lines.map((line) => line.text),
|
|
390
455
|
at: now(),
|
|
391
456
|
});
|
|
392
|
-
|
|
457
|
+
const authored = new Map(added.map((finding, i) => [finding.id, lines[i]]));
|
|
458
|
+
const findings = ledger.findings.map((finding) =>
|
|
459
|
+
authored.get(finding.id)?.by
|
|
460
|
+
? { ...finding, by: 'referee', rule: authored.get(finding.id).rule }
|
|
461
|
+
: finding,
|
|
462
|
+
);
|
|
463
|
+
scorecard(
|
|
464
|
+
dir,
|
|
465
|
+
writeLedger(dir, { ...ledger, findings }),
|
|
466
|
+
'registró hallazgos',
|
|
467
|
+
);
|
|
468
|
+
proposeRules(
|
|
469
|
+
dir,
|
|
470
|
+
findings.filter((finding) => authored.has(finding.id)),
|
|
471
|
+
);
|
|
393
472
|
for (const finding of added) console.log(`${finding.id} · ${finding.text}`);
|
|
394
473
|
}
|
|
395
474
|
|