@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,204 @@
1
+ """Silent outcome predictions on Clef: will a task's first attempt pass, will this retry pass.
2
+
3
+ Recorded and never read by a run. `forge-jev.py first-pass` runs once per plan, after the
4
+ table rewrite that assigns every row its dwarf and reviewer; `forge-jev.py retry-pass` runs
5
+ when a dwarf attempt above 1 begins. Each writes tasks/<id>/clef.first_pass or
6
+ clef.retry_pass.<n> and, through client.ask, a `jev` event in the run dataset. Nothing
7
+ here is put into a prompt, a verdict, tasks.tsv, a retry's spec or its findings.
8
+
9
+ The state is forge_jev.outcome's, the one the export rebuilds for the same rubrics, so a
10
+ live prediction and the measurement it is judged by see the same shape of evidence.
11
+ Bounded: one deadline for the whole command, at most 4 requests in flight, model
12
+ resolution cached per (role, spec).
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import json
18
+ import subprocess
19
+ import tempfile
20
+ import threading
21
+ import time
22
+ from pathlib import Path
23
+
24
+ from . import load_config, site_ready, write_json
25
+ from . import outcome, questions
26
+ from .client import ask, noul
27
+ from .features import task_size
28
+ from .routing import _excerpt, _memory
29
+
30
+ SCRIPTS = Path(__file__).resolve().parents[1]
31
+ DISPATCH = SCRIPTS / 'forge-dispatch.sh'
32
+ CONCURRENCY = 4
33
+
34
+
35
+ def _text(path: Path) -> str:
36
+ try:
37
+ return path.read_text(errors='replace')
38
+ except OSError:
39
+ return ''
40
+
41
+
42
+ def rows(plan: Path) -> list[dict]:
43
+ out = []
44
+ for line in _text(plan / 'tasks.tsv').splitlines():
45
+ if not line.strip() or line.startswith('#'):
46
+ continue
47
+ parts = line.split('\t')
48
+ if len(parts) < 7:
49
+ continue
50
+ out.append(dict(id=parts[0], deps=parts[1], difficulty=parts[2], files=parts[3], dwarf=parts[4],
51
+ qa=parts[5], title=parts[6]))
52
+ return out
53
+
54
+
55
+ class Resolver:
56
+ """{model, effort, harness} of a role and spec, by dispatch's own dry run, once per pair."""
57
+
58
+ def __init__(self, deadline_at: float):
59
+ self.deadline_at = deadline_at
60
+ self.cache = {}
61
+ self.lock = threading.Lock()
62
+
63
+ def __call__(self, role: str, spec: str) -> dict:
64
+ key = (role, spec)
65
+ with self.lock:
66
+ if key in self.cache:
67
+ return self.cache[key]
68
+ found = {}
69
+ remaining = self.deadline_at - time.time()
70
+ if remaining > 0.5:
71
+ with tempfile.TemporaryDirectory(prefix='forge-clef-resolve-') as tmp:
72
+ prompt = Path(tmp) / 'input'
73
+ prompt.write_text('Offline resolution')
74
+ try:
75
+ out = subprocess.run(['/bin/bash', str(DISPATCH), role, spec, '--run-dir', tmp,
76
+ '--prompt-file', str(prompt), '--dry-run'],
77
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True,
78
+ timeout=min(10.0, remaining))
79
+ fields = dict(line.split('=', 1) for line in out.stdout.splitlines() if '=' in line)
80
+ if fields.get('model'):
81
+ found = dict(model=fields.get('model'), effort=fields.get('effort'),
82
+ harness=fields.get('harness'))
83
+ except (OSError, subprocess.SubprocessError, ValueError):
84
+ found = {}
85
+ with self.lock:
86
+ self.cache[key] = found
87
+ return found
88
+
89
+
90
+ def task_state(plan: Path, repo: Path, row: dict, table: list, resolve: Resolver) -> dict | None:
91
+ prompt = _text(plan / 'tasks' / row['id'] / 'prompt.md')
92
+ if not prompt.strip():
93
+ return None
94
+ dwarf, reviewer = resolve('dwarf', row['dwarf']), resolve('qa', row['qa'])
95
+ if not dwarf or not reviewer:
96
+ return None
97
+ level = _text(plan / 'decompose_level').strip() or None
98
+ return outcome.assemble(
99
+ goal=_text(plan / 'goal.txt'), task_id=row['id'], title=row['title'], files=row['files'], deps=row['deps'],
100
+ prompt=prompt, approach=_text(plan / 'tasks' / row['id'] / 'approach.md'),
101
+ plan_tasks=outcome.siblings(table, row['id']), file_excerpts=_excerpt(repo, row['files']),
102
+ memory=_memory(repo), dwarf=dwarf, reviewer=reviewer,
103
+ size=task_size(prompt=prompt, declared_files=row['files'], plan_tasks=len(table), deps=row['deps'],
104
+ decompose_level=level))
105
+
106
+
107
+ def _mint_run_uid(plan: Path) -> None:
108
+ """The plan's dataset uid, before any request: otherwise each prediction's run is a
109
+ legacy id that never joins the attempts the plan goes on to run."""
110
+ try:
111
+ from forge_dataset.capture import mint_run_uid
112
+ mint_run_uid(plan)
113
+ except Exception:
114
+ pass
115
+
116
+
117
+ def _assigned(row: dict) -> bool:
118
+ return row['dwarf'] not in ('', '-', 'UNASSIGNED') and row['qa'] not in ('', '-')
119
+
120
+
121
+ def first_pass(plan: Path, repo: Path, *, config: dict | None = None, budget_s: float | None = None) -> int:
122
+ """One prediction per assigned row; returns how many were recorded."""
123
+ config = config or load_config()
124
+ if not site_ready('clef-first-pass', config):
125
+ return 0
126
+ deadline_at = time.time() + (budget_s if budget_s is not None else 2 * config.get('deadline_s', 5.0))
127
+ table = rows(plan)
128
+ todo = [r for r in table if _assigned(r)]
129
+ if not todo:
130
+ return 0
131
+ _mint_run_uid(plan)
132
+ revision = hashlib.sha256(_text(plan / 'tasks.tsv').encode()).hexdigest()
133
+ resolve = Resolver(deadline_at)
134
+ gate = threading.BoundedSemaphore(CONCURRENCY)
135
+ recorded = []
136
+
137
+ def one(row):
138
+ with gate:
139
+ if time.time() >= deadline_at:
140
+ return
141
+ state = task_state(plan, repo, row, table, resolve)
142
+ if state is None or time.time() >= deadline_at:
143
+ return
144
+ result = ask(state, dict(first_pass=questions.first_pass()), site='clef-first-pass', run_dir=str(plan),
145
+ config=config, deadline_s=max(0.1, deadline_at - time.time()),
146
+ context=dict(task=row['id'], stage='plan', intended_attempt=1, plan_table=revision,
147
+ assignment=dict(dwarf=row['dwarf'], qa=row['qa'])))
148
+ p = noul(result, 'first_pass')
149
+ if p is None:
150
+ return
151
+ try:
152
+ write_json(plan / 'tasks' / row['id'] / 'clef.first_pass',
153
+ dict(p=p, model=result.get('model'), at=time.time(), plan_table=revision,
154
+ assignment=dict(dwarf=row['dwarf'], qa=row['qa'])))
155
+ recorded.append(row['id'])
156
+ except OSError:
157
+ pass
158
+
159
+ threads = [threading.Thread(target=one, args=(r,), daemon=True) for r in todo]
160
+ for thread in threads:
161
+ thread.start()
162
+ for thread in threads:
163
+ thread.join(max(0.0, deadline_at - time.time()))
164
+ return len(recorded)
165
+
166
+
167
+ def retry_pass(plan: Path, repo: Path, task: str, attempt: int, *, config: dict | None = None,
168
+ budget_s: float | None = None) -> bool:
169
+ """The prediction for a dwarf attempt above 1, with the findings it was handed."""
170
+ config = config or load_config()
171
+ out = plan / 'tasks' / task / ('clef.retry_pass.%d' % attempt)
172
+ findings = _text(plan / 'tasks' / task / 'retry_findings.md')
173
+ if attempt < 2 or not findings.strip() or out.exists() or not site_ready('clef-retry-pass', config):
174
+ return False
175
+ deadline_at = time.time() + (budget_s if budget_s is not None else 2 * config.get('deadline_s', 5.0))
176
+ table = rows(plan)
177
+ row = next((r for r in table if r['id'] == task), None)
178
+ if row is None or not _assigned(row):
179
+ return False
180
+ state = task_state(plan, repo, row, table, Resolver(deadline_at))
181
+ if state is None:
182
+ return False
183
+ state = outcome.retry(state, attempt=attempt, previous_review=findings)
184
+ result = ask(state, dict(retry_pass=questions.retry_pass()), site='clef-retry-pass',
185
+ run_dir=str(plan), config=config, deadline_s=max(0.1, deadline_at - time.time()),
186
+ context=dict(task=task, stage='retry', attempt=attempt,
187
+ assignment=dict(dwarf=row['dwarf'], qa=row['qa'])))
188
+ p = noul(result, 'retry_pass')
189
+ if p is None:
190
+ return False
191
+ try:
192
+ write_json(out, dict(p=p, model=result.get('model'), at=time.time(), attempt=attempt,
193
+ assignment=dict(dwarf=row['dwarf'], qa=row['qa'])))
194
+ except OSError:
195
+ return False
196
+ return True
197
+
198
+
199
+ def main_first_pass(args) -> int:
200
+ return 0 if first_pass(Path(args.plan), Path(args.repo)) else 3
201
+
202
+
203
+ def main_retry_pass(args) -> int:
204
+ return 0 if retry_pass(Path(args.plan), Path(args.repo), args.task, args.attempt) else 3