@giamme/forge 0.8.0 → 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 +24 -0
- package/README.md +106 -1766
- package/SKILL.md +3 -2
- package/VERSION +1 -1
- 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 +5 -0
- package/package.json +3 -2
- package/references/decompose.md +1 -1
- package/references/fractal.md +1 -1
- package/references/harnesses.md +18 -6
- package/references/solo.md +1 -1
- package/scripts/forge-dispatch.sh +7 -1
- package/scripts/forge-runs.py +121 -0
- package/scripts/forge-runtime.py +59 -6
- package/scripts/forge-version.py +54 -12
- package/scripts/forge_dataset/harvest.py +1 -1
- package/scripts/forge_dataset/importer.py +1 -1
- package/scripts/forge_fractal/__init__.py +2 -1
- package/ui/README.md +80 -20
- package/ui/THIRD_PARTY_NOTICES.md +108 -0
- package/ui/docs/api.md +99 -17
- package/ui/forge_ui/export.py +16 -6
- package/ui/forge_ui/fractal.py +1 -2
- package/ui/forge_ui/reader.py +95 -7
- package/ui/forge_ui/server.py +10 -1
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +279 -124
- package/ui/forge_ui/static/cloud3d.js +3 -2
- package/ui/forge_ui/static/cloudui.js +8 -5
- 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/graph.js +3 -3
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +25 -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 +68 -32
- 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/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +924 -469
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
package/ui/forge_ui/fractal.py
CHANGED
|
@@ -47,8 +47,7 @@ def supported():
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def state_root():
|
|
50
|
-
"""forge_fractal.state_root()
|
|
51
|
-
resolves '' to its working directory)."""
|
|
50
|
+
"""forge_fractal.state_root(): an empty XDG_STATE_HOME counts as unset."""
|
|
52
51
|
base = os.environ.get('XDG_STATE_HOME') or os.path.join(os.path.expanduser('~'), '.local', 'state')
|
|
53
52
|
return os.path.join(os.path.realpath(os.path.expanduser(base)), 'forge', 'fractal')
|
|
54
53
|
|
package/ui/forge_ui/reader.py
CHANGED
|
@@ -11,7 +11,7 @@ import re
|
|
|
11
11
|
import stat
|
|
12
12
|
import urllib.parse
|
|
13
13
|
|
|
14
|
-
from forge_ui import diffs, executions, findings, fractal
|
|
14
|
+
from forge_ui import diffs, executions, findings, fractal, transcripts
|
|
15
15
|
from forge_ui.findings import verdict as _verdict
|
|
16
16
|
|
|
17
17
|
|
|
@@ -824,9 +824,8 @@ def task_detail(path, task_id, source='plan'):
|
|
|
824
824
|
reviews=reviews, truncated=reader.truncated, warnings=reader.warnings)
|
|
825
825
|
|
|
826
826
|
|
|
827
|
-
def
|
|
828
|
-
"""
|
|
829
|
-
reader = _Read(path)
|
|
827
|
+
def _execution(reader, name):
|
|
828
|
+
"""(Execution, owner, owner dir) of the execution `name` of the run, or None."""
|
|
830
829
|
kind = _kind(reader.path)
|
|
831
830
|
if kind is None or not executions.NAME.fullmatch(name or ''):
|
|
832
831
|
return None
|
|
@@ -856,8 +855,16 @@ def execution_detail(path, name, source='plan'):
|
|
|
856
855
|
execs = _final_review(reader, items[index], index == 0, _published(reader))[1]
|
|
857
856
|
owner, owner_dir = dict(kind='final_review', id=parts[0]), items[index][3]
|
|
858
857
|
item = next((candidate for candidate in execs if candidate['id'] == name), None)
|
|
859
|
-
if item is None
|
|
858
|
+
return None if item is None else (item, owner, owner_dir)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def execution_detail(path, name, source='plan'):
|
|
862
|
+
"""ExecutionDetail of one attempts/<role>-<uuid>/ dir of the run, or None."""
|
|
863
|
+
reader = _Read(path)
|
|
864
|
+
found = _execution(reader, name)
|
|
865
|
+
if found is None:
|
|
860
866
|
return None
|
|
867
|
+
item, owner, owner_dir = found
|
|
861
868
|
role, running = item['role'], item['state'] == 'running'
|
|
862
869
|
resolved = executions.resolved_of(reader, f'{item.rel}/{role}.resolved')
|
|
863
870
|
prompt = last = listed = None
|
|
@@ -880,6 +887,37 @@ def execution_detail(path, name, source='plan'):
|
|
|
880
887
|
return result
|
|
881
888
|
|
|
882
889
|
|
|
890
|
+
def execution_transcript(path, name, source='plan'):
|
|
891
|
+
"""ExecutionTranscript of a dwarf or qa execution: its agent's conversation, read from its `.log`; None
|
|
892
|
+
when there is no such execution or it is of another role."""
|
|
893
|
+
reader = _Read(path)
|
|
894
|
+
found = _execution(reader, name)
|
|
895
|
+
if found is None or found[0]['role'] not in ('dwarf', 'qa'):
|
|
896
|
+
return None
|
|
897
|
+
item, owner, owner_dir = found
|
|
898
|
+
role, running = item['role'], item['state'] == 'running'
|
|
899
|
+
# A finished execution's log is its copy; the owner dir's is the one being written, or the next one's.
|
|
900
|
+
rel = posixpath.join(owner_dir, role + '.log') if running else f'{item.rel}/{role}.log'
|
|
901
|
+
log = None
|
|
902
|
+
try:
|
|
903
|
+
real = Path(os.path.realpath(reader.path / rel))
|
|
904
|
+
if executions.inside(reader.path, str(real)) and stat.S_ISREG(real.stat().st_mode):
|
|
905
|
+
log = real
|
|
906
|
+
elif real.exists():
|
|
907
|
+
reader.warn(rel, 'outside the run dir or not a regular file')
|
|
908
|
+
except (OSError, ValueError):
|
|
909
|
+
pass
|
|
910
|
+
resolved = executions.resolved_of(reader, f'{item.rel}/{role}.resolved')
|
|
911
|
+
if resolved is None and running:
|
|
912
|
+
resolved = executions.parse_resolved(reader.text(posixpath.join(owner_dir, role + '.resolved'))) or None
|
|
913
|
+
model = item['model'] or {}
|
|
914
|
+
result = transcripts.transcript(log, model.get('harness'), running)
|
|
915
|
+
result.update(run=run_key(reader.path), execution=name, role=role, state=item['state'], attempt=item['attempt'],
|
|
916
|
+
spec=model.get('spec'), harness=model.get('harness'), repo=(resolved or {}).get('repo') or None,
|
|
917
|
+
owner=owner, warnings=reader.warnings + result['warnings'])
|
|
918
|
+
return result
|
|
919
|
+
|
|
920
|
+
|
|
883
921
|
def final_review_detail(path, name, source='plan'):
|
|
884
922
|
"""FinalReviewDetail of final-review-<e>-<p> or integrate-<e>-<p>, or None."""
|
|
885
923
|
reader = _Read(path)
|
|
@@ -1028,8 +1066,8 @@ def _diff_detail(reader, key, origin, sources, loaded, reason, reviews):
|
|
|
1028
1066
|
found = []
|
|
1029
1067
|
for review in reviews:
|
|
1030
1068
|
text = reader.text(review['last']) if review['last'] else None
|
|
1031
|
-
for item in findings.listing(text) or []:
|
|
1032
|
-
found.append(dict(review={key: review[key] for key in ('kind', 'execution', 'attempt', 'spec')},
|
|
1069
|
+
for index, item in enumerate(findings.listing(text) or []):
|
|
1070
|
+
found.append(dict(id=_finding_id(review, index), review={key: review[key] for key in ('kind', 'execution', 'attempt', 'spec')},
|
|
1033
1071
|
severity=item['severity'], status=item['status'], location=item['location'],
|
|
1034
1072
|
summary=item['summary'], text=item['text']))
|
|
1035
1073
|
return dict(run=key, source=origin, sources=sources, available=loaded is not None,
|
|
@@ -1141,6 +1179,50 @@ def final_review_diff(path, name, source='plan'):
|
|
|
1141
1179
|
_NOT_INLINED if prompted else 'no review prompt yet', [review])
|
|
1142
1180
|
|
|
1143
1181
|
|
|
1182
|
+
# --- findings: every review's findings of a run, for the stage's marks ---------------------
|
|
1183
|
+
|
|
1184
|
+
def _finding_id(review, index):
|
|
1185
|
+
"""A Finding's id: its review's execution (its kind when it has none) and its place in that reply."""
|
|
1186
|
+
return f"{review['execution'] or review['kind']}:{index}"
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
def _findings_of(reader, review):
|
|
1190
|
+
listed = findings.listing(reader.text(review['last'])) if review['last'] else None
|
|
1191
|
+
return [dict(id=_finding_id(review, index), severity=item['severity'], status=item['status'],
|
|
1192
|
+
location=item['location'], summary=item['summary'])
|
|
1193
|
+
for index, item in enumerate(listed or [])]
|
|
1194
|
+
|
|
1195
|
+
|
|
1196
|
+
def run_findings(path, source='plan'):
|
|
1197
|
+
"""RunFindings: each review's Findings, without their text, by task and by final review; None if no run."""
|
|
1198
|
+
reader = _Read(path)
|
|
1199
|
+
kind = _kind(reader.path)
|
|
1200
|
+
if kind is None:
|
|
1201
|
+
return None
|
|
1202
|
+
keys = ('kind', 'execution', 'attempt', 'spec', 'verdict')
|
|
1203
|
+
tasks = []
|
|
1204
|
+
for task_id in ['solo'] if kind == 'solo' else [task['id'] for task in _tasks(reader)]:
|
|
1205
|
+
ctx = _diff_task(reader, task_id)
|
|
1206
|
+
reviews = [dict({key: review[key] for key in keys}, findings=found)
|
|
1207
|
+
for review in (ctx['reviews'] if ctx else []) for found in [_findings_of(reader, review)] if found]
|
|
1208
|
+
if reviews:
|
|
1209
|
+
tasks.append(dict(id=task_id, reviews=reviews))
|
|
1210
|
+
finals = []
|
|
1211
|
+
if kind == 'plan':
|
|
1212
|
+
items, published = executions.review_dirs(reader)[0], _published(reader)
|
|
1213
|
+
for index, item in enumerate(items[:_MAX_REVIEWS]):
|
|
1214
|
+
summary = _final_review(reader, item, index == 0, published)[0]
|
|
1215
|
+
# the same review final_review_diff pins, so a Finding's id opens it there
|
|
1216
|
+
review = _diff_review('final', None, None, summary['spec'], summary['verdict'], item[3] + '/qa.last', [],
|
|
1217
|
+
summary['execution'])
|
|
1218
|
+
found = _findings_of(reader, review)
|
|
1219
|
+
if found:
|
|
1220
|
+
finals.append(dict(name=item[0], execution=summary['execution'], spec=summary['spec'],
|
|
1221
|
+
verdict=summary['verdict'], findings=found))
|
|
1222
|
+
return dict(run=run_key(reader.path), available=findings.available(), tasks=tasks, final_reviews=finals,
|
|
1223
|
+
truncated=reader.truncated, warnings=reader.warnings)
|
|
1224
|
+
|
|
1225
|
+
|
|
1144
1226
|
# --- files: who planned, changed, drifted into or conflicted on each file --------------------
|
|
1145
1227
|
|
|
1146
1228
|
_MAX_FILES, _MAX_TASKS, _MAX_OVERLAPS = 2000, 300, 5000
|
|
@@ -1316,12 +1398,18 @@ class Catalog:
|
|
|
1316
1398
|
def execution_diff(self, key, name):
|
|
1317
1399
|
return self._find(key, execution_diff, name)
|
|
1318
1400
|
|
|
1401
|
+
def transcript(self, key, name):
|
|
1402
|
+
return self._find(key, execution_transcript, name)
|
|
1403
|
+
|
|
1319
1404
|
def final_review_diff(self, key, name):
|
|
1320
1405
|
return self._find(key, final_review_diff, name)
|
|
1321
1406
|
|
|
1322
1407
|
def files(self, key):
|
|
1323
1408
|
return self._find(key, files_detail)
|
|
1324
1409
|
|
|
1410
|
+
def findings(self, key):
|
|
1411
|
+
return self._find(key, run_findings)
|
|
1412
|
+
|
|
1325
1413
|
def fractal(self, key, task_id):
|
|
1326
1414
|
return self._find(key, fractal_detail, task_id)
|
|
1327
1415
|
|
package/ui/forge_ui/server.py
CHANGED
|
@@ -28,16 +28,19 @@ _EXEC_PATH = re.compile(r'/api/runs/([0-9a-f]{12})/executions/((?:task|dwarf|qa|
|
|
|
28
28
|
_REVIEW_PATH = re.compile(r'/api/runs/([0-9a-f]{12})/final-reviews/((?:final-review|integrate)-[0-9]+-[0-9]+)')
|
|
29
29
|
_TASK_DIFF_PATH = re.compile(_TASK_PATH.pattern + r'/diff(?:/(current|prev|committed|existing))?')
|
|
30
30
|
_EXEC_DIFF_PATH = re.compile(_EXEC_PATH.pattern + '/diff')
|
|
31
|
+
_TRANSCRIPT_PATH = re.compile(_EXEC_PATH.pattern + '/transcript')
|
|
31
32
|
_REVIEW_DIFF_PATH = re.compile(_REVIEW_PATH.pattern + '/diff')
|
|
32
33
|
_FILES_PATH = re.compile(r'/api/runs/([0-9a-f]{12})/files')
|
|
34
|
+
_FINDINGS_PATH = re.compile(r'/api/runs/([0-9a-f]{12})/findings')
|
|
33
35
|
_EXPORT_PATH = re.compile(r'/api/runs/([0-9a-f]{12})/export')
|
|
34
36
|
_FRACTAL_PATH = re.compile(_TASK_PATH.pattern + r'/fractal(?:/nodes/([A-Za-z0-9][A-Za-z0-9_-]{0,100})(/diff)?)?')
|
|
35
|
-
_STATIC_PATH = re.compile(r'/([A-Za-z0-9_.-]+\.(?:html|js|css|svg))')
|
|
37
|
+
_STATIC_PATH = re.compile(r'/([A-Za-z0-9_.-]+\.(?:html|js|css|svg|woff2))')
|
|
36
38
|
_TYPES = {
|
|
37
39
|
'html': 'text/html; charset=utf-8',
|
|
38
40
|
'js': 'text/javascript; charset=utf-8',
|
|
39
41
|
'css': 'text/css; charset=utf-8',
|
|
40
42
|
'svg': 'image/svg+xml',
|
|
43
|
+
'woff2': 'font/woff2',
|
|
41
44
|
}
|
|
42
45
|
_JSON = 'application/json; charset=utf-8'
|
|
43
46
|
_CSP = ("default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; "
|
|
@@ -106,9 +109,15 @@ def api_get(catalog, path):
|
|
|
106
109
|
match = _REVIEW_DIFF_PATH.fullmatch(path)
|
|
107
110
|
if match:
|
|
108
111
|
return catalog.final_review_diff(*match.groups())
|
|
112
|
+
match = _TRANSCRIPT_PATH.fullmatch(path)
|
|
113
|
+
if match:
|
|
114
|
+
return catalog.transcript(*match.groups())
|
|
109
115
|
match = _FILES_PATH.fullmatch(path)
|
|
110
116
|
if match:
|
|
111
117
|
return catalog.files(match.group(1))
|
|
118
|
+
match = _FINDINGS_PATH.fullmatch(path)
|
|
119
|
+
if match:
|
|
120
|
+
return catalog.findings(match.group(1))
|
|
112
121
|
match = _FRACTAL_PATH.fullmatch(path)
|
|
113
122
|
if match:
|
|
114
123
|
key, task_id, node, diff = match.group(1), _task_id(match.group(2)), match.group(3), match.group(4)
|
|
Binary file
|