@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/SKILL.md +4 -3
  3. package/VERSION +1 -1
  4. package/docs/artifacts.md +13 -1
  5. package/docs/commands.md +11 -5
  6. package/docs/dataset.md +13 -0
  7. package/docs/decomposed-runs.md +13 -3
  8. package/docs/forge-ui.md +8 -3
  9. package/docs/memory.md +4 -2
  10. package/docs/reviews.md +73 -2
  11. package/forge +16 -1
  12. package/package.json +1 -1
  13. package/references/dataset.md +86 -4
  14. package/references/decompose.md +24 -6
  15. package/references/harnesses.md +43 -1
  16. package/references/ui.md +4 -1
  17. package/scripts/forge-config.py +5 -2
  18. package/scripts/forge-control.py +4 -0
  19. package/scripts/forge-dispatch.sh +235 -121
  20. package/scripts/forge-issues.py +345 -17
  21. package/scripts/forge-lib-plans.sh +6 -2
  22. package/scripts/forge-lib-review.sh +158 -3
  23. package/scripts/forge-lib-task.sh +132 -14
  24. package/scripts/forge-parallel.sh +96 -11
  25. package/scripts/forge-prompt.py +61 -4
  26. package/scripts/forge-route.py +72 -12
  27. package/scripts/forge-runtime.py +47 -1
  28. package/scripts/forge-ui.py +8 -4
  29. package/scripts/forge_dataset/__init__.py +7 -2
  30. package/scripts/forge_dataset/cli.py +44 -1
  31. package/scripts/forge_dataset/export.py +36 -3
  32. package/scripts/forge_dataset/forecast.py +355 -0
  33. package/scripts/forge_dataset/harvest.py +23 -2
  34. package/scripts/forge_dataset/importer.py +20 -8
  35. package/scripts/forge_dataset/reviewers.py +145 -0
  36. package/scripts/forge_dataset/routing.py +1 -0
  37. package/scripts/forge_jev/__init__.py +7 -3
  38. package/scripts/forge_registry.py +15 -10
  39. package/ui/README.md +5 -4
  40. package/ui/docs/api.md +88 -23
  41. package/ui/forge_ui/control.py +26 -5
  42. package/ui/forge_ui/reader.py +184 -11
  43. package/ui/forge_ui/static/app.js +24 -10
  44. package/ui/forge_ui/static/cloud3d.js +4 -1
  45. package/ui/forge_ui/static/cloudmodel.js +1 -1
  46. package/ui/forge_ui/static/cloudui.js +13 -4
  47. package/ui/forge_ui/static/findingsview.js +65 -14
  48. package/ui/forge_ui/static/graph.js +6 -5
  49. package/ui/forge_ui/static/leaderboard.js +18 -0
  50. package/ui/forge_ui/static/modelstats.js +19 -1
  51. package/ui/forge_ui/static/panel.js +52 -10
  52. package/ui/forge_ui/static/runmodel.js +128 -13
  53. package/ui/forge_ui/static/scene3d.js +32 -15
  54. package/ui/forge_ui/static/settings.js +48 -12
  55. package/ui/forge_ui/static/style.css +7 -3
@@ -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`. That keeps one task's confused turn from poisoning another and keeps
270
- each task's context small, but it also leaves each dwarf blind to the wider run.
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 QA-only resume
484
- leaves it for the next full retry; Fractal plans never apply pins; a MERGED task refuses one.
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
@@ -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> (--permission-mode acceptEdits --allow Bash [--deny Edit --deny Write] | --always-approve)
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. The
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`.
@@ -201,8 +201,11 @@ def dataset_block():
201
201
  if explicit:
202
202
  home = Path(explicit).expanduser()
203
203
  else:
204
- base = os.environ.get('XDG_DATA_HOME') or str(Path.home() / '.local' / 'share')
205
- home = Path(base).expanduser() / 'forge'
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
 
@@ -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)