@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.
- package/CHANGELOG.md +205 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/SKILL.md +123 -0
- package/VERSION +1 -0
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +69 -0
- package/docs/commands.md +315 -0
- package/docs/dataset.md +20 -0
- package/docs/decomposed-runs.md +268 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +92 -0
- package/docs/fractal.md +181 -0
- package/docs/install.md +160 -0
- package/docs/memory.md +156 -0
- package/docs/models.md +118 -0
- package/docs/reviews.md +231 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +79 -0
- package/package.json +27 -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 +365 -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 +671 -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-runs.py +121 -0
- package/scripts/forge-runtime.py +586 -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 +240 -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 +84 -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 +347 -0
- package/ui/THIRD_PARTY_NOTICES.md +177 -0
- package/ui/docs/api.md +970 -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 +214 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +582 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1424 -0
- package/ui/forge_ui/server.py +312 -0
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +1079 -0
- package/ui/forge_ui/static/cloud3d.js +647 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +397 -0
- package/ui/forge_ui/static/convo.js +295 -0
- package/ui/forge_ui/static/datatable.js +232 -0
- package/ui/forge_ui/static/diffview.js +235 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +67 -0
- package/ui/forge_ui/static/iso.js +816 -0
- package/ui/forge_ui/static/leaderboard.js +311 -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 +562 -0
- package/ui/forge_ui/static/runmodel.js +667 -0
- package/ui/forge_ui/static/scene3d.js +1471 -0
- package/ui/forge_ui/static/search.js +120 -0
- package/ui/forge_ui/static/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +1073 -0
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/* forge-ui notifications: a run that finishes or fails, a task that fails, a final review's verdict.
|
|
2
|
+
Seen on every poll of the run list (RM.statusEvents); shown in the bell's log, as an unread count in the
|
|
3
|
+
tab title and a dot on its icon, and, once allowed from the bell, as browser notifications while this tab
|
|
4
|
+
is in the background. Several open tabs notify once. All values enter the DOM as text.
|
|
5
|
+
window.ForgeNotify = { init(ctx), observe(runs), open(), background() }, ctx = { go(href) }. */
|
|
6
|
+
window.ForgeNotify = (() => {
|
|
7
|
+
'use strict';
|
|
8
|
+
const RM = window.ForgeRunModel;
|
|
9
|
+
const PREFS = 'forge-ui-notify', LOG = 'forge-ui-notify-log', SEEN = 'forge-ui-notify-seen';
|
|
10
|
+
const LOG_CAP = 100, SEEN_TTL = 600;
|
|
11
|
+
const KINDS = [['run', 'Runs that finish or fail'], ['task', 'Tasks that fail'], ['review', 'Final review verdicts']];
|
|
12
|
+
const kindOf = e => e.kind === 'task-failed' ? 'task' : e.kind === 'review' ? 'review' : 'run';
|
|
13
|
+
const get = (k, d) => { try { const v = JSON.parse(localStorage.getItem(k) || 'null'); return v === null ? d : v; } catch (_) { return d; } };
|
|
14
|
+
const set = (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch (_) { /* private mode: this tab only */ } };
|
|
15
|
+
function el(tag, text, cls) {
|
|
16
|
+
const n = document.createElement(tag);
|
|
17
|
+
if (text !== undefined && text !== null) n.textContent = String(text);
|
|
18
|
+
if (cls) n.className = cls;
|
|
19
|
+
return n;
|
|
20
|
+
}
|
|
21
|
+
function words(e) {
|
|
22
|
+
const where = [e.repo, e.run_id].filter(Boolean).join(' · ');
|
|
23
|
+
if (e.kind === 'run-done') return { title: 'Run finished', body: e.goal, foot: where, bad: false };
|
|
24
|
+
if (e.kind === 'run-failed') return { title: 'Run ended with failures', body: e.goal, foot: where, bad: true };
|
|
25
|
+
if (e.kind === 'task-failed') return { title: `Task ${e.task} ${String(e.status).toLowerCase()}`, body: e.title ? `${e.title} — ${e.goal}` : e.goal, foot: where, bad: true };
|
|
26
|
+
return { title: `Final review ${e.verdict === 'PASS' ? 'passed' : 'failed'}`, body: e.goal, foot: where, bad: e.verdict !== 'PASS' };
|
|
27
|
+
}
|
|
28
|
+
const supported = () => typeof Notification === 'function';
|
|
29
|
+
|
|
30
|
+
let ctx = {}, prev = null, prefs = null, bell = null, badge = null, pop = null, toast = null, toastTimer = 0, icon = null, baseTitle = document.title || 'forge-ui';
|
|
31
|
+
const log = () => { const v = get(LOG, []); return Array.isArray(v) ? v : []; };
|
|
32
|
+
const unread = () => log().filter(x => !x.read);
|
|
33
|
+
|
|
34
|
+
/* ---------- the bell and its popover ---------- */
|
|
35
|
+
function init(c) {
|
|
36
|
+
ctx = c || {};
|
|
37
|
+
prefs = Object.assign({ kinds: { run: true, task: true, review: true }, browser: false }, get(PREFS, {}));
|
|
38
|
+
bell = document.getElementById('bell'); badge = bell && bell.querySelector('.bell-count');
|
|
39
|
+
icon = document.querySelector('link[rel=icon]');
|
|
40
|
+
pop = el('div', undefined, 'notify-pop'); pop.id = 'notify-pop'; pop.hidden = true; pop.setAttribute('role', 'dialog'); pop.setAttribute('aria-label', 'Notifications');
|
|
41
|
+
toast = el('div', undefined, 'notify-toast'); toast.hidden = true; toast.setAttribute('role', 'status');
|
|
42
|
+
document.body.append(pop, toast);
|
|
43
|
+
if (bell) {
|
|
44
|
+
bell.setAttribute('aria-controls', 'notify-pop'); bell.setAttribute('aria-expanded', 'false');
|
|
45
|
+
bell.addEventListener('click', e => { e.stopPropagation(); if (pop.hidden) open(); else close(); });
|
|
46
|
+
}
|
|
47
|
+
document.addEventListener('click', e => { if (!pop.hidden && !pop.contains(e.target) && e.target !== bell) close(); });
|
|
48
|
+
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !pop.hidden) { close(); bell && bell.focus(); } });
|
|
49
|
+
// another tab read, cleared or logged something: follow it
|
|
50
|
+
window.addEventListener('storage', e => { if (e.key === LOG || e.key === PREFS) { if (e.key === PREFS) prefs = Object.assign(prefs, get(PREFS, {})); sync(); if (!pop.hidden) renderPop(); } });
|
|
51
|
+
sync();
|
|
52
|
+
}
|
|
53
|
+
function open() {
|
|
54
|
+
renderPop(); pop.hidden = false; bell && bell.setAttribute('aria-expanded', 'true');
|
|
55
|
+
const r = bell && bell.getBoundingClientRect();
|
|
56
|
+
if (r) { pop.style.top = `${Math.round(r.bottom + 8)}px`; pop.style.right = `${Math.max(8, Math.round(window.innerWidth - r.right))}px`; }
|
|
57
|
+
const first = pop.querySelector('button, input, a'); if (first) first.focus({ preventScroll: true });
|
|
58
|
+
markRead();
|
|
59
|
+
}
|
|
60
|
+
function close() { pop.hidden = true; bell && bell.setAttribute('aria-expanded', 'false'); }
|
|
61
|
+
function savePrefs() { set(PREFS, prefs); }
|
|
62
|
+
function renderPop() {
|
|
63
|
+
pop.replaceChildren();
|
|
64
|
+
const head = el('div', undefined, 'notify-head'); head.append(el('h2', 'Notifications'));
|
|
65
|
+
const clear = el('button', 'Clear', 'linkish'); clear.type = 'button';
|
|
66
|
+
clear.addEventListener('click', () => { set(LOG, []); sync(); renderPop(); });
|
|
67
|
+
head.append(clear); pop.append(head);
|
|
68
|
+
const kinds = el('fieldset', undefined, 'notify-kinds'); kinds.append(el('legend', 'Tell me about'));
|
|
69
|
+
for (const [k, label] of KINDS) {
|
|
70
|
+
const l = el('label'), c = el('input'); c.type = 'checkbox'; c.checked = !!prefs.kinds[k];
|
|
71
|
+
c.addEventListener('change', () => { prefs.kinds[k] = c.checked; savePrefs(); });
|
|
72
|
+
l.append(c, document.createTextNode(' ' + label)); kinds.append(l);
|
|
73
|
+
}
|
|
74
|
+
pop.append(kinds);
|
|
75
|
+
const br = el('div', undefined, 'notify-browser');
|
|
76
|
+
if (!supported()) br.append(el('p', 'This browser has no notifications: changes show here and in the tab title.', 'muted'));
|
|
77
|
+
else if (Notification.permission === 'denied') br.append(el('p', 'Browser notifications are blocked for this page in the browser\'s site settings.', 'muted'));
|
|
78
|
+
else if (prefs.browser && Notification.permission === 'granted') {
|
|
79
|
+
br.append(el('p', 'Browser notifications are on while this tab is in the background; the run list is then checked every 30 seconds.', 'muted'));
|
|
80
|
+
const off = el('button', 'Turn off', ''); off.type = 'button'; off.addEventListener('click', () => { prefs.browser = false; savePrefs(); renderPop(); }); br.append(off);
|
|
81
|
+
} else {
|
|
82
|
+
const on = el('button', 'Enable browser notifications', 'primary'); on.type = 'button';
|
|
83
|
+
on.addEventListener('click', async () => {
|
|
84
|
+
let p = Notification.permission;
|
|
85
|
+
try { if (p === 'default') p = await Notification.requestPermission(); } catch (_) { p = Notification.permission; }
|
|
86
|
+
prefs.browser = p === 'granted'; savePrefs(); renderPop();
|
|
87
|
+
});
|
|
88
|
+
br.append(on, el('p', 'Only while this tab is open; nothing leaves this machine.', 'muted'));
|
|
89
|
+
}
|
|
90
|
+
pop.append(br);
|
|
91
|
+
const items = log();
|
|
92
|
+
const ul = el('ul', undefined, 'notify-log');
|
|
93
|
+
if (!items.length) ul.append(el('li', 'Nothing yet. Runs are watched while this page is open.', 'muted'));
|
|
94
|
+
for (const x of items) {
|
|
95
|
+
const li = el('li', undefined, 'notify-item' + (x.read ? '' : ' unread') + (x.bad ? ' bad' : ''));
|
|
96
|
+
const a = el('a', x.title); a.href = x.href; a.addEventListener('click', close);
|
|
97
|
+
li.append(a, el('span', x.body, 'notify-body'), el('span', `${x.foot ? x.foot + ' · ' : ''}${new Date(x.at * 1000).toLocaleString()}`, 'muted notify-when'));
|
|
98
|
+
ul.append(li);
|
|
99
|
+
}
|
|
100
|
+
pop.append(ul);
|
|
101
|
+
}
|
|
102
|
+
function markRead() { const items = log(); if (items.some(x => !x.read)) { set(LOG, items.map(x => ({ ...x, read: true }))); sync(); } }
|
|
103
|
+
|
|
104
|
+
/* ---------- the unread count: in the bell, the title and the tab's icon ---------- */
|
|
105
|
+
function sync() {
|
|
106
|
+
const u = unread(), n = u.length;
|
|
107
|
+
if (badge) { badge.textContent = n ? String(n > 99 ? '99+' : n) : ''; badge.hidden = !n; }
|
|
108
|
+
if (bell) bell.setAttribute('aria-label', n ? `Notifications, ${n} unread` : 'Notifications');
|
|
109
|
+
document.title = (n ? `(${n}) ` : '') + baseTitle;
|
|
110
|
+
favicon(n ? (u.some(x => x.bad) ? '#d23b45' : '#2f7de1') : null);
|
|
111
|
+
}
|
|
112
|
+
let iconImg = null;
|
|
113
|
+
function favicon(dot) {
|
|
114
|
+
if (!icon) return;
|
|
115
|
+
if (!dot) { icon.href = 'favicon.svg'; return; }
|
|
116
|
+
const draw = () => {
|
|
117
|
+
const c = document.createElement('canvas'); c.width = c.height = 64;
|
|
118
|
+
const x = c.getContext('2d'); if (!x) return;
|
|
119
|
+
if (iconImg && iconImg.complete && iconImg.naturalWidth) x.drawImage(iconImg, 0, 0, 64, 64);
|
|
120
|
+
x.beginPath(); x.arc(48, 16, 14, 0, Math.PI * 2); x.fillStyle = dot; x.fill(); x.lineWidth = 4; x.strokeStyle = '#ffffff'; x.stroke();
|
|
121
|
+
try { icon.href = c.toDataURL('image/png'); } catch (_) { /* keep the plain icon */ }
|
|
122
|
+
};
|
|
123
|
+
if (!iconImg) { iconImg = new Image(); iconImg.onload = draw; iconImg.src = 'favicon.svg'; }
|
|
124
|
+
draw();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ---------- each poll: what changed, told once across tabs ---------- */
|
|
128
|
+
function claim(ids) {
|
|
129
|
+
const now = Date.now() / 1000, seen = get(SEEN, {}), fresh = [];
|
|
130
|
+
for (const [k, t] of Object.entries(seen)) if (now - t > SEEN_TTL) delete seen[k];
|
|
131
|
+
for (const id of ids) if (!seen[id]) { seen[id] = now; fresh.push(id); }
|
|
132
|
+
set(SEEN, seen);
|
|
133
|
+
return new Set(fresh);
|
|
134
|
+
}
|
|
135
|
+
function deliver(events) {
|
|
136
|
+
const run = () => {
|
|
137
|
+
const mine = claim(events.map(e => e.id));
|
|
138
|
+
const now = Date.now() / 1000, add = [];
|
|
139
|
+
for (const e of events) {
|
|
140
|
+
if (!mine.has(e.id)) continue;
|
|
141
|
+
const w = words(e);
|
|
142
|
+
add.push({ id: e.id, kind: e.kind, href: e.href, title: w.title, body: w.body, foot: w.foot, bad: w.bad, at: now, read: false });
|
|
143
|
+
if (prefs.browser && supported() && Notification.permission === 'granted' && (document.hidden || !document.hasFocus())) {
|
|
144
|
+
try {
|
|
145
|
+
const n = new Notification(w.title, { body: `${w.body}\n${w.foot}`, tag: e.id, icon: 'favicon.svg' });
|
|
146
|
+
n.onclick = () => { window.focus(); if (ctx.go) ctx.go(e.href); n.close(); };
|
|
147
|
+
} catch (_) { /* some platforms only allow notifications from a service worker */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!add.length) return;
|
|
151
|
+
set(LOG, add.reverse().concat(log()).slice(0, LOG_CAP));
|
|
152
|
+
sync();
|
|
153
|
+
if (!pop.hidden) renderPop();
|
|
154
|
+
if (!document.hidden) showToast(add[0], add.length);
|
|
155
|
+
const live = document.getElementById('live'); if (live) live.textContent = add.map(x => `${x.title}: ${x.body}`).slice(0, 3).join('; ');
|
|
156
|
+
};
|
|
157
|
+
// one tab at a time decides what is new, so two tabs do not both notify
|
|
158
|
+
if (navigator.locks && navigator.locks.request) navigator.locks.request('forge-ui-notify', run).catch(run); else run();
|
|
159
|
+
}
|
|
160
|
+
function showToast(x, n) {
|
|
161
|
+
clearTimeout(toastTimer);
|
|
162
|
+
const a = el('a', x.title); a.href = x.href;
|
|
163
|
+
toast.replaceChildren(a, el('span', x.body, 'notify-body'));
|
|
164
|
+
if (n > 1) toast.append(el('span', `and ${n - 1} more in the bell`, 'muted'));
|
|
165
|
+
toast.classList.toggle('bad', !!x.bad); toast.hidden = false;
|
|
166
|
+
toastTimer = setTimeout(() => { toast.hidden = true; }, 7000);
|
|
167
|
+
}
|
|
168
|
+
function observe(runs) {
|
|
169
|
+
if (!prefs) return [];
|
|
170
|
+
const next = Array.isArray(runs) ? runs.map(r => ({ key: r.key, run_id: r.run_id, repo: r.repo, goal: r.goal, updated: r.updated, counts: r.counts, final_review: r.final_review, task_statuses: r.task_statuses })) : [];
|
|
171
|
+
const events = RM.statusEvents(prev, next, { now: Date.now() / 1000 }).filter(e => prefs.kinds[kindOf(e)]);
|
|
172
|
+
prev = next;
|
|
173
|
+
if (events.length) deliver(events);
|
|
174
|
+
return events;
|
|
175
|
+
}
|
|
176
|
+
// poll the run list while the tab is hidden: only when there is someone to tell
|
|
177
|
+
const background = () => !!(prefs && prefs.browser && supported() && Notification.permission === 'granted');
|
|
178
|
+
return { init, observe, open, background };
|
|
179
|
+
})();
|