@giamme/forge 0.8.0 → 0.10.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 +50 -0
- package/README.md +106 -1766
- package/SKILL.md +22 -3
- package/VERSION +1 -1
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +84 -0
- package/docs/commands.md +365 -0
- package/docs/dataset.md +29 -0
- package/docs/decomposed-runs.md +310 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +128 -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 +245 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +20 -0
- package/package.json +3 -2
- package/references/dataset.md +18 -1
- package/references/decompose.md +20 -2
- package/references/fractal.md +1 -1
- package/references/harnesses.md +18 -6
- package/references/solo.md +1 -1
- package/references/ui.md +27 -5
- package/scripts/forge-config.py +347 -0
- package/scripts/forge-control.py +645 -0
- package/scripts/forge-dispatch.sh +7 -1
- package/scripts/forge-issues.py +806 -0
- package/scripts/forge-lib-task.sh +234 -35
- package/scripts/forge-parallel-options.sh +3 -1
- package/scripts/forge-parallel.sh +3 -2
- package/scripts/forge-route.py +184 -27
- package/scripts/forge-runs-rm.py +840 -0
- package/scripts/forge-runs.py +139 -0
- package/scripts/forge-runtime.py +59 -6
- package/scripts/forge-schedule.py +227 -11
- package/scripts/forge-ui.py +85 -19
- package/scripts/forge-version.py +54 -12
- package/scripts/forge_dataset/__init__.py +77 -6
- package/scripts/forge_dataset/capture.py +5 -2
- package/scripts/forge_dataset/harvest.py +23 -1
- package/scripts/forge_dataset/importer.py +420 -18
- package/scripts/forge_fractal/__init__.py +2 -1
- package/scripts/forge_registry.py +168 -16
- package/ui/README.md +116 -23
- package/ui/THIRD_PARTY_NOTICES.md +108 -0
- package/ui/docs/api.md +283 -23
- package/ui/forge_ui/__main__.py +10 -2
- package/ui/forge_ui/control.py +575 -0
- package/ui/forge_ui/export.py +33 -9
- package/ui/forge_ui/fractal.py +1 -2
- package/ui/forge_ui/reader.py +121 -8
- package/ui/forge_ui/server.py +135 -5
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +358 -131
- package/ui/forge_ui/static/cloud3d.js +3 -2
- package/ui/forge_ui/static/cloudui.js +8 -5
- package/ui/forge_ui/static/controls.js +619 -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 +29 -10
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/findingsview.js +412 -0
- package/ui/forge_ui/static/graph.js +3 -3
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +31 -13
- package/ui/forge_ui/static/iso.js +70 -19
- package/ui/forge_ui/static/leaderboard.js +44 -48
- package/ui/forge_ui/static/panel.js +92 -36
- package/ui/forge_ui/static/rundelete.js +388 -0
- package/ui/forge_ui/static/runmodel.js +17 -1
- package/ui/forge_ui/static/scene3d.js +518 -43
- package/ui/forge_ui/static/search.js +2 -1
- package/ui/forge_ui/static/settings.js +399 -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 +1099 -469
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/static/uikit.js +161 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* forge-ui stage controls: the bank of keys over the 3D stage and the keyboard behind it. Fit (F), zoom (+ −),
|
|
2
|
+
projection (V: perspective, true isometric, top-down), reset (0), full screen, the legend of what the stage
|
|
3
|
+
draws now (L), the route (I) and the sheet of every key (?). scene3d.js says what each key does and what the
|
|
4
|
+
legend holds (ctl); this module only draws the bank, the legend and the sheet, with createElement and
|
|
5
|
+
textContent like the rest of the page. The keys work wherever focus is, while the stage is on screen and
|
|
6
|
+
nothing is being typed. */
|
|
7
|
+
export const PROJECTIONS = [['persp', 'Perspective', 'persp'], ['iso', 'Isometric', 'iso'], ['top', 'Top-down', 'top']];
|
|
8
|
+
// the sheet: [group, [[keys, what]]]
|
|
9
|
+
const SHEET = [
|
|
10
|
+
['Modules', [['1', 'Runs'], ['2', 'Inspector'], ['3', 'Tasks']]],
|
|
11
|
+
['Stage', [['F', 'Fit what is shown'], ['+ −', 'Zoom in, out'], ['V', 'Perspective, isometric, top-down'], ['0', 'Reset the view'],
|
|
12
|
+
['L', 'Legend'], ['I', 'Route: keep a chosen task’s route lit'], ['Shift F', 'Full screen'], ['?', 'These keys']]],
|
|
13
|
+
['With the stage focused', [['← →', 'Previous, next task (on the map: run)'], ['↑ ↓', 'On the map: previous, next repository'], ['Enter', 'On the map: open the run'],
|
|
14
|
+
['E', 'Plan or execution'], ['Esc', 'Clear the selection, then back to the map']]],
|
|
15
|
+
['In a diff', [['n p', 'Next, previous finding'], ['j k', 'Next, previous file']]],
|
|
16
|
+
['In a conversation', [['/', 'Search what it said and ran'], ['E', 'Next error']]],
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
|
|
20
|
+
const icon = name => window.ForgeIcons ? window.ForgeIcons.icon(name) : el('span');
|
|
21
|
+
const typing = t => t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName));
|
|
22
|
+
|
|
23
|
+
// ctl: { view() → 'run'|'atlas'|'cloud'|null, fit(), reset(), zoom(k), projection, setProjection(name),
|
|
24
|
+
// legend() → [{title, note?, rows: [{sw, label}]}], toggleRoute() }
|
|
25
|
+
export function createControls(host, ctl) {
|
|
26
|
+
const bank = el('div', 'stage-ctl'); bank.setAttribute('role', 'toolbar'); bank.setAttribute('aria-label', 'Stage view'); bank.setAttribute('aria-orientation', 'vertical');
|
|
27
|
+
const key = (name, label, keys, fn, ico = name) => {
|
|
28
|
+
const b = el('button', 'icon-btn'); b.type = 'button'; b.dataset.ctl = name;
|
|
29
|
+
b.setAttribute('aria-label', label); b.title = `${label} (${keys})`; b.setAttribute('aria-keyshortcuts', keys);
|
|
30
|
+
b.append(icon(ico)); b.addEventListener('click', fn); return b;
|
|
31
|
+
};
|
|
32
|
+
// momentary keys (fit, zoom, reset) have no lamp; a latching one lights while it is in (the projection bank, legend, full screen)
|
|
33
|
+
const latch = b => { b.classList.add('latch'); b.setAttribute('aria-pressed', 'false'); return b; };
|
|
34
|
+
const projs = el('div', 'stage-ctl-group'); projs.setAttribute('role', 'group'); projs.setAttribute('aria-label', 'Projection (V)');
|
|
35
|
+
for (const [name, label, ico] of PROJECTIONS) projs.append(latch(key('proj-' + name, label, 'V', () => { ctl.setProjection(name); sync(); }, ico)));
|
|
36
|
+
const full = latch(key('fullscreen', 'Full screen', 'Shift+F', () => fullscreen(), 'fullscreen'));
|
|
37
|
+
const leg = latch(key('legend', 'Legend', 'L', () => legend(), 'legend'));
|
|
38
|
+
leg.setAttribute('aria-expanded', 'false');
|
|
39
|
+
bank.append(
|
|
40
|
+
key('fit', 'Fit what is shown', 'F', () => ctl.fit()),
|
|
41
|
+
key('zoom-in', 'Zoom in', '+', () => ctl.zoom(1.35)),
|
|
42
|
+
key('zoom-out', 'Zoom out', '-', () => ctl.zoom(1 / 1.35)),
|
|
43
|
+
key('reset', 'Reset the view', '0', () => { ctl.reset(); sync(); }),
|
|
44
|
+
el('span', 'stage-ctl-rule'), projs, el('span', 'stage-ctl-rule'),
|
|
45
|
+
leg, full,
|
|
46
|
+
key('keys', 'Keys', '?', () => sheet(), 'keyboard'),
|
|
47
|
+
);
|
|
48
|
+
if (!document.fullscreenEnabled) full.hidden = true;
|
|
49
|
+
host.append(bank);
|
|
50
|
+
|
|
51
|
+
// the legend: what the stage draws now, rebuilt each time it opens and when the view changes under it
|
|
52
|
+
const panel = el('section', 'stage-legend'); panel.id = 'stage-legend'; panel.hidden = true; panel.setAttribute('aria-label', 'Legend');
|
|
53
|
+
leg.setAttribute('aria-controls', panel.id);
|
|
54
|
+
host.append(panel);
|
|
55
|
+
function fillLegend() {
|
|
56
|
+
const head = el('div', 'stage-legend-head'); head.append(el('h3', 'plate', 'Legend'));
|
|
57
|
+
const x = el('button', 'icon-btn'); x.type = 'button'; x.setAttribute('aria-label', 'Close the legend'); x.append(icon('close')); x.addEventListener('click', () => legend(false));
|
|
58
|
+
head.append(x);
|
|
59
|
+
const body = [head];
|
|
60
|
+
for (const sec of ctl.legend()) {
|
|
61
|
+
const s = el('div', 'lg-sec'); s.append(el('h4', 'plate lg-title', sec.title));
|
|
62
|
+
for (const r of sec.rows) { const row = el('div', 'lg-row'); row.append(swatch(r.sw), el('span', 'lg-label', r.label)); s.append(row); }
|
|
63
|
+
if (sec.note) s.append(el('p', 'lg-note', sec.note));
|
|
64
|
+
body.push(s);
|
|
65
|
+
}
|
|
66
|
+
panel.replaceChildren(...body);
|
|
67
|
+
}
|
|
68
|
+
function swatch(sw = {}) {
|
|
69
|
+
const s = el('span', 'lg-sw');
|
|
70
|
+
if (sw.lamp) { s.classList.add('lg-lamp'); s.append(el('i', 'lamp ' + sw.lamp)); }
|
|
71
|
+
else if (sw.route) { s.classList.add('lg-lamp'); s.append(el('i', 'lamp lamp-route')); }
|
|
72
|
+
else if (sw.sev) { s.classList.add('lg-sev', 'sev-' + sw.sev); if (sw.pale) s.classList.add('pale'); }
|
|
73
|
+
else if (sw.cable) s.classList.add('lg-cable', 'cable-' + sw.cable);
|
|
74
|
+
else if (sw.bead) s.classList.add('lg-bead');
|
|
75
|
+
else if (sw.ring) s.classList.add('lg-ring');
|
|
76
|
+
else if (sw.color) { s.classList.add('lg-color'); s.style.setProperty('--sw', sw.color); }
|
|
77
|
+
else if (sw.scale) { s.classList.add('lg-scale'); s.append(el('span', 'lg-min', sw.scale[0]), el('span', 'lg-bar'), el('span', 'lg-max', sw.scale[1])); }
|
|
78
|
+
else if (sw.height) { s.classList.add('lg-height'); s.append(el('i'), el('i'), el('i')); }
|
|
79
|
+
return s;
|
|
80
|
+
}
|
|
81
|
+
function legend(open = panel.hidden) {
|
|
82
|
+
if (open) fillLegend();
|
|
83
|
+
panel.hidden = !open; leg.setAttribute('aria-expanded', String(open)); leg.setAttribute('aria-pressed', String(open));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// the projection in use is the bank's lit key
|
|
87
|
+
function sync() {
|
|
88
|
+
for (const b of projs.children) b.setAttribute('aria-pressed', String(b.dataset.ctl === 'proj-' + ctl.projection));
|
|
89
|
+
full.setAttribute('aria-pressed', String(document.fullscreenElement === host));
|
|
90
|
+
const v = ctl.view(); bank.hidden = v !== 'run' && v !== 'atlas';
|
|
91
|
+
if (bank.hidden && !panel.hidden) legend(false);
|
|
92
|
+
else if (!panel.hidden) fillLegend();
|
|
93
|
+
}
|
|
94
|
+
function cycle() {
|
|
95
|
+
const i = PROJECTIONS.findIndex(x => x[0] === ctl.projection);
|
|
96
|
+
ctl.setProjection(PROJECTIONS[(i + 1) % PROJECTIONS.length][0]); sync();
|
|
97
|
+
}
|
|
98
|
+
function fullscreen() {
|
|
99
|
+
if (document.fullscreenElement === host) document.exitFullscreen();
|
|
100
|
+
else if (host.requestFullscreen) host.requestFullscreen().catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
const onFull = () => sync(); // the engine refits a framing the user has not moved
|
|
103
|
+
document.addEventListener('fullscreenchange', onFull);
|
|
104
|
+
|
|
105
|
+
// the sheet of every key, a dialog made the first time it opens
|
|
106
|
+
let dlg = null;
|
|
107
|
+
function sheet() {
|
|
108
|
+
if (!dlg) {
|
|
109
|
+
dlg = el('dialog', 'keys-sheet'); dlg.setAttribute('aria-label', 'Keys');
|
|
110
|
+
const head = el('div', 'keys-head'); head.append(el('span', 'plate', 'Keys'));
|
|
111
|
+
const x = el('button', 'icon-btn'); x.type = 'button'; x.setAttribute('aria-label', 'Close'); x.append(icon('close')); x.addEventListener('click', () => dlg.close());
|
|
112
|
+
head.append(x); dlg.append(head);
|
|
113
|
+
const grid = el('div', 'keys-grid');
|
|
114
|
+
for (const [title, rows] of SHEET) {
|
|
115
|
+
const g = el('section', 'keys-group'); g.append(el('h4', 'plate lg-title', title));
|
|
116
|
+
const dl = el('dl');
|
|
117
|
+
for (const [k, what] of rows) { const dt = el('dt'); for (const part of k.split(' ')) dt.append(el('kbd', null, part)); dl.append(dt, el('dd', null, what)); }
|
|
118
|
+
g.append(dl); grid.append(g);
|
|
119
|
+
}
|
|
120
|
+
dlg.append(grid);
|
|
121
|
+
dlg.addEventListener('click', e => { if (e.target === dlg) dlg.close(); }); // a click on the backdrop
|
|
122
|
+
document.body.append(dlg); // a modal dialog is drawn over a full-screen stage too
|
|
123
|
+
}
|
|
124
|
+
if (!dlg.open) dlg.showModal();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// the keys: anywhere on the page while the stage is on screen and nothing is being typed
|
|
128
|
+
const shown = () => host.isConnected && !host.hidden && host.getClientRects().length > 0 && !bank.hidden;
|
|
129
|
+
function onKey(e) {
|
|
130
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey || typing(e.target)) return;
|
|
131
|
+
if (document.querySelector('dialog[open]') && !(dlg && dlg.open)) return;
|
|
132
|
+
if (!shown()) return;
|
|
133
|
+
const k = e.key;
|
|
134
|
+
if (dlg && dlg.open) return;
|
|
135
|
+
if (k === '?') { e.preventDefault(); sheet(); return; }
|
|
136
|
+
if (e.repeat && !['+', '=', '-', '_'].includes(k)) return;
|
|
137
|
+
if (k === 'F' && e.shiftKey) { e.preventDefault(); fullscreen(); return; }
|
|
138
|
+
const act = { f: () => ctl.fit(), '+': () => ctl.zoom(1.35), '=': () => ctl.zoom(1.35), '-': () => ctl.zoom(1 / 1.35), _: () => ctl.zoom(1 / 1.35),
|
|
139
|
+
v: () => cycle(), 0: () => { ctl.reset(); sync(); }, l: () => legend(), i: () => { ctl.toggleRoute(); if (!panel.hidden) fillLegend(); } }[k.length === 1 ? k.toLowerCase() : k];
|
|
140
|
+
if (!act) return;
|
|
141
|
+
e.preventDefault(); act();
|
|
142
|
+
}
|
|
143
|
+
document.addEventListener('keydown', onKey);
|
|
144
|
+
sync();
|
|
145
|
+
return {
|
|
146
|
+
sync, bank, legend: panel,
|
|
147
|
+
get legendOpen() { return !panel.hidden; },
|
|
148
|
+
dispose() { document.removeEventListener('keydown', onKey); document.removeEventListener('fullscreenchange', onFull); bank.remove(); panel.remove(); if (dlg) dlg.remove(); },
|
|
149
|
+
};
|
|
150
|
+
}
|