@giamme/forge 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -2
  4. package/SKILL.md +123 -0
  5. package/VERSION +1 -0
  6. package/docs/README.md +26 -0
  7. package/docs/add-ons.md +33 -0
  8. package/docs/artifacts.md +69 -0
  9. package/docs/commands.md +315 -0
  10. package/docs/dataset.md +20 -0
  11. package/docs/decomposed-runs.md +268 -0
  12. package/docs/development.md +95 -0
  13. package/docs/forge-ui.md +92 -0
  14. package/docs/fractal.md +181 -0
  15. package/docs/install.md +160 -0
  16. package/docs/memory.md +156 -0
  17. package/docs/models.md +118 -0
  18. package/docs/reviews.md +231 -0
  19. package/docs/safety.md +55 -0
  20. package/docs/troubleshooting.md +54 -0
  21. package/forge +79 -0
  22. package/package.json +27 -4
  23. package/references/dataset.md +329 -0
  24. package/references/decompose.md +667 -0
  25. package/references/fractal-dashboard.md +21 -0
  26. package/references/fractal.md +307 -0
  27. package/references/harnesses.md +365 -0
  28. package/references/jev.md +865 -0
  29. package/references/memory.md +228 -0
  30. package/references/planning.md +42 -0
  31. package/references/ripwire.md +47 -0
  32. package/references/runtime.md +64 -0
  33. package/references/solo.md +113 -0
  34. package/references/ui.md +112 -0
  35. package/registry.tsv +81 -0
  36. package/scripts/forge-artifact.sh +78 -0
  37. package/scripts/forge-contract.py +430 -0
  38. package/scripts/forge-dataset.py +6 -0
  39. package/scripts/forge-dispatch.sh +671 -0
  40. package/scripts/forge-fail-first.py +195 -0
  41. package/scripts/forge-fractal-dispatch.sh +4 -0
  42. package/scripts/forge-fractal-options.sh +54 -0
  43. package/scripts/forge-fractal.py +6 -0
  44. package/scripts/forge-guard.py +266 -0
  45. package/scripts/forge-install-fractal.sh +4 -0
  46. package/scripts/forge-install-ripwire.sh +52 -0
  47. package/scripts/forge-install.sh +135 -0
  48. package/scripts/forge-jev-options.sh +259 -0
  49. package/scripts/forge-jev.py +6 -0
  50. package/scripts/forge-lib-plans.sh +785 -0
  51. package/scripts/forge-lib-review.sh +494 -0
  52. package/scripts/forge-lib-task.sh +302 -0
  53. package/scripts/forge-lib-verify.sh +30 -0
  54. package/scripts/forge-memory.sh +575 -0
  55. package/scripts/forge-metrics.sh +39 -0
  56. package/scripts/forge-parallel-options.sh +76 -0
  57. package/scripts/forge-parallel.sh +1717 -0
  58. package/scripts/forge-plan-check.py +118 -0
  59. package/scripts/forge-prompt.py +45 -0
  60. package/scripts/forge-ripwire.py +136 -0
  61. package/scripts/forge-route.py +344 -0
  62. package/scripts/forge-runs.py +121 -0
  63. package/scripts/forge-runtime.py +586 -0
  64. package/scripts/forge-schedule.py +312 -0
  65. package/scripts/forge-shadow.py +305 -0
  66. package/scripts/forge-solo.sh +347 -0
  67. package/scripts/forge-ui-options.sh +42 -0
  68. package/scripts/forge-ui.py +435 -0
  69. package/scripts/forge-update.sh +71 -0
  70. package/scripts/forge-verify-coverage.py +956 -0
  71. package/scripts/forge-version.py +240 -0
  72. package/scripts/forge_dataset/__init__.py +439 -0
  73. package/scripts/forge_dataset/capture.py +330 -0
  74. package/scripts/forge_dataset/checks.py +109 -0
  75. package/scripts/forge_dataset/cli.py +233 -0
  76. package/scripts/forge_dataset/export.py +1009 -0
  77. package/scripts/forge_dataset/harvest.py +716 -0
  78. package/scripts/forge_dataset/importer.py +412 -0
  79. package/scripts/forge_dataset/outcomes.py +275 -0
  80. package/scripts/forge_dataset/routing.py +278 -0
  81. package/scripts/forge_fractal/__init__.py +84 -0
  82. package/scripts/forge_fractal/bridge.py +46 -0
  83. package/scripts/forge_fractal/cli.py +202 -0
  84. package/scripts/forge_fractal/dashboard.css +138 -0
  85. package/scripts/forge_fractal/dashboard.html +41 -0
  86. package/scripts/forge_fractal/dashboard.js +687 -0
  87. package/scripts/forge_fractal/decomposition.py +262 -0
  88. package/scripts/forge_fractal/execution.py +659 -0
  89. package/scripts/forge_fractal/inspection.py +261 -0
  90. package/scripts/forge_fractal/install.py +158 -0
  91. package/scripts/forge_fractal/invocation.py +94 -0
  92. package/scripts/forge_fractal/pipeline.py +124 -0
  93. package/scripts/forge_fractal/selection.py +179 -0
  94. package/scripts/forge_jev/__init__.py +376 -0
  95. package/scripts/forge_jev/backtest.py +322 -0
  96. package/scripts/forge_jev/cli.py +980 -0
  97. package/scripts/forge_jev/client.py +418 -0
  98. package/scripts/forge_jev/corpus.py +156 -0
  99. package/scripts/forge_jev/coupling.py +244 -0
  100. package/scripts/forge_jev/features.py +24 -0
  101. package/scripts/forge_jev/gates.py +100 -0
  102. package/scripts/forge_jev/memory.py +163 -0
  103. package/scripts/forge_jev/outcome.py +49 -0
  104. package/scripts/forge_jev/predict.py +204 -0
  105. package/scripts/forge_jev/questions.py +661 -0
  106. package/scripts/forge_jev/review.py +73 -0
  107. package/scripts/forge_jev/routing.py +271 -0
  108. package/scripts/forge_jev/subset.py +123 -0
  109. package/scripts/forge_jev/verify.py +453 -0
  110. package/scripts/forge_registry.py +140 -0
  111. package/ui/README.md +347 -0
  112. package/ui/THIRD_PARTY_NOTICES.md +177 -0
  113. package/ui/docs/api.md +970 -0
  114. package/ui/forge_ui/__init__.py +20 -0
  115. package/ui/forge_ui/__main__.py +135 -0
  116. package/ui/forge_ui/diffs.py +454 -0
  117. package/ui/forge_ui/executions.py +436 -0
  118. package/ui/forge_ui/export.py +214 -0
  119. package/ui/forge_ui/findings.py +132 -0
  120. package/ui/forge_ui/fractal.py +582 -0
  121. package/ui/forge_ui/models.py +216 -0
  122. package/ui/forge_ui/reader.py +1424 -0
  123. package/ui/forge_ui/server.py +312 -0
  124. package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
  125. package/ui/forge_ui/static/app.js +1079 -0
  126. package/ui/forge_ui/static/cloud3d.js +647 -0
  127. package/ui/forge_ui/static/cloudmodel.js +375 -0
  128. package/ui/forge_ui/static/cloudui.js +397 -0
  129. package/ui/forge_ui/static/convo.js +295 -0
  130. package/ui/forge_ui/static/datatable.js +232 -0
  131. package/ui/forge_ui/static/diffview.js +235 -0
  132. package/ui/forge_ui/static/favicon.svg +1 -0
  133. package/ui/forge_ui/static/files.js +776 -0
  134. package/ui/forge_ui/static/findings3d.js +134 -0
  135. package/ui/forge_ui/static/gantt.js +97 -0
  136. package/ui/forge_ui/static/graph.js +266 -0
  137. package/ui/forge_ui/static/icons.js +94 -0
  138. package/ui/forge_ui/static/index.html +67 -0
  139. package/ui/forge_ui/static/iso.js +816 -0
  140. package/ui/forge_ui/static/leaderboard.js +311 -0
  141. package/ui/forge_ui/static/map3d.js +45 -0
  142. package/ui/forge_ui/static/modelstats.js +249 -0
  143. package/ui/forge_ui/static/notify.js +179 -0
  144. package/ui/forge_ui/static/panel.js +562 -0
  145. package/ui/forge_ui/static/runmodel.js +667 -0
  146. package/ui/forge_ui/static/scene3d.js +1471 -0
  147. package/ui/forge_ui/static/search.js +120 -0
  148. package/ui/forge_ui/static/shell.js +137 -0
  149. package/ui/forge_ui/static/stagectl.js +150 -0
  150. package/ui/forge_ui/static/style.css +1073 -0
  151. package/ui/forge_ui/static/theme.js +25 -0
  152. package/ui/forge_ui/transcripts.py +608 -0
@@ -0,0 +1,311 @@
1
+ /* forge-ui model leaderboard: how each implementer and reviewer model did, from GET /api/models as aggregated by
2
+ modelstats.js. All API values enter the DOM as text.
3
+ window.ForgeLeaderboard = { render(host, data, ctx) }, where ctx = { runs, prefs, savePrefs(prefs), modelHref(alias), now }
4
+ and prefs = { filter: { project, difficulty, kind, days, exploratoryOnly, minN }, open: [difficulty, ...] }. */
5
+ window.ForgeLeaderboard = (() => {
6
+ 'use strict';
7
+ const MS = window.ForgeModelStats, RM = window.ForgeRunModel;
8
+ const SVGNS = 'http://www.w3.org/2000/svg';
9
+ const list = v => Array.isArray(v) ? v : [];
10
+ const num = v => typeof v === 'number' && Number.isFinite(v) ? v : null;
11
+ const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
12
+ const DIFF_LABEL = { low: 'Low', medium: 'Medium', high: 'High', none: 'Difficulty not set' };
13
+ const PERIODS = [[0, 'any time'], [7, 'last 7 days'], [30, 'last 30 days'], [90, 'last 90 days']];
14
+ const MIN_NS = [1, 3, 5, 10, 20, 30, 50];
15
+
16
+ function el(tag, text, cls) {
17
+ const n = document.createElement(tag);
18
+ if (text !== undefined && text !== null) n.textContent = String(text);
19
+ if (cls) n.className = cls;
20
+ return n;
21
+ }
22
+ function button(text, cls, onClick, title) {
23
+ const b = el('button', text, cls); b.type = 'button';
24
+ if (title) b.title = title;
25
+ if (onClick) b.addEventListener('click', onClick);
26
+ return b;
27
+ }
28
+ function svg(tag, attrs, cls) {
29
+ const n = document.createElementNS(SVGNS, tag);
30
+ for (const [k, v] of Object.entries(attrs || {})) n.setAttribute(k, String(v));
31
+ if (cls) n.setAttribute('class', cls);
32
+ return n;
33
+ }
34
+ function ownDur(s) {
35
+ s = num(s); if (s === null) return '—';
36
+ s = Math.max(0, Math.round(s));
37
+ if (s < 60) return s + 's';
38
+ if (s < 3600) return Math.floor(s / 60) + 'm' + String(s % 60).padStart(2, '0') + 's';
39
+ return Math.floor(s / 3600) + 'h' + String(Math.floor(s % 3600 / 60)).padStart(2, '0') + 'm';
40
+ }
41
+ const dur = s => RM && RM.fmtDur ? RM.fmtDur(s) : ownDur(s);
42
+ const pct = p => num(p) === null ? '—' : Math.round(p * 100) + '%';
43
+ const count = n => Number(n || 0).toLocaleString();
44
+ const plural = (n, one, many) => `${count(n)} ${n === 1 ? one : many || one + 's'}`;
45
+ const r2 = v => Math.round(v * 100) / 100;
46
+ function compact(n) {
47
+ n = num(n); if (n === null) return '—';
48
+ const a = Math.abs(n), f = (v, s) => (Math.abs(v) >= 10 ? Math.round(v) : Math.round(v * 10) / 10) + s;
49
+ return a >= 999.5e6 ? f(n / 1e9, 'G') : a >= 999.5e3 ? f(n / 1e6, 'M') : a >= 1e3 ? f(n / 1e3, 'k') : String(Math.round(n));
50
+ }
51
+ function readPrefs(p) {
52
+ p = p && typeof p === 'object' ? p : {};
53
+ return { filter: MS.normFilter(p.filter), open: list(p.open).filter(d => MS.DIFFS.includes(d)) };
54
+ }
55
+ const nowOf = ctx => num(typeof ctx.now === 'function' ? ctx.now() : ctx.now) ?? Date.now() / 1000;
56
+
57
+ /* ---------- cells ---------- */
58
+ function numCell(v, title) {
59
+ const td = el('td', v, 'lb-num' + (v === 0 ? ' lb-zero' : ''));
60
+ if (title) td.title = title;
61
+ return td;
62
+ }
63
+ function modelCell(alias, ctx, few) {
64
+ const td = el('td', undefined, 'lb-model');
65
+ const href = ctx.modelHref ? ctx.modelHref(alias) : null;
66
+ const plate = window.ForgeIcons ? window.ForgeIcons.modelPlate(alias) : el('span', alias, 'mono');
67
+ if (href) { const a = el('a', undefined, 'lb-alias'); a.href = href; a.title = 'show its runs in the cloud'; a.append(plate); td.append(a); }
68
+ else td.append(plate);
69
+ if (few) { const s = el('span', 'few', 'lb-few'); s.title = few; td.append(s); }
70
+ return td;
71
+ }
72
+ // the 95% Wilson interval on a 0–100% track: the band is the interval, the tick the observed rate
73
+ function ciBar(w) {
74
+ const W = 100, H = 14;
75
+ const s = svg('svg', { width: W, height: H, viewBox: `0 0 ${W} ${H}`, 'aria-hidden': 'true', focusable: 'false' }, 'lb-ci');
76
+ s.append(svg('rect', { x: 0, y: 5, width: W, height: 4, rx: 2 }, 'lb-ci-track'), svg('rect', { x: W / 2 - .5, y: 3, width: 1, height: 8 }, 'lb-ci-grid'));
77
+ if (num(w.lo) !== null) {
78
+ const x0 = w.lo * W, x1 = w.hi * W;
79
+ s.append(svg('rect', { x: r2(Math.min(x0, W - 2)), y: 2, width: r2(Math.max(2, x1 - x0)), height: 10, rx: 2 }, 'lb-ci-band'));
80
+ s.append(svg('rect', { x: r2(clamp(w.p * W - 1, 0, W - 2)), y: 0, width: 2, height: H }, 'lb-ci-tick'));
81
+ }
82
+ return s;
83
+ }
84
+ function rateCell(w) {
85
+ const td = el('td', undefined, 'lb-rate');
86
+ const box = el('div', undefined, 'lb-rate-box'), txt = el('span', undefined, 'lb-rate-text');
87
+ txt.append(el('strong', pct(w.p)), el('span', `${w.k}/${w.n}`, 'muted'));
88
+ box.append(txt, ciBar(w)); td.append(box);
89
+ td.title = w.n ? `${w.k} of ${w.n} first attempts passed (${pct(w.p)}); 95% Wilson interval ${pct(w.lo)}–${pct(w.hi)}` : 'No first attempt passed or failed';
90
+ return td;
91
+ }
92
+ function tokenCell(t, total, unit) {
93
+ const used = t.attempts ?? t.reviews ?? 0;
94
+ if (!used) return numCell('—', 'No tokens recorded');
95
+ const td = el('td', `${compact(t.input)} / ${compact(t.output)}`, 'lb-num');
96
+ if (num(t.coverage) !== null && t.coverage < 1) td.append(el('span', `${pct(t.coverage)} of ${unit}s`, 'lb-sub'));
97
+ td.title = `input ${count(t.input)} (cached ${count(t.cached)}) · output ${count(t.output)} · recorded by ${count(used)} of ${plural(total, unit)}`;
98
+ return td;
99
+ }
100
+
101
+ /* ---------- implementers ---------- */
102
+ const IMPL_COLS = [
103
+ ['Model', '', 'The implementer (dwarf) model; its effort levels are counted together'],
104
+ ['Tasks', 'lb-num', 'Tasks whose first attempt this model ran'],
105
+ ['First-attempt pass', 'lb-num', 'First attempts that passed review, over those that passed or failed. Band: 95% Wilson interval; tick: the observed rate'],
106
+ ['Censored', 'lb-num', 'First attempts that said nothing about the implementer (reviewer or forge trouble, no changes, interrupted): in no rate'],
107
+ ['Retries', 'lb-num', 'Attempts this model ran after a task\'s first attempt'],
108
+ ['Escalated away', 'lb-num', 'Times forge escalated a task away from this model'],
109
+ ['Timeouts', 'lb-num', 'Attempts that timed out: the implementer, its reviewer or forge\'s own steps'],
110
+ ['Gate accepts', 'lb-num', 'Attempts that passed although their reviewer said FAIL: the severity gate let them through'],
111
+ ['Human accepts', 'lb-num', 'Tasks a human accepted although no attempt passed, counted for the model of the last attempt'],
112
+ ['Median model time', 'lb-num', 'Median time in the model phase per attempt'],
113
+ ['Tokens in / out', 'lb-num', 'Summed over the attempts that recorded tokens'],
114
+ ];
115
+ const colsOf = (spec, cells) => spec.map(([label, cls, title], i) => ({ ...cells[i], label, cls, title }));
116
+ // a sortable, reorderable table (datatable.js); one set of preferences per kind of table, shared by its copies
117
+ function table(id, label, columns, rows, ctx, rowClass) {
118
+ const host = el('div', undefined, 'lb-dt');
119
+ window.ForgeTable.render(host, { id, label, columns, rows, className: 'lb-table', rowClass,
120
+ prefs: ctx.tablePrefs ? ctx.tablePrefs(id) : null, savePrefs: p => ctx.saveTablePrefs && ctx.saveTablePrefs(id, p) });
121
+ return host;
122
+ }
123
+ function implementerTable(rows, ctx, minN, caption) {
124
+ const tk = r => r.timeoutKinds;
125
+ const columns = colsOf(IMPL_COLS, [
126
+ { id: 'model', value: r => r.alias, cell: r => modelCell(r.alias, ctx, r.small ? `Fewer than ${plural(minN, 'first-attempt outcome')}: too few to rank` : '') },
127
+ { id: 'tasks', value: r => r.tasks, cell: r => numCell(r.tasks) },
128
+ { id: 'pass', value: r => r.first.n ? r.first.lo : null, cell: r => rateCell(r.first) },
129
+ { id: 'censored', value: r => r.first.censored, cell: r => numCell(r.first.censored, Object.entries(r.first.reasons).map(([k, v]) => `${v} ${k.replace(/_/g, ' ')}`).join(', ') || undefined) },
130
+ { id: 'retries', value: r => r.retries, cell: r => numCell(r.retries) },
131
+ { id: 'escalated', value: r => r.escalationsAway, cell: r => numCell(r.escalationsAway) },
132
+ { id: 'timeouts', value: r => r.timeouts, cell: r => numCell(r.timeouts, r.timeouts ? `implementer ${tk(r).implementer} · reviewer ${tk(r).reviewer} · forge ${tk(r).forge}` : undefined) },
133
+ { id: 'gate', value: r => r.gateAccepts, cell: r => numCell(r.gateAccepts) },
134
+ { id: 'human', value: r => r.humanAccepts, cell: r => numCell(r.humanAccepts) },
135
+ { id: 'model-time', value: r => num(r.medianModelS), cell: r => numCell(dur(r.medianModelS), `median wall time per attempt ${dur(r.medianDurationS)}`) },
136
+ { id: 'tokens', value: r => (r.tokens.attempts ?? r.tokens.reviews) ? (r.tokens.input || 0) + (r.tokens.output || 0) : null, cell: r => tokenCell(r.tokens, r.attempts.total, 'attempt') },
137
+ ]);
138
+ columns[2].title += '. Sorts by the lower end of the interval, as the ranking does';
139
+ return table('implementers', caption, columns, rows, ctx, r => r.small ? 'lb-small' : '');
140
+ }
141
+
142
+ /* ---------- reviewers ---------- */
143
+ const SEV_TITLE = s => `Confirmed ${s} findings per review (mean over the reviews whose reply could be read)`;
144
+ const REV_COLS = [
145
+ ['Model', '', 'The reviewer (qa) model'],
146
+ ['Primary', 'lb-num', 'Primary reviews of an attempt'],
147
+ ['Shadow', 'lb-num', 'Shadow reviews run beside a primary review'],
148
+ ['Final', 'lb-num', 'Final reviews of a whole run'],
149
+ ['Verdicts', '', 'PASS and FAIL verdicts over every review'],
150
+ ...MS.SEVERITIES.map(s => [s, 'lb-num', SEV_TITLE(s)]),
151
+ ['Shadow agreement', 'lb-num', 'How often this model\'s shadow verdict matched the primary reviewer\'s (both PASS or FAIL)'],
152
+ ['Median time', 'lb-num', 'Median duration of a review'],
153
+ ['Tokens in / out', 'lb-num', 'Summed over the reviews that recorded tokens'],
154
+ ];
155
+ function verdictCell(v) {
156
+ const td = el('td', undefined, 'lb-verdicts'), box = el('div', undefined, 'lb-verdict-box');
157
+ const total = v.PASS + v.FAIL + v.other, W = 80, H = 10;
158
+ const s = svg('svg', { width: W, height: H, viewBox: `0 0 ${W} ${H}`, 'aria-hidden': 'true', focusable: 'false' }, 'lb-vbar');
159
+ s.append(svg('rect', { x: 0, y: 0, width: W, height: H, rx: 2 }, 'lb-vbar-track'));
160
+ let x = 0;
161
+ for (const [k, st] of [['PASS', 'pass'], ['FAIL', 'fail'], ['other', 'none']]) {
162
+ if (!v[k] || !total) continue;
163
+ const w = v[k] / total * W;
164
+ s.append(svg('rect', { x: r2(x), y: 0, width: r2(w), height: H }, 'lb-vbar-seg st-' + st)); x += w;
165
+ }
166
+ const txt = el('span', `${v.PASS} PASS · ${v.FAIL} FAIL${v.other ? ` · ${v.other} other` : ''}`, 'lb-verdict-text');
167
+ box.append(s, txt); td.append(box);
168
+ td.title = total ? `${pct(v.PASS / total)} PASS` : 'No verdicts';
169
+ return td;
170
+ }
171
+ function findingCell(r, s) {
172
+ const avg = r.findings[s];
173
+ if (avg === null) return numCell('—', 'No review reply could be read');
174
+ const td = numCell(avg === 0 ? 0 : avg.toFixed(avg < 10 ? 2 : 1), `${count(r.findingTotals[s])} confirmed ${s} over ${plural(r.findings.n, 'review')}`);
175
+ if (s === 'P0' && avg > 0) td.classList.add('lb-p0');
176
+ return td;
177
+ }
178
+ function agreementCell(a) {
179
+ if (!a.n) return numCell('—', a.unknown ? `${a.unknown} shadow review${a.unknown === 1 ? '' : 's'} without two comparable verdicts` : 'No shadow reviews');
180
+ const td = el('td', undefined, 'lb-num');
181
+ td.append(el('strong', pct(a.p)), document.createTextNode(' '), el('span', `${a.k}/${a.n}`, 'muted'));
182
+ td.title = `${a.k} of ${a.n} shadow verdicts matched the primary's; 95% Wilson interval ${pct(a.lo)}–${pct(a.hi)}${a.unknown ? ` · ${a.unknown} without two comparable verdicts` : ''}`;
183
+ return td;
184
+ }
185
+ function reviewerTable(rows, ctx) {
186
+ const columns = colsOf(REV_COLS, [
187
+ { id: 'model', value: r => r.alias, cell: r => modelCell(r.alias, ctx, '') },
188
+ { id: 'primary', value: r => r.reviews.primary, cell: r => numCell(r.reviews.primary) },
189
+ { id: 'shadow', value: r => r.reviews.shadow, cell: r => numCell(r.reviews.shadow) },
190
+ { id: 'final', value: r => r.reviews.final, cell: r => numCell(r.reviews.final) },
191
+ { id: 'verdicts', value: r => { const v = r.verdicts, t = v.PASS + v.FAIL + v.other; return t ? v.PASS / t : null; }, cell: r => verdictCell(r.verdicts) },
192
+ ...MS.SEVERITIES.map(sv => ({ id: 'sev-' + sv, value: r => r.findings[sv], cell: r => findingCell(r, sv) })),
193
+ { id: 'agreement', value: r => r.agreement.n ? r.agreement.p : null, cell: r => agreementCell(r.agreement) },
194
+ { id: 'time', value: r => num(r.medianDurationS), cell: r => numCell(dur(r.medianDurationS)) },
195
+ { id: 'tokens', value: r => (r.tokens.attempts ?? r.tokens.reviews) ? (r.tokens.input || 0) + (r.tokens.output || 0) : null, cell: r => tokenCell(r.tokens, r.reviews.total, 'review') },
196
+ ]);
197
+ return table('reviewers', 'Reviewers', columns, rows, ctx);
198
+ }
199
+
200
+ /* ---------- header, caveat and filters ---------- */
201
+ function header(L, O, f, update, withNote) {
202
+ const t = L.totals, frag = document.createDocumentFragment();
203
+ const top = el('header', undefined, 'lb-head');
204
+ top.append(el('h1', 'Models', 'module-title'), el('p', `${plural(t.runs, 'run')} · ${plural(t.tasks, 'task')} · ${plural(t.attempts, 'attempt')} · ${plural(t.reviews, 'review')} counted`, 'muted lb-summary'));
205
+ const note = el('div', undefined, 'lb-caveat'); note.setAttribute('role', 'note');
206
+ const p = el('p');
207
+ p.append(el('strong', 'Observational, not causal. '), document.createTextNode('Forge routes each task to a model by its difficulty and by how models did before, so a model sent the hard tasks looks worse here than it is. Exploratory picks, the tasks where forge drew the model at random, are the fairest comparison.'));
208
+ const l = el('label', undefined, 'filter check lb-explore'), c = el('input');
209
+ c.type = 'checkbox'; c.checked = f.exploratoryOnly; c.dataset.key = 'exploratoryOnly';
210
+ c.addEventListener('change', () => update({ exploratoryOnly: c.checked }));
211
+ l.append(c, document.createTextNode(` Exploratory picks only (${plural(O.explored, 'task')})`));
212
+ note.append(p, l);
213
+ frag.append(top);
214
+ if (withNote) frag.append(note);
215
+ return frag;
216
+ }
217
+ function filters(O, L, f, update) {
218
+ const bar = el('form', undefined, 'filters lb-filters'); bar.setAttribute('aria-label', 'Filter the tasks counted');
219
+ bar.addEventListener('submit', e => e.preventDefault());
220
+ const add = (label, key, entries, title, parse = v => v) => {
221
+ const l = el('label', undefined, 'filter'); l.append(el('span', label));
222
+ if (title) l.title = title;
223
+ const s = el('select'); s.dataset.key = key;
224
+ // a saved choice that is no longer offered stays visible, so an empty table has a visible reason
225
+ if (!entries.some(([v]) => String(v) === String(f[key]))) entries.push([f[key], String(f[key])]);
226
+ for (const [v, text, tip] of entries) { const o = el('option', text); o.value = String(v); if (tip) o.title = tip; if (String(v) === String(f[key])) o.selected = true; s.append(o); }
227
+ s.addEventListener('change', () => update({ [key]: parse(s.value) }));
228
+ l.append(s); bar.append(l);
229
+ };
230
+ add('Project', 'project', [['', 'any'], ...O.projects.map(p => [p.repo, p.name, p.repo])]);
231
+ add('Difficulty', 'difficulty', [['', 'any'], ...O.difficulties.map(d => [d.value, `${d.label} (${d.tasks})`])]);
232
+ add('Kind', 'kind', [['', 'any'], ...['plan', 'solo'].map(k => { const o = O.kinds.find(x => x.value === k); return [k, `${k} (${o ? o.tasks : 0})`]; })]);
233
+ add('Period', 'days', PERIODS.map(([d, t]) => [d, t]), 'Tasks of runs created in this period', Number);
234
+ add('Min n', 'minN', MIN_NS.map(n => [n, `${n}+`]), 'Grey the models with fewer first-attempt outcomes than this', Number);
235
+ const reset = button('Reset', '', () => update(MS.emptyFilter()), 'Count every task');
236
+ reset.dataset.key = 'reset';
237
+ bar.append(reset, el('span', `${count(L.totals.tasks)} of ${plural(L.totals.allTasks, 'task')}`, 'muted lb-count'));
238
+ return bar;
239
+ }
240
+ function emptyNote(title, lines, action) {
241
+ const box = el('div', undefined, 'empty lb-empty');
242
+ box.append(el('p', title, 'lb-empty-title'), ...lines.map(t => el('p', t)));
243
+ if (action) box.append(action);
244
+ return box;
245
+ }
246
+
247
+ /* ---------- the view ---------- */
248
+ function render(host, data, ctx) {
249
+ ctx = ctx || {};
250
+ const prefs = readPrefs(ctx.prefs), f = prefs.filter, now = nowOf(ctx);
251
+ const active = document.activeElement, focusKey = active && host.contains(active) && active.dataset ? active.dataset.key : null;
252
+ const save = () => { if (ctx.savePrefs) ctx.savePrefs({ filter: { ...f }, open: prefs.open.slice() }); };
253
+ const update = change => {
254
+ const next = { filter: MS.normFilter({ ...f, ...change }), open: prefs.open.slice() };
255
+ if (ctx.savePrefs) ctx.savePrefs(next);
256
+ render(host, data, { ...ctx, prefs: next });
257
+ };
258
+ const O = MS.options(data, ctx.runs), L = MS.leaderboard(data, f, now, ctx.runs);
259
+ const root = el('div', undefined, 'lb');
260
+ const anything = list(data && data.tasks).some(t => t && list(t.attempts).length) || list(data && data.reviews).length > 0;
261
+ if (!anything) {
262
+ root.append(header(L, O, f, update, false), emptyNote('No attempts or reviews to count yet.', [
263
+ 'The leaderboard counts the attempts recorded in each task\'s attempts.tsv (a solo run\'s own attempts.tsv) and the reviews that have executions.',
264
+ 'Older runs may lack them, and a task that has not started has none.']));
265
+ host.replaceChildren(root);
266
+ return;
267
+ }
268
+ root.append(header(L, O, f, update, true), filters(O, L, f, update));
269
+ if (L.caveats.length) { const ul = el('ul', undefined, 'lb-notes'); for (const c of L.caveats) ul.append(el('li', c)); root.append(ul); }
270
+ if (!L.totals.tasks && !L.totals.reviews) {
271
+ const reset = button('Reset the filters', '', () => update(MS.emptyFilter())); reset.dataset.key = 'reset-empty';
272
+ root.append(emptyNote('No tasks or reviews match these filters.', [], reset));
273
+ } else {
274
+ const impl = el('section', undefined, 'lb-section');
275
+ impl.append(el('h2', 'Implementers', 'plate'), el('p', 'Each task counts once, for the model that ran its first attempt; retries, escalations and accepts show what happened after. Ranked by the lower end of the 95% interval, so a high rate on few tasks does not jump the table.', 'lb-lead'));
276
+ impl.append(L.implementers.length ? implementerTable(L.implementers, ctx, f.minN, 'Implementers') : el('p', 'No attempts with a recorded model match these filters.', 'muted'));
277
+ root.append(impl);
278
+ if (!f.difficulty && L.implementers.length) {
279
+ const by = el('section', undefined, 'lb-section');
280
+ by.append(el('h2', 'By difficulty', 'plate'), el('p', 'The same table for the tasks of one declared difficulty: the comparison routing distorts least.', 'lb-lead'));
281
+ for (const d of MS.DIFFS) {
282
+ const rows = L.byDifficulty[d];
283
+ if (!rows.length) continue;
284
+ const k = rows.reduce((s, r) => s + r.first.k, 0), n = rows.reduce((s, r) => s + r.first.n, 0), tasks = rows.reduce((s, r) => s + r.tasks, 0);
285
+ const det = el('details', undefined, 'lb-diff'); det.open = prefs.open.includes(d);
286
+ const sum = el('summary');
287
+ sum.append(el('span', DIFF_LABEL[d], 'lb-diff-name'), el('span', `${plural(tasks, 'task')} · ${plural(rows.length, 'model')} · first-attempt pass ${n ? `${pct(k / n)} (${k}/${n})` : '—'}`, 'muted'));
288
+ det.append(sum, implementerTable(rows, ctx, f.minN, `Implementers, ${DIFF_LABEL[d].toLowerCase()}`));
289
+ det.addEventListener('toggle', () => {
290
+ if (det.open === prefs.open.includes(d)) return;
291
+ prefs.open = det.open ? prefs.open.concat(d) : prefs.open.filter(x => x !== d); save();
292
+ });
293
+ by.append(det);
294
+ }
295
+ root.append(by);
296
+ }
297
+ const rev = el('section', undefined, 'lb-section');
298
+ rev.append(el('h2', 'Reviewers', 'plate'), el('p', 'Primary reviews of each attempt, shadow reviews run beside them and final reviews of a whole run. Findings are confirmed findings per review.', 'lb-lead'));
299
+ rev.append(L.reviewers.length ? reviewerTable(L.reviewers, ctx) : el('p', 'No reviews with a recorded model match these filters.', 'muted'));
300
+ root.append(rev);
301
+ }
302
+ host.replaceChildren(root);
303
+ // keep the keyboard on the control that changed (a vanished one hands over to Reset)
304
+ if (focusKey) {
305
+ const keyed = [...root.querySelectorAll('[data-key]')], n = keyed.find(x => x.dataset.key === focusKey) || keyed.find(x => x.dataset.key === 'reset');
306
+ if (n) n.focus({ preventScroll: true });
307
+ }
308
+ }
309
+
310
+ return { render };
311
+ })();
@@ -0,0 +1,45 @@
1
+ /* forge-ui: loads the 3D stage only when it is used. scene3d.js imports three.js 0.160.0 from
2
+ cdn.jsdelivr.net through index.html's import map; without WebGL, without import maps, offline or behind
3
+ a server that predates the page's CSP, the load fails and the page keeps its 2D views.
4
+ Defines window.ForgeMap3D = {supported(), load(), state, reason, message()}. */
5
+ window.ForgeMap3D = (() => {
6
+ 'use strict';
7
+ const M = { state: 'idle', reason: null, module: null };
8
+ let loading = null;
9
+ M.supported = () => {
10
+ if (!(window.HTMLScriptElement && typeof HTMLScriptElement.supports === 'function' && HTMLScriptElement.supports('importmap'))) return 'importmap';
11
+ try {
12
+ const c = document.createElement('canvas'), gl = c.getContext('webgl2') || c.getContext('webgl');
13
+ if (!gl) return 'webgl';
14
+ const lose = gl.getExtension('WEBGL_lose_context'); if (lose) lose.loseContext(); // a probe must not hold a context
15
+ } catch (_) { return 'webgl'; }
16
+ return null;
17
+ };
18
+ M.load = () => {
19
+ if (loading) return loading;
20
+ const why = M.supported();
21
+ if (why) { M.state = 'unavailable'; M.reason = why; loading = Promise.reject(new Error(why)); loading.catch(() => {}); return loading; }
22
+ M.state = 'loading';
23
+ let timer = null;
24
+ const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('timeout')), 12000); });
25
+ // an exported page carries the stage as one module text (forge_ui/export.py), imported from a blob: URL
26
+ const bundle = document.getElementById('forge-stage');
27
+ const target = bundle ? URL.createObjectURL(new Blob([bundle.textContent], { type: 'text/javascript' })) : './scene3d.js';
28
+ loading = Promise.race([import(target), timeout]).then(mod => {
29
+ clearTimeout(timer); M.state = 'ready'; M.module = mod; return mod;
30
+ }, err => {
31
+ clearTimeout(timer); M.state = 'unavailable'; M.reason = err && err.message === 'timeout' ? 'timeout' : 'load';
32
+ throw err;
33
+ });
34
+ loading.catch(() => {});
35
+ return loading;
36
+ };
37
+ M.message = () => ({
38
+ importmap: '3D view unavailable: this browser does not support import maps.',
39
+ webgl: '3D view unavailable: WebGL is not available in this browser.',
40
+ timeout: '3D view unavailable: three.js took too long to load from cdn.jsdelivr.net.',
41
+ load: '3D view unavailable: three.js could not load from cdn.jsdelivr.net (offline, blocked, or a forge-ui server older than this page: run `forge ui open` to restart it).',
42
+ stage: '3D view unavailable: the 3D stage could not start in this browser.',
43
+ })[M.reason] || '3D view unavailable.';
44
+ return M;
45
+ })();
@@ -0,0 +1,249 @@
1
+ /* forge-ui model leaderboard: pure aggregation of GET /api/models facts (no DOM, no I/O). A plain script in the
2
+ page (window.ForgeModelStats) and a CommonJS module under node:test.
3
+ Outcomes are the server's labels, which follow scripts/forge_dataset/export.py `_outcome`: a censored attempt
4
+ (reviewer or forge trouble, no changes, interrupted, ...) says nothing about the implementer and counts in no
5
+ rate. First-attempt rates follow scripts/forge_dataset/routing.py: each task's first attempt (`attempts[0]`),
6
+ n = pass + fail, with routing.py's `wilson` interval. */
7
+ (function (root, factory) {
8
+ 'use strict';
9
+ const RM = typeof module === 'object' && module.exports ? require('./runmodel.js') : root.ForgeRunModel;
10
+ const api = factory(RM);
11
+ if (typeof module === 'object' && module.exports) module.exports = api;
12
+ else root.ForgeModelStats = api;
13
+ })(typeof self !== 'undefined' ? self : this, function (RM) {
14
+ 'use strict';
15
+
16
+ const list = v => Array.isArray(v) ? v : [];
17
+ const num = v => typeof v === 'number' && Number.isFinite(v) ? v : null;
18
+ const obj = v => v && typeof v === 'object' && !Array.isArray(v) ? v : null;
19
+ const DIFFS = ['low', 'medium', 'high', 'none'];
20
+ const KINDS = ['plan', 'solo'];
21
+ const OUTCOMES = ['pass', 'fail', 'censored'];
22
+ const REVIEW_KINDS = ['primary', 'shadow', 'final'];
23
+ const SEVERITIES = ['P0', 'P1', 'P2', 'P3'];
24
+ const DAY = 86400;
25
+ const DEFAULT_MIN_N = 5;
26
+ const diffOf = t => { const d = String(t && t.difficulty || '').toLowerCase(); return DIFFS.includes(d) ? d : 'none'; };
27
+ const aliasOf = x => (x && x.alias) || RM.alias(x && x.spec) || null;
28
+ const taskKey = (run, task) => `${run}\u0000${task}`;
29
+
30
+ // routing.py `wilson`, the same arithmetic in the same order, so the bounds match it to the last bit
31
+ function wilson(k, n, z = 1.96) {
32
+ if (!n) return { p: null, lo: null, hi: null };
33
+ const p = k / n;
34
+ const centre = (p + z * z / (2 * n)) / (1 + z * z / n);
35
+ const half = z * Math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n);
36
+ return { p, lo: Math.max(0, centre - half), hi: Math.min(1, centre + half) };
37
+ }
38
+ // statistics.median: the mean of the middle two for an even count; nulls are ignored
39
+ function median(xs) {
40
+ const v = list(xs).filter(x => num(x) !== null).sort((a, b) => a - b);
41
+ if (!v.length) return null;
42
+ const m = v.length >> 1;
43
+ return v.length % 2 ? v[m] : (v[m - 1] + v[m]) / 2;
44
+ }
45
+
46
+ /* ---------- filters ---------- */
47
+ function emptyFilter() { return { project: '', difficulty: '', kind: '', days: 0, exploratoryOnly: false, minN: DEFAULT_MIN_N }; }
48
+ function normFilter(f) {
49
+ f = obj(f) || {};
50
+ return {
51
+ project: typeof f.project === 'string' ? f.project : '',
52
+ difficulty: DIFFS.includes(f.difficulty) ? f.difficulty : '',
53
+ kind: KINDS.includes(f.kind) ? f.kind : '',
54
+ days: num(f.days) !== null && f.days > 0 ? Math.floor(f.days) : 0,
55
+ exploratoryOnly: f.exploratoryOnly === true,
56
+ minN: num(f.minN) !== null && f.minN >= 0 ? Math.floor(f.minN) : DEFAULT_MIN_N,
57
+ };
58
+ }
59
+ function inPeriod(created, f, now) {
60
+ return !f.days || num(now) === null || (num(created) !== null && created >= now - f.days * DAY);
61
+ }
62
+ // a run's own facts (repo, kind, created): from its task rows, else from the run summaries
63
+ function runIndex(data, runs) {
64
+ const by = new Map();
65
+ for (const t of list(data.tasks)) if (obj(t) && t.run && !by.has(t.run)) by.set(t.run, { repo: t.repo || null, kind: t.kind || null, created: num(t.created) });
66
+ for (const r of list(runs)) if (obj(r) && r.key && !by.has(r.key)) by.set(r.key, { repo: r.repo || null, kind: r.kind || null, created: num(r.created) });
67
+ return by;
68
+ }
69
+ function taskMatch(t, f, now) {
70
+ if (f.project && t.repo !== f.project) return false;
71
+ if (f.difficulty && diffOf(t) !== f.difficulty) return false;
72
+ if (f.kind && t.kind !== f.kind) return false;
73
+ if (!inPeriod(t.created, f, now)) return false;
74
+ if (f.exploratoryOnly && t.explored !== true) return false;
75
+ return true;
76
+ }
77
+ // a task's review goes with its task; a final review belongs to a whole run, so it has no difficulty and was never explored
78
+ function reviewMatch(v, f, now, tasks, runs) {
79
+ if (v.task !== null && v.task !== undefined) {
80
+ const t = tasks.get(taskKey(v.run, v.task));
81
+ if (t) return taskMatch(t, f, now);
82
+ }
83
+ if (f.difficulty || f.exploratoryOnly) return false;
84
+ const r = runs.get(v.run) || {};
85
+ if (f.project && r.repo !== f.project) return false;
86
+ if (f.kind && r.kind !== f.kind) return false;
87
+ return inPeriod(r.created, f, now);
88
+ }
89
+
90
+ /* ---------- the choices the filters offer ---------- */
91
+ function projectNames(repos) {
92
+ const out = [...repos].map(([repo, tasks]) => ({ repo, name: RM.basename(repo), tasks }));
93
+ // two repos with the same folder name: add the parent folder (as cloudmodel.js projects())
94
+ const names = new Map(); for (const p of out) names.set(p.name, (names.get(p.name) || 0) + 1);
95
+ for (const p of out) if (names.get(p.name) > 1) { const parts = String(p.repo).split(/[\\/]/).filter(Boolean); p.name = `${p.name} (${parts[parts.length - 2] || '/'})`; }
96
+ return out.sort((a, b) => a.name.localeCompare(b.name) || a.repo.localeCompare(b.repo));
97
+ }
98
+ function options(data, runs) {
99
+ data = obj(data) || {};
100
+ const tasks = list(data.tasks).filter(obj);
101
+ const repos = new Map(), diff = {}, kinds = {}, aliases = new Set();
102
+ let first = null, last = null, explored = 0;
103
+ for (const t of tasks) {
104
+ if (t.repo) repos.set(t.repo, (repos.get(t.repo) || 0) + 1);
105
+ const d = diffOf(t); diff[d] = (diff[d] || 0) + 1;
106
+ if (t.kind) kinds[t.kind] = (kinds[t.kind] || 0) + 1;
107
+ const c = num(t.created);
108
+ if (c !== null) { first = first === null ? c : Math.min(first, c); last = last === null ? c : Math.max(last, c); }
109
+ if (t.explored === true) explored++;
110
+ for (const a of list(t.attempts)) { const x = obj(a) && aliasOf(a); if (x) aliases.add(x); }
111
+ }
112
+ // a run with reviews but no task rows still names its project
113
+ const idx = runIndex(data, runs);
114
+ for (const v of list(data.reviews)) {
115
+ if (!obj(v)) continue;
116
+ const x = aliasOf(v); if (x) aliases.add(x);
117
+ const r = idx.get(v.run); if (r && r.repo && !repos.has(r.repo)) repos.set(r.repo, 0);
118
+ }
119
+ return {
120
+ projects: projectNames(repos),
121
+ difficulties: DIFFS.filter(d => diff[d]).map(d => ({ value: d, label: d === 'none' ? 'not set' : d, tasks: diff[d] })),
122
+ kinds: KINDS.filter(k => kinds[k]).map(k => ({ value: k, tasks: kinds[k] })),
123
+ range: { first, last },
124
+ explored,
125
+ aliases: [...aliases].sort(),
126
+ };
127
+ }
128
+
129
+ /* ---------- implementers: one row per alias over the attempts of the matching tasks ---------- */
130
+ function implementerRows(tasks, minN) {
131
+ const by = new Map();
132
+ let unattributed = 0;
133
+ const row = alias => {
134
+ if (!by.has(alias)) by.set(alias, { alias, tasks: 0, first: { k: 0, n: 0, censored: 0, reasons: {} }, attempts: { pass: 0, fail: 0, censored: 0, total: 0 },
135
+ retries: 0, escalationsAway: 0, timeouts: 0, timeoutKinds: { implementer: 0, reviewer: 0, forge: 0 }, gateAccepts: 0, humanAccepts: 0,
136
+ model: [], duration: [], tokens: { input: 0, cached: 0, output: 0, attempts: 0, coverage: null } });
137
+ return by.get(alias);
138
+ };
139
+ for (const t of tasks) {
140
+ const atts = list(t.attempts).filter(obj).slice().sort((a, b) => (num(a.n) ?? 0) - (num(b.n) ?? 0));
141
+ atts.forEach((a, i) => {
142
+ const alias = aliasOf(a);
143
+ if (!alias) { unattributed++; return; }
144
+ const r = row(alias), o = OUTCOMES.includes(a.outcome) ? a.outcome : 'censored', reason = String(a.reason || '');
145
+ r.attempts[o]++; r.attempts.total++;
146
+ if (i === 0) {
147
+ r.tasks++;
148
+ if (o === 'censored') { r.first.censored++; r.first.reasons[reason || 'unknown'] = (r.first.reasons[reason || 'unknown'] || 0) + 1; }
149
+ else { r.first.n++; if (o === 'pass') r.first.k++; }
150
+ } else r.retries++;
151
+ if (a.timed_out === true || reason.endsWith('_timeout')) {
152
+ r.timeouts++;
153
+ r.timeoutKinds[a.timed_out === true ? 'implementer' : reason === 'reviewer_timeout' ? 'reviewer' : reason === 'forge_timeout' ? 'forge' : 'implementer']++;
154
+ }
155
+ if (a.gate === true) r.gateAccepts++;
156
+ r.model.push(a.model_s); r.duration.push(a.duration_s);
157
+ const tk = obj(a.tokens);
158
+ if (tk) { r.tokens.attempts++; for (const k of ['input', 'cached', 'output']) r.tokens[k] += num(tk[k]) || 0; }
159
+ });
160
+ // A human accepted the task although no attempt passed: the work accepted is the last attempt's.
161
+ if (t.accepted === true && atts.length && !atts.some(a => a.outcome === 'pass')) {
162
+ const alias = aliasOf(atts[atts.length - 1]); if (alias) row(alias).humanAccepts++;
163
+ }
164
+ for (const e of list(t.escalations)) { const from = obj(e) && RM.alias(e.from); if (from) row(from).escalationsAway++; }
165
+ }
166
+ const rows = [...by.values()].map(r => {
167
+ const { model, duration, ...rest } = r;
168
+ return { ...rest, first: { ...r.first, ...wilson(r.first.k, r.first.n) }, small: r.first.n < minN,
169
+ medianModelS: median(model), medianDurationS: median(duration),
170
+ tokens: { ...r.tokens, coverage: r.attempts.total ? r.tokens.attempts / r.attempts.total : null } };
171
+ });
172
+ rows.sort((a, b) => (b.first.lo ?? -1) - (a.first.lo ?? -1) || b.first.n - a.first.n || a.alias.localeCompare(b.alias));
173
+ return { rows, unattributed };
174
+ }
175
+
176
+ /* ---------- reviewers: one row per alias over the matching reviews ---------- */
177
+ function reviewerRows(reviews) {
178
+ const by = new Map();
179
+ let unattributed = 0;
180
+ for (const v of reviews) {
181
+ const alias = aliasOf(v);
182
+ if (!alias) { unattributed++; continue; }
183
+ if (!by.has(alias)) by.set(alias, { alias, reviews: { primary: 0, shadow: 0, final: 0, total: 0 }, verdicts: { PASS: 0, FAIL: 0, other: 0 },
184
+ sums: { P0: 0, P1: 0, P2: 0, P3: 0, unlabelled: 0, plausible: 0 }, withFindings: 0, agreement: { k: 0, n: 0, unknown: 0 },
185
+ duration: [], tokens: { input: 0, cached: 0, output: 0, reviews: 0, coverage: null } });
186
+ const r = by.get(alias);
187
+ if (REVIEW_KINDS.includes(v.kind)) r.reviews[v.kind]++;
188
+ r.reviews.total++;
189
+ r.verdicts[v.verdict === 'PASS' || v.verdict === 'FAIL' ? v.verdict : 'other']++;
190
+ const f = obj(v.findings);
191
+ if (f) { r.withFindings++; for (const k of Object.keys(r.sums)) r.sums[k] += num(f[k]) || 0; }
192
+ // agree/disagree need both verdicts; `unknown` (or none) says nothing either way
193
+ if (v.kind === 'shadow') { if (v.agreement === 'agree' || v.agreement === 'disagree') { r.agreement.n++; if (v.agreement === 'agree') r.agreement.k++; } else r.agreement.unknown++; }
194
+ r.duration.push(v.duration_s);
195
+ const tk = obj(v.tokens);
196
+ if (tk) { r.tokens.reviews++; for (const k of ['input', 'cached', 'output']) r.tokens[k] += num(tk[k]) || 0; }
197
+ }
198
+ const rows = [...by.values()].map(r => {
199
+ const { sums, withFindings, duration, ...rest } = r;
200
+ const findings = { n: withFindings };
201
+ for (const k of Object.keys(sums)) findings[k] = withFindings ? sums[k] / withFindings : null;
202
+ return { ...rest, findings, findingTotals: sums, agreement: { ...r.agreement, ...wilson(r.agreement.k, r.agreement.n) },
203
+ medianDurationS: median(duration), tokens: { ...r.tokens, coverage: r.reviews.total ? r.tokens.reviews / r.reviews.total : null } };
204
+ });
205
+ rows.sort((a, b) => b.reviews.total - a.reviews.total || a.alias.localeCompare(b.alias));
206
+ return { rows, unattributed };
207
+ }
208
+
209
+ /* ---------- the leaderboard ---------- */
210
+ function leaderboard(data, filter, now, runs) {
211
+ data = obj(data) || {};
212
+ const f = normFilter(filter);
213
+ const allTasks = list(data.tasks).filter(t => obj(t) && t.run), allReviews = list(data.reviews).filter(v => obj(v) && v.run);
214
+ const byTask = new Map(allTasks.map(t => [taskKey(t.run, t.task), t])), idx = runIndex(data, runs);
215
+ const tasks = allTasks.filter(t => taskMatch(t, f, now));
216
+ const reviews = allReviews.filter(v => reviewMatch(v, f, now, byTask, idx));
217
+ const imp = implementerRows(tasks, f.minN), rev = reviewerRows(reviews);
218
+ const byDifficulty = {};
219
+ for (const d of DIFFS) byDifficulty[d] = implementerRows(tasks.filter(t => diffOf(t) === d), f.minN).rows;
220
+
221
+ const attempts = tasks.reduce((s, t) => s + list(t.attempts).filter(obj).length, 0);
222
+ const first = { k: 0, n: 0, censored: 0 };
223
+ for (const r of imp.rows) { first.k += r.first.k; first.n += r.first.n; first.censored += r.first.censored; }
224
+ const withTokens = imp.rows.reduce((s, r) => s + r.tokens.attempts, 0);
225
+ const finalsAll = allReviews.filter(v => v.task === null || v.task === undefined).length;
226
+ const totals = {
227
+ runs: new Set([...tasks.map(t => t.run), ...reviews.map(v => v.run)]).size, tasks: tasks.length,
228
+ tasksRun: tasks.filter(t => list(t.attempts).some(obj)).length, attempts, first: { ...first, ...wilson(first.k, first.n) },
229
+ reviews: reviews.length, unattributedAttempts: imp.unattributed, unattributedReviews: rev.unattributed,
230
+ allTasks: allTasks.length, allReviews: allReviews.length, servedRuns: num(data.runs), truncated: data.truncated === true,
231
+ warnings: list(data.warnings).length,
232
+ };
233
+
234
+ const caveats = [];
235
+ const s = (n, one, many) => `${n} ${n === 1 ? one : many || one + 's'}`;
236
+ if (totals.truncated) caveats.push('Only the newest runs were read: the server stops at 50,000 task rows.');
237
+ if (totals.warnings) caveats.push(`${s(totals.warnings, 'unreadable entry', 'unreadable entries')} in the runs' files ${totals.warnings === 1 ? 'was' : 'were'} skipped.`);
238
+ if (imp.unattributed) caveats.push(`${s(imp.unattributed, 'attempt')} with no recorded model ${imp.unattributed === 1 ? 'is' : 'are'} left out.`);
239
+ if (rev.unattributed) caveats.push(`${s(rev.unattributed, 'review')} with no recorded model ${rev.unattributed === 1 ? 'is' : 'are'} left out.`);
240
+ if ((f.difficulty || f.exploratoryOnly) && finalsAll) caveats.push('Final reviews cover a whole run, so the difficulty and exploratory filters leave them out.');
241
+ if (attempts && withTokens < attempts - imp.unattributed) caveats.push(`Token counts cover ${withTokens} of ${attempts - imp.unattributed} attempts: older runs did not record them.`);
242
+ if (imp.rows.some(r => r.small)) caveats.push(`Models with fewer than ${s(f.minN, 'first-attempt outcome')} are greyed: their interval is too wide to rank them.`);
243
+ if (first.censored) caveats.push(`${s(first.censored, 'first attempt')} ended without a verdict on the implementer (reviewer or forge trouble, no changes, interrupted) and ${first.censored === 1 ? 'counts' : 'count'} in no rate.`);
244
+
245
+ return { implementers: imp.rows, reviewers: rev.rows, byDifficulty, totals, caveats, filter: f };
246
+ }
247
+
248
+ return { DIFFS, KINDS, SEVERITIES, DEFAULT_MIN_N, wilson, emptyFilter, normFilter, taskMatch, options, leaderboard };
249
+ });