@giamme/forge 0.0.0-stage → 0.8.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.
- package/CHANGELOG.md +181 -0
- package/LICENSE +21 -0
- package/README.md +1854 -2
- package/SKILL.md +122 -0
- package/VERSION +1 -0
- package/forge +74 -0
- package/package.json +26 -4
- package/references/dataset.md +329 -0
- package/references/decompose.md +667 -0
- package/references/fractal-dashboard.md +21 -0
- package/references/fractal.md +307 -0
- package/references/harnesses.md +353 -0
- package/references/jev.md +865 -0
- package/references/memory.md +228 -0
- package/references/planning.md +42 -0
- package/references/ripwire.md +47 -0
- package/references/runtime.md +64 -0
- package/references/solo.md +113 -0
- package/references/ui.md +112 -0
- package/registry.tsv +81 -0
- package/scripts/forge-artifact.sh +78 -0
- package/scripts/forge-contract.py +430 -0
- package/scripts/forge-dataset.py +6 -0
- package/scripts/forge-dispatch.sh +665 -0
- package/scripts/forge-fail-first.py +195 -0
- package/scripts/forge-fractal-dispatch.sh +4 -0
- package/scripts/forge-fractal-options.sh +54 -0
- package/scripts/forge-fractal.py +6 -0
- package/scripts/forge-guard.py +266 -0
- package/scripts/forge-install-fractal.sh +4 -0
- package/scripts/forge-install-ripwire.sh +52 -0
- package/scripts/forge-install.sh +135 -0
- package/scripts/forge-jev-options.sh +259 -0
- package/scripts/forge-jev.py +6 -0
- package/scripts/forge-lib-plans.sh +785 -0
- package/scripts/forge-lib-review.sh +494 -0
- package/scripts/forge-lib-task.sh +302 -0
- package/scripts/forge-lib-verify.sh +30 -0
- package/scripts/forge-memory.sh +575 -0
- package/scripts/forge-metrics.sh +39 -0
- package/scripts/forge-parallel-options.sh +76 -0
- package/scripts/forge-parallel.sh +1717 -0
- package/scripts/forge-plan-check.py +118 -0
- package/scripts/forge-prompt.py +45 -0
- package/scripts/forge-ripwire.py +136 -0
- package/scripts/forge-route.py +344 -0
- package/scripts/forge-runtime.py +533 -0
- package/scripts/forge-schedule.py +312 -0
- package/scripts/forge-shadow.py +305 -0
- package/scripts/forge-solo.sh +347 -0
- package/scripts/forge-ui-options.sh +42 -0
- package/scripts/forge-ui.py +435 -0
- package/scripts/forge-update.sh +71 -0
- package/scripts/forge-verify-coverage.py +956 -0
- package/scripts/forge-version.py +198 -0
- package/scripts/forge_dataset/__init__.py +439 -0
- package/scripts/forge_dataset/capture.py +330 -0
- package/scripts/forge_dataset/checks.py +109 -0
- package/scripts/forge_dataset/cli.py +233 -0
- package/scripts/forge_dataset/export.py +1009 -0
- package/scripts/forge_dataset/harvest.py +716 -0
- package/scripts/forge_dataset/importer.py +412 -0
- package/scripts/forge_dataset/outcomes.py +275 -0
- package/scripts/forge_dataset/routing.py +278 -0
- package/scripts/forge_fractal/__init__.py +83 -0
- package/scripts/forge_fractal/bridge.py +46 -0
- package/scripts/forge_fractal/cli.py +202 -0
- package/scripts/forge_fractal/dashboard.css +138 -0
- package/scripts/forge_fractal/dashboard.html +41 -0
- package/scripts/forge_fractal/dashboard.js +687 -0
- package/scripts/forge_fractal/decomposition.py +262 -0
- package/scripts/forge_fractal/execution.py +659 -0
- package/scripts/forge_fractal/inspection.py +261 -0
- package/scripts/forge_fractal/install.py +158 -0
- package/scripts/forge_fractal/invocation.py +94 -0
- package/scripts/forge_fractal/pipeline.py +124 -0
- package/scripts/forge_fractal/selection.py +179 -0
- package/scripts/forge_jev/__init__.py +376 -0
- package/scripts/forge_jev/backtest.py +322 -0
- package/scripts/forge_jev/cli.py +980 -0
- package/scripts/forge_jev/client.py +418 -0
- package/scripts/forge_jev/corpus.py +156 -0
- package/scripts/forge_jev/coupling.py +244 -0
- package/scripts/forge_jev/features.py +24 -0
- package/scripts/forge_jev/gates.py +100 -0
- package/scripts/forge_jev/memory.py +163 -0
- package/scripts/forge_jev/outcome.py +49 -0
- package/scripts/forge_jev/predict.py +204 -0
- package/scripts/forge_jev/questions.py +661 -0
- package/scripts/forge_jev/review.py +73 -0
- package/scripts/forge_jev/routing.py +271 -0
- package/scripts/forge_jev/subset.py +123 -0
- package/scripts/forge_jev/verify.py +453 -0
- package/scripts/forge_registry.py +140 -0
- package/ui/README.md +287 -0
- package/ui/THIRD_PARTY_NOTICES.md +69 -0
- package/ui/docs/api.md +888 -0
- package/ui/forge_ui/__init__.py +20 -0
- package/ui/forge_ui/__main__.py +135 -0
- package/ui/forge_ui/diffs.py +454 -0
- package/ui/forge_ui/executions.py +436 -0
- package/ui/forge_ui/export.py +204 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +583 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1336 -0
- package/ui/forge_ui/server.py +303 -0
- package/ui/forge_ui/static/app.js +924 -0
- package/ui/forge_ui/static/cloud3d.js +646 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +394 -0
- package/ui/forge_ui/static/diffview.js +216 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/index.html +55 -0
- package/ui/forge_ui/static/iso.js +765 -0
- package/ui/forge_ui/static/leaderboard.js +315 -0
- package/ui/forge_ui/static/map3d.js +45 -0
- package/ui/forge_ui/static/modelstats.js +249 -0
- package/ui/forge_ui/static/notify.js +179 -0
- package/ui/forge_ui/static/panel.js +526 -0
- package/ui/forge_ui/static/runmodel.js +651 -0
- package/ui/forge_ui/static/scene3d.js +996 -0
- package/ui/forge_ui/static/search.js +119 -0
- package/ui/forge_ui/static/style.css +618 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/* forge-ui inspector: what a selected node is, which model owned it, what prompt launched it, how long it
|
|
2
|
+
took and what its reviewer found. Defines window.ForgePanel.render(host, ctx). All API values enter the
|
|
3
|
+
DOM as text. Large texts (prompts, reports) come from the lazy endpoints and are fetched once per node. */
|
|
4
|
+
window.ForgePanel = (() => {
|
|
5
|
+
'use strict';
|
|
6
|
+
const RM = window.ForgeRunModel;
|
|
7
|
+
const list = v => Array.isArray(v) ? v : [];
|
|
8
|
+
const num = v => typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
9
|
+
const display = v => v === null || v === undefined || v === '' ? '—' : typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v);
|
|
10
|
+
const STATUSES = new Set(['merged', 'pass', 'running', 'pending', 'fail', 'error', 'infra', 'timeout', 'unknown', 'invalidated', 'blocked',
|
|
11
|
+
'interrupted', 'nochanges', 'conflict', 'accepted', 'none']);
|
|
12
|
+
const SEV = { P0: 'fail', P1: 'conflict', P2: 'timeout', P3: 'unknown' };
|
|
13
|
+
const cache = new Map(); // path → {data, error, at}
|
|
14
|
+
function el(tag, text, className) {
|
|
15
|
+
const node = document.createElement(tag);
|
|
16
|
+
if (text !== undefined) node.textContent = display(text);
|
|
17
|
+
if (className) node.className = className;
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
function chip(value, cls) {
|
|
21
|
+
const s = String(value || 'unknown').toLowerCase();
|
|
22
|
+
return el('span', value || '—', 'chip st-' + (cls || (STATUSES.has(s) ? s : 'unknown')));
|
|
23
|
+
}
|
|
24
|
+
function time(v) {
|
|
25
|
+
if (num(v) === null) return '—';
|
|
26
|
+
const d = new Date(v * 1000); return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString();
|
|
27
|
+
}
|
|
28
|
+
function fields(parent, entries) {
|
|
29
|
+
const dl = el('dl', undefined, 'fields');
|
|
30
|
+
for (const [label, value, mono] of entries) {
|
|
31
|
+
if (value === undefined) continue;
|
|
32
|
+
dl.append(el('dt', label));
|
|
33
|
+
const dd = el('dd', undefined, mono ? 'mono' : '');
|
|
34
|
+
dd.append(value instanceof Node ? value : document.createTextNode(display(value)));
|
|
35
|
+
dl.append(dd);
|
|
36
|
+
}
|
|
37
|
+
parent.append(dl); return dl;
|
|
38
|
+
}
|
|
39
|
+
function section(parent, title) { const s = el('section', undefined, 'insp-section'); s.append(el('h3', title)); parent.append(s); return s; }
|
|
40
|
+
function details(parent, title, value, key, { open = false, cls = '' } = {}) {
|
|
41
|
+
const d = el('details', undefined, cls); d.dataset.keep = key; if (open) d.open = true;
|
|
42
|
+
const pre = el('pre', value); pre.dataset.scroll = key + '-text';
|
|
43
|
+
d.append(el('summary', title), pre); parent.append(d); return d;
|
|
44
|
+
}
|
|
45
|
+
function link(text, hash, onClick) {
|
|
46
|
+
const a = el('a', text); a.href = hash;
|
|
47
|
+
if (onClick) a.addEventListener('click', e => { e.preventDefault(); onClick(); });
|
|
48
|
+
return a;
|
|
49
|
+
}
|
|
50
|
+
function muted(parent, text) { parent.append(el('p', text, 'muted')); }
|
|
51
|
+
// a link into the diff viewer (ctx.diffHash is absent where there is no viewer)
|
|
52
|
+
function diffLink(ctx, text, spec, cls = 'diff-link') { if (!ctx.diffHash) return null; const a = el('a', text, cls); a.href = ctx.diffHash(spec); return a; }
|
|
53
|
+
// a lazy resource: returns cached data, or starts the fetch and re-renders when it lands
|
|
54
|
+
function lazy(ctx, path, fresh) {
|
|
55
|
+
const c = cache.get(path);
|
|
56
|
+
if (c && (c.fresh === fresh || c.pending)) return c;
|
|
57
|
+
const entry = { pending: true, fresh, data: c && c.data || null, error: null };
|
|
58
|
+
cache.set(path, entry);
|
|
59
|
+
ctx.fetchJSON(path).then(data => { entry.data = data; entry.error = null; }, err => { entry.error = err; })
|
|
60
|
+
.finally(() => { entry.pending = false; ctx.rerender(); });
|
|
61
|
+
return entry;
|
|
62
|
+
}
|
|
63
|
+
function loading(parent, entry, what) {
|
|
64
|
+
if (entry.error) {
|
|
65
|
+
parent.append(el('p', entry.error.status === 404 ? `${what} is not available from this server. If forge-ui was started before this version, run \`forge ui open\` again to restart it.` : `Could not load ${what}: ${entry.error.message}`, 'muted'));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (!entry.data) { parent.append(el('p', `Loading ${what}…`, 'muted')); return true; }
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ---------- findings: P0–P3, confirmed or plausible, with where and what ---------- */
|
|
73
|
+
// FindingsSummary from a list of Findings (the lazy endpoints) or as given (the summaries)
|
|
74
|
+
function countsOf(f) {
|
|
75
|
+
if (!Array.isArray(f)) return f || null;
|
|
76
|
+
const c = { P0: 0, P1: 0, P2: 0, P3: 0, unlabelled: 0, plausible: 0, confirmed: 0, worst: null };
|
|
77
|
+
for (const x of f) {
|
|
78
|
+
if (x.status === 'PLAUSIBLE') { c.plausible++; continue; }
|
|
79
|
+
if (x.status === 'UNSTATED' || !x.severity) { c.unlabelled++; if (x.status === 'CONFIRMED') c.confirmed++; continue; }
|
|
80
|
+
c.confirmed++; c[x.severity]++;
|
|
81
|
+
}
|
|
82
|
+
c.worst = ['P0', 'P1', 'P2', 'P3'].find(k => c[k]) || null;
|
|
83
|
+
return c;
|
|
84
|
+
}
|
|
85
|
+
function findingCounts(parent, f) {
|
|
86
|
+
f = countsOf(f);
|
|
87
|
+
const row = el('div', undefined, 'chips');
|
|
88
|
+
if (!f) { row.append(el('span', 'no findings parsed', 'muted')); parent.append(row); return; }
|
|
89
|
+
let any = false;
|
|
90
|
+
for (const sev of ['P0', 'P1', 'P2', 'P3']) if (f[sev]) { row.append(chip(`${sev} × ${f[sev]}`, SEV[sev])); any = true; }
|
|
91
|
+
if (f.unlabelled) { row.append(chip(`unlabelled × ${f.unlabelled}`, 'unknown')); any = true; }
|
|
92
|
+
if (f.plausible) { row.append(chip(`plausible × ${f.plausible}`, 'pending')); any = true; }
|
|
93
|
+
if (!any) row.append(el('span', 'no confirmed findings', 'muted'));
|
|
94
|
+
parent.append(row);
|
|
95
|
+
}
|
|
96
|
+
/* ---------- fail-first (opt-in --fail-first): did the change's new tests fail without it? ---------- */
|
|
97
|
+
const FAIL_FIRST = { proven: ['fails without the change', 'pass'], passes_without_change: ['passes without the change: proves nothing', 'fail'],
|
|
98
|
+
inconclusive: ['fails even with the change: no evidence', 'unknown'], error: ['could not run', 'unknown'] };
|
|
99
|
+
function failFirst(parent, ff) {
|
|
100
|
+
if (!ff) return;
|
|
101
|
+
const box = el('div', undefined, 'review');
|
|
102
|
+
const h = el('div', undefined, 'attempt-head');
|
|
103
|
+
h.append(el('strong', 'fail-first evidence'));
|
|
104
|
+
const weak = (ff.counts && ff.counts.passes_without_change) || 0;
|
|
105
|
+
if (weak) h.append(chip(`${weak} pass without the change`, 'fail'));
|
|
106
|
+
box.append(h);
|
|
107
|
+
if (ff.status !== 'ran') { muted(box, ff.reason || `Fail-first did not run (${ff.status}).`); parent.append(box); return; }
|
|
108
|
+
if (ff.command) box.append(el('p', ff.command, 'mono muted'));
|
|
109
|
+
const ul = el('ul', undefined, 'findings');
|
|
110
|
+
for (const t of list(ff.tests)) {
|
|
111
|
+
const [label, cls] = FAIL_FIRST[t.result] || [t.result, 'unknown'];
|
|
112
|
+
const li = el('li');
|
|
113
|
+
li.append(el('span', t.path, 'mono'), document.createTextNode(' '), chip(label, cls));
|
|
114
|
+
ul.append(li);
|
|
115
|
+
}
|
|
116
|
+
box.append(ul);
|
|
117
|
+
parent.append(box);
|
|
118
|
+
}
|
|
119
|
+
function findingsList(parent, findings, key) {
|
|
120
|
+
if (findings === null || findings === undefined) { muted(parent, 'Findings could not be parsed (forge-contract.py was not available to the server).'); return; }
|
|
121
|
+
if (!findings.length) { muted(parent, 'The reviewer reported no findings in the expected `- [P1][CONFIRMED] file:line — …` form.'); return; }
|
|
122
|
+
const order = f => (f.severity ? Number(f.severity.slice(1)) : 4) + (f.status === 'CONFIRMED' ? 0 : .5);
|
|
123
|
+
const ul = el('ul', undefined, 'findings');
|
|
124
|
+
findings.slice().sort((a, b) => order(a) - order(b)).forEach((f, i) => {
|
|
125
|
+
const li = el('li', undefined, 'finding' + (f.status === 'CONFIRMED' ? '' : ' plausible'));
|
|
126
|
+
const head = el('div', undefined, 'finding-head');
|
|
127
|
+
head.append(chip(f.severity || 'P?', f.severity ? SEV[f.severity] : 'unknown'), el('span', f.status, 'tag'));
|
|
128
|
+
if (f.location) head.append(el('code', f.location));
|
|
129
|
+
li.append(head, el('p', f.summary || ''));
|
|
130
|
+
if (f.text && f.text !== f.summary) { const d = el('details'); d.dataset.keep = `${key}-f${i}`; d.append(el('summary', 'Full finding'), el('pre', f.text)); li.append(d); }
|
|
131
|
+
ul.append(li);
|
|
132
|
+
});
|
|
133
|
+
parent.append(ul);
|
|
134
|
+
}
|
|
135
|
+
function gateLine(parent, gate) {
|
|
136
|
+
if (!gate) return;
|
|
137
|
+
fields(parent, [['Blocking threshold', gate.threshold], ['Gate decision', gate.decision === 'accept' ? chip('accept', 'pass') : chip(gate.decision || '—', 'unknown')],
|
|
138
|
+
['Worst confirmed', gate.worst], ['Confirmed / unlabelled / tolerated', `${display(gate.n_confirmed)} / ${display(gate.n_unlabelled)} / ${display(gate.n_below)}`]]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ---------- timings ---------- */
|
|
142
|
+
function phaseBar(parent, phases, total) {
|
|
143
|
+
const entries = Object.entries(phases || {}).filter(([, v]) => num(v) !== null && v > 0);
|
|
144
|
+
const sum = entries.reduce((a, [, v]) => a + v, 0) || num(total) || 0;
|
|
145
|
+
if (!entries.length || !sum) return;
|
|
146
|
+
const bar = el('div', undefined, 'phase-bar'); bar.setAttribute('role', 'img');
|
|
147
|
+
bar.setAttribute('aria-label', entries.map(([k, v]) => `${k} ${RM.fmtDur(v)}`).join(', '));
|
|
148
|
+
const legend = el('div', undefined, 'phase-legend');
|
|
149
|
+
for (const [k, v] of entries) {
|
|
150
|
+
const seg = el('span', undefined, 'phase ph-' + k.replace(/[^a-z]/g, ''));
|
|
151
|
+
seg.style.flexGrow = String(Math.max(.02, v / sum)); // CSSOM, allowed by the page CSP
|
|
152
|
+
seg.title = `${k}: ${RM.fmtDur(v)}`;
|
|
153
|
+
bar.append(seg);
|
|
154
|
+
const item = el('span', undefined, 'phase-key'); item.append(el('i', undefined, 'ph-' + k.replace(/[^a-z]/g, '')), document.createTextNode(`${k} ${RM.fmtDur(v)}`));
|
|
155
|
+
legend.append(item);
|
|
156
|
+
}
|
|
157
|
+
parent.append(bar, legend);
|
|
158
|
+
}
|
|
159
|
+
const tokens = t => t ? `${display(t.input)} in (${display(t.cached)} cached) · ${display(t.output)} out` : '—';
|
|
160
|
+
function modelFields(m) {
|
|
161
|
+
if (!m) return [['Model', '—']];
|
|
162
|
+
return [['Spec', m.spec, true], ['Harness', m.harness, true], ['Model', m.model, true], ['Effort', m.effort], m.clamped ? ['Clamped', m.clamped] : ['Clamped', undefined]];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ---------- views ---------- */
|
|
166
|
+
function runView(host, ctx) {
|
|
167
|
+
const run = ctx.run, now = ctx.now;
|
|
168
|
+
const head = el('div', undefined, 'insp-head');
|
|
169
|
+
head.append(el('div', run.kind === 'solo' ? 'solo run' : 'run', 'insp-kind'), el('h2', String(run.goal || 'Untitled run').split('\n')[0]));
|
|
170
|
+
const chips = el('div', undefined, 'chips');
|
|
171
|
+
for (const [s, n] of Object.entries(run.counts || {})) { const c = chip(s); c.append(document.createTextNode(' ' + n)); chips.append(c); }
|
|
172
|
+
head.append(chips); host.append(head);
|
|
173
|
+
const tr = RM.timeRange(run, now);
|
|
174
|
+
const s = section(host, 'Timeline');
|
|
175
|
+
fields(s, [['Started', time(tr && tr.start)], ['Last activity', time(tr && tr.end)], ['Wall time', tr ? RM.fmtDur(tr.end - tr.start) : '—'],
|
|
176
|
+
['Concurrency limit', run.settings && run.settings.max_parallel != null ? run.settings.max_parallel : (run.kind === 'solo' ? '—' : '3 (default; not recorded)')]]);
|
|
177
|
+
if (run.kind !== 'solo') {
|
|
178
|
+
const cp = RM.criticalPath(run, { now });
|
|
179
|
+
if (cp.ids.length) {
|
|
180
|
+
const p = el('p', undefined, 'crit');
|
|
181
|
+
p.append(document.createTextNode(cp.mode === 'exec' ? 'Critical path (what bounded the wall time): ' : 'Heaviest chain by difficulty: '));
|
|
182
|
+
cp.ids.forEach((id, i) => { if (i) p.append(document.createTextNode(' → ')); p.append(link(id, ctx.hash({ kind: 'task', id }), () => ctx.onSelect({ kind: 'task', id }))); });
|
|
183
|
+
s.append(p);
|
|
184
|
+
for (const w of cp.waits) muted(s, `${w.to} waited ${RM.fmtDur(w.seconds)} after ${w.from} merged.`);
|
|
185
|
+
}
|
|
186
|
+
const c = RM.concurrency(run, { now });
|
|
187
|
+
if (c.series.length) muted(s, `Peak concurrency: ${c.peak} at once (limit ${c.cap}).`);
|
|
188
|
+
}
|
|
189
|
+
const f = section(host, 'Finish');
|
|
190
|
+
const F = RM.finishState(run);
|
|
191
|
+
if (run.kind === 'solo') fields(f, [['Verdict', chip(list(run.tasks)[0] && list(run.tasks)[0].verdict)]]);
|
|
192
|
+
else {
|
|
193
|
+
fields(f, [['Integration', link(`${F.integration.merged}/${F.integration.total} merged`, ctx.hash({ kind: 'integration' }), () => ctx.onSelect({ kind: 'integration' }))],
|
|
194
|
+
['Verification', link(F.verification.status, ctx.hash({ kind: 'verification' }), () => ctx.onSelect({ kind: 'verification' }))],
|
|
195
|
+
['Final review', link(F.final.status === 'NONE' ? 'not requested' : F.final.status, ctx.hash({ kind: 'final' }), () => ctx.onSelect({ kind: 'final' }))]]);
|
|
196
|
+
if (F.final.findings) findingCounts(f, F.final.findings);
|
|
197
|
+
}
|
|
198
|
+
if (list(run.deferrals).length) details(host, `Deferred tasks (${run.deferrals.length})`, run.deferrals.join('\n'), 'deferrals');
|
|
199
|
+
muted(host, 'Select a task, an attempt or a finish node on the map or in the table to see its prompts, models, timings and findings.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function taskView(host, ctx, task) {
|
|
203
|
+
const run = ctx.run, now = ctx.now, solo = run.kind === 'solo';
|
|
204
|
+
const head = el('div', undefined, 'insp-head');
|
|
205
|
+
head.append(el('div', solo ? 'solo task' : `task · wave ${display(task.wave)}`, 'insp-kind'), el('h2', solo ? (task.title || 'Solo run') : `${task.id} · ${task.title || 'Untitled task'}`));
|
|
206
|
+
const chips = el('div', undefined, 'chips');
|
|
207
|
+
chips.append(chip(task.status));
|
|
208
|
+
if (task.verdict) chips.append(chip('review ' + task.verdict, task.verdict.toLowerCase()));
|
|
209
|
+
if (task.explored) chips.append(el('span', '* explored', 'tag'));
|
|
210
|
+
if (task.accepted_by) chips.append(chip('accepted over FAIL', 'nochanges'));
|
|
211
|
+
head.append(chips);
|
|
212
|
+
const dl = diffLink(ctx, solo ? 'View the diff →' : 'View its diff, findings pinned to their lines →', { kind: 'task', task: task.id, source: 'current' }); if (dl) head.append(dl);
|
|
213
|
+
host.append(head);
|
|
214
|
+
if (!solo) fields(host, [['Difficulty', task.difficulty], ['Depends on', depLinks(task.deps, ctx)], ['Files', list(task.files).join('\n') || '—', true], ['Branch', task.branch, true]]);
|
|
215
|
+
|
|
216
|
+
// the models: who owned it, how it was routed, what it was escalated to
|
|
217
|
+
const ms = section(host, 'Models');
|
|
218
|
+
fields(ms, [['Dwarf', task.dwarf, true], ['Reviewer', task.qa, true], ['Attempt', task.attempt]]);
|
|
219
|
+
const route = list(task.route).slice(-1)[0];
|
|
220
|
+
if (route) fields(ms, [['Routed', `${display(route.chosen)} from ${list(route.candidates).join(', ') || '—'}${route.explored ? ` (exploratory, p=${display(route.propensity)})` : ''}`, true], ['Tier · ε', `${display(route.tier)} · ${display(route.epsilon)}`]]);
|
|
221
|
+
for (const e of list(task.escalations)) muted(ms, `Escalated after attempt ${display(e.attempt)}: ${display(e.from)} → ${display(e.to)} at ${time(e.at)}`);
|
|
222
|
+
|
|
223
|
+
// timings
|
|
224
|
+
const tt = RM.taskTimes(task, run, now);
|
|
225
|
+
const ts = section(host, 'Timings');
|
|
226
|
+
fields(ts, [['First start', time(tt.first)], ['Last activity', time(tt.last)], ['Busy', RM.fmtDur(tt.busy)], ['Model time', RM.fmtDur(tt.model)],
|
|
227
|
+
['Overhead', RM.fmtDur(tt.overhead)], ['Queue wait', tt.wait === null ? '—' : RM.fmtDur(tt.wait)], ['Merged', time(task.merged_at)],
|
|
228
|
+
['Tokens', tt.tokens.input || tt.tokens.output ? `${tt.tokens.input} in (${tt.tokens.cached} cached) · ${tt.tokens.output} out` : '—']]);
|
|
229
|
+
|
|
230
|
+
// attempts, each with its dwarf and its review
|
|
231
|
+
const as = section(host, 'Attempts');
|
|
232
|
+
const tp = RM.taskParts(task);
|
|
233
|
+
if (!list(task.executions).length) muted(as, 'No executions recorded yet' + (task.status === 'PENDING' ? ': this task has not started.' : '.'));
|
|
234
|
+
for (const z of tp.zones) {
|
|
235
|
+
const box = el('div', undefined, 'attempt');
|
|
236
|
+
const row = list(task.attempts).filter(a => a.attempt === z.attempt).pop();
|
|
237
|
+
const h = el('div', undefined, 'attempt-head'); h.append(el('strong', z.name.toLowerCase())); if (row) h.append(chip(row.status), el('span', time(row.at), 'muted'));
|
|
238
|
+
box.append(h);
|
|
239
|
+
for (const pid of z.parts) {
|
|
240
|
+
const p = tp.parts.find(x => x.id === pid); if (!p || p.planned) continue;
|
|
241
|
+
const line = el('div', undefined, 'exec-line');
|
|
242
|
+
const e = p.exec && list(task.executions).find(x => x.id === p.exec.id) || null;
|
|
243
|
+
const label = `${p.label.toLowerCase()} · ${p.spec || '—'}`;
|
|
244
|
+
const target = p.exec && p.exec.id ? { kind: 'execution', task: task.id, id: task.id, exec: p.exec.id, shadow: p.kind === 'shadow' } : null;
|
|
245
|
+
line.append(target ? link(label, ctx.hash(target), () => ctx.onSelect(target)) : el('span', label), chip(p.status));
|
|
246
|
+
if (e) line.append(el('span', `${RM.fmtDur(e.duration_s ?? (e.state === 'running' && num(e.started) !== null ? now - e.started : null))}${e.state === 'running' && e.phase ? ' · ' + e.phase : ''}`, 'muted'));
|
|
247
|
+
if (p.kind === 'shadow' && p.shadow) line.append(el('span', p.shadow.agreement === 'disagree' ? '⚑ disagrees with the primary review' : p.shadow.agreement === 'agree' ? 'agrees' : '', p.shadow.agreement === 'disagree' ? 'warn' : 'muted'));
|
|
248
|
+
const fs = (e && e.findings) || (p.shadow && p.shadow.findings);
|
|
249
|
+
if (fs && fs.worst) line.append(chip(`${fs.worst}×${fs[fs.worst]}`, SEV[fs.worst]));
|
|
250
|
+
if ((p.kind === 'qa' || p.kind === 'shadow') && p.exec && p.exec.id) { const a = diffLink(ctx, 'diff it read', { kind: 'task', task: task.id, source: p.exec.id }, 'diff-link small'); if (a) line.append(a); }
|
|
251
|
+
box.append(line);
|
|
252
|
+
}
|
|
253
|
+
for (const l of tp.links.filter(l => l.kind === 'escalation' && z.parts.includes(l.to))) box.append(el('p', `escalated: ${l.label}`, 'muted'));
|
|
254
|
+
as.append(box);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (run.fractal && run.fractal.enabled) fractalSection(host, ctx, task);
|
|
258
|
+
|
|
259
|
+
// reviews and their findings
|
|
260
|
+
const rs = section(host, 'Review findings');
|
|
261
|
+
gateLine(rs, task.gate);
|
|
262
|
+
findingCounts(rs, task.findings);
|
|
263
|
+
failFirst(rs, task.fail_first);
|
|
264
|
+
const detail = lazy(ctx, `/api/runs/${encodeURIComponent(run.key)}/tasks/${encodeURIComponent(task.id)}`, run.updated);
|
|
265
|
+
if (!loading(rs, detail, 'the full findings')) {
|
|
266
|
+
const reviews = list(detail.data.reviews);
|
|
267
|
+
if (!reviews.length) muted(rs, 'No review has run yet.');
|
|
268
|
+
reviews.forEach((r, i) => {
|
|
269
|
+
const box = el('div', undefined, 'review');
|
|
270
|
+
const h = el('div', undefined, 'attempt-head');
|
|
271
|
+
h.append(el('strong', `${r.kind} review · attempt ${display(r.attempt)}`), el('span', r.spec || '', 'mono'), chip(r.verdict || r.state || 'unknown'));
|
|
272
|
+
box.append(h);
|
|
273
|
+
if (r.state === 'running') muted(box, 'Still reviewing; findings appear when it finishes.');
|
|
274
|
+
else findingsList(box, r.findings, `task-${task.id}-r${i}`);
|
|
275
|
+
rs.append(box);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// Clef: what was predicted, against what happened
|
|
279
|
+
const clef = task.clef || {};
|
|
280
|
+
const preds = [];
|
|
281
|
+
if (clef.first_pass) preds.push(['First attempt', clef.first_pass]);
|
|
282
|
+
for (const [a, p] of Object.entries(clef.retry_pass || {})) preds.push([`Retry · attempt ${a}`, p]);
|
|
283
|
+
if (preds.length) {
|
|
284
|
+
const cs = section(host, 'Clef predictions');
|
|
285
|
+
for (const [label, p] of preds) {
|
|
286
|
+
const n = Number(String(label).match(/\d+/) || 1), outcome = list(task.attempts).filter(a => a.attempt === n).pop();
|
|
287
|
+
fields(cs, [[label, `p(pass) = ${display(p.p)} → ${outcome ? outcome.status : 'not finished'}`], ['Assignment', p.assignment ? `${display(p.assignment.dwarf)} / ${display(p.assignment.qa)}` : '—', true]]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// the prompts and the final messages
|
|
291
|
+
const ps = section(host, 'Prompts and output');
|
|
292
|
+
if (!detail.data) details(ps, 'Requirements (prompt.md)', task.prompt, `task-${task.id}-prompt`);
|
|
293
|
+
else {
|
|
294
|
+
const t = detail.data.texts || {};
|
|
295
|
+
for (const [title, key] of [['Requirements (prompt.md)', 'prompt'], ['Intended approach', 'approach'], ['Retry findings', 'retry_findings'],
|
|
296
|
+
['Dwarf prompt as assembled (dwarf.input)', 'dwarf_input'], ['Review prompt as assembled (qa.input)', 'qa_input'], ['Known issues', 'known_issues'], ['Accepted by', 'accepted_by']]) {
|
|
297
|
+
if (t[key] != null) details(ps, title, t[key], `task-${task.id}-${key}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (task.dwarf_tail != null) details(ps, 'Dwarf final message', task.dwarf_tail, `task-${task.id}-dwarf_tail`);
|
|
301
|
+
if (task.qa_tail != null) details(ps, 'Reviewer final message', task.qa_tail, `task-${task.id}-qa_tail`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function execView(host, ctx, task, sel) {
|
|
305
|
+
const run = ctx.run, now = ctx.now;
|
|
306
|
+
const brief = list(task && task.executions).find(e => e.id === sel.exec) || list(run.timeline && run.timeline.verifications).find(e => e.id === sel.exec) || null;
|
|
307
|
+
const entry = lazy(ctx, `/api/runs/${encodeURIComponent(run.key)}/executions/${encodeURIComponent(sel.exec)}`, brief && brief.state === 'done' ? 'done' : run.updated);
|
|
308
|
+
const e = entry.data || brief;
|
|
309
|
+
const head = el('div', undefined, 'insp-head');
|
|
310
|
+
const role = e ? e.role : String(sel.exec).split('-')[0];
|
|
311
|
+
head.append(el('div', `${sel.shadow ? 'shadow ' : ''}${role === 'qa' ? 'review' : role} · attempt ${display(e && e.attempt)}`, 'insp-kind'),
|
|
312
|
+
el('h2', `${task ? task.id + ' · ' : ''}${role === 'qa' ? 'reviewer' : role === 'dwarf' ? 'dwarf' : role}`));
|
|
313
|
+
const chips = el('div', undefined, 'chips');
|
|
314
|
+
if (e) { chips.append(chip(RM.execStatus(e))); if (e.verdict) chips.append(chip('verdict ' + e.verdict, e.verdict.toLowerCase())); }
|
|
315
|
+
head.append(chips);
|
|
316
|
+
if (task) head.append(link('← task ' + task.id, ctx.hash({ kind: 'task', id: task.id }), () => ctx.onSelect({ kind: 'task', id: task.id })));
|
|
317
|
+
if (task && (role === 'qa' || role === 'dwarf')) { const a = diffLink(ctx, role === 'qa' ? 'The diff this review read →' : 'The diff its review read →', { kind: 'task', task: task.id, source: sel.exec }); if (a) head.append(a); }
|
|
318
|
+
host.append(head);
|
|
319
|
+
if (!e) { loading(host, entry, 'this execution'); return; }
|
|
320
|
+
const ms = section(host, 'Model');
|
|
321
|
+
fields(ms, modelFields(e.model));
|
|
322
|
+
const ts = section(host, 'Timings');
|
|
323
|
+
const running = e.state === 'running';
|
|
324
|
+
fields(ts, [['Started', time(e.started)], ['Ended', running ? 'running' : time(e.ended)], ['Duration', RM.fmtDur(e.duration_s ?? (running && num(e.started) !== null ? now - e.started : null))],
|
|
325
|
+
['Phase', running ? `${display(e.phase)}${num(e.phase_started) !== null ? ' for ' + RM.fmtDur(now - e.phase_started) : ''}` : e.phase], ['Tokens', tokens(e.tokens)],
|
|
326
|
+
['Prompt size', e.prompt_bytes != null ? `${e.prompt_bytes} bytes` : '—'], ['Exit', e.exit_code], ['Infra', e.infra_class], ['Timed out', e.timed_out ? 'yes' : undefined]]);
|
|
327
|
+
phaseBar(ts, e.phases, e.duration_s);
|
|
328
|
+
if (role === 'qa') {
|
|
329
|
+
const rs = section(host, 'Findings');
|
|
330
|
+
if (running) muted(rs, 'Still reviewing; findings appear when it finishes.');
|
|
331
|
+
else {
|
|
332
|
+
findingCounts(rs, entry.data ? entry.data.findings : e.findings);
|
|
333
|
+
if (entry.data) findingsList(rs, entry.data.findings, `exec-${sel.exec}`); else loading(rs, entry, 'the findings');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const ps = section(host, 'Prompt and output');
|
|
337
|
+
if (loading(ps, entry, 'the prompt')) return;
|
|
338
|
+
const d = entry.data;
|
|
339
|
+
details(ps, 'Prompt that launched it', d.prompt, `exec-${sel.exec}-prompt`, { open: false });
|
|
340
|
+
details(ps, 'Final message', d.last, `exec-${sel.exec}-last`);
|
|
341
|
+
if (d.resolved) details(ps, 'Resolved dispatch settings', Object.entries(d.resolved).map(([k, v]) => `${k}=${v}`).join('\n'), `exec-${sel.exec}-resolved`);
|
|
342
|
+
if (list(d.warnings).length) details(ps, `Reader warnings (${d.warnings.length})`, d.warnings.join('\n'), `exec-${sel.exec}-warn`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* ---------- Fractal: the child tasks a task was split into ---------- */
|
|
346
|
+
const money = v => num(v) === null ? '—' : '$' + (v < 1 ? v.toFixed(3) : v.toFixed(2));
|
|
347
|
+
const fractalPath = (run, task) => `/api/runs/${encodeURIComponent(run.key)}/tasks/${encodeURIComponent(task)}/fractal`;
|
|
348
|
+
function fractalSection(host, ctx, task) {
|
|
349
|
+
const run = ctx.run, fs = section(host, 'Fractal tree');
|
|
350
|
+
if (!run.fractal.available) { muted(fs, `Fractal ran this run, but its state cannot be read: ${run.fractal.reason || 'unavailable'}.`); return; }
|
|
351
|
+
const entry = lazy(ctx, fractalPath(run, task.id), run.updated);
|
|
352
|
+
if (loading(fs, entry, 'the Fractal tree')) return;
|
|
353
|
+
const F = entry.data;
|
|
354
|
+
if (ctx.onFractal) ctx.onFractal(task.id, F.available ? F : null);
|
|
355
|
+
if (!F.available) { muted(fs, F.reason || 'No Fractal state for this task.'); return; }
|
|
356
|
+
const st = F.state || {}, out = F.outcome || {};
|
|
357
|
+
const chips = el('div', undefined, 'chips');
|
|
358
|
+
if (st.status) chips.append(chip(st.status === 'active' ? 'RUNNING' : st.status, st.status === 'active' ? 'running' : undefined));
|
|
359
|
+
if (out.acceptance || st.acceptance) chips.append(chip('acceptance ' + (out.acceptance || st.acceptance), String(out.acceptance || '').toLowerCase() === 'pass' ? 'pass' : 'none'));
|
|
360
|
+
if (out.verification) chips.append(chip('verification ' + out.verification, out.verification === 'passed' ? 'pass' : 'none'));
|
|
361
|
+
fs.append(chips);
|
|
362
|
+
const nodes = list(F.nodes), cost = nodes.reduce((a, n) => a + (num(n.cost) || 0), 0);
|
|
363
|
+
muted(fs, `${nodes.length} node${nodes.length === 1 ? '' : 's'} · ${RM.fmtDur(st.elapsed)} · ${money(cost)} so far · click a node for its goal, decision and output`);
|
|
364
|
+
const T = RM.fractalTree(nodes), ul = el('ul', undefined, 'fractal-tree');
|
|
365
|
+
for (const n of nodes) {
|
|
366
|
+
const li = el('li', undefined, 'ft-node ft-d' + Math.min(6, (T.pos[n.id] || {}).depth || 0) + (n.reserved ? ' reserved' : ''));
|
|
367
|
+
li.append(el('i', undefined, 'led st-' + String(n.ui_status || 'unknown').toLowerCase()));
|
|
368
|
+
const target = { kind: 'fractal', task: task.id, node: n.id };
|
|
369
|
+
li.append(link(n.id, ctx.hash(target), () => ctx.onSelect(target)));
|
|
370
|
+
const bits = [n.reserved ? 'reserved' : n.status, n.difficulty, n.model, n.phase ? `${n.phase}${n.iteration ? ' #' + n.iteration : ''}` : null, num(n.cost) !== null ? money(n.cost) : null].filter(Boolean);
|
|
371
|
+
li.append(el('span', bits.join(' · '), 'muted'));
|
|
372
|
+
const dec = n.decomposition;
|
|
373
|
+
if (dec && dec.decision) li.append(el('span', dec.decision === 'split' ? `split into ${list(dec.children).length}` : dec.decision, 'tag'));
|
|
374
|
+
if (n.has_diff) { const a = diffLink(ctx, 'diff', { kind: 'fractal', task: task.id, node: n.id }, 'diff-link small'); if (a) li.append(a); }
|
|
375
|
+
if (n.goal) li.title = n.goal;
|
|
376
|
+
ul.append(li);
|
|
377
|
+
}
|
|
378
|
+
fs.append(ul);
|
|
379
|
+
if (list(F.events).length) details(fs, `Recent events (${F.events.length})`, F.events.map(e => `${time(e.at)} ${e.kind}${e.node ? ' · ' + e.node : ''}${e.decision ? ' · ' + e.decision : ''}${e.status ? ' · ' + e.status : ''}${e.reason ? ' — ' + e.reason : ''}`).join('\n'), `fractal-${task.id}-events`);
|
|
380
|
+
if (list(F.warnings).length) details(fs, `Reader warnings (${F.warnings.length})`, F.warnings.join('\n'), `fractal-${task.id}-warn`);
|
|
381
|
+
}
|
|
382
|
+
function fractalNodeView(host, ctx, sel) {
|
|
383
|
+
const run = ctx.run, path = `${fractalPath(run, sel.task)}/nodes/${encodeURIComponent(sel.node)}`;
|
|
384
|
+
const entry = lazy(ctx, path, run.updated), n = entry.data;
|
|
385
|
+
const head = el('div', undefined, 'insp-head');
|
|
386
|
+
head.append(el('div', `Fractal node · task ${sel.task}`, 'insp-kind'), el('h2', sel.node));
|
|
387
|
+
const back = { kind: 'task', id: sel.task };
|
|
388
|
+
if (n) { const chips = el('div', undefined, 'chips'); chips.append(chip(n.reserved ? 'reserved' : n.ui_status || n.status, n.reserved ? 'none' : undefined)); if (n.decomposition && n.decomposition.decision) chips.append(el('span', n.decomposition.decision, 'tag')); head.append(chips); }
|
|
389
|
+
head.append(link('← task ' + sel.task, ctx.hash(back), () => ctx.onSelect(back)));
|
|
390
|
+
if (n && n.has_diff) { const a = diffLink(ctx, 'Its candidate diff →', { kind: 'fractal', task: sel.task, node: sel.node }); if (a) head.append(a); }
|
|
391
|
+
host.append(head);
|
|
392
|
+
if (loading(host, entry, 'this node')) return;
|
|
393
|
+
if (n.goal) host.append(el('p', n.goal));
|
|
394
|
+
fields(host, [['Parent', n.parent ? link(n.parent, ctx.hash({ kind: 'fractal', task: sel.task, node: n.parent }), () => ctx.onSelect({ kind: 'fractal', task: sel.task, node: n.parent })) : '—'],
|
|
395
|
+
['Difficulty', n.difficulty], ['Model', n.model, true], ['Resolved', n.resolved_model, true], ['Paths', list(n.paths).join('\n') || '—', true], ['Depends on', list(n.deps).join(', ') || '—', true],
|
|
396
|
+
['Phase', n.phase ? `${n.phase}${n.iteration ? ' · iteration ' + n.iteration : ''}` : '—'], ['Started', time(n.started_at)], ['Ended', time(n.ended_at)],
|
|
397
|
+
['Cost', `${money(n.cost)}${n.unknown_cost_steps ? ` (+${n.unknown_cost_steps} steps of unknown cost)` : ''}`], ['Tokens', n.tokens ? `${display(n.tokens.input_tokens)} in (${display(n.tokens.cached_input_tokens)} cached) · ${display(n.tokens.output_tokens)} out` : '—']]);
|
|
398
|
+
if (n.error) host.append(el('p', n.error, 'warn'));
|
|
399
|
+
const dec = n.decomposition;
|
|
400
|
+
if (dec) {
|
|
401
|
+
const ds = section(host, 'Decomposition');
|
|
402
|
+
fields(ds, [['Decision', dec.decision], ['Phase', dec.phase], ['Attempts', dec.attempts]]);
|
|
403
|
+
if (dec.reason) ds.append(el('p', dec.reason));
|
|
404
|
+
const adm = list(dec.admission);
|
|
405
|
+
if (adm.length) {
|
|
406
|
+
const ul = el('ul', undefined, 'plain');
|
|
407
|
+
for (const a of adm) { const li = el('li'); li.append(link(a.name, ctx.hash({ kind: 'fractal', task: sel.task, node: a.name }), () => ctx.onSelect({ kind: 'fractal', task: sel.task, node: a.name })), document.createTextNode(` · ${a.difficulty || '—'} · ${a.model || '—'} — ${a.goal || ''}`)); ul.append(li); }
|
|
408
|
+
ds.append(el('p', 'Children it admitted:', 'muted'), ul);
|
|
409
|
+
}
|
|
410
|
+
if (dec.error) ds.append(el('p', dec.error, 'warn'));
|
|
411
|
+
}
|
|
412
|
+
if (list(n.decomposition_history).length > 1) details(host, `Decomposition history (${n.decomposition_history.length})`, n.decomposition_history.map(h => `${h.phase || '—'} · ${h.decision || '—'}${h.reason ? ' — ' + h.reason : ''}`).join('\n'), `fnode-${sel.task}-${sel.node}-hist`);
|
|
413
|
+
const ps = section(host, 'Output');
|
|
414
|
+
if (n.last != null) details(ps, 'Last message', n.last, `fnode-${sel.task}-${sel.node}-last`);
|
|
415
|
+
if (n.planning_last != null) details(ps, 'Planner\'s last message', n.planning_last, `fnode-${sel.task}-${sel.node}-plan`);
|
|
416
|
+
if (n.last == null && n.planning_last == null) muted(ps, 'No output yet.');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function finalView(host, ctx, sel) {
|
|
420
|
+
const run = ctx.run, reviews = list(run.final_reviews);
|
|
421
|
+
const head = el('div', undefined, 'insp-head');
|
|
422
|
+
head.append(el('div', 'finish · whole-run review', 'insp-kind'), el('h2', 'Final review'));
|
|
423
|
+
host.append(head);
|
|
424
|
+
const pick = sel.name ? reviews.find(r => r.name === sel.name) : reviews[0];
|
|
425
|
+
if (!pick) {
|
|
426
|
+
head.append(chip(run.settings && run.settings.final_qa ? 'pending' : 'not requested', 'none'));
|
|
427
|
+
muted(host, 'No whole-run review has been run for this plan. Per-task reviews only see one slice; the final review reads the combined diff with every task\'s requirements.');
|
|
428
|
+
host.append(el('pre', `bash <forge>/scripts/forge-parallel.sh review ${run.path}${run.settings && run.settings.final_qa ? '' : ' --qa <spec>'}`, 'cmd'));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const chips = el('div', undefined, 'chips');
|
|
432
|
+
chips.append(chip(pick.state === 'running' ? 'RUNNING' : pick.verdict || pick.state || 'unknown'));
|
|
433
|
+
if (pick.published) chips.append(el('span', 'published', 'tag'));
|
|
434
|
+
chips.append(el('span', pick.source === 'integrate' ? 'during integrate' : 'forge review', 'tag'));
|
|
435
|
+
head.append(chips);
|
|
436
|
+
fields(host, [['Reviewer', pick.spec, true], ['Threshold', pick.threshold], ['Reviewed', pick.base || pick.tip ? `${String(pick.base || '?').slice(0, 10)}..${String(pick.tip || '?').slice(0, 10)}` : '—', true],
|
|
437
|
+
['Started', time(pick.started ?? pick.at)], ['Ended', time(pick.ended)], ['Duration', RM.fmtDur(pick.duration_s)]]);
|
|
438
|
+
if (pick.execution) { const s = { kind: 'execution', task: null, exec: pick.execution }; host.append(link('Model, timings and exact prompt →', ctx.hash(s), () => ctx.onSelect(s))); }
|
|
439
|
+
{ const a = diffLink(ctx, 'The combined diff it read, findings pinned →', { kind: 'final', name: pick.name }); if (a) { host.append(el('br')); host.append(a); } }
|
|
440
|
+
const fs = section(host, 'Findings');
|
|
441
|
+
if (pick.state === 'running') muted(fs, 'The reviewer is still reading the combined diff; its findings appear here when it finishes.');
|
|
442
|
+
gateLine(fs, pick.gate);
|
|
443
|
+
if (pick.state !== 'running') findingCounts(fs, pick.findings);
|
|
444
|
+
const entry = lazy(ctx, `/api/runs/${encodeURIComponent(run.key)}/final-reviews/${encodeURIComponent(pick.name)}`, pick.state === 'done' ? 'done:' + pick.verdict : run.updated);
|
|
445
|
+
if (!loading(fs, entry, 'the review')) {
|
|
446
|
+
const d = entry.data;
|
|
447
|
+
if (pick.state !== 'running') findingsList(fs, d.findings, `final-${pick.name}`);
|
|
448
|
+
const ps = section(host, 'Report and prompt');
|
|
449
|
+
details(ps, 'Reviewer report (qa.last)', d.qa_last, `final-${pick.name}-last`);
|
|
450
|
+
details(ps, 'Prompt (qa.input)', d.qa_input, `final-${pick.name}-input`);
|
|
451
|
+
if (d.tasks_summary) details(ps, 'Tasks it was told about', d.tasks_summary, `final-${pick.name}-tasks`);
|
|
452
|
+
if (d.known_issues) details(ps, 'Known issues', d.known_issues, `final-${pick.name}-known`);
|
|
453
|
+
}
|
|
454
|
+
if (reviews.length > 1) {
|
|
455
|
+
const hs = section(host, `History (${reviews.length})`);
|
|
456
|
+
const ul = el('ul', undefined, 'plain');
|
|
457
|
+
for (const r of reviews) {
|
|
458
|
+
const li = el('li');
|
|
459
|
+
li.append(link(`${time(r.started ?? r.at)} · ${r.verdict || r.state}`, ctx.hash({ kind: 'final', name: r.name }), () => ctx.onSelect({ kind: 'final', name: r.name })));
|
|
460
|
+
if (r.findings && r.findings.worst) li.append(document.createTextNode(' '), chip(`${r.findings.worst}×${r.findings[r.findings.worst]}`, SEV[r.findings.worst]));
|
|
461
|
+
if (r.name === pick.name) li.className = 'current';
|
|
462
|
+
ul.append(li);
|
|
463
|
+
}
|
|
464
|
+
hs.append(ul);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function verificationView(host, ctx) {
|
|
469
|
+
const run = ctx.run, v = run.verification;
|
|
470
|
+
const head = el('div', undefined, 'insp-head');
|
|
471
|
+
head.append(el('div', 'finish', 'insp-kind'), el('h2', 'Verification'), chip(v && v.status || 'pending'));
|
|
472
|
+
host.append(head);
|
|
473
|
+
if (!v) { muted(host, 'Verification has not run: it runs after every task is merged into the integration branch.'); return; }
|
|
474
|
+
fields(host, [['Command', v.command, true], ['Commit', v.commit, true]]);
|
|
475
|
+
if (list(v.runs).length) {
|
|
476
|
+
const ul = el('ul', undefined, 'plain');
|
|
477
|
+
for (const r of v.runs) ul.append(el('li', `#${display(r.n)} ${display(r.kind)} · exit ${display(r.exit)} · ${RM.fmtDur(r.duration_s)}${r.tree_changed ? ' · changed the tree' : ''}`));
|
|
478
|
+
section(host, 'Runs').append(ul);
|
|
479
|
+
}
|
|
480
|
+
for (const x of list(run.timeline && run.timeline.verifications)) { const s = { kind: 'execution', task: null, exec: x.id }; host.append(el('p'), link(`Timings of the verification started ${time(x.started)} →`, ctx.hash(s), () => ctx.onSelect(s))); }
|
|
481
|
+
if (v.log_tail != null) details(host, 'Output', v.log_tail, 'verification-output');
|
|
482
|
+
}
|
|
483
|
+
function integrationView(host, ctx) {
|
|
484
|
+
const run = ctx.run, ig = run.integration || {};
|
|
485
|
+
const F = RM.finishState(run);
|
|
486
|
+
const head = el('div', undefined, 'insp-head');
|
|
487
|
+
head.append(el('div', 'finish', 'insp-kind'), el('h2', 'Integration'), chip(F.integration.status));
|
|
488
|
+
host.append(head);
|
|
489
|
+
fields(host, [['Merged', `${F.integration.merged} of ${F.integration.total} tasks`], ['Base', ig.base, true], ['Accepted at', ig.accepted, true]]);
|
|
490
|
+
if (list(ig.merged).length) {
|
|
491
|
+
const ul = el('ul', undefined, 'plain');
|
|
492
|
+
for (const m of ig.merged) { const li = el('li'); li.append(link(m.id, ctx.hash({ kind: 'task', id: m.id }), () => ctx.onSelect({ kind: 'task', id: m.id })), document.createTextNode(' · ' + time(m.at))); ul.append(li); }
|
|
493
|
+
section(host, 'Merge order').append(ul);
|
|
494
|
+
}
|
|
495
|
+
if (list(ig.integrations).length) {
|
|
496
|
+
const ul = el('ul', undefined, 'plain');
|
|
497
|
+
for (const i of ig.integrations) ul.append(el('li', `${i.name} · verification ${display(i.verification_status)}${i.final_review ? ' · with final review' : ''}`));
|
|
498
|
+
section(host, 'Integrate attempts').append(ul);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function depLinks(deps, ctx) {
|
|
502
|
+
const span = el('span', undefined, 'mono');
|
|
503
|
+
list(deps).forEach((id, i) => { if (i) span.append(document.createTextNode(', ')); span.append(link(id, ctx.hash({ kind: 'task', id }), () => ctx.onSelect({ kind: 'task', id }))); });
|
|
504
|
+
if (!span.hasChildNodes()) span.textContent = '—';
|
|
505
|
+
return span;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function render(host, ctx) {
|
|
509
|
+
host.replaceChildren();
|
|
510
|
+
const run = ctx.run, sel = ctx.sel;
|
|
511
|
+
if (!run) return;
|
|
512
|
+
const close = sel && !(run.kind === 'solo' && sel.kind === 'task') ? link('Close ✕', ctx.hash(null), () => ctx.onSelect(null)) : null;
|
|
513
|
+
if (close) { close.className = 'insp-close'; host.append(close); }
|
|
514
|
+
const tid = sel && (sel.kind === 'task' ? sel.id : sel.kind === 'execution' ? sel.task : null);
|
|
515
|
+
const task = tid ? list(run.tasks).find(t => t.id === tid) : run.kind === 'solo' && sel && sel.kind === 'execution' ? list(run.tasks)[0] : null;
|
|
516
|
+
if (!sel) return runView(host, ctx);
|
|
517
|
+
if (sel.kind === 'task') { if (!task) { muted(host, `Task ${sel.id} is not in this run.`); return; } return taskView(host, ctx, task); }
|
|
518
|
+
if (sel.kind === 'execution') return execView(host, ctx, task, sel);
|
|
519
|
+
if (sel.kind === 'final') return finalView(host, ctx, sel);
|
|
520
|
+
if (sel.kind === 'verification') return verificationView(host, ctx);
|
|
521
|
+
if (sel.kind === 'integration') return integrationView(host, ctx);
|
|
522
|
+
if (sel.kind === 'fractal') return fractalNodeView(host, ctx, sel);
|
|
523
|
+
return runView(host, ctx);
|
|
524
|
+
}
|
|
525
|
+
return { render, cache };
|
|
526
|
+
})();
|