@giamme/forge 0.10.0 → 0.11.1
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 +43 -0
- package/SKILL.md +4 -3
- package/VERSION +1 -1
- package/docs/artifacts.md +13 -1
- package/docs/commands.md +11 -5
- package/docs/dataset.md +13 -0
- package/docs/decomposed-runs.md +13 -3
- package/docs/forge-ui.md +8 -3
- package/docs/memory.md +4 -2
- package/docs/reviews.md +73 -2
- package/forge +16 -1
- package/package.json +1 -1
- package/references/dataset.md +86 -4
- package/references/decompose.md +24 -6
- package/references/harnesses.md +43 -1
- package/references/ui.md +4 -1
- package/scripts/forge-config.py +5 -2
- package/scripts/forge-control.py +4 -0
- package/scripts/forge-dispatch.sh +235 -121
- package/scripts/forge-issues.py +345 -17
- package/scripts/forge-lib-plans.sh +6 -2
- package/scripts/forge-lib-review.sh +158 -3
- package/scripts/forge-lib-task.sh +132 -14
- package/scripts/forge-parallel.sh +96 -11
- package/scripts/forge-prompt.py +61 -4
- package/scripts/forge-route.py +72 -12
- package/scripts/forge-runtime.py +47 -1
- package/scripts/forge-ui.py +8 -4
- package/scripts/forge_dataset/__init__.py +7 -2
- package/scripts/forge_dataset/cli.py +44 -1
- package/scripts/forge_dataset/export.py +36 -3
- package/scripts/forge_dataset/forecast.py +355 -0
- package/scripts/forge_dataset/harvest.py +23 -2
- package/scripts/forge_dataset/importer.py +20 -8
- package/scripts/forge_dataset/reviewers.py +145 -0
- package/scripts/forge_dataset/routing.py +1 -0
- package/scripts/forge_jev/__init__.py +7 -3
- package/scripts/forge_registry.py +15 -10
- package/ui/README.md +5 -4
- package/ui/docs/api.md +88 -23
- package/ui/forge_ui/control.py +26 -5
- package/ui/forge_ui/reader.py +184 -11
- package/ui/forge_ui/static/app.js +24 -10
- package/ui/forge_ui/static/cloud3d.js +4 -1
- package/ui/forge_ui/static/cloudmodel.js +1 -1
- package/ui/forge_ui/static/cloudui.js +13 -4
- package/ui/forge_ui/static/findingsview.js +65 -14
- package/ui/forge_ui/static/graph.js +6 -5
- package/ui/forge_ui/static/leaderboard.js +18 -0
- package/ui/forge_ui/static/modelstats.js +19 -1
- package/ui/forge_ui/static/panel.js +52 -10
- package/ui/forge_ui/static/runmodel.js +128 -13
- package/ui/forge_ui/static/scene3d.js +32 -15
- package/ui/forge_ui/static/settings.js +48 -12
- package/ui/forge_ui/static/style.css +7 -3
package/references/decompose.md
CHANGED
|
@@ -266,8 +266,10 @@ So:
|
|
|
266
266
|
## The capsule
|
|
267
267
|
|
|
268
268
|
Every dispatch is a clean slate — forge never passes `--continue`, `--resume`, or
|
|
269
|
-
`codex exec resume
|
|
270
|
-
|
|
269
|
+
`codex exec resume`, except the opt-in `plan --reviewer-session` experiment (it continues
|
|
270
|
+
a reviewer's session across rounds on claude, openclaude and codex). That keeps one task's
|
|
271
|
+
confused turn from poisoning another and keeps each task's context small, but it also
|
|
272
|
+
leaves each dwarf blind to the wider run.
|
|
271
273
|
|
|
272
274
|
`tasks/<id>/capsule.md` records ownership and baseline context. `baseline.capsule` preserves
|
|
273
275
|
the implementation-time capsule for QA. MERGED means the reviewed commit is an ancestor of
|
|
@@ -275,7 +277,8 @@ this task's pinned baseline, not merely that another task finished while QA was
|
|
|
275
277
|
Complete requirements, approach, retry findings and distinct memory facts are assembled once
|
|
276
278
|
per role; implementation and review instructions are separate.
|
|
277
279
|
|
|
278
|
-
It exists to prevent two specific failures that clean slates create
|
|
280
|
+
It exists to prevent two specific failures that clean slates create, which the opt-in
|
|
281
|
+
`--reviewer-session` experiment does not change:
|
|
279
282
|
|
|
280
283
|
- a dwarf reimplementing a helper an earlier task already merged, because nothing told it
|
|
281
284
|
that task was done;
|
|
@@ -425,7 +428,8 @@ Two details that are load-bearing rather than incidental:
|
|
|
425
428
|
diff.
|
|
426
429
|
- **An identical retry is not re-reviewed.** If the diff comes back byte-identical to the
|
|
427
430
|
previous attempt, the task fails again without a QA dispatch. Paying a reviewer to read
|
|
428
|
-
the same code twice buys nothing.
|
|
431
|
+
the same code twice buys nothing. A retry that only rejects the findings, and leaves
|
|
432
|
+
that diff unchanged, is still this identical retry.
|
|
429
433
|
|
|
430
434
|
`retry` is a command a human types. Forge does not loop a dwarf against its own reviewer on
|
|
431
435
|
its own initiative; the one exception is the opt-in `run --retry-failed N` below.
|
|
@@ -437,6 +441,14 @@ work: no dwarf, no new commit, no attempt spent. A `tasks/<id>/resume` marker re
|
|
|
437
441
|
revalidated against the branch tip and the saved source fingerprint before it is trusted; if the
|
|
438
442
|
worktree changed, the full task runs. Findings are carried only from a real review.
|
|
439
443
|
|
|
444
|
+
On a retry whose diff changed, QA receives the previous round under **Findings from the
|
|
445
|
+
previous review round**, the diff since that round (`tasks/<id>/changes.since.diff`,
|
|
446
|
+
`changes.since.range` and `changes.since.stat`; omitted when the earlier commit is
|
|
447
|
+
missing or not an ancestor, or when nothing changed) and the implementer's reply. It is
|
|
448
|
+
told to restate only findings that are still open, as `- [Px][CONFIRMED|PLAUSIBLE]`
|
|
449
|
+
lines, because every labelled line is counted. That is the retry's review, not the
|
|
450
|
+
QA-only re-run above.
|
|
451
|
+
|
|
440
452
|
**In-run retries.** `run <plan> --retry-failed N` re-queues a task whose reviewer said
|
|
441
453
|
`FAIL`/`UNKNOWN` as soon as it ends, up to `N` more times in that run, preparing the retry exactly
|
|
442
454
|
as `retry` does (same findings carry-forward, same pinned base, same cumulative review). Other
|
|
@@ -466,6 +478,10 @@ turns it off):
|
|
|
466
478
|
QA-only resume, which spends no implementer attempt.
|
|
467
479
|
- **Reporting:** the policy's state is decided after every attempt, so a run whose retry budget ran out
|
|
468
480
|
still reports `its next retry goes to <spec>` or the split suggestion under the results.
|
|
481
|
+
A pending pin is reported ahead of both, as `its next retry goes to <spec> (pinned)`, or
|
|
482
|
+
`(pinned; after the QA-only re-run)` only when the next entry really re-runs QA (the
|
|
483
|
+
`resume` marker, or an unreviewed verdict, still matches the worktree). A marker the
|
|
484
|
+
next retry will discard is not that wait.
|
|
469
485
|
|
|
470
486
|
History cannot yet say whether escalating rescues a task (31% of the 29 later attempts on a
|
|
471
487
|
changed spec passed, mostly effort bumps); the attempts this policy makes are what will.
|
|
@@ -480,8 +496,10 @@ decision, removed when it stops; `tasks/<id>/retries.used` stays), so a change d
|
|
|
480
496
|
run. `pin <task> <spec>` sets the model the task's next full retry uses, once (checked with
|
|
481
497
|
`forge-dispatch.sh doctor`, no quota): `prepare_retry` claims `escalation.pin` as
|
|
482
498
|
`escalation.pin.applying`, rewrites `tasks.tsv`, appends the `escalations.tsv` row with
|
|
483
|
-
`source=pin`, clears `escalation.stop`/`split.suggested`, then consumes the pin. A
|
|
484
|
-
|
|
499
|
+
`source=pin`, clears `escalation.stop`/`split.suggested`, then consumes the pin. A symlinked
|
|
500
|
+
`escalations.tsv`, or a symlink in any directory from the plan down to it, is not written:
|
|
501
|
+
the pin stays and nothing is retried. A QA-only resume leaves it for the next full retry;
|
|
502
|
+
Fractal plans never apply pins; a MERGED task refuses one.
|
|
485
503
|
The last column of `escalations.tsv` says who decided: `policy`, `retry` (an explicit
|
|
486
504
|
`--dwarf`) or `pin`; four-column rows read as `policy`. Every change goes to `control.log` (JSON lines:
|
|
487
505
|
`at`, `actor` `cli|ui`, `action`, `task`, `key`, `value`, `previous`) and to the dataset as a
|
package/references/harnesses.md
CHANGED
|
@@ -22,6 +22,7 @@ provider test. See [Fractal execution](fractal.md).
|
|
|
22
22
|
- [opencode](#opencode)
|
|
23
23
|
- [antigravity (agy)](#antigravity-agy)
|
|
24
24
|
- [grok](#grok)
|
|
25
|
+
- [Continuing a reviewer session](#continuing-a-reviewer-session)
|
|
25
26
|
- [Adding a harness](#adding-a-harness)
|
|
26
27
|
|
|
27
28
|
## At a glance
|
|
@@ -265,7 +266,8 @@ passes 30m and exposes `--agy-timeout` to change it.
|
|
|
265
266
|
|
|
266
267
|
```
|
|
267
268
|
grok --model <model> --prompt-file <prompt> --output-format streaming-messages-json \
|
|
268
|
-
--reasoning-effort <effort>
|
|
269
|
+
--reasoning-effort <effort> --background-wait-timeout <limit> \
|
|
270
|
+
(--permission-mode acceptEdits --allow Bash [--deny Edit --deny Write] | --always-approve)
|
|
269
271
|
```
|
|
270
272
|
|
|
271
273
|
The xAI Grok Build CLI, signed in with `grok login` (or `XAI_API_KEY`); `grok models` lists
|
|
@@ -294,6 +296,15 @@ GitHub Copilot) as a second row.
|
|
|
294
296
|
- **Untested:** whether `--permission-mode dontAsk` (deny without cancelling) together with
|
|
295
297
|
explicit Edit/Write/Bash allows would avoid the cancel. Nothing changes until that is
|
|
296
298
|
proven live.
|
|
299
|
+
- **Background work waits as long as forge's limit allows.** After its last turn a headless run
|
|
300
|
+
waits for the background jobs it started (a test suite run in the background, say) and wakes
|
|
301
|
+
the model with each result. By default it waits only 600 s, then stops the work and exits 1:
|
|
302
|
+
a real dwarf whose full suite ran longer ended as an error. forge passes the role's own limit
|
|
303
|
+
as `--background-wait-timeout`, or a day when the limit is off (`--timeout 0`; grok refuses
|
|
304
|
+
0). grok counts from the last turn and forge from the start, so forge's watchdog always fires
|
|
305
|
+
first and the run ends as a timeout. The flag is missing from `grok --help`; preflight checks
|
|
306
|
+
it with `grok --background-wait-timeout 1 --help` instead, because grok rejects an unknown flag
|
|
307
|
+
that comes before `--help` (exit 2).
|
|
297
308
|
- The prompt goes by `--prompt-file`, never argv, and stdin is `/dev/null`.
|
|
298
309
|
|
|
299
310
|
Verified live on 2026-10-19 with a `FORGE_OK` dispatch: exit 0, `permissionMode` acceptEdits,
|
|
@@ -301,6 +312,37 @@ the answer extracted from the `result` line, tokens recorded, nothing left runni
|
|
|
301
312
|
day, grok-4.7 dwarfs implemented and passed review on real tasks (the forge-ui integration run).
|
|
302
313
|
A grok reviewer has not been proven live yet.
|
|
303
314
|
|
|
315
|
+
## Continuing a reviewer session
|
|
316
|
+
|
|
317
|
+
`forge-dispatch.sh qa <spec> ... --continue-session <id>` continues an earlier reviewer session instead of
|
|
318
|
+
starting a fresh one. `<id>` is the session or thread id a dispatch left in `<run-dir>/<role>.session`
|
|
319
|
+
(8 to 128 characters of `[A-Za-z0-9-]`, not starting with `-`). It is accepted for the `qa` role on claude,
|
|
320
|
+
openclaude and codex in custom-prompt mode only; any other role, harness, `--native-review` or a malformed id
|
|
321
|
+
is a usage error (exit 2). Nothing in forge asks for it by default.
|
|
322
|
+
|
|
323
|
+
Probed 2026-10-22 (claude 2.1.307, codex 0.162.0):
|
|
324
|
+
|
|
325
|
+
| Harness | Argv | Notes |
|
|
326
|
+
|---|---|---|
|
|
327
|
+
| claude, openclaude | the fresh QA argv plus `--resume <id>` | Continues from a different working directory and keeps the same session id. Permission flags and the read-only restriction stay; the prompt is still on stdin. |
|
|
328
|
+
| codex | `codex exec resume <id> --json -m MODEL --skip-git-repo-check -o LAST [-c model_reasoning_effort=…] -c 'sandbox_mode="read-only"' -` | Continues from a different working directory. `exec resume` has **no `-C/--cd` and no `-s/--sandbox`**, so the dispatch's own `cd "$REPO"` sets the directory and the sandbox is passed as a config override (the quotes are TOML). The first turn's sandbox was inherited when probed (read-only); the override makes it explicit. Under `--yolo` the bypass flag replaces the override. |
|
|
329
|
+
|
|
330
|
+
The prompt is preceded by a short fixed preamble (this continues your earlier review, the work has changed,
|
|
331
|
+
the working directory is now `<repo>`, re-read files, this round's instructions win). `<role>.prompt` includes
|
|
332
|
+
it. `<role>.continue` records the attempt as `requested=<id>`, then `mode=continued` or `mode=failed` with
|
|
333
|
+
`reason=<one line>`, and is archived with the attempt.
|
|
334
|
+
|
|
335
|
+
**Exit 9** means the session was not continued and the caller may dispatch fresh. Dispatch never retries fresh
|
|
336
|
+
itself, so each dispatch keeps its own honest record. It happens when:
|
|
337
|
+
|
|
338
|
+
- the harness does not advertise the flags continuing needs (`codex exec resume --help`, or `--resume` in
|
|
339
|
+
claude's help) but would pass preflight for a fresh QA dispatch: `reason=unsupported`, before any model call;
|
|
340
|
+
a harness that fails the fresh preflight too keeps exit 3;
|
|
341
|
+
- the harness ran and exited non-zero: `reason=` the log's last error line.
|
|
342
|
+
|
|
343
|
+
A timeout stays exit 7 and an infrastructure stop stays exit 8; both still write `mode=failed`. `doctor --spec`
|
|
344
|
+
without `--continue-session` is unchanged.
|
|
345
|
+
|
|
304
346
|
## Installing forge into a harness
|
|
305
347
|
|
|
306
348
|
Each harness discovers skills differently, and the differences are not guessable — verify
|
package/references/ui.md
CHANGED
|
@@ -38,7 +38,10 @@ run goes on, with the same exit code.
|
|
|
38
38
|
model; the bell tells you when a run finishes or fails, a task fails or a final review gives its
|
|
39
39
|
verdict, and can raise browser notifications while the tab is in the background.
|
|
40
40
|
- **Plan run:** tasks in their planned waves with dependency cables, then the finish line:
|
|
41
|
-
integration, verification and the whole-run final review, each a node with its status.
|
|
41
|
+
integration, verification and the whole-run final review, each a node with its status. Tasks
|
|
42
|
+
merged with `accept` show as accepted (hollow green), not failed. A final review superseded
|
|
43
|
+
by later task merges into the integration branch shows as OUTDATED (hollow unlit) instead of
|
|
44
|
+
as the run's verdict; changes on the user's own branch after an integrate are not tracked. The
|
|
42
45
|
*Execution* view shows the same run on wall-clock time, one block per attempt in the lane it
|
|
43
46
|
ran in, with projected blocks for what has not started and a strip of busy slots against
|
|
44
47
|
`--max-parallel`.
|
package/scripts/forge-config.py
CHANGED
|
@@ -201,8 +201,11 @@ def dataset_block():
|
|
|
201
201
|
if explicit:
|
|
202
202
|
home = Path(explicit).expanduser()
|
|
203
203
|
else:
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
value = os.environ.get('XDG_DATA_HOME') or '' # an empty or relative value counts as unset
|
|
205
|
+
base = Path(value).expanduser() if value else None
|
|
206
|
+
if base is None or not base.is_absolute():
|
|
207
|
+
base = Path.home() / '.local' / 'share'
|
|
208
|
+
home = base / 'forge'
|
|
206
209
|
enabled = os.environ.get('FORGE_DATASET', 'on').strip().lower() not in ('off', '0', 'no', 'false')
|
|
207
210
|
return dict(path=str(home / 'dataset'), enabled=enabled)
|
|
208
211
|
|
package/scripts/forge-control.py
CHANGED
|
@@ -225,6 +225,10 @@ def remove_safe(plan, rel):
|
|
|
225
225
|
|
|
226
226
|
def append_line(plan, rel, line):
|
|
227
227
|
fd, leaf = _open_parent(plan, rel)
|
|
228
|
+
# A missing parent leaves no directory descriptor. Opening the leaf then would pass
|
|
229
|
+
# dir_fd=None and create it in the process cwd, outside the plan, before the close failed.
|
|
230
|
+
if fd is None:
|
|
231
|
+
raise Invalid(f'{rel}: a parent directory is missing')
|
|
228
232
|
try:
|
|
229
233
|
_regular(fd, leaf, rel)
|
|
230
234
|
afd = os.open(leaf, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_NOFOLLOW | os.O_CLOEXEC, 0o644, dir_fd=fd)
|