@giamme/forge 0.10.0 → 0.11.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 +30 -0
- package/SKILL.md +4 -3
- package/VERSION +1 -1
- package/docs/artifacts.md +11 -0
- package/docs/commands.md +8 -3
- 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 +344 -16
- 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_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 +4 -3
- package/ui/docs/api.md +75 -15
- package/ui/forge_ui/control.py +12 -0
- 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 +29 -3
- 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/style.css +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and versions follow the format `{major}.{minor}[.{patch}]` ([...] parts appear only when non-zero).
|
|
7
7
|
|
|
8
|
+
## [0.11] - 2026-10-26
|
|
9
|
+
|
|
10
|
+
Reviews now remember the last round: a retry's review starts from the previous findings, and a repeated whole-run review from the last report. forge dataset can forecast a plan before it runs and grade how often each reviewer's PASS held, and forge-ui shows a superseded final review as OUTDATED instead of FAIL.
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- `forge dataset reviewers`: a report card for reviewer models. For each reviewer, how often work it PASSed was later reverted or mostly rewritten within the outcome horizon (14 days by default), with a 95% interval, or "not enough data" below 10 outcomes. forge-ui's Models view shows it as the Reviewers table's Did not hold column. Advisory only: it never changes routing.
|
|
15
|
+
- `forge dataset forecast <plan>` estimates a plan from past tasks at the same tier and model: median and p90 tokens and time, and the first-try PASS rate with a 95% interval, per task and in total, flagging tasks unlikely to pass first time. `forge-parallel.sh run` prints it before dispatching and records it for later checks (`--no-forecast` turns it off). It is advisory only.
|
|
16
|
+
- On a retry, the per-task reviewer now first checks the previous round's findings, sees the diff between the two attempts and reads the implementer's reply, instead of starting cold. This applies on every harness, so a finding that was fixed is confirmed as fixed and one that was not is raised again.
|
|
17
|
+
- A final review that later merges superseded now shows as OUTDATED in the forge-ui run view and on the cloud map, not as the run's verdict, and tasks merged with `accept` show as accepted instead of failed. `forge issues` and the Open-findings view mark the findings of an outdated review, and accepted findings get their own origin and issue text.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `plan --reviewer-session` is an opt-in experiment that continues the reviewer's own session across rounds on claude, openclaude and codex, and falls back to a fresh session whenever it cannot. Those reviews are tagged in the dataset and kept out of the reviewer statistics, so the experiment does not skew the independence numbers.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- A mistyped command now says which one you most likely meant, for example `forge rnus` answers "Did you mean `forge runs`?" above the usage.
|
|
26
|
+
- A repeated whole-run review now carries the previous final review's report and the diff since it, so it checks what the earlier review found rather than reviewing the run from scratch.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- forge-ui started directly (`python3 -m forge_ui`) now finds forge's run registry the way forge writes it: an `XDG_CONFIG_HOME` that starts with `~` lists your runs instead of none.
|
|
31
|
+
- A grok dwarf or reviewer that runs its tests in the background no longer ends as an error after 10 minutes: grok now waits for that work as long as forge's own time limit allows, and `forge-dispatch.sh doctor` checks that the installed grok supports this.
|
|
32
|
+
- A relative `XDG_CONFIG_HOME` now counts as unset, as the XDG spec says, so forge registers runs and keeps `jev.json` in `~/.config` instead of recording nothing. Before, `forge ui open` printed a link to a run that forge-ui then did not list.
|
|
33
|
+
- The dataset now imports the traces from the archive of a run whose directory name starts with `review-`; before, nothing from it was imported.
|
|
34
|
+
- An escalation pin, or any escalation, no longer writes through a symlinked `escalations.tsv`.
|
|
35
|
+
- The run summary, and `escalation.next` in the router's `--check`, no longer say a pin waits for a QA-only re-run when the next retry will really be a full attempt.
|
|
36
|
+
- A relative `XDG_DATA_HOME` now counts as unset, as the XDG spec says, so the run dataset, run archives and exports stay in `~/.local/share/forge` instead of a folder inside whichever repo forge ran from. `forge config show` reports the same place.
|
|
37
|
+
|
|
8
38
|
## [0.10] - 2026-10-23
|
|
9
39
|
|
|
10
40
|
forge-ui can now change a run: live QA threshold, retries and timeout, a one-shot model pin, open findings filed as GitHub issues, deleting runs while the dataset keeps them, and every config source in one view. Each control is also a forge CLI: forge control, forge runs rm, forge config and new forge issues modes.
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: forge
|
|
3
3
|
description: Dispatch implementation and independent diff review to user-selected models through local agent CLIs. Use for /forge or explicit requests to delegate coding or review; explanation-only questions do not authorize dispatch.
|
|
4
|
-
argument-hint: '"<goal>" --dwarf <alias>[:effort[:harness]] [--qa <alias>] [--planner <alias>] [--yolo-dwarf] [--yolo-qa] [--decompose-level low|medium|high] [--fractal|--no-fractal|--fractal-auto-decompose] [--fractal-planner <spec>] [--jev|--no-jev] [--jev-shadow] [--no-memory] [--no-ripwire] [--ui|--no-ui] [--timeout <seconds>] [--retry-failed <n>] [--escalate-after <n>] [--qa-threshold P0|P1|P2|P3] [--file-issues ask|auto|off] [--shadow-qa <spec>[,<spec>]] [--shadow-rate <0..1>] [--explore <0..0.5>] [--fail-first]'
|
|
4
|
+
argument-hint: '"<goal>" --dwarf <alias>[:effort[:harness]] [--qa <alias>] [--planner <alias>] [--yolo-dwarf] [--yolo-qa] [--decompose-level low|medium|high] [--fractal|--no-fractal|--fractal-auto-decompose] [--fractal-planner <spec>] [--jev|--no-jev] [--jev-shadow] [--no-memory] [--no-ripwire] [--ui|--no-ui] [--timeout <seconds>] [--retry-failed <n>] [--escalate-after <n>] [--qa-threshold P0|P1|P2|P3] [--file-issues ask|auto|off] [--shadow-qa <spec>[,<spec>]] [--shadow-rate <0..1>] [--reviewer-session] [--explore <0..0.5>] [--fail-first]'
|
|
5
5
|
allowed-tools: [Bash, Read]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -43,8 +43,9 @@ For explanation requests, explain the workflow without spending model quota.
|
|
|
43
43
|
separate authorization and `integrate --approved`. Never push automatically.
|
|
44
44
|
- Show the decomposed task/model table and wait for approval before dispatch. Retry only
|
|
45
45
|
when requested; preserve failed task branches and worktrees. `run --retry-failed N`,
|
|
46
|
-
`--infra-retries M`, `--qa-threshold Pn
|
|
47
|
-
sampled share of attempts, never read, to measure reviewer agreement)
|
|
46
|
+
`--infra-retries M`, `--qa-threshold Pn`, `plan --shadow-qa` (a second review of a
|
|
47
|
+
sampled share of attempts, never read, to measure reviewer agreement) and
|
|
48
|
+
`plan --reviewer-session` (continue the reviewer's session across rounds) are opt-in: offer
|
|
48
49
|
them, never add them unprompted. So is `--fail-first` (solo or `plan`): the change's new
|
|
49
50
|
tests also run without its non-test changes, and the reviewer is asked to report any that
|
|
50
51
|
still pass as P2; it needs `.forge/verify-subset` and never changes a result by itself. `plan` explores by default (`--explore 0.2`: now and then
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.11
|
package/docs/artifacts.md
CHANGED
|
@@ -17,6 +17,8 @@ as if the dwarf had written it.
|
|
|
17
17
|
<role>.last the final message — read this first
|
|
18
18
|
<role>.infra only after an infrastructure failure: class, retry_after, detail
|
|
19
19
|
<role>.orphans only when processes were left running after the harness exited
|
|
20
|
+
<role>.session session id reported by claude, openclaude or codex; absent when the dispatch published none
|
|
21
|
+
<role>.continue only when the dispatch was asked to continue a reviewer session: requested=, mode=continued|failed, reason=
|
|
20
22
|
changes.diff the dwarf's real diff, which is qa's input
|
|
21
23
|
```
|
|
22
24
|
|
|
@@ -52,9 +54,18 @@ A decomposed run adds:
|
|
|
52
54
|
tasks/<id>/escalations.tsv epoch, attempt, from-spec, to-spec, source (policy|retry|pin); older rows have 4 columns
|
|
53
55
|
tasks/<id>/escalation.pin a human's one-shot model for the next full retry: one dwarf spec on one line
|
|
54
56
|
tasks/<id>/escalation.pin.applying the pin while a retry has claimed it; removed once the retry is recorded
|
|
57
|
+
tasks/<id>/retry_findings.commit commit the carried review judged
|
|
58
|
+
tasks/<id>/retry_findings.session session id of that review, when it recorded one
|
|
59
|
+
tasks/<id>/retry_findings.resolved that review's resolution (harness=, model=, effort=)
|
|
60
|
+
tasks/<id>/changes.since.diff diff from that commit to the one under review; omitted when there is no such range
|
|
61
|
+
tasks/<id>/changes.since.range the two commits, full hashes
|
|
62
|
+
tasks/<id>/changes.since.stat diff --stat of that range
|
|
63
|
+
tasks/<id>/review_sessions.tsv one row per review round while plan --reviewer-session is on: round, attempt, mode, reason, session id
|
|
55
64
|
tasks/<id>/retry_budget, retries.used live per-task retry budget (a number); retries spent (kept after the run)
|
|
56
65
|
results.tsv id status branch
|
|
57
66
|
timeout, qa_threshold, verify_retries options remembered from run/retry flags (also escalate_after)
|
|
67
|
+
reviewer_session holds on; present while plan --reviewer-session is on
|
|
68
|
+
<final-review-dir>/review.meta final-review-<epoch>-<pid>/ or integrate-*/final-review/; base=, tip=, source=, spec=, threshold=, integration=, previous=, since=; session= and session_reason= while reviewer_session is set
|
|
58
69
|
control.log one JSON line per human change made with forge control
|
|
59
70
|
scheduler.live, control.lock only while a scheduler runs: {"pid", "started"}; the lock serialises forge control
|
|
60
71
|
retry_budget, infra_budget only while a scheduler runs: its live budgets (a number each)
|
package/docs/commands.md
CHANGED
|
@@ -51,15 +51,19 @@ deletion or the run directory was kept (the same command finishes the job).
|
|
|
51
51
|
|
|
52
52
|
`forge issues <plan dir, task dir, review dir, known_issues.md or qa.last>` drafts one GitHub
|
|
53
53
|
issue per finding the run left open (`--group task|run` for fewer) and files them only with
|
|
54
|
-
`--apply`. The findings are the CONFIRMED ones `--qa-threshold` tolerated
|
|
55
|
-
|
|
54
|
+
`--apply`. The findings are the CONFIRMED ones `--qa-threshold` tolerated (origin
|
|
55
|
+
`tolerated`), the CONFIRMED ones a person accepted with `accept` (origin `accepted`, with
|
|
56
|
+
their own issue text: a person accepted the work over the reviewer's verdict), PLAUSIBLE
|
|
57
|
+
ones with `--include-plausible`, and what the whole-run review left open. A whole-run
|
|
58
|
+
review the integration branch has moved past is outdated: the issue says so and names the
|
|
59
|
+
tasks that merged after it. Each issue carries a hidden
|
|
56
60
|
fingerprint, so a finding is never filed twice, by this command or by gh-proman. Also:
|
|
57
61
|
`--min-severity`, `--repo`, `--lang`, `--label`, `--confirm-new`, `--max-issues` (default 10; pass
|
|
58
62
|
the size of a `--only` selection).
|
|
59
63
|
|
|
60
64
|
| Flag | Does |
|
|
61
65
|
|---|---|
|
|
62
|
-
| `--json` | machine-readable output; `findings` lists each one with its fingerprint, severity, origin, task, `filed` state and `dismissed` mark |
|
|
66
|
+
| `--json` | machine-readable output; `findings` lists each one with its fingerprint, severity, origin (`tolerated`, `accepted`, `review` or `plausible`), task, `review`, `filed` state and `dismissed` mark. A whole-run finding's `review` is `{name, outdated, merged_since}` (`outdated` is null when the review has no verdict or the plan has no accepted integration commit; `merged_since` is empty unless the review is outdated). Every other finding has `review` null |
|
|
63
67
|
| `--offline` | never run `gh`: no filed state, commit publication judged from local refs |
|
|
64
68
|
| `--only FP` | keep only these 12-hex fingerprints (repeatable or comma-separated) |
|
|
65
69
|
| `--all-runs` | read `RUN` as a repository (or a plan dir naming one) and collect every run in its registry; the newest plan wins for a finding in several |
|
|
@@ -320,6 +324,7 @@ Bash 3.2 entrypoints use a Python standard-library coordinator to schedule eligi
|
|
|
320
324
|
```
|
|
321
325
|
forge-parallel.sh plan <plan-dir> --repo <dir> [routing flags] [--planner <spec>] [--no-memory]
|
|
322
326
|
[--decompose-level L] [--shadow-qa <spec>[,..]|none] [--shadow-rate R]
|
|
327
|
+
[--reviewer-session | --no-reviewer-session]
|
|
323
328
|
[--explore E] [--ui | --no-ui] [--fail-first | --no-fail-first]
|
|
324
329
|
forge-parallel.sh run <plan-dir> [--max-parallel N] [--yolo-dwarf] [--yolo-qa] [--dry-run]
|
|
325
330
|
[--verify <cmd>] [--setup <cmd>] [--timeout S]
|
package/docs/dataset.md
CHANGED
|
@@ -14,8 +14,21 @@ forge dataset status # size, counts, and anything that looks incomple
|
|
|
14
14
|
forge dataset import ~/.local/share/forge/legacy # runs that predate live capture
|
|
15
15
|
forge dataset export # labeled decision cases for benchmarks and fine-tuning
|
|
16
16
|
forge dataset outcomes # which integrated lines survived, read from git history
|
|
17
|
+
forge dataset reviewers # how often work each reviewer PASSed was later reverted or rewritten
|
|
18
|
+
forge dataset forecast <plan> # likely tokens, time and first-try PASS rate for a plan's tasks
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
`forge dataset reviewers` is a report card for reviewer models, built on those outcomes. It is
|
|
22
|
+
advisory only and never changes routing, and it shows "not enough data" below 10 outcomes per
|
|
23
|
+
reviewer rather than a rate a handful of tasks would make up. See
|
|
24
|
+
[the reviewer report card](../references/dataset.md#reviewer-report-card).
|
|
25
|
+
|
|
26
|
+
`forge dataset forecast <plan>` estimates a plan from past tasks at the same tier and model:
|
|
27
|
+
median and p90 tokens and time, and the first-try PASS rate with a 95% interval, per task and in
|
|
28
|
+
total. It flags tasks below 50% and says "not enough history" under 5 past tasks. `run` prints
|
|
29
|
+
the same block before it dispatches (`--no-forecast` turns it off) and records it, so its accuracy
|
|
30
|
+
can be checked later. It is advisory only. See [plan forecast](../references/dataset.md#plan-forecast).
|
|
31
|
+
|
|
19
32
|
Deleting a run keeps what it taught the dataset. `forge runs rm <run-dir>` first imports the run
|
|
20
33
|
**strictly** (every event must be stored, or nothing is deleted), then writes a self-describing
|
|
21
34
|
archive under `<data home>/archive/`: a `.tar.gz` whose `FORGE_ARCHIVE.json` records the run
|
package/docs/decomposed-runs.md
CHANGED
|
@@ -99,8 +99,9 @@ that baseline, and QA sees only baseline membership recorded for that task.
|
|
|
99
99
|
## Fresh session per task, plus a capsule
|
|
100
100
|
|
|
101
101
|
Every dispatch is a **clean slate** — forge never passes `--continue`, `--resume`, or
|
|
102
|
-
`codex exec resume
|
|
103
|
-
|
|
102
|
+
`codex exec resume`, except the opt-in `plan --reviewer-session` experiment (it continues
|
|
103
|
+
a reviewer's session across rounds on claude, openclaude and codex). One task's confused
|
|
104
|
+
turn can't poison another, and each task's context stays small.
|
|
104
105
|
|
|
105
106
|
Clean slates alone would leave every dwarf blind to the wider run, so a short `capsule.md` is
|
|
106
107
|
generated from the task's pinned baseline and preserved for both implementation and QA:
|
|
@@ -181,6 +182,10 @@ bash scripts/forge-parallel.sh run "$PLAN" --max-parallel 4 --yolo-dwarf
|
|
|
181
182
|
bash scripts/forge-parallel.sh integrate "$PLAN" --approved
|
|
182
183
|
```
|
|
183
184
|
|
|
185
|
+
`run` starts by printing a forecast for the plan from the dataset: likely tokens, time and
|
|
186
|
+
first-try PASS rate per task, with tasks unlikely to pass first time flagged. It is advisory only,
|
|
187
|
+
and `--no-forecast` turns it off. See [plan forecast](../references/dataset.md#plan-forecast).
|
|
188
|
+
|
|
184
189
|
`integrate` refuses without `--approved`, and refuses on a dirty working tree.
|
|
185
190
|
For Fractal execution, pass tier pools to `plan` and backend/limit flags to `run`.
|
|
186
191
|
`--max-parallel` limits task pipelines; the shared `--fractal-concurrency` limit includes
|
|
@@ -232,7 +237,12 @@ Each change is validated, written under `control.lock`, and says when it applies
|
|
|
232
237
|
`forge-dispatch.sh doctor`, which spends no quota). A QA-only resume spends no implementer
|
|
233
238
|
attempt and leaves the pin for later. The retry applies it, writes it into `tasks.tsv`, records
|
|
234
239
|
an `escalations.tsv` row with `source` `pin`, clears any `split.suggested` stop and consumes
|
|
235
|
-
it.
|
|
240
|
+
it. That row is never written through a symlink: if `escalations.tsv`, or any directory
|
|
241
|
+
from the plan down to it, is one, the pin stays and nothing is retried. The run summary
|
|
242
|
+
says a pin waits (`pinned; after the QA-only re-run`) only when the next entry really
|
|
243
|
+
re-runs QA: a `resume` marker, or an unreviewed verdict, that still matches the
|
|
244
|
+
worktree. A marker the next retry will discard is reported as `pinned`.
|
|
245
|
+
Fractal plans never apply pins, and a MERGED task refuses one.
|
|
236
246
|
- **Who decided an escalation.** The last column of `tasks/<id>/escalations.tsv` is `policy`
|
|
237
247
|
(`--escalate-after`), `retry` (a typed `retry --dwarf`) or `pin`; rows from before it was added
|
|
238
248
|
have four columns and read as `policy`.
|
package/docs/forge-ui.md
CHANGED
|
@@ -32,7 +32,10 @@ and the cloud remembers it.
|
|
|
32
32
|
|
|
33
33
|
The page reads like a signal box's desk: engraved plates for headings, push buttons with lamps
|
|
34
34
|
for switches, and lamps that keep one meaning everywhere (amber running, green merged, red failed,
|
|
35
|
-
unlit pending).
|
|
35
|
+
unlit pending). Tasks merged with `accept` show as accepted (hollow green) rather than failed.
|
|
36
|
+
A final review superseded by tasks merged into the integration branch shows as OUTDATED
|
|
37
|
+
(hollow unlit) instead of as the run's verdict; changes on the user's own branch after an
|
|
38
|
+
integrate are not tracked. Two layouts share it. *Immersive*, the default, floats the runs, the inspector and the tasks
|
|
36
39
|
over a full-window stage (on a first visit the inspector and the tasks start folded, so a run
|
|
37
40
|
opens on its plate); *Workbench* docks them beside it with splitters. `1`, `2` and
|
|
38
41
|
`3` fold each module away, the header switches layout and theme (system, light or dark), and the
|
|
@@ -69,8 +72,10 @@ indistinguishable from the same command typed in a terminal, except that `contro
|
|
|
69
72
|
apply (a budget with no live scheduler, a merged task) is disabled and says why.
|
|
70
73
|
- **Open findings:** the review findings a run left open (tolerated by `--qa-threshold`,
|
|
71
74
|
PLAUSIBLE, or left by the whole-run review), for this run or every run of its repository.
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
Findings of an outdated review carry an "outdated review" marker, and findings a person
|
|
76
|
+
accepted have origin `accepted`. Select some and file them as GitHub issues, or dismiss the
|
|
77
|
+
ones that need no work (and take that back). It runs `forge issues`, so a finding is never
|
|
78
|
+
filed twice.
|
|
74
79
|
- **Delete a run:** a dialog that first shows a dry run, then deletes the run directory
|
|
75
80
|
through `forge runs rm`. The dataset keeps the run's traces, and by default a `.tar.gz` of
|
|
76
81
|
them too; you can also remove its clean worktrees and its branches that nothing has checked
|
package/docs/memory.md
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
What forge learns about a repository, where it keeps it, what each role is told, and what forge has spent there.
|
|
6
6
|
|
|
7
|
-
Every forge dispatch is a clean slate
|
|
8
|
-
|
|
7
|
+
Every forge dispatch is a clean slate — the opt-in `plan --reviewer-session` experiment,
|
|
8
|
+
which continues a reviewer's session across rounds, is the exception — so without help the
|
|
9
|
+
tenth run in a repo rediscovers what the first one learned. forge keeps that in the repo,
|
|
10
|
+
in two files:
|
|
9
11
|
|
|
10
12
|
```
|
|
11
13
|
.forge/memory.md small, capped, injected into every prompt
|
package/docs/reviews.md
CHANGED
|
@@ -51,7 +51,20 @@ Two things it deliberately does:
|
|
|
51
51
|
from — not just the fix. Reviewing the fix alone would let the first attempt's code through
|
|
52
52
|
unread.
|
|
53
53
|
- **Refuses to re-review identical code.** If the retry produces a byte-identical diff, the
|
|
54
|
-
task fails again without spending a QA dispatch.
|
|
54
|
+
task fails again without spending a QA dispatch. A retry that only rejects the findings,
|
|
55
|
+
and leaves that diff unchanged, is still this identical retry: it writes
|
|
56
|
+
`tasks/<id>/noretry` and QA is not dispatched.
|
|
57
|
+
|
|
58
|
+
When the diff did change, QA on that retry is handed the previous round as well as the
|
|
59
|
+
cumulative diff. The report goes under **Findings from the previous review round**. The
|
|
60
|
+
diff between the two attempts goes under **Changes since that review**, and is kept as
|
|
61
|
+
`tasks/<id>/changes.since.diff`, `changes.since.range` and `changes.since.stat` (the
|
|
62
|
+
range is omitted when the earlier commit is missing, unknown or not an ancestor, or when
|
|
63
|
+
nothing changed; a diff over 100 KB is replaced in the prompt by its stat). The
|
|
64
|
+
implementer's reply goes under **The implementer's reply (its own account; verify
|
|
65
|
+
every claim against the code)**. The prompt tells the reviewer to restate only findings that are still
|
|
66
|
+
open, as `- [Px][CONFIRMED|PLAUSIBLE]` lines, and to mention a fixed or rejected finding
|
|
67
|
+
only in a plain sentence. The gate and `forge issues` count every labelled line.
|
|
55
68
|
|
|
56
69
|
`retry` is something you run. Forge never loops a dwarf against its own reviewer on its own
|
|
57
70
|
initiative — unless you ask a *run* to with `--retry-failed N`, see
|
|
@@ -133,6 +146,16 @@ them on the team's project board; anywhere else forge files them itself with pla
|
|
|
133
146
|
|
|
134
147
|
Each issue gets the finding, its severity, the task, reviewer and a code link at the merged
|
|
135
148
|
commit. A hidden fingerprint, shared with gh-proman, keeps a finding from being filed twice.
|
|
149
|
+
Findings a person merged with `accept` are origin `accepted`: the CONFIRMED lines under
|
|
150
|
+
`## Accepted by a human despite the reviewer's <status> verdict` in `known_issues.md`.
|
|
151
|
+
Their issue says a person accepted the work over that verdict, and gives the recorded
|
|
152
|
+
reason. Findings above that heading, which a threshold tolerated, stay origin
|
|
153
|
+
`tolerated`. A whole-run review whose integration branch has moved since it is outdated.
|
|
154
|
+
Its findings stay listed, and the issue says the review is outdated and names the tasks
|
|
155
|
+
that merged after it. In `--json` each whole-run finding carries `review`: `name`,
|
|
156
|
+
`outdated` and `merged_since`. `outdated` is null when the review has no verdict or the
|
|
157
|
+
plan has no accepted integration commit, and `merged_since` is empty unless the review
|
|
158
|
+
is outdated. Every other finding has `review` null.
|
|
136
159
|
|
|
137
160
|
## Accepting a task despite its reviewer
|
|
138
161
|
|
|
@@ -149,7 +172,9 @@ it on its own. It accepts only a `FAIL` or `UNKNOWN` task whose reviewed work is
|
|
|
149
172
|
the branch is still at the reviewed commit and the worktree still has the saved fingerprint —
|
|
150
173
|
otherwise it exits `3` and says to `retry` for a fresh review (accepting would sign off on code
|
|
151
174
|
nobody reviewed). It writes `tasks/<id>/known_issues.md` (your reason, who and when, and the
|
|
152
|
-
reviewer's reply copied verbatim
|
|
175
|
+
reviewer's reply copied verbatim, under
|
|
176
|
+
`## Accepted by a human despite the reviewer's <status> verdict`) and `accepted.by`,
|
|
177
|
+
merges through the same checks as any other
|
|
153
178
|
task, and puts everything back if the merge conflicts (exit `6`). Dependents are not touched;
|
|
154
179
|
`run` continues them.
|
|
155
180
|
|
|
@@ -179,6 +204,52 @@ the one remembered in `final_qa`, else the single spec every task shares (otherw
|
|
|
179
204
|
Tasks that are not merged are not in the result, and it says so. `integrate` also names unmerged
|
|
180
205
|
tasks even without a review.
|
|
181
206
|
|
|
207
|
+
A later whole-run review carries the previous one's report, under **Findings from the
|
|
208
|
+
previous final review**, and the diff since it, under **Changes since that review**
|
|
209
|
+
(inlined up to 200 KB, otherwise its stat). `review.meta` records `integration=` (the
|
|
210
|
+
accepted integration commit, or `none`), `previous=` (that review's directory, or
|
|
211
|
+
`none`) and `since=` (its tip, when that commit is an ancestor of this review;
|
|
212
|
+
otherwise `none`, and the prompt says the range cannot be shown). When the two reviews
|
|
213
|
+
share a commit, `since=` is that commit and the prompt says nothing changed. The same
|
|
214
|
+
labelled-line rule applies: restate only what is still open, because every labelled line
|
|
215
|
+
is filed.
|
|
216
|
+
|
|
217
|
+
## Reviewer session (opt-in experiment)
|
|
218
|
+
|
|
219
|
+
`plan --reviewer-session` continues the same reviewer's session across rounds. It is off
|
|
220
|
+
unless you pass it; `--no-reviewer-session` turns it off again and removes the plan's
|
|
221
|
+
`reviewer_session` marker. While the marker is present, per-task QA and a repeated
|
|
222
|
+
whole-run review continue the previous round's session on claude, openclaude and codex,
|
|
223
|
+
when the resolved harness, model and effort are unchanged. The approval table names it.
|
|
224
|
+
Dispatch details are in
|
|
225
|
+
[continuing a reviewer session](../references/harnesses.md#continuing-a-reviewer-session).
|
|
226
|
+
|
|
227
|
+
Any round that cannot continue starts a fresh session. That is the first round, a missing
|
|
228
|
+
or malformed session id, a resolution that is missing or different, any harness other
|
|
229
|
+
than those three, a Fractal plan, and a continue that the harness refuses or that fails
|
|
230
|
+
(dispatch exit **9**). Exit 9 means the session was not continued. Forge sends the same
|
|
231
|
+
prompt again as a fresh session, and that dispatch decides the round. One row per round
|
|
232
|
+
is appended to `tasks/<id>/review_sessions.tsv`: round, attempt, `continued` or `fresh`,
|
|
233
|
+
the reason, and the session id the round considered (empty when there was none). The
|
|
234
|
+
row describes the dispatch whose result counts, so a failed continue is a single
|
|
235
|
+
`fresh` row. Reasons are `same-spec`, `first-round`, `no-session`, `invalid-session`,
|
|
236
|
+
`spec-changed`, `harness-unsupported`, `fractal`, `continue-unsupported` and
|
|
237
|
+
`continue-failed`. A final review records the same outcome as `session=` and
|
|
238
|
+
`session_reason=` in `review.meta`.
|
|
239
|
+
|
|
240
|
+
A shadow review never continues a session. It stays a separate fresh review of a copy of
|
|
241
|
+
the prompt.
|
|
242
|
+
|
|
243
|
+
A review that was asked to continue is tagged `session_continue` in the dataset
|
|
244
|
+
(`continued`, or `failed` when the continue did not succeed) and left out of the
|
|
245
|
+
reviewer slices and the agreement rate. The fresh dispatch after exit 9 is not tagged,
|
|
246
|
+
so it still counts. See [the dataset](../references/dataset.md).
|
|
247
|
+
|
|
248
|
+
This is separate from a QA-only resume, the re-run after an infrastructure stop. That
|
|
249
|
+
re-run is the `tasks/<id>/resume` marker: forge revalidates it against the worktree,
|
|
250
|
+
then asks QA again about unchanged work, and no attempt is spent. The reviewer session
|
|
251
|
+
continues the reviewer's conversation into a later round, and only when you opt in.
|
|
252
|
+
|
|
182
253
|
## Shadow reviews: how much of a verdict is the reviewer?
|
|
183
254
|
|
|
184
255
|
`plan --shadow-qa <spec>[,<spec>...] [--shadow-rate R]` (default R = 0.1) sends a sampled
|
package/forge
CHANGED
|
@@ -33,6 +33,18 @@ usage() {
|
|
|
33
33
|
'Runs start from your agent harness (/forge "<goal>" --dwarf <model>); see README.md.'
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
# suggest WORD — the listed command WORD most likely meant (a unique prefix or a close
|
|
37
|
+
# spelling), read from usage so the two never drift apart; prints nothing when none is close
|
|
38
|
+
# or python3 is missing. -I keeps a stray module in the current directory out of it.
|
|
39
|
+
suggest() {
|
|
40
|
+
usage | python3 -I -c 'import difflib, re, sys
|
|
41
|
+
word = sys.argv[1].lower()
|
|
42
|
+
names = re.findall(r"^ ([a-z]+) ", sys.stdin.read(), re.M)
|
|
43
|
+
prefixed = [name for name in names if len(word) >= 2 and name.startswith(word)]
|
|
44
|
+
hit = prefixed if len(prefixed) == 1 else difflib.get_close_matches(word, names, 1, 0.6)
|
|
45
|
+
print(hit[0] if hit else "")' "$1" 2>/dev/null
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
case "${1:-}" in
|
|
37
49
|
install)
|
|
38
50
|
shift
|
|
@@ -87,7 +99,10 @@ case "${1:-}" in
|
|
|
87
99
|
exit 2
|
|
88
100
|
;;
|
|
89
101
|
*)
|
|
90
|
-
printf 'forge: unknown command: %s\n
|
|
102
|
+
printf 'forge: unknown command: %s\n' "$1" >&2
|
|
103
|
+
GUESS="$(suggest "$1")"
|
|
104
|
+
[ -n "$GUESS" ] && printf 'Did you mean `forge %s`?\n' "$GUESS" >&2
|
|
105
|
+
printf '\n' >&2
|
|
91
106
|
usage >&2
|
|
92
107
|
exit 2
|
|
93
108
|
;;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giamme/forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Multi-model coding with a reviewer that reads the code: plan, build in parallel worktrees, review every real diff, merge only what passes.",
|
|
5
5
|
"keywords": ["claude-code", "codex", "opencode", "agent-skill", "code-review", "multi-model"],
|
|
6
6
|
"license": "MIT",
|
package/references/dataset.md
CHANGED
|
@@ -14,6 +14,8 @@ ${FORGE_DATA_HOME:-${XDG_DATA_HOME:-~/.local/share}/forge}/dataset/
|
|
|
14
14
|
errors.log capture failures
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
An empty or relative `XDG_DATA_HOME` counts as unset, as the XDG spec says.
|
|
18
|
+
|
|
17
19
|
Capture is on by default and costs a few milliseconds per execution. `FORGE_DATASET=off`
|
|
18
20
|
turns it off for one invocation.
|
|
19
21
|
|
|
@@ -28,10 +30,10 @@ time order: an attempt that PASSed, then a merge that CONFLICTed, then a human `
|
|
|
28
30
|
| kind | one per | what it holds |
|
|
29
31
|
|---|---|---|
|
|
30
32
|
| `start` | execution | a receipt written by `begin()`. A start with no matching end is an execution that was killed or is still running |
|
|
31
|
-
| `dispatch` | model call | role, alias, harness, **resolved** model, effort after clamping, timeout and its source, delivery (custom or native review), phase timings, exit, timeout, infra class, tokens, parsed verdict and findings, `FORGE_LEARNING` lines
|
|
33
|
+
| `dispatch` | model call | role, alias, harness, **resolved** model, effort after clamping, timeout and its source, delivery (custom or native review), phase timings, exit, timeout, infra class, tokens, parsed verdict and findings, `FORGE_LEARNING` lines, `session_id`, `session_continue`. Blobs: the delivered prompt and the final message |
|
|
32
34
|
| `attempt` | task attempt or solo run | status, attempt number (with refund and resume flags), trigger (`initial`, `auto`, `human`), dwarf and qa spec, declared vs changed files, numstat, drift, guard notes, gate line, patch key, the files this execution wrote and the ones it only carried. Blobs: the diff, retry findings, memory slices, capsule, Jev task files |
|
|
33
35
|
| `verification` / `verify_run` | verify, and each command execution | command and its source (flag, `.forge/verify`, Jev), status, flaky, Jev triage. One `verify_run` per execution, each with its own log |
|
|
34
|
-
| `plan` | `plan` | goal, original request, decompose level, exploratory routing decisions (`tasks[id].route`, with their odds), author (orchestrator or planner), per-task declared vs executed difficulty and who set it, pools, waves, Jev routing and coupling. Blobs: both task tables, prompts, approaches, excerpts of the declared files at plan time, the file inventory, `memory.md` |
|
|
36
|
+
| `plan` | `plan` | goal, original request, decompose level, exploratory routing decisions (`tasks[id].route`, with their odds), author (orchestrator or planner), per-task declared vs executed difficulty and who set it, pools, waves, Jev routing and coupling, options (including `reviewer_session` when that file is present). Blobs: both task tables, prompts, approaches, excerpts of the declared files at plan time, the file inventory, `memory.md` |
|
|
35
37
|
| `run_end` | `run` | results per task, the accepted integration commit, `memory.md` |
|
|
36
38
|
| `invocation` | user-facing `forge-parallel.sh` subcommand | argv, exit, duration, plus final review, accept, integrate and split/combine payloads |
|
|
37
39
|
| `event` | point event | `merge` (MERGED, CONFLICT with the files, INVALIDATED and why), `integrate_advanced`, `integrate_conflict`, `combine_conflict`, `sched_terminal`, `subset_run` (every outcome), `memory_record`, `memory_prune` |
|
|
@@ -42,8 +44,22 @@ time order: an attempt that PASSed, then a merge that CONFLICTed, then a human `
|
|
|
42
44
|
| `jev` | Jev request | site, answers, usage, latency, mode. Blob: the **full request** (state and questions). Its original sha is `jev.jsonl`'s `key` |
|
|
43
45
|
| `fractal_step` / `fractal_node` | Fractal step, node state | decomposition decision, iteration, resolved model, cost, tokens, candidate diff |
|
|
44
46
|
| `ledger` | ledger row | imported `.forge/ledger.tsv` rows |
|
|
47
|
+
| `forecast` | forecast shown by `run` | the plan forecast printed before dispatch (see [plan forecast](#plan-forecast)), once per run and content. Also in the plan as `forecast.json` |
|
|
45
48
|
| `outcome` | integrated task, per horizon | written by `forge dataset outcomes`, never by a run: how many of the lines a task added survive N days after `integrate`, and what reverted it |
|
|
46
49
|
|
|
50
|
+
A dispatch carries `session_id` and `session_continue`. `session_id` comes from the attempt
|
|
51
|
+
dir's `<role>.session`, and from `metrics.json` when that file is absent. `session_continue`
|
|
52
|
+
is null when the attempt dir has no `<role>.continue`. That file is written only when the
|
|
53
|
+
dispatch was asked to continue a reviewer session: `mode=continued` is `continued`, and
|
|
54
|
+
`mode=failed`, a missing mode or any other mode is `failed`. The file's presence means
|
|
55
|
+
continuing was requested, so a failed continue that still printed a verdict is tagged too.
|
|
56
|
+
The `attempt` event's `resumed` flag is a different fact: a QA-only re-run after an
|
|
57
|
+
infrastructure stop (the `tasks/<id>/resume` marker).
|
|
58
|
+
|
|
59
|
+
`plan --reviewer-session` writes `$PLAN/reviewer_session` (`on`). The plan event records
|
|
60
|
+
it in `options`. The file joins the plan id only when it is present, so every plan already
|
|
61
|
+
in a store keeps its id, and turning the experiment on stores a new plan event.
|
|
62
|
+
|
|
47
63
|
Each blob reference carries `sha` (the stored bytes), `original_sha`, `n` (the original
|
|
48
64
|
size), `kept`, and `cut`. A cut keeps the head and the tail, because a QA prompt ends with
|
|
49
65
|
the diff and a log ends with the failure. Caps: prompt 1 MiB, diff 2 MiB, message 256 KiB,
|
|
@@ -117,6 +133,8 @@ forge still running keeps writing.
|
|
|
117
133
|
./forge dataset export [--slice S]... [--limit N] [--seed N] [--out DIR] # labeled decision cases
|
|
118
134
|
./forge dataset outcomes [--horizon-days 14] [--repo PATH] [--dry-run] # what integrated work became
|
|
119
135
|
./forge dataset routing [--repo PATH] [--since DAYS] [--json] # pass rate and runtime per tier and model
|
|
136
|
+
./forge dataset reviewers [--horizon-days 14] [--repo PATH] [--json] # how each reviewer's PASSed work held up
|
|
137
|
+
./forge dataset forecast <plan> [--repo PATH] [--json] # likely cost and first-try PASS rate per task
|
|
120
138
|
```
|
|
121
139
|
|
|
122
140
|
`status` runs a different completeness check for each mode, because each mode
|
|
@@ -185,6 +203,18 @@ finished on an untouched snapshot and reviewed the same commit counts. The prima
|
|
|
185
203
|
labels never change, because `qa_shadow` dispatches are not reviews of record.
|
|
186
204
|
`./forge dataset status` prints the agreement rate, overall and per model pair.
|
|
187
205
|
|
|
206
|
+
A dispatch with `session_continue` set is left out of every slice built from QA rows
|
|
207
|
+
(review cases, citation cases) and out of the shadow agreement pairs. That covers a
|
|
208
|
+
continue that succeeded and one that failed after printing a verdict. A fresh fallback
|
|
209
|
+
dispatch in the same round has `session_continue` null and stays in. The slices have no
|
|
210
|
+
switch that puts the continued reviews back, so a group never mixes the two. Agreement
|
|
211
|
+
still selects the attempt's last verdict-bearing primary, and then drops the pair when
|
|
212
|
+
that primary continued a session: an earlier fresh verdict does not stand in for it.
|
|
213
|
+
`./forge dataset status` prints the number of continued reviews and the number of failed
|
|
214
|
+
continues on their own lines whenever either is non-zero, including in a store with no
|
|
215
|
+
shadow review. The experiment's comparison reads the `dispatch` events (`session_continue`,
|
|
216
|
+
tokens, verdicts, attempts), not these slices.
|
|
217
|
+
|
|
188
218
|
`meta.jsonl` records each case's slice, run, repo and label source. It also holds hosted
|
|
189
219
|
Jev's own answers where forge recorded them, as baselines that are never part of a case.
|
|
190
220
|
Routing and retry cases add:
|
|
@@ -280,6 +310,56 @@ exporter attaches it to that diff's review.
|
|
|
280
310
|
Each verification event also records the commit it verified (`verification.commit`, named
|
|
281
311
|
before any early return), so a FAIL or UNVERIFIED result can be joined to its code too.
|
|
282
312
|
|
|
313
|
+
## Reviewer report card
|
|
314
|
+
|
|
315
|
+
`forge dataset reviewers` joins the survival outcomes above to the reviews that let the work
|
|
316
|
+
through. For each reviewed commit with an outcome at the horizon (`--horizon-days`, default 14,
|
|
317
|
+
only outcomes recorded at exactly that horizon), every primary reviewer (role `qa`) that gave
|
|
318
|
+
that attempt a PASS is credited with one landed outcome. A row is keyed by the reviewer's alias,
|
|
319
|
+
or its model when no alias was recorded, as the forge-ui Models view keys it. A landed outcome
|
|
320
|
+
**did not hold** when a later commit reverted it, or when under 80% of its added lines were kept
|
|
321
|
+
(rewritten): the export's `survives` rule.
|
|
322
|
+
|
|
323
|
+
- The rate of work that did not hold has a Wilson 95% interval, shown only from 10 landed
|
|
324
|
+
outcomes on. Below that the row reads `not enough data (n/10)`, and the JSON has
|
|
325
|
+
`enough: false` with `rate` and `ci` null.
|
|
326
|
+
- A shadow review's PASS decided nothing, so it is not credited. Work that landed after a FAIL
|
|
327
|
+
(the severity gate or a human accepted it) and work with no recorded review are counted as
|
|
328
|
+
unattributed. `lost` and `no_additions` outcomes are skipped.
|
|
329
|
+
- Survival is only followed for parallel integrations, so solo runs and hand merges are not here.
|
|
330
|
+
- It only advises: nothing that routes a task or builds a prompt reads it.
|
|
331
|
+
|
|
332
|
+
forge-ui shows the rate as the Reviewers table's **Did not hold** column, through
|
|
333
|
+
`GET /api/models/reviewers`, which runs this command with `--json`.
|
|
334
|
+
|
|
335
|
+
## Plan forecast
|
|
336
|
+
|
|
337
|
+
`forge dataset forecast <plan>` reads the plan's `tasks.tsv` (the declared tier from
|
|
338
|
+
`tasks.declared.tsv` when there is one) and, for each task not yet MERGED, looks up past tasks
|
|
339
|
+
that ran at the same tier on the same resolved model. The effort must match too when the task's
|
|
340
|
+
spec names one. It uses the same task history as `forge dataset routing`, without the plan's own
|
|
341
|
+
run.
|
|
342
|
+
|
|
343
|
+
- Tokens are input plus output (cached input is not added again), summed over every implement
|
|
344
|
+
and review dispatch of the task, retries included. Time is the sum of those dispatches'
|
|
345
|
+
durations. Both show median and p90 (nearest rank).
|
|
346
|
+
- The first-try PASS rate has a Wilson 95% interval. Tasks below 50% are flagged as likely to
|
|
347
|
+
need a retry. Under 5 past tasks the row reads `not enough history (n/5)` and the JSON has
|
|
348
|
+
`enough: false` with no rate.
|
|
349
|
+
- Totals add up the per-task medians and p90s, the wall time by wave (the slowest task of each
|
|
350
|
+
wave), and the expected number of first-try passes.
|
|
351
|
+
- It never creates the store, and prints nothing when the store is missing or has no history.
|
|
352
|
+
|
|
353
|
+
`forge-parallel.sh run` prints the same block before it dispatches. It runs with a time budget
|
|
354
|
+
(`FORGE_FORECAST_BUDGET_S`, default 3 seconds), prints one line and moves on if reading the
|
|
355
|
+
history takes longer, never prints errors and never changes the exit code. `--no-forecast` or
|
|
356
|
+
`FORGE_FORECAST=off` turn it off. The block is written to the plan as `forecast.json` and
|
|
357
|
+
recorded once as a `forecast` event, so it can later be compared to what the run did.
|
|
358
|
+
|
|
359
|
+
The history is observational: harder tasks tend to go to stronger models, so a rate describes
|
|
360
|
+
what happened to similar tasks, not what a model would do on this one. It only advises: nothing
|
|
361
|
+
that routes a task or builds a prompt reads it.
|
|
362
|
+
|
|
283
363
|
## Importing old runs
|
|
284
364
|
|
|
285
365
|
`import` reads run dirs that predate live capture through the same readers. An execution
|
|
@@ -312,7 +392,8 @@ so the importer will not guess which execution wrote a mutable file:
|
|
|
312
392
|
importer never reads are left out and listed in the excluded file. Importing such an archive maps
|
|
313
393
|
it back to `original_path`, so its run id and uid match the original dir's; an unreadable path
|
|
314
394
|
during archiving is an error, not a silent omission. The archive is written to a temp name, fsynced
|
|
315
|
-
and renamed.
|
|
395
|
+
and renamed. `<role>.session` and `<role>.continue` are plain files in the attempt dir, so a
|
|
396
|
+
run archive carries them and import tags the dispatch from those files.
|
|
316
397
|
|
|
317
398
|
`~/.local/share/forge/legacy/` holds archives of run dirs taken before live capture, with a
|
|
318
399
|
`MANIFEST.tsv`. Passing that dir to `import` reads every archive, plus the ledger copies
|
|
@@ -325,7 +406,8 @@ listed in `ledgers/REPOS.tsv`.
|
|
|
325
406
|
- **Never read by a prompt builder.** Memory inject, the capsule and Ripwire must not read
|
|
326
407
|
the store. A reviewer that could see earlier runs' verdicts would not be independent.
|
|
327
408
|
`tests/test_dataset.py` enforces that outside its own package, the dataset is only
|
|
328
|
-
imported for capture.
|
|
409
|
+
imported for capture. The one read is `run`'s [plan forecast](#plan-forecast), which goes to
|
|
410
|
+
the operator's terminal and `forecast.json`, never into a prompt.
|
|
329
411
|
- **Read-only in your repos.** Capture and the harvester run only object-store git queries
|
|
330
412
|
in a repo: `rev-parse`, `rev-list`, `log`, `show`, `ls-tree`, `cat-file`, `diff` between
|
|
331
413
|
commits and `blame` at a commit. All run with `GIT_OPTIONAL_LOCKS=0`. None takes the
|
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
|