@giamme/forge 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -2
  4. package/SKILL.md +123 -0
  5. package/VERSION +1 -0
  6. package/docs/README.md +26 -0
  7. package/docs/add-ons.md +33 -0
  8. package/docs/artifacts.md +69 -0
  9. package/docs/commands.md +315 -0
  10. package/docs/dataset.md +20 -0
  11. package/docs/decomposed-runs.md +268 -0
  12. package/docs/development.md +95 -0
  13. package/docs/forge-ui.md +92 -0
  14. package/docs/fractal.md +181 -0
  15. package/docs/install.md +160 -0
  16. package/docs/memory.md +156 -0
  17. package/docs/models.md +118 -0
  18. package/docs/reviews.md +231 -0
  19. package/docs/safety.md +55 -0
  20. package/docs/troubleshooting.md +54 -0
  21. package/forge +79 -0
  22. package/package.json +27 -4
  23. package/references/dataset.md +329 -0
  24. package/references/decompose.md +667 -0
  25. package/references/fractal-dashboard.md +21 -0
  26. package/references/fractal.md +307 -0
  27. package/references/harnesses.md +365 -0
  28. package/references/jev.md +865 -0
  29. package/references/memory.md +228 -0
  30. package/references/planning.md +42 -0
  31. package/references/ripwire.md +47 -0
  32. package/references/runtime.md +64 -0
  33. package/references/solo.md +113 -0
  34. package/references/ui.md +112 -0
  35. package/registry.tsv +81 -0
  36. package/scripts/forge-artifact.sh +78 -0
  37. package/scripts/forge-contract.py +430 -0
  38. package/scripts/forge-dataset.py +6 -0
  39. package/scripts/forge-dispatch.sh +671 -0
  40. package/scripts/forge-fail-first.py +195 -0
  41. package/scripts/forge-fractal-dispatch.sh +4 -0
  42. package/scripts/forge-fractal-options.sh +54 -0
  43. package/scripts/forge-fractal.py +6 -0
  44. package/scripts/forge-guard.py +266 -0
  45. package/scripts/forge-install-fractal.sh +4 -0
  46. package/scripts/forge-install-ripwire.sh +52 -0
  47. package/scripts/forge-install.sh +135 -0
  48. package/scripts/forge-jev-options.sh +259 -0
  49. package/scripts/forge-jev.py +6 -0
  50. package/scripts/forge-lib-plans.sh +785 -0
  51. package/scripts/forge-lib-review.sh +494 -0
  52. package/scripts/forge-lib-task.sh +302 -0
  53. package/scripts/forge-lib-verify.sh +30 -0
  54. package/scripts/forge-memory.sh +575 -0
  55. package/scripts/forge-metrics.sh +39 -0
  56. package/scripts/forge-parallel-options.sh +76 -0
  57. package/scripts/forge-parallel.sh +1717 -0
  58. package/scripts/forge-plan-check.py +118 -0
  59. package/scripts/forge-prompt.py +45 -0
  60. package/scripts/forge-ripwire.py +136 -0
  61. package/scripts/forge-route.py +344 -0
  62. package/scripts/forge-runs.py +121 -0
  63. package/scripts/forge-runtime.py +586 -0
  64. package/scripts/forge-schedule.py +312 -0
  65. package/scripts/forge-shadow.py +305 -0
  66. package/scripts/forge-solo.sh +347 -0
  67. package/scripts/forge-ui-options.sh +42 -0
  68. package/scripts/forge-ui.py +435 -0
  69. package/scripts/forge-update.sh +71 -0
  70. package/scripts/forge-verify-coverage.py +956 -0
  71. package/scripts/forge-version.py +240 -0
  72. package/scripts/forge_dataset/__init__.py +439 -0
  73. package/scripts/forge_dataset/capture.py +330 -0
  74. package/scripts/forge_dataset/checks.py +109 -0
  75. package/scripts/forge_dataset/cli.py +233 -0
  76. package/scripts/forge_dataset/export.py +1009 -0
  77. package/scripts/forge_dataset/harvest.py +716 -0
  78. package/scripts/forge_dataset/importer.py +412 -0
  79. package/scripts/forge_dataset/outcomes.py +275 -0
  80. package/scripts/forge_dataset/routing.py +278 -0
  81. package/scripts/forge_fractal/__init__.py +84 -0
  82. package/scripts/forge_fractal/bridge.py +46 -0
  83. package/scripts/forge_fractal/cli.py +202 -0
  84. package/scripts/forge_fractal/dashboard.css +138 -0
  85. package/scripts/forge_fractal/dashboard.html +41 -0
  86. package/scripts/forge_fractal/dashboard.js +687 -0
  87. package/scripts/forge_fractal/decomposition.py +262 -0
  88. package/scripts/forge_fractal/execution.py +659 -0
  89. package/scripts/forge_fractal/inspection.py +261 -0
  90. package/scripts/forge_fractal/install.py +158 -0
  91. package/scripts/forge_fractal/invocation.py +94 -0
  92. package/scripts/forge_fractal/pipeline.py +124 -0
  93. package/scripts/forge_fractal/selection.py +179 -0
  94. package/scripts/forge_jev/__init__.py +376 -0
  95. package/scripts/forge_jev/backtest.py +322 -0
  96. package/scripts/forge_jev/cli.py +980 -0
  97. package/scripts/forge_jev/client.py +418 -0
  98. package/scripts/forge_jev/corpus.py +156 -0
  99. package/scripts/forge_jev/coupling.py +244 -0
  100. package/scripts/forge_jev/features.py +24 -0
  101. package/scripts/forge_jev/gates.py +100 -0
  102. package/scripts/forge_jev/memory.py +163 -0
  103. package/scripts/forge_jev/outcome.py +49 -0
  104. package/scripts/forge_jev/predict.py +204 -0
  105. package/scripts/forge_jev/questions.py +661 -0
  106. package/scripts/forge_jev/review.py +73 -0
  107. package/scripts/forge_jev/routing.py +271 -0
  108. package/scripts/forge_jev/subset.py +123 -0
  109. package/scripts/forge_jev/verify.py +453 -0
  110. package/scripts/forge_registry.py +140 -0
  111. package/ui/README.md +347 -0
  112. package/ui/THIRD_PARTY_NOTICES.md +177 -0
  113. package/ui/docs/api.md +970 -0
  114. package/ui/forge_ui/__init__.py +20 -0
  115. package/ui/forge_ui/__main__.py +135 -0
  116. package/ui/forge_ui/diffs.py +454 -0
  117. package/ui/forge_ui/executions.py +436 -0
  118. package/ui/forge_ui/export.py +214 -0
  119. package/ui/forge_ui/findings.py +132 -0
  120. package/ui/forge_ui/fractal.py +582 -0
  121. package/ui/forge_ui/models.py +216 -0
  122. package/ui/forge_ui/reader.py +1424 -0
  123. package/ui/forge_ui/server.py +312 -0
  124. package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
  125. package/ui/forge_ui/static/app.js +1079 -0
  126. package/ui/forge_ui/static/cloud3d.js +647 -0
  127. package/ui/forge_ui/static/cloudmodel.js +375 -0
  128. package/ui/forge_ui/static/cloudui.js +397 -0
  129. package/ui/forge_ui/static/convo.js +295 -0
  130. package/ui/forge_ui/static/datatable.js +232 -0
  131. package/ui/forge_ui/static/diffview.js +235 -0
  132. package/ui/forge_ui/static/favicon.svg +1 -0
  133. package/ui/forge_ui/static/files.js +776 -0
  134. package/ui/forge_ui/static/findings3d.js +134 -0
  135. package/ui/forge_ui/static/gantt.js +97 -0
  136. package/ui/forge_ui/static/graph.js +266 -0
  137. package/ui/forge_ui/static/icons.js +94 -0
  138. package/ui/forge_ui/static/index.html +67 -0
  139. package/ui/forge_ui/static/iso.js +816 -0
  140. package/ui/forge_ui/static/leaderboard.js +311 -0
  141. package/ui/forge_ui/static/map3d.js +45 -0
  142. package/ui/forge_ui/static/modelstats.js +249 -0
  143. package/ui/forge_ui/static/notify.js +179 -0
  144. package/ui/forge_ui/static/panel.js +562 -0
  145. package/ui/forge_ui/static/runmodel.js +667 -0
  146. package/ui/forge_ui/static/scene3d.js +1471 -0
  147. package/ui/forge_ui/static/search.js +120 -0
  148. package/ui/forge_ui/static/shell.js +137 -0
  149. package/ui/forge_ui/static/stagectl.js +150 -0
  150. package/ui/forge_ui/static/style.css +1073 -0
  151. package/ui/forge_ui/static/theme.js +25 -0
  152. package/ui/forge_ui/transcripts.py +608 -0
@@ -0,0 +1,216 @@
1
+ """Model leaderboard facts: one row per task (with its attempts) and per review, for every run.
2
+
3
+ Rows are facts, not scores: the page aggregates them. Outcomes follow forge's own labelling
4
+ (scripts/forge_dataset/export.py `_attempts`/`_outcome`): an attempt that says nothing about the
5
+ implementer is censored, never counted as a failure.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections import OrderedDict
10
+ import json
11
+ import math
12
+ import os
13
+ import threading
14
+ import time
15
+
16
+ from forge_ui import executions, reader
17
+
18
+
19
+ _MAX_ROWS = 50000
20
+ _MAX_WARNINGS = 500
21
+ _RUNNING_S = 120
22
+ _SIZE = 4096
23
+ _PASS = ('PASS', 'MERGED')
24
+ _LOCK = threading.Lock()
25
+ _RUNS = OrderedDict() # realpath -> (signature, computed at, facts)
26
+
27
+
28
+ def _alias(spec):
29
+ return (spec.split(':', 1)[0] or None) if isinstance(spec, str) else None
30
+
31
+
32
+ def _attempt_rows(rd, name):
33
+ """{n: (status, dwarf spec)} from attempts.tsv, oldest first. Infra-refunded rows spent no
34
+ attempt; a QA-only resume reuses its number, so the number's last row holds its status."""
35
+ rows = {}
36
+ for label, row in rd.rows(name, 3):
37
+ try:
38
+ n = int(row[1])
39
+ except ValueError:
40
+ rd.warn(label, 'invalid attempt')
41
+ continue
42
+ status = row[2].strip()
43
+ if not status or status == 'INFRA' or (len(row) > 3 and reader._nullable(row[3].strip())):
44
+ continue
45
+ rows[n] = (status, (row[4].strip() or None) if len(row) > 4 else None)
46
+ return dict(sorted(rows.items()))
47
+
48
+
49
+ def _outcome(status, dwarfs, reviews):
50
+ if status in _PASS:
51
+ return 'pass', None
52
+ if status == 'FAIL':
53
+ return 'fail', None
54
+ if status in ('TIMEOUT', 'ERROR'):
55
+ # Both come from the implementer, the reviewer or forge's own steps between them; only an
56
+ # implementer that ran out of time failed the task.
57
+ if reviews:
58
+ return 'censored', 'reviewer_' + status.lower()
59
+ last = dwarfs[-1] if dwarfs else None
60
+ if status == 'TIMEOUT' and last is not None and (last['exit_code'] == 7 or last['timed_out']):
61
+ return 'fail', None
62
+ code = last['exit_code'] if last is not None else None
63
+ return 'censored', ('dwarf_' if code not in (0, None) else 'forge_') + status.lower()
64
+ return 'censored', status.lower()
65
+
66
+
67
+ def _attempts(rows, execs, plan_spec):
68
+ result = []
69
+ for n, (status, logged) in rows.items():
70
+ dwarfs = [item for item in execs if item['role'] == 'dwarf' and item['attempt'] == n]
71
+ reviews = [item for item in execs if item['role'] == 'qa' and not item['shadow'] and item['attempt'] == n]
72
+ outcome, reason = _outcome(status, dwarfs, reviews)
73
+ last = dwarfs[-1] if dwarfs else None
74
+ model = last['model'] if last is not None else None
75
+ if model:
76
+ spec, source = model['spec'], 'execution'
77
+ elif logged:
78
+ spec, source = logged, 'attempts.tsv'
79
+ elif n == 1 and plan_spec:
80
+ spec, source = plan_spec, 'plan'
81
+ else:
82
+ spec = source = None
83
+ verdict = reviews[-1]['verdict'] if reviews else None
84
+ result.append(dict(n=n, alias=model['alias'] if model else _alias(spec), spec=spec, source=source,
85
+ outcome=outcome, reason=reason, gate=outcome == 'pass' and verdict == 'FAIL',
86
+ model_s=last['phases'].get('model') if last is not None else None,
87
+ duration_s=last['duration_s'] if last is not None else None,
88
+ tokens=dict(last['tokens']) if last is not None and last['tokens'] else None,
89
+ timed_out=bool(last is not None and last['timed_out'])))
90
+ return result
91
+
92
+
93
+ def _route(rd, base):
94
+ explored, propensity = rd.exists(base + '/route.explored'), None
95
+ for n, line in enumerate((rd.text(base + '/route.jsonl') or '').splitlines(), 1):
96
+ obj = rd.object(f'{base}/route.jsonl line {n}', line) if line.strip() else None
97
+ if obj is None:
98
+ continue
99
+ explored = explored or obj.get('explored') is True
100
+ value = obj.get('propensity')
101
+ # The newest decision is the one the task ran with.
102
+ propensity = float(value) if type(value) in (int, float) and math.isfinite(value) else propensity
103
+ return explored, propensity
104
+
105
+
106
+ def _reviews(key, ident, execs, shadows):
107
+ rows = []
108
+ for item in execs:
109
+ if item['role'] == 'qa' and not item['shadow'] and item['state'] == 'done':
110
+ model = item['model'] or {}
111
+ rows.append(dict(run=key, task=ident, n=item['attempt'], kind='primary', alias=model.get('alias'),
112
+ spec=model.get('spec'), verdict=item['verdict'], findings=item['findings'],
113
+ duration_s=item['duration_s'], tokens=item['tokens'], agreement=None, primary_alias=None))
114
+ for entry, items, _ in shadows:
115
+ if entry['status'] != 'done':
116
+ continue
117
+ model = (items[-1]['model'] if items else None) or {}
118
+ spec = entry['spec'] if isinstance(entry['spec'], str) else model.get('spec')
119
+ primaries = [item for item in execs if item['role'] == 'qa' and not item['shadow']
120
+ and item['attempt'] == entry['attempt'] and item['model']]
121
+ rows.append(dict(run=key, task=ident, n=entry['attempt'], kind='shadow', alias=model.get('alias') or _alias(spec),
122
+ spec=spec, verdict=entry['verdict'], findings=entry['findings'],
123
+ duration_s=entry['duration_s'], tokens=items[-1]['tokens'] if items else None,
124
+ agreement=entry['agreement'],
125
+ primary_alias=primaries[-1]['model']['alias'] if primaries else _alias(entry['primary'])))
126
+ return rows
127
+
128
+
129
+ def _facts(path, summary):
130
+ rd = reader._Read(path)
131
+ key = summary['key']
132
+ base_row = dict(run=key, repo=summary['repo'], created=summary['created'])
133
+ tasks, reviews = [], []
134
+ if summary['kind'] == 'solo':
135
+ execs = reader._solo_execs(rd, reader._solo_status(rd), reader._history(rd, 'attempts.tsv'))
136
+ tasks.append(dict(base_row, task='solo', kind='solo', difficulty=None, explored=False, propensity=None,
137
+ final_status=reader._solo_status(rd), accepted=False,
138
+ attempts=_attempts(_attempt_rows(rd, 'attempts.tsv'), execs, reader._role_spec(rd, 'dwarf')),
139
+ escalations=[]))
140
+ reviews += _reviews(key, 'solo', execs, [])
141
+ else:
142
+ for task in reader._tasks(rd):
143
+ base = f'tasks/{task["id"]}'
144
+ task['attempts'], task['attempt'] = reader._attempt_state(rd, base)
145
+ execs = reader._task_execs(rd, task)
146
+ shadows = reader._shadows(rd, task['id'], execs)
147
+ explored, propensity = _route(rd, base)
148
+ tasks.append(dict(base_row, task=task['id'], kind='plan', difficulty=task['difficulty'] or None,
149
+ explored=explored, propensity=propensity, final_status=task['status'],
150
+ accepted=reader._nonempty(rd, base + '/accepted.by'),
151
+ attempts=_attempts(_attempt_rows(rd, base + '/attempts.tsv'), execs, task['dwarf']),
152
+ escalations=[{'n': item['attempt'], 'from': item['from'], 'to': item['to']}
153
+ for item in reader._history(rd, base + '/escalations.tsv', True)]))
154
+ reviews += _reviews(key, task['id'], execs, shadows)
155
+ items = executions.review_dirs(rd)[0]
156
+ published = reader._published(rd)
157
+ for index, item in enumerate(items):
158
+ review, execs, _ = reader._final_review(rd, item, index == 0, published)
159
+ if review['state'] != 'done':
160
+ continue
161
+ run = execs[-1] if execs else None
162
+ model = (run['model'] if run else None) or {}
163
+ spec = review['spec'] or model.get('spec')
164
+ reviews.append(dict(run=key, task=None, n=None, kind='final', alias=model.get('alias') or _alias(spec),
165
+ spec=spec, verdict=review['verdict'] if review['verdict'] in ('PASS', 'FAIL') else None,
166
+ findings=review['findings'], duration_s=review['duration_s'],
167
+ tokens=run['tokens'] if run else None, agreement=None, primary_alias=None))
168
+ order = ('run', 'task', 'kind', 'repo', 'difficulty', 'created', 'explored', 'propensity', 'final_status',
169
+ 'accepted', 'attempts', 'escalations')
170
+ return dict(tasks=[{name: row[name] for name in order} for row in tasks], reviews=reviews,
171
+ warnings=[f'{key}: {item}' for item in rd.warnings])
172
+
173
+
174
+ def _signature(summary):
175
+ return (summary['updated'], summary['task_count'], json.dumps(summary['counts'], sort_keys=True),
176
+ summary['final_review'])
177
+
178
+
179
+ def _cached(path, summary, now):
180
+ real = os.path.realpath(path)
181
+ sig = _signature(summary)
182
+ hit = _RUNS.get(real)
183
+ if hit is not None and hit[0] == sig and not ('RUNNING' in summary['counts'] and now - hit[1] > _RUNNING_S):
184
+ _RUNS.move_to_end(real)
185
+ return hit[2]
186
+ facts = _facts(path, summary)
187
+ _RUNS[real] = (sig, now, facts)
188
+ _RUNS.move_to_end(real)
189
+ while len(_RUNS) > _SIZE:
190
+ _RUNS.popitem(last=False)
191
+ return facts
192
+
193
+
194
+ def collect(found):
195
+ """Models: the facts of every run of `found` ((path, source) pairs), newest run first."""
196
+ # One computation at a time: a cold cache reads every run once, not once per request.
197
+ with _LOCK:
198
+ now = time.time()
199
+ runs = []
200
+ for path, source in found:
201
+ summary = reader.summarize(path, source)
202
+ if reader._kind(path):
203
+ runs.append((path, summary))
204
+ runs.sort(key=lambda item: item[1]['updated'] if item[1]['updated'] is not None else float('-inf'), reverse=True)
205
+ tasks, reviews, warnings, count, truncated = [], [], [], 0, False
206
+ for path, summary in runs:
207
+ if len(tasks) + summary['task_count'] > _MAX_ROWS:
208
+ truncated = True
209
+ break
210
+ facts = _cached(path, summary, now)
211
+ tasks += facts['tasks']
212
+ reviews += facts['reviews']
213
+ warnings += facts['warnings']
214
+ count += 1
215
+ return dict(generated=now, runs=count, truncated=truncated, tasks=tasks, reviews=reviews,
216
+ warnings=warnings[:_MAX_WARNINGS])