@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
|
@@ -8,14 +8,22 @@
|
|
|
8
8
|
// A fight that starts at a sparring is one chronicle (fight.mjs): `--sparring <slug>`, or a bout
|
|
9
9
|
// opened `--from-sparring <slug>`, prints the sparring's falls and every bout of it as the third.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
11
|
+
// node play-by-play.mjs embed <page.html> [<bout dir>/db-diagram.html…] [--contract <bout dir>…]
|
|
12
|
+
// [--interview <bout dir>…] (the questions' context for «Ver más información»)
|
|
13
|
+
// [--routes <bout dir>…] (a frontend bout's «Rutas del frontend»)
|
|
14
|
+
// [--plan <bout dir>…] («El plan del técnico», from plan-tecnico.md)
|
|
15
|
+
// after writing the page: each bout's diagram and endpoints go after their
|
|
16
|
+
// placeholders, and the page's scripts at its end (page-embed.mjs)
|
|
17
|
+
//
|
|
18
|
+
// Output: { pr, repo, boutDir, generatedAt, timezone, copilotOnHead, weighIn, interview, rounds,
|
|
19
|
+
// events, score, gaps, notes }. `weighIn` and `interview` are the bout's preparation, told before
|
|
20
|
+
// its first round (preparation.mjs); the events they tell carry `stage`. Every event carries its real time (`at`, plus `local` HH:MM in --tz) and its `source`.
|
|
13
21
|
// Anything without a recorded time goes to `gaps` with the two known times around it; nothing is
|
|
14
22
|
// ever given an invented time. --extra adds times the human or another tool provides (an array of
|
|
15
23
|
// { at, type, title, detail, ref }, inline or in a file), always marked `provided`.
|
|
16
24
|
import { execFileSync } from 'node:child_process';
|
|
17
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
-
import { dirname, join } from 'node:path';
|
|
25
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { basename, dirname, join } from 'node:path';
|
|
19
27
|
|
|
20
28
|
import {
|
|
21
29
|
checkEvents,
|
|
@@ -34,11 +42,33 @@ import {
|
|
|
34
42
|
boutDir,
|
|
35
43
|
isMain,
|
|
36
44
|
projectDir,
|
|
45
|
+
RING_ROOT,
|
|
37
46
|
readState,
|
|
38
47
|
repoSlug,
|
|
39
48
|
slugFromRemote,
|
|
40
49
|
} from './config.mjs';
|
|
41
50
|
import { collectFight } from './fight.mjs';
|
|
51
|
+
import {
|
|
52
|
+
INTERVIEW,
|
|
53
|
+
contextWarnings,
|
|
54
|
+
pageContexts,
|
|
55
|
+
parseInterview,
|
|
56
|
+
withLocalTimes,
|
|
57
|
+
withStages,
|
|
58
|
+
} from './preparation.mjs';
|
|
59
|
+
import {
|
|
60
|
+
collectContractSources,
|
|
61
|
+
endpointsBlock,
|
|
62
|
+
} from './contract-endpoints.mjs';
|
|
63
|
+
import { collectFrontRoutes, routesBlock } from './front-routes.mjs';
|
|
64
|
+
import { collectPreparation } from './preparation-collect.mjs';
|
|
65
|
+
import { planBlock } from './tech-plan-log.mjs';
|
|
66
|
+
import {
|
|
67
|
+
embedBlocks,
|
|
68
|
+
embedContexts,
|
|
69
|
+
embedDiagrams,
|
|
70
|
+
embedScripts,
|
|
71
|
+
} from './page-embed.mjs';
|
|
42
72
|
|
|
43
73
|
const run = (bin, args, cwd) =>
|
|
44
74
|
execFileSync(bin, args, {
|
|
@@ -82,7 +112,109 @@ export function readExtra(value) {
|
|
|
82
112
|
}
|
|
83
113
|
|
|
84
114
|
const USAGE =
|
|
85
|
-
'usage: play-by-play.mjs <pr> [--repo <owner/name>] | --sparring <slug>; [--tz <zone>] [--extra <json|file>] [--no-gh]';
|
|
115
|
+
'usage: play-by-play.mjs <pr> [--repo <owner/name>] | --sparring <slug>; [--tz <zone>] [--extra <json|file>] [--no-gh] | embed <page.html> [<bout dir>/db-diagram.html…] [--contract <bout dir>…] [--interview <bout dir>…] [--routes <bout dir>…] [--plan <bout dir>…]';
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* `embed <page> [<bout dir>/db-diagram.html…] [--contract <bout dir>…]`: puts each bout's diagram
|
|
119
|
+
* after its `ring:db-diagram #<pr>` placeholder (the bout's number is its directory's name), each
|
|
120
|
+
* bout's «Endpoints del contrato» after its `ring:api-contract #<pr>` placeholder, and the page's
|
|
121
|
+
* scripts at its end (page-embed.mjs). Run it after every rewrite of the page.
|
|
122
|
+
*/
|
|
123
|
+
const FLAGS = ['--contract', '--interview', '--routes', '--plan'];
|
|
124
|
+
|
|
125
|
+
function embed(argv) {
|
|
126
|
+
const warnings = [];
|
|
127
|
+
const [page] = argv;
|
|
128
|
+
if (!page) throw new Error(USAGE);
|
|
129
|
+
const valuesOf = (flag) =>
|
|
130
|
+
argv.flatMap((arg, index) => (argv[index - 1] === flag ? [arg] : []));
|
|
131
|
+
const contracts = valuesOf('--contract');
|
|
132
|
+
const interviews = valuesOf('--interview');
|
|
133
|
+
const routeDirs = valuesOf('--routes');
|
|
134
|
+
const planDirs = valuesOf('--plan');
|
|
135
|
+
const diagrams = argv
|
|
136
|
+
.slice(1)
|
|
137
|
+
.filter(
|
|
138
|
+
(arg, index, rest) =>
|
|
139
|
+
!FLAGS.includes(arg) && !FLAGS.includes(rest[index - 1]),
|
|
140
|
+
);
|
|
141
|
+
const byPr = Object.fromEntries(
|
|
142
|
+
diagrams.map((path) => [
|
|
143
|
+
basename(dirname(path)),
|
|
144
|
+
readFileSync(path, 'utf8'),
|
|
145
|
+
]),
|
|
146
|
+
);
|
|
147
|
+
const specs = Object.fromEntries(
|
|
148
|
+
contracts.map((dir) => {
|
|
149
|
+
const sources = collectContractSources(dir);
|
|
150
|
+
return [String(sources.pr), endpointsBlock(sources)];
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
let after = embedDiagrams(readFileSync(page, 'utf8'), byPr);
|
|
154
|
+
after = embedBlocks(after, 'api-contract', specs);
|
|
155
|
+
const routeBlocks = Object.fromEntries(
|
|
156
|
+
routeDirs.map((dir) => {
|
|
157
|
+
const routes = collectFrontRoutes(dir);
|
|
158
|
+
return [String(routes.pr), routesBlock(routes)];
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
after = embedBlocks(after, 'front-routes', routeBlocks);
|
|
162
|
+
const planBlocks = Object.fromEntries(
|
|
163
|
+
planDirs.map((dir) => {
|
|
164
|
+
const block = planBlock(dir);
|
|
165
|
+
return [String(block.pr), block.html];
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
after = embedBlocks(after, 'tech-plan', planBlocks);
|
|
169
|
+
const contexts = Object.assign(
|
|
170
|
+
{},
|
|
171
|
+
...interviews.map((dir) => {
|
|
172
|
+
const file = join(dir, INTERVIEW);
|
|
173
|
+
const entries = parseInterview(
|
|
174
|
+
existsSync(file) ? readFileSync(file, 'utf8') : '',
|
|
175
|
+
);
|
|
176
|
+
const pr = readState(dir).pr;
|
|
177
|
+
const empty = contextWarnings(pr, entries);
|
|
178
|
+
warnings.push(
|
|
179
|
+
...empty.map(
|
|
180
|
+
(id) =>
|
|
181
|
+
`${id}: its context has no «## Contexto <n> · …» section for those questions, so they do not show «Ver más información»`,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
return pageContexts(pr, entries);
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
after = embedContexts(after, contexts);
|
|
188
|
+
const missing = [
|
|
189
|
+
...Object.keys(byPr).map((pr) => `ring:db-diagram #${pr}`),
|
|
190
|
+
...Object.keys(specs).map((pr) => `ring:api-contract #${pr}`),
|
|
191
|
+
...Object.keys(routeBlocks).map((pr) => `ring:front-routes #${pr}`),
|
|
192
|
+
...Object.keys(planBlocks).map((pr) => `ring:tech-plan #${pr}`),
|
|
193
|
+
].filter((marker) => !after.includes(`<!-- ${marker} -->`));
|
|
194
|
+
if (missing.length)
|
|
195
|
+
throw new Error(`${page} has no placeholder <!-- ${missing[0]} -->`);
|
|
196
|
+
const template = (name) =>
|
|
197
|
+
readFileSync(
|
|
198
|
+
join(RING_ROOT, 'skills', 'play-by-play', 'templates', name),
|
|
199
|
+
'utf8',
|
|
200
|
+
);
|
|
201
|
+
after = embedScripts(after, {
|
|
202
|
+
css: template('blocks.css'),
|
|
203
|
+
form: template('interview-form.js'),
|
|
204
|
+
endpoints: template('endpoints-view.js'),
|
|
205
|
+
panel: template('now-panel.js'),
|
|
206
|
+
});
|
|
207
|
+
writeFileSync(page, after);
|
|
208
|
+
return {
|
|
209
|
+
page,
|
|
210
|
+
diagrams: Object.keys(byPr).map(Number),
|
|
211
|
+
contracts: Object.keys(specs).map(Number),
|
|
212
|
+
routes: Object.keys(routeBlocks).map(Number),
|
|
213
|
+
plans: Object.keys(planBlocks).map(Number),
|
|
214
|
+
contexts: Object.keys(contexts),
|
|
215
|
+
warnings,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
86
218
|
|
|
87
219
|
const timezoneOf = (argv) =>
|
|
88
220
|
option(argv, 'tz') ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -109,8 +241,9 @@ export function collectBout([pr, ...argv]) {
|
|
|
109
241
|
const read = (name) =>
|
|
110
242
|
existsSync(join(dir, name)) ? readFileSync(join(dir, name), 'utf8') : '';
|
|
111
243
|
|
|
244
|
+
const decisionsText = read('decisions.md');
|
|
112
245
|
const decisions = parseLedger(
|
|
113
|
-
|
|
246
|
+
decisionsText,
|
|
114
247
|
'decisions',
|
|
115
248
|
'file:decisions.md',
|
|
116
249
|
);
|
|
@@ -235,6 +368,18 @@ export function collectBout([pr, ...argv]) {
|
|
|
235
368
|
gaps.push(...extra.gaps);
|
|
236
369
|
}
|
|
237
370
|
|
|
371
|
+
const preparation = collectPreparation({
|
|
372
|
+
dir,
|
|
373
|
+
state,
|
|
374
|
+
opened,
|
|
375
|
+
decisionsText,
|
|
376
|
+
notes,
|
|
377
|
+
});
|
|
378
|
+
events = withStages(
|
|
379
|
+
[...events, ...preparation.events],
|
|
380
|
+
preparation.interview,
|
|
381
|
+
preparation.scale,
|
|
382
|
+
);
|
|
238
383
|
const sliced = sliceRounds(events);
|
|
239
384
|
events = sliced.events.map((event) => ({
|
|
240
385
|
...event,
|
|
@@ -248,6 +393,7 @@ export function collectBout([pr, ...argv]) {
|
|
|
248
393
|
timezone,
|
|
249
394
|
opened,
|
|
250
395
|
copilotOnHead: onHead,
|
|
396
|
+
...withLocalTimes(preparation, (at) => localTime(at, timezone)),
|
|
251
397
|
rounds: sliced.rounds,
|
|
252
398
|
events,
|
|
253
399
|
score: score(events, sliced.rounds),
|
|
@@ -274,7 +420,9 @@ function collect(argv) {
|
|
|
274
420
|
|
|
275
421
|
if (isMain(import.meta.url)) {
|
|
276
422
|
try {
|
|
277
|
-
|
|
423
|
+
const argv = process.argv.slice(2);
|
|
424
|
+
const result = argv[0] === 'embed' ? embed(argv.slice(1)) : collect(argv);
|
|
425
|
+
console.log(JSON.stringify(result, null, 2));
|
|
278
426
|
} catch (error) {
|
|
279
427
|
console.error(
|
|
280
428
|
`play-by-play: ${String(error.stderr || error.message).trim()}`,
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Reads a bout's preparation from its records (the interview ledger, decisions.md, findings.json,
|
|
2
|
+
// db-diagram.html) and from git (the size of the deliverable), and hands it to the pure rules of
|
|
3
|
+
// preparation.mjs. Read-only and zero-dependency. What goes inside the page (the diagram, the
|
|
4
|
+
// endpoints, the scripts) is page-embed.mjs.
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
collectContractSources,
|
|
11
|
+
contractSourcesHash,
|
|
12
|
+
} from './contract-endpoints.mjs';
|
|
13
|
+
import { readLedger } from './findings.mjs';
|
|
14
|
+
import { collectFrontRoutes, routesSourcesHash } from './front-routes.mjs';
|
|
15
|
+
import {
|
|
16
|
+
contractFindings,
|
|
17
|
+
INTERVIEW,
|
|
18
|
+
interviewEvents,
|
|
19
|
+
interviewOf,
|
|
20
|
+
parseInterview,
|
|
21
|
+
parseShortstat,
|
|
22
|
+
preparationEnd,
|
|
23
|
+
scaleOf,
|
|
24
|
+
} from './preparation.mjs';
|
|
25
|
+
import {
|
|
26
|
+
madeWeight,
|
|
27
|
+
parseVerdicts,
|
|
28
|
+
VERDICTS,
|
|
29
|
+
verdictSummary,
|
|
30
|
+
} from './verdicts.mjs';
|
|
31
|
+
|
|
32
|
+
export const DIAGRAM = 'db-diagram.html';
|
|
33
|
+
|
|
34
|
+
const git = (dir, args) =>
|
|
35
|
+
execFileSync('git', ['-C', dir, ...args], {
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
38
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
39
|
+
}).trim();
|
|
40
|
+
|
|
41
|
+
/** `origin/<base>` when the remote has it, else the local branch, else null. */
|
|
42
|
+
function baseRef(worktree, base) {
|
|
43
|
+
for (const ref of [`origin/${base}`, base]) {
|
|
44
|
+
try {
|
|
45
|
+
git(worktree, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
46
|
+
return ref;
|
|
47
|
+
} catch {
|
|
48
|
+
// try the next one
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The deliverable's size against the stack base: as it was weighed (the tip when the bout opened)
|
|
56
|
+
* and now. Null when the worktree is gone or the base cannot be found.
|
|
57
|
+
*/
|
|
58
|
+
function sizeOf(state, opened, notes) {
|
|
59
|
+
if (!state.worktree || !existsSync(state.worktree)) return null;
|
|
60
|
+
const base = baseRef(state.worktree, state.base);
|
|
61
|
+
if (!base) {
|
|
62
|
+
notes.push(`pesaje: no base ${state.base} in the worktree; no size`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const stat = (tip) =>
|
|
66
|
+
parseShortstat(
|
|
67
|
+
git(state.worktree, ['diff', '--shortstat', `${base}...${tip}`]),
|
|
68
|
+
);
|
|
69
|
+
const openTip = opened
|
|
70
|
+
? git(state.worktree, ['rev-list', '-1', `--before=${opened}`, 'HEAD'])
|
|
71
|
+
: '';
|
|
72
|
+
return {
|
|
73
|
+
base,
|
|
74
|
+
atOpen: openTip ? { sha: openTip.slice(0, 9), ...stat(openTip) } : null,
|
|
75
|
+
now: {
|
|
76
|
+
sha: git(state.worktree, ['rev-parse', '--short=9', 'HEAD']),
|
|
77
|
+
...stat('HEAD'),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The wrestler's first commit after the bout opened, or null. */
|
|
83
|
+
function firstCommitAt(state, opened) {
|
|
84
|
+
if (!opened || !state.worktree || !existsSync(state.worktree)) return null;
|
|
85
|
+
const times = git(state.worktree, [
|
|
86
|
+
'log',
|
|
87
|
+
'--no-merges',
|
|
88
|
+
`--since=${opened}`,
|
|
89
|
+
'--format=%cI',
|
|
90
|
+
'HEAD',
|
|
91
|
+
])
|
|
92
|
+
.split('\n')
|
|
93
|
+
.filter(Boolean);
|
|
94
|
+
return times.at(-1) ?? null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the page has a board for the judge: changed endpoints or changed routes. When they
|
|
99
|
+
* cannot be read (the worktree is gone) it says so in `notes` and answers true, so the scale
|
|
100
|
+
* stays red instead of greening without the verdicts.
|
|
101
|
+
*/
|
|
102
|
+
function hasBoards(dir, notes) {
|
|
103
|
+
try {
|
|
104
|
+
return (
|
|
105
|
+
collectContractSources(dir).roots.length > 0 ||
|
|
106
|
+
collectFrontRoutes(dir).routes.length > 0
|
|
107
|
+
);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
notes.push(
|
|
110
|
+
`pesaje: could not read the contract or routes boards (${error.message}); the scale waits for the verdicts`,
|
|
111
|
+
);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The bout's preparation: `weighIn` (promotion, size, diagram, contract findings, scale),
|
|
118
|
+
* `interview`, and the interview's timed events. `notes` collects what could not be read.
|
|
119
|
+
*/
|
|
120
|
+
export function collectPreparation({
|
|
121
|
+
dir,
|
|
122
|
+
state,
|
|
123
|
+
opened,
|
|
124
|
+
decisionsText,
|
|
125
|
+
notes,
|
|
126
|
+
}) {
|
|
127
|
+
const read = (name) =>
|
|
128
|
+
existsSync(join(dir, name)) ? readFileSync(join(dir, name), 'utf8') : '';
|
|
129
|
+
const entries = parseInterview(read(INTERVIEW));
|
|
130
|
+
const scale = scaleOf(decisionsText);
|
|
131
|
+
const until = preparationEnd(scale, firstCommitAt(state, opened));
|
|
132
|
+
const verdictEntries = parseVerdicts(read(VERDICTS));
|
|
133
|
+
const verdicts = verdictEntries.length
|
|
134
|
+
? verdictSummary(verdictEntries, {
|
|
135
|
+
api: contractSourcesHash(dir),
|
|
136
|
+
routes: routesSourcesHash(dir),
|
|
137
|
+
})
|
|
138
|
+
: null;
|
|
139
|
+
const diagramPath = join(dir, DIAGRAM);
|
|
140
|
+
const diagram = existsSync(diagramPath)
|
|
141
|
+
? {
|
|
142
|
+
path: diagramPath,
|
|
143
|
+
drawnAt: statSync(diagramPath).mtime.toISOString(),
|
|
144
|
+
placeholder: `<!-- ring:db-diagram #${state.pr} -->`,
|
|
145
|
+
}
|
|
146
|
+
: null;
|
|
147
|
+
if (!entries.length)
|
|
148
|
+
notes.push(
|
|
149
|
+
`entrevista: no ${INTERVIEW}; escalations and answers are logged with \`judges-card.mjs log <pr> escalation|answer\``,
|
|
150
|
+
);
|
|
151
|
+
return {
|
|
152
|
+
weighIn: {
|
|
153
|
+
promotion: {
|
|
154
|
+
pr: state.pr,
|
|
155
|
+
branch: state.branch,
|
|
156
|
+
base: state.base,
|
|
157
|
+
worktree: state.worktree,
|
|
158
|
+
},
|
|
159
|
+
size: sizeOf(state, opened, notes),
|
|
160
|
+
diagram,
|
|
161
|
+
contractFindings: contractFindings(readLedger(dir), until),
|
|
162
|
+
verdicts,
|
|
163
|
+
scale: {
|
|
164
|
+
...scale,
|
|
165
|
+
approvedByJudge: scale.madeWeight,
|
|
166
|
+
madeWeight: madeWeight(
|
|
167
|
+
scale,
|
|
168
|
+
verdicts,
|
|
169
|
+
scale.madeWeight ? hasBoards(dir, notes) : Boolean(verdicts),
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
interview: interviewOf(entries, decisionsText, until),
|
|
174
|
+
events: interviewEvents(entries, until),
|
|
175
|
+
scale,
|
|
176
|
+
};
|
|
177
|
+
}
|