@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.
- package/CHANGELOG.md +205 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/SKILL.md +123 -0
- package/VERSION +1 -0
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +69 -0
- package/docs/commands.md +315 -0
- package/docs/dataset.md +20 -0
- package/docs/decomposed-runs.md +268 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +92 -0
- package/docs/fractal.md +181 -0
- package/docs/install.md +160 -0
- package/docs/memory.md +156 -0
- package/docs/models.md +118 -0
- package/docs/reviews.md +231 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +79 -0
- package/package.json +27 -4
- package/references/dataset.md +329 -0
- package/references/decompose.md +667 -0
- package/references/fractal-dashboard.md +21 -0
- package/references/fractal.md +307 -0
- package/references/harnesses.md +365 -0
- package/references/jev.md +865 -0
- package/references/memory.md +228 -0
- package/references/planning.md +42 -0
- package/references/ripwire.md +47 -0
- package/references/runtime.md +64 -0
- package/references/solo.md +113 -0
- package/references/ui.md +112 -0
- package/registry.tsv +81 -0
- package/scripts/forge-artifact.sh +78 -0
- package/scripts/forge-contract.py +430 -0
- package/scripts/forge-dataset.py +6 -0
- package/scripts/forge-dispatch.sh +671 -0
- package/scripts/forge-fail-first.py +195 -0
- package/scripts/forge-fractal-dispatch.sh +4 -0
- package/scripts/forge-fractal-options.sh +54 -0
- package/scripts/forge-fractal.py +6 -0
- package/scripts/forge-guard.py +266 -0
- package/scripts/forge-install-fractal.sh +4 -0
- package/scripts/forge-install-ripwire.sh +52 -0
- package/scripts/forge-install.sh +135 -0
- package/scripts/forge-jev-options.sh +259 -0
- package/scripts/forge-jev.py +6 -0
- package/scripts/forge-lib-plans.sh +785 -0
- package/scripts/forge-lib-review.sh +494 -0
- package/scripts/forge-lib-task.sh +302 -0
- package/scripts/forge-lib-verify.sh +30 -0
- package/scripts/forge-memory.sh +575 -0
- package/scripts/forge-metrics.sh +39 -0
- package/scripts/forge-parallel-options.sh +76 -0
- package/scripts/forge-parallel.sh +1717 -0
- package/scripts/forge-plan-check.py +118 -0
- package/scripts/forge-prompt.py +45 -0
- package/scripts/forge-ripwire.py +136 -0
- package/scripts/forge-route.py +344 -0
- package/scripts/forge-runs.py +121 -0
- package/scripts/forge-runtime.py +586 -0
- package/scripts/forge-schedule.py +312 -0
- package/scripts/forge-shadow.py +305 -0
- package/scripts/forge-solo.sh +347 -0
- package/scripts/forge-ui-options.sh +42 -0
- package/scripts/forge-ui.py +435 -0
- package/scripts/forge-update.sh +71 -0
- package/scripts/forge-verify-coverage.py +956 -0
- package/scripts/forge-version.py +240 -0
- package/scripts/forge_dataset/__init__.py +439 -0
- package/scripts/forge_dataset/capture.py +330 -0
- package/scripts/forge_dataset/checks.py +109 -0
- package/scripts/forge_dataset/cli.py +233 -0
- package/scripts/forge_dataset/export.py +1009 -0
- package/scripts/forge_dataset/harvest.py +716 -0
- package/scripts/forge_dataset/importer.py +412 -0
- package/scripts/forge_dataset/outcomes.py +275 -0
- package/scripts/forge_dataset/routing.py +278 -0
- package/scripts/forge_fractal/__init__.py +84 -0
- package/scripts/forge_fractal/bridge.py +46 -0
- package/scripts/forge_fractal/cli.py +202 -0
- package/scripts/forge_fractal/dashboard.css +138 -0
- package/scripts/forge_fractal/dashboard.html +41 -0
- package/scripts/forge_fractal/dashboard.js +687 -0
- package/scripts/forge_fractal/decomposition.py +262 -0
- package/scripts/forge_fractal/execution.py +659 -0
- package/scripts/forge_fractal/inspection.py +261 -0
- package/scripts/forge_fractal/install.py +158 -0
- package/scripts/forge_fractal/invocation.py +94 -0
- package/scripts/forge_fractal/pipeline.py +124 -0
- package/scripts/forge_fractal/selection.py +179 -0
- package/scripts/forge_jev/__init__.py +376 -0
- package/scripts/forge_jev/backtest.py +322 -0
- package/scripts/forge_jev/cli.py +980 -0
- package/scripts/forge_jev/client.py +418 -0
- package/scripts/forge_jev/corpus.py +156 -0
- package/scripts/forge_jev/coupling.py +244 -0
- package/scripts/forge_jev/features.py +24 -0
- package/scripts/forge_jev/gates.py +100 -0
- package/scripts/forge_jev/memory.py +163 -0
- package/scripts/forge_jev/outcome.py +49 -0
- package/scripts/forge_jev/predict.py +204 -0
- package/scripts/forge_jev/questions.py +661 -0
- package/scripts/forge_jev/review.py +73 -0
- package/scripts/forge_jev/routing.py +271 -0
- package/scripts/forge_jev/subset.py +123 -0
- package/scripts/forge_jev/verify.py +453 -0
- package/scripts/forge_registry.py +140 -0
- package/ui/README.md +347 -0
- package/ui/THIRD_PARTY_NOTICES.md +177 -0
- package/ui/docs/api.md +970 -0
- package/ui/forge_ui/__init__.py +20 -0
- package/ui/forge_ui/__main__.py +135 -0
- package/ui/forge_ui/diffs.py +454 -0
- package/ui/forge_ui/executions.py +436 -0
- package/ui/forge_ui/export.py +214 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +582 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1424 -0
- package/ui/forge_ui/server.py +312 -0
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +1079 -0
- package/ui/forge_ui/static/cloud3d.js +647 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +397 -0
- package/ui/forge_ui/static/convo.js +295 -0
- package/ui/forge_ui/static/datatable.js +232 -0
- package/ui/forge_ui/static/diffview.js +235 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +67 -0
- package/ui/forge_ui/static/iso.js +816 -0
- package/ui/forge_ui/static/leaderboard.js +311 -0
- package/ui/forge_ui/static/map3d.js +45 -0
- package/ui/forge_ui/static/modelstats.js +249 -0
- package/ui/forge_ui/static/notify.js +179 -0
- package/ui/forge_ui/static/panel.js +562 -0
- package/ui/forge_ui/static/runmodel.js +667 -0
- package/ui/forge_ui/static/scene3d.js +1471 -0
- package/ui/forge_ui/static/search.js +120 -0
- package/ui/forge_ui/static/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +1073 -0
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
# Optional Jev advisory judgments
|
|
2
|
+
|
|
3
|
+
Jev is TypeSafe's "System One" model: it answers narrow, typed questions (a
|
|
4
|
+
yes/no probability, a single choice, an ordered score) over supplied state in
|
|
5
|
+
about 100ms, with calibrated probabilities. Many questions per request evaluate
|
|
6
|
+
in parallel, so asking a few extra costs little additional latency. Forge uses
|
|
7
|
+
Jev to *advise*; Forge keeps every existing decision and gate. Jev never
|
|
8
|
+
replaces model routing logic, test selection, QA acceptance or repository
|
|
9
|
+
verification — it only offers a probability alongside them.
|
|
10
|
+
|
|
11
|
+
## Privacy
|
|
12
|
+
|
|
13
|
+
Off by default, and this is the main reason why. Each enabled capability
|
|
14
|
+
transmits a narrow slice of state to an external API:
|
|
15
|
+
|
|
16
|
+
- **routing** — task prompts and candidate model identifiers
|
|
17
|
+
- **tests** — diff hunks and test names
|
|
18
|
+
- **gates** — diff hunks and file excerpts relevant to the gate
|
|
19
|
+
- **memory** — memory entry text being considered for curation
|
|
20
|
+
|
|
21
|
+
`.forge/` and `.git` are excluded from every payload, in every capability, with
|
|
22
|
+
no exception. The API key lives at
|
|
23
|
+
`${XDG_CONFIG_HOME:-$HOME/.config}/forge/jev.json`, mode `0600`. It is never
|
|
24
|
+
written to a run directory, a prompt, or a log.
|
|
25
|
+
|
|
26
|
+
A site mapped to the Cloudflare provider (see [Cloudflare Clef](#cloudflare-clef-a-second-provider))
|
|
27
|
+
sends the same slice to Cloudflare Workers AI instead of TypeSafe.
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
./forge jev setup # store an API key
|
|
33
|
+
./forge jev status # resolved config, enabled capabilities
|
|
34
|
+
./forge jev doctor [--live] # config/key checks; --live also probes the API
|
|
35
|
+
./forge jev enable [--capability NAME] # persist enabled (optionally scoped)
|
|
36
|
+
./forge jev disable [--capability NAME]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`TYPESAFE_API_KEY` in the environment overrides the stored key for that
|
|
40
|
+
invocation only. Keys come from `console.typesafe.ai`.
|
|
41
|
+
|
|
42
|
+
A second provider, Cloudflare's hosted Clef, is set up with
|
|
43
|
+
`./forge jev setup --provider cloudflare --account-id <id>`; see
|
|
44
|
+
[Cloudflare Clef](#cloudflare-clef-a-second-provider).
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
User-facing:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
forge jev setup # store a key, validate it, enable Jev
|
|
52
|
+
forge jev status | doctor [--live] # what is on; whether it works
|
|
53
|
+
forge jev enable | disable [--capability routing|tests|gates|memory]
|
|
54
|
+
forge jev backtest --repo <path> [--capability tests|drift] [--execute]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`score-plan`, `verify-discover` and `verify-triage` are also subcommands, but they are
|
|
58
|
+
called by `forge-parallel.sh` rather than by hand. They are documented here only so that
|
|
59
|
+
a line in a run log is traceable to the code that produced it.
|
|
60
|
+
|
|
61
|
+
## Gating and precedence
|
|
62
|
+
|
|
63
|
+
Four steps, implemented identically by `scripts/forge-jev-options.sh` (shell)
|
|
64
|
+
and `scripts/forge_jev/` (Python) — keep them in sync:
|
|
65
|
+
|
|
66
|
+
1. `FORGE_JEV=off` wins over everything: everything off, full stop. This is
|
|
67
|
+
the hard kill switch.
|
|
68
|
+
2. Any `FORGE_JEV_<CAP>=on` acts as an allowlist — only those capabilities
|
|
69
|
+
run. `--jev-tests` alone works without `--jev`.
|
|
70
|
+
3. Otherwise `FORGE_JEV=on` (from `--jev`) or the stored config's `enabled`
|
|
71
|
+
flag turns it on, and a capability explicitly set `off` is subtracted.
|
|
72
|
+
4. Default: off.
|
|
73
|
+
|
|
74
|
+
Flags: `--jev` / `--no-jev` (mutually exclusive), `--jev-act`, `--jev-shadow`,
|
|
75
|
+
and `--jev-<cap>` / `--no-jev-<cap>` for `routing`, `tests`, `gates`, `memory`.
|
|
76
|
+
A run directory freezes its resolved selection in `jev-selection.json`, the
|
|
77
|
+
same way Fractal freezes `fractal-selection.json`, so resume and explicit
|
|
78
|
+
retry make the same decisions as the original run.
|
|
79
|
+
|
|
80
|
+
## Advisory, always
|
|
81
|
+
|
|
82
|
+
Jev never selects a model, never changes a task's tier, never overturns a
|
|
83
|
+
`FORGE_VERDICT`, and never marks anything verified. `--jev-shadow` records
|
|
84
|
+
judgments and changes nothing at all.
|
|
85
|
+
|
|
86
|
+
`--jev-act` still parses, so old commands and a frozen `jev-selection.json` keep
|
|
87
|
+
working, and it still marks each judgment `acting` in `jev.jsonl` and the run
|
|
88
|
+
dataset. Nothing reads it as permission: the one thing it unlocked, writing Jev's
|
|
89
|
+
tier into the `difficulty` column, was removed on 2026-10-17 (see Routing).
|
|
90
|
+
|
|
91
|
+
## Fail-open
|
|
92
|
+
|
|
93
|
+
Any error, timeout, or missing key is logged as a skip reason and the run
|
|
94
|
+
proceeds exactly as it would without Jev. A Jev network call can never fail a
|
|
95
|
+
run.
|
|
96
|
+
|
|
97
|
+
## Observability
|
|
98
|
+
|
|
99
|
+
- `<run-dir>/jev.jsonl` — one row per request: site, question ids, answers,
|
|
100
|
+
confidences, latency, tokens, and whether the judgment was applied.
|
|
101
|
+
- `<run-dir>/jev.skip` — present when a request was skipped, with the reason.
|
|
102
|
+
|
|
103
|
+
## Backtest
|
|
104
|
+
|
|
105
|
+
`forge jev backtest` measures whether Jev's `test_relevance` rubric (see
|
|
106
|
+
`scripts/forge_jev/questions.py`) picks the right tests for a change, before
|
|
107
|
+
that judgment is ever wired into a live decision.
|
|
108
|
+
|
|
109
|
+
The label comes from git history, not from a model: a commit where a human
|
|
110
|
+
changed source and test files together is treated as ground truth — "these
|
|
111
|
+
are the tests a person judged relevant." Non-circular because nothing Jev (or
|
|
112
|
+
any model) produced enters the label. `scripts/forge_jev/corpus.py` extracts
|
|
113
|
+
these (change, tests-touched) pairs from `git log`.
|
|
114
|
+
|
|
115
|
+
Candidates for a commit are the test files present in its **parent** tree, not
|
|
116
|
+
at HEAD. Scoring against HEAD would ask about tests that did not exist yet:
|
|
117
|
+
measured on one repo, a commit 300 back had 94 test files in tree against 416
|
|
118
|
+
at HEAD, which would inflate the suite-reduction metric more than fourfold.
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
./forge jev backtest --repo PATH --limit 50 # dry estimate, no API call
|
|
122
|
+
./forge jev backtest --repo PATH --execute --max-requests 200
|
|
123
|
+
./forge jev backtest --repo PATH --execute --capability drift --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`--execute` is required before backtest makes a single API call; without it,
|
|
127
|
+
the command only estimates the run (commit count, request count) and says so.
|
|
128
|
+
`--max-requests` caps spend by bounding how many Jev requests the run is
|
|
129
|
+
allowed to make, regardless of `--limit`. Results land under `--out` (default:
|
|
130
|
+
a directory under the run's own temp/output location) as raw judgments plus
|
|
131
|
+
the printed summary — precision/recall of the rubric's "true" answers against
|
|
132
|
+
the commit's actual test changes.
|
|
133
|
+
|
|
134
|
+
The summary sweeps thresholds and reports micro/macro recall, precision,
|
|
135
|
+
suite reduction, and `records_with_full_recall` — the fraction of commits where
|
|
136
|
+
*every* labeled test was selected. That last one is the number that decides
|
|
137
|
+
whether the judgment is safe to act on: a selector averaging 95% recall by
|
|
138
|
+
missing one test in every commit is useless. For `--capability drift` the
|
|
139
|
+
headline is instead precision at the warn threshold, because a warning that
|
|
140
|
+
fires on files nobody edits trains people to ignore it.
|
|
141
|
+
|
|
142
|
+
Three honesty caveats, printed with every run so they travel with the numbers:
|
|
143
|
+
|
|
144
|
+
- Commit messages are written **after** the work and often name the very files
|
|
145
|
+
or behaviour that changed; a Forge task prompt is written before. Backtest
|
|
146
|
+
scores are therefore an **upper bound** on live performance.
|
|
147
|
+
- A commit's test changes are a **lower bound** on the tests that were
|
|
148
|
+
actually relevant — humans miss tests too. High recall against this label
|
|
149
|
+
is necessary to trust the rubric, but not sufficient; it cannot prove the
|
|
150
|
+
rubric would catch tests no human thought to touch. (This one does not apply
|
|
151
|
+
to `--capability drift`, whose label is the commit's complete file set.)
|
|
152
|
+
- Co-change encodes **one team's testing habits and repo layout**, not a
|
|
153
|
+
universal property of the change. A number measured on one repo does not
|
|
154
|
+
transfer to another's thresholds without re-measuring there. Measured here:
|
|
155
|
+
labeled tests are 1.6% of the candidate suite in one repo and 13.2% in
|
|
156
|
+
another, which are not the same problem.
|
|
157
|
+
|
|
158
|
+
## Verification (the first live capability)
|
|
159
|
+
|
|
160
|
+
Two judgments inside `verify_result`, the decomposed runner's verification step.
|
|
161
|
+
Both are off unless the `tests` capability is enabled and a key is configured; with
|
|
162
|
+
Jev inactive the function behaves exactly as it did before.
|
|
163
|
+
|
|
164
|
+
**Finding a command.** Only when neither `--verify` nor `.forge/verify` exists — the
|
|
165
|
+
case that reports `UNVERIFIED` today even when QA passed. Code enumerates commands
|
|
166
|
+
that *demonstrably exist* in the repo (`package.json` scripts, Makefile targets,
|
|
167
|
+
tox/nox, runner scripts under `tests/`, `scripts/`, `bin/`, a configured pytest,
|
|
168
|
+
`go.mod`, `Cargo.toml`, `run:` steps in GitHub workflows, and facts recorded under the
|
|
169
|
+
`## Verify` heading of `.forge/memory.md`); Jev picks one, or `none`. Each candidate is
|
|
170
|
+
sent with its source and an excerpt of what it actually runs — a make target's recipe
|
|
171
|
+
rather than the head of the Makefile — because the command name alone does not say
|
|
172
|
+
whether it runs the suite. It selects, never generates, so it cannot propose `make test`
|
|
173
|
+
for a repo with no Makefile. A non-executable runner is
|
|
174
|
+
offered with its interpreter — forge's own `tests/check.sh` is mode 644 and its README
|
|
175
|
+
says `bash tests/check.sh`.
|
|
176
|
+
|
|
177
|
+
The chosen command is announced through `note` before it runs, recorded with its
|
|
178
|
+
confidence and evidence in `<out>/verification.jev.json`, and then flows through the
|
|
179
|
+
existing run/fingerprint/status logic unchanged. `UNVERIFIED` remains the outcome
|
|
180
|
+
whenever nothing is chosen: Jev may find a command, never declare something verified.
|
|
181
|
+
|
|
182
|
+
**Re-running a corroborated flake.** On a non-zero exit, Jev classifies the failure as
|
|
183
|
+
`real_regression`, `known_flake`, `environment` or `generated_drift`. The command is
|
|
184
|
+
re-run **once**, and only when all of these hold:
|
|
185
|
+
|
|
186
|
+
- the exit was non-zero *and* the tree did not change — a tree change is never a flake;
|
|
187
|
+
- the verdict is `known_flake` at or above the `flake_act` threshold (0.90 by default);
|
|
188
|
+
- a `trap` already recorded in `.forge/memory.md` shares a distinctive token with this
|
|
189
|
+
failure's output.
|
|
190
|
+
|
|
191
|
+
That last condition is deterministic and computed in code, not asked of the model. A
|
|
192
|
+
model alone calling a failure a flake is how a real regression ships, and a trap noted
|
|
193
|
+
months ago must not excuse an unrelated failure today. If the re-run also fails the
|
|
194
|
+
status stays `FAIL` and the first log is preserved. `real_regression` is never re-run
|
|
195
|
+
at any confidence.
|
|
196
|
+
|
|
197
|
+
**Interaction with the deterministic rerun.** `forge-parallel.sh` now reruns *any* failed
|
|
198
|
+
verify command once by default (`--verify-retries`, default `1`) when the tree did not
|
|
199
|
+
change, with no model and no recorded trap needed: the rerun itself is the evidence, and a pass
|
|
200
|
+
is reported loudly as `FLAKY` (`verification.flaky`) rather than as a clean pass. That rerun
|
|
201
|
+
comes first, so Jev's triage above only runs when the deterministic one is disabled
|
|
202
|
+
(`--verify-retries 0`) — in which case the corroboration requirements above still apply
|
|
203
|
+
unchanged. A deterministic rerun that also fails ends in `FAIL` without consulting Jev.
|
|
204
|
+
|
|
205
|
+
## Calibration (measured 2026-09-18)
|
|
206
|
+
|
|
207
|
+
First measurements against the live API. Two repos for test selection, 18 for verify
|
|
208
|
+
discovery. Every number below is from this machine's repos and does not transfer —
|
|
209
|
+
re-measure before trusting a threshold elsewhere.
|
|
210
|
+
|
|
211
|
+
### `tests_act` = 0.70 is supported by evidence, not a guess
|
|
212
|
+
|
|
213
|
+
Discovery was run over every git repo on this machine (18 with candidates). The pick
|
|
214
|
+
was compared against the command each repo's own README or `package.json` documents.
|
|
215
|
+
|
|
216
|
+
| | |
|
|
217
|
+
|---|---|
|
|
218
|
+
| Fires at 0.70 | 10 of 18, **every pick correct** |
|
|
219
|
+
| Lowest correct pick that fires | 0.73 (`ctrlrisk-tools-redigo`) |
|
|
220
|
+
| Highest *incorrect* pick | 0.55 (`spankai` → `cargo test`) |
|
|
221
|
+
| Margin around the threshold | **0.18** |
|
|
222
|
+
|
|
223
|
+
`spankai` is the one confirmed wrong pick: a Rust + TypeScript monorepo whose README
|
|
224
|
+
says `npm run check`, where Jev chose `cargo test` — a real test command that covers
|
|
225
|
+
only the Rust half. The gate blocked it. Lowering the threshold to 0.58 would add four
|
|
226
|
+
correct picks but leave only a 0.03 margin above that wrong one, so 0.70 stays.
|
|
227
|
+
|
|
228
|
+
Confidence tracks how ambiguous the candidate set is, not how likely the pick is to be
|
|
229
|
+
wrong: repos with 2 candidates land at 0.73–0.81, repos with 11–25 land at 0.58–0.61.
|
|
230
|
+
This is why the gate was not `runs_tests`, which sat at 0.79–0.98 for every repo
|
|
231
|
+
including the wrong `cargo test` at 0.97. `runs_tests` answers "is this a test command" —
|
|
232
|
+
true of `cargo test` — and is nearly useless as a gate.
|
|
233
|
+
|
|
234
|
+
Repeating the identical request three times gives a spread of 0.02–0.08 and never
|
|
235
|
+
changes the pick, so these numbers are reproducible rather than one lucky sample.
|
|
236
|
+
|
|
237
|
+
#### The gate is no longer Choice confidence, because that answers the wrong question
|
|
238
|
+
|
|
239
|
+
Confidence measures *which* option wins. For a selection among several **acceptable**
|
|
240
|
+
options that is the wrong quantity: two commands that both run the suite split the mass
|
|
241
|
+
and depress it, and a tie is not doubt. Measured live on the jevscope repo, three times
|
|
242
|
+
running — `bash scripts/check.sh` and `make test` both run its suite —
|
|
243
|
+
|
|
244
|
+
| | |
|
|
245
|
+
|---|---|
|
|
246
|
+
| Choice confidence | **0.48–0.52**, against a 0.70 gate |
|
|
247
|
+
| Mass on `none` | **0.01–0.02** |
|
|
248
|
+
| Actual state of the repo | 48 tests, all passing |
|
|
249
|
+
|
|
250
|
+
So forge reported `UNVERIFIED` for a verifiable repo while the model was 98% sure some
|
|
251
|
+
listed command verified it. The gate now reads **1 − P(none)** — the probability that the
|
|
252
|
+
repo is verifiable at all, which is what `tests_act` was always meant to bound. A split
|
|
253
|
+
between correct commands leaves it high; genuine doubt piles onto `none` and drops it,
|
|
254
|
+
and the winner is then just the tie-break. `client.probabilities` exposes the
|
|
255
|
+
distribution the response was already returning and `choice()` was discarding. When a
|
|
256
|
+
response omits it, the old confidence gate stands — a missing field is not certainty.
|
|
257
|
+
|
|
258
|
+
#### …and 0.70 itself is now stale, for either gate
|
|
259
|
+
|
|
260
|
+
`spankai` is the one confirmed wrong pick in the table above: `cargo test` covers only
|
|
261
|
+
the Rust half of a Rust + TypeScript monorepo, and at 0.55 the 0.70 gate blocked it.
|
|
262
|
+
**It no longer does.** Re-run today it scores 0.77–0.81, which clears 0.70 on the old
|
|
263
|
+
gate as well as the new one.
|
|
264
|
+
|
|
265
|
+
The cause is measurable rather than guessed. Suppressing `_evidence_excerpt` — restoring
|
|
266
|
+
the state as it was when 0.55 was recorded — reproduces 0.52–0.59; restoring it gives
|
|
267
|
+
0.77–0.81. Sending a `make` target's recipe raised confidence across the board, and it
|
|
268
|
+
raised the wrong pick with everything else.
|
|
269
|
+
|
|
270
|
+
So the whole table above was measured against a state production no longer produces, for
|
|
271
|
+
the seventh time in this file. `tests_act` needs re-measuring across those 18 repos in
|
|
272
|
+
the current state before 0.70 means anything, and **no number should be moved until it
|
|
273
|
+
is** — fitting a new one to the single data point above is exactly the mistake being
|
|
274
|
+
recorded here.
|
|
275
|
+
|
|
276
|
+
### Test selection: no single threshold works across repos
|
|
277
|
+
|
|
278
|
+
`backtest --capability tests` over 25 commits of `ctrlrisk-tools-enrich` (40 requests)
|
|
279
|
+
and all 44 of `spankai` (44 requests), sweeping the per-file relevance probability:
|
|
280
|
+
|
|
281
|
+
| repo | highest threshold holding recall ≥ 0.95 | suite reduction there |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `ctrlrisk-tools-enrich` | 0.20 | 46% |
|
|
284
|
+
| `spankai` | 0.45 | 36% |
|
|
285
|
+
|
|
286
|
+
The safe threshold differs by 2.25× between two repos, which settles the question of
|
|
287
|
+
whether one global default could serve: it cannot. Any future test-subsetting capability
|
|
288
|
+
must calibrate per repo and store the result, not ship a constant. Note also that no
|
|
289
|
+
answer ever exceeded 0.90 — the top of the probability range is unused by this rubric,
|
|
290
|
+
so a threshold set there silently disables the capability.
|
|
291
|
+
|
|
292
|
+
### A rejected change, recorded so it is not retried
|
|
293
|
+
|
|
294
|
+
Feeding each repo's documented commands (README, CONTRIBUTING, AGENTS.md) into
|
|
295
|
+
discovery state looked obviously right and **measured worse**. It fixed `spankai`'s pick
|
|
296
|
+
but cut repos firing at 0.70 from 10 to 3, and it destabilised the judgment: against a
|
|
297
|
+
baseline spread of 0.02–0.08, `ctrlrisk-intelligence` fell from a steady 0.81/0.81/0.83
|
|
298
|
+
on the correct command to 0.30 on a narrow migration check. A README lists many
|
|
299
|
+
commands and nothing marks which one is the gate, so the evidence is mostly noise.
|
|
300
|
+
Half the damage was a prompt artifact — naming the state key when the list was empty
|
|
301
|
+
cost 0.12–0.21 confidence on every repo that documents nothing, because "no candidate
|
|
302
|
+
matches" reads as evidence against rather than absence of evidence. Fixing that
|
|
303
|
+
recovered those repos exactly, and the doc-rich repos stayed erratic. Reverted.
|
|
304
|
+
|
|
305
|
+
## Routing (shadow-first)
|
|
306
|
+
|
|
307
|
+
Five `Score` rubrics per task, all in one request, evaluated in parallel. **This code,
|
|
308
|
+
not the model, turns them into a tier** — `forge_jev/routing.py` holds the weights, so
|
|
309
|
+
re-tuning them against accrued outcomes replays recorded scores instead of paying for
|
|
310
|
+
inference again.
|
|
311
|
+
|
|
312
|
+
| Rubric | Weight | Why |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| design judgment | 0.35 | `decompose.md` defines difficulty by this first |
|
|
315
|
+
| state & concurrency subtlety | 0.25 | that table names it as what makes a ten-line diff `high` |
|
|
316
|
+
| blast radius | 0.20 | how far a mistake reaches |
|
|
317
|
+
| spec clarity | 0.15 | is the approach given, or must it be invented |
|
|
318
|
+
| existing test coverage | **−0.05** | inverted: good coverage lowers the tier |
|
|
319
|
+
|
|
320
|
+
Coverage is weighted lightly on purpose. It does not reduce the judgment a task demands,
|
|
321
|
+
only what a mistake costs, and letting it dominate would send a genuinely hard change to
|
|
322
|
+
a cheap model because the area happens to be well tested.
|
|
323
|
+
|
|
324
|
+
The planner's own difficulty rating is **not** sent. Jev is a second opinion on the same
|
|
325
|
+
evidence; showing it the answer first buys an agreement rate, not a judgment.
|
|
326
|
+
|
|
327
|
+
**Escalation.** A composite within 0.05 of a tier boundary, or a confidence below 0.55,
|
|
328
|
+
escalates one tier and records why (`boundary` / `low-confidence`). Paying for a stronger
|
|
329
|
+
model on an ambiguous task is the cheap error.
|
|
330
|
+
|
|
331
|
+
### Two modes, and why the tier is never written
|
|
332
|
+
|
|
333
|
+
| Mode | What happens |
|
|
334
|
+
|---|---|
|
|
335
|
+
| `--jev-shadow` | Scores every task, writes `jev-routing.tsv` and `tasks/<id>/jev.json`, **changes nothing else** — not the table, not one dispatch |
|
|
336
|
+
| `--jev` (default) | Adds an advisory line per task; the `difficulty` column is untouched |
|
|
337
|
+
|
|
338
|
+
A third mode, `--jev-act`, could write the tier into `difficulty` once a repo held a
|
|
339
|
+
calibration for that tier (`forge jev calibrate --write`: 30 tasks, a 95% Wilson lower
|
|
340
|
+
bound over 0.80). No repo ever reached it, so it never acted, and on 2026-10-17 it was
|
|
341
|
+
removed along with `calibrate` and the `routing_act` and `calibrate_floor` thresholds.
|
|
342
|
+
|
|
343
|
+
The run dataset had settled the question by then. Jev's score predicts a task's first
|
|
344
|
+
pass about as well as cheap features do (AUC 0.69 against 0.64–0.67 on older runs; on the
|
|
345
|
+
newest runs every method, Jev included, sat near chance), and a model-swap test on Clef
|
|
346
|
+
showed a decision model judging how hard a task is, not which model does it better:
|
|
347
|
+
swapping the model explained 2% of the variance in its predictions. A tier is a choice of model, so it is measured instead: exploratory routing,
|
|
348
|
+
on by default, and `forge dataset routing` compare models per tier from randomized
|
|
349
|
+
assignments (see `decompose.md`, Exploratory routing).
|
|
350
|
+
|
|
351
|
+
### What was measured
|
|
352
|
+
|
|
353
|
+
Six real tasks from this repo, hand-labelled first, scored live:
|
|
354
|
+
|
|
355
|
+
- **5 of 6 matched.** The sixth was a boundary escalation `medium`→`high`, the intended
|
|
356
|
+
conservative direction.
|
|
357
|
+
- Confidence is high on easy work (0.89–0.93) and lower on hard work (0.67–0.76). That
|
|
358
|
+
asymmetry is the useful one: Jev is confident exactly where routing *down* saves money,
|
|
359
|
+
and defers to the human where getting it wrong is expensive.
|
|
360
|
+
- End to end, a spelling fix declared `high` was routed to the cheap pool at 0.864 while a
|
|
361
|
+
concurrency redesign stayed on the expensive one.
|
|
362
|
+
|
|
363
|
+
Composite confidence is the **weight-weighted** mean, not the minimum. The minimum was
|
|
364
|
+
tried first and measured wrong: on a real task `blast_radius` returned 0.0 while
|
|
365
|
+
`design_judgment` (weight 0.35) was 0.94, and the minimum reported 0.0 for a composite
|
|
366
|
+
that was mostly settled. The weakest dimension is still reported separately as `weakest`.
|
|
367
|
+
|
|
368
|
+
Six hand-labelled tasks is **not** a calibration. It is a smoke test that the rubrics
|
|
369
|
+
point the right way.
|
|
370
|
+
|
|
371
|
+
## Pre-dispatch gates
|
|
372
|
+
|
|
373
|
+
Three plan-time warnings. All advisory, all fail-open, none can block a dispatch — a gate
|
|
374
|
+
that can stop a dispatch is a gate that will eventually stop a good one.
|
|
375
|
+
|
|
376
|
+
**Prompt adequacy** and **independent verifiability** ride inside the routing request.
|
|
377
|
+
They need exactly the state routing already sends, and questions in one request evaluate
|
|
378
|
+
in parallel, so they cost **no extra round trip**.
|
|
379
|
+
|
|
380
|
+
**`files` drift prediction** needs a different universe — every tracked file, not the task
|
|
381
|
+
text — so it is the one extra request Phase 4 adds, and only when the `gates` capability
|
|
382
|
+
is on. `decompose.md` calls the `files` column "a promise, not a prediction"; Forge
|
|
383
|
+
already detects a broken promise in `drift.txt`, but only after the wave has run. Asking
|
|
384
|
+
at plan time turns a post-mortem into a warning while the plan can still change.
|
|
385
|
+
|
|
386
|
+
### Each rubric has its own threshold, because they are not on one scale
|
|
387
|
+
|
|
388
|
+
This is the same trap `tests_act` set earlier, and it was measured rather than guessed.
|
|
389
|
+
|
|
390
|
+
| Gate | Threshold | Evidence |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| drift | `gate_warn` 0.60 | 50 commits × 2 repos: precision **1.000** and **0.934** |
|
|
393
|
+
| prompt adequacy | `prompt_warn` 0.55 | unjudgeable prompts **0.06–0.27**, prompts stating checkable behaviour **0.85–0.96** |
|
|
394
|
+
| independent verifiability | `verifiable_warn` 0.55 | fragments **0.26–0.65**, self-contained tasks **0.65–0.96** — precision gate, see below |
|
|
395
|
+
|
|
396
|
+
Both text gates were first measured at a time when `prompt.md` did not reach the model:
|
|
397
|
+
`score_task` sent only the task title. Those numbers (vague 0.04, specific 0.55–0.87)
|
|
398
|
+
described data production never produced, and both thresholds were fitted to them.
|
|
399
|
+
|
|
400
|
+
Re-measured with the real state — three runs over eight probe prompts against a real
|
|
401
|
+
repo, plus the eight prompts of a real plan:
|
|
402
|
+
|
|
403
|
+
**`prompt_adequacy` separates cleanly.** "Make the error messages better" scores 0.06;
|
|
404
|
+
"harden the server — fix what you find" 0.15; "make it faster without breaking anything"
|
|
405
|
+
0.27. A prompt that states checkable behaviour scores 0.85–0.96, and so did every one of
|
|
406
|
+
the eight real task prompts. A 0.58-wide gap, so the threshold sits in the middle of it
|
|
407
|
+
with 0.28 clear on each side. The old 0.30 classified every case correctly too, but sat
|
|
408
|
+
**0.03** above the worst true positive — correct by luck rather than by margin, against a
|
|
409
|
+
measured repeat-noise of 0.07.
|
|
410
|
+
|
|
411
|
+
**`independently_verifiable` was unanswerable, and rewording it was never going to
|
|
412
|
+
help.** The rubric asked whether confirming a task requires "work declared in a different
|
|
413
|
+
task" — and `score_task` sent no other task. The model was asked about a plan it had
|
|
414
|
+
never been shown, so it could only guess whether such a sibling existed. That is why the
|
|
415
|
+
classes overlapped (0.34–0.56 against 0.43–0.90), and it is the same failure as
|
|
416
|
+
`prompt.md`: a rubric scored against evidence that never reached it.
|
|
417
|
+
|
|
418
|
+
The fix is state, not wording. `routing._siblings` now sends every other task in the plan
|
|
419
|
+
— id, title, file scope, declared deps, capped at `MAX_SIBLINGS` — plus the task's own
|
|
420
|
+
`deps`, and the rubric points at that list by name.
|
|
421
|
+
|
|
422
|
+
Re-measured on the eight tasks of the real jevscope plan, eight repeats on every task the
|
|
423
|
+
threshold depends on. Ground truth was taken from the **import graph and wave position**,
|
|
424
|
+
not from the task titles — which corrected the labelling, not the model:
|
|
425
|
+
|
|
426
|
+
| task | score | truth |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `server` | 0.26–0.34 | fragment: wave 1, imports `discover`/`events`/`stats` and serves `page`'s `index.html`, **none declared** |
|
|
429
|
+
| `page` | 0.43–0.53 | fragment: consumes `server`'s API; both defects that shipped were `page`↔`events` shape mismatches |
|
|
430
|
+
| `readme` | 0.60–0.65 | fragment: documents the wave-2 CLI, and shipped wrong |
|
|
431
|
+
| `cli` | 0.65–0.75 | verifiable: wave 2, so everything it needs has already landed |
|
|
432
|
+
| `stats`, `events`, `discover`, `fixtures` | 0.67–0.96 | verifiable: own file set, own test file |
|
|
433
|
+
|
|
434
|
+
**At the old 0.38 this gate caught nothing on a real plan** — every genuine fragment
|
|
435
|
+
scored above it — exactly as wave coupling had never fired. 0.55 catches `server` with
|
|
436
|
+
0.21 to spare and `page` with 0.02, and keeps **0.10** to the nearest true negative,
|
|
437
|
+
which is the only margin here that beats the 0.07 noise floor. An advisory gate has to
|
|
438
|
+
hold precision above all: one that cries wolf is one people switch off.
|
|
439
|
+
|
|
440
|
+
The classes still touch at 0.65, where `readme` meets `cli`. So this is a precision gate
|
|
441
|
+
that buys a clean left-hand side by giving up `readme` entirely, and **silence above 0.55
|
|
442
|
+
still means "no evidence", not "verifiable"**.
|
|
443
|
+
|
|
444
|
+
Using the single `gate_warn` of 0.60 for all three — which is what shipped first — warned
|
|
445
|
+
on 5 of 9 and 5 of 11 perfectly good tasks in the hand-labelled set. It was caught by a
|
|
446
|
+
live plan where a precisely specified task tripped both text gates. Precision is the only
|
|
447
|
+
number that matters for a warning: one that fires on good work teaches people to ignore
|
|
448
|
+
every future warning.
|
|
449
|
+
|
|
450
|
+
Drift is the strongest of the three and the only one measured at corpus scale. Its
|
|
451
|
+
threshold sits comfortably clear of the noise on real repos (non-edited files scored
|
|
452
|
+
≤0.23 in a live check where the true file scored 0.65), but note that 0.65 is not a wide
|
|
453
|
+
margin — a borderline file can fall either side between runs.
|
|
454
|
+
|
|
455
|
+
The verifiability rubric was nearly discarded on a first reading that showed it failing
|
|
456
|
+
to separate vague prompts from clear ones. That was a bad label, not a bad rubric:
|
|
457
|
+
vagueness is not the same property as needing a sibling task to land first. Against the
|
|
458
|
+
right labels it separates with a 0.27 margin.
|
|
459
|
+
|
|
460
|
+
## Post-review annotation
|
|
461
|
+
|
|
462
|
+
When a reviewer returns `FORGE_VERDICT: FAIL`, Jev reads the review against the diff and
|
|
463
|
+
says whether the failure looks sound. **It cannot change the verdict, and no caller is
|
|
464
|
+
given a way to.** The status file is written before this runs and nothing here rewrites
|
|
465
|
+
it; a test asserts that structurally, by reading the shipped block and failing if a
|
|
466
|
+
status write ever appears inside it.
|
|
467
|
+
|
|
468
|
+
That restraint is the point. Forge's second invariant is reviewer independence, and a
|
|
469
|
+
model that could talk a reviewer out of a FAIL would produce confident PASSes on code
|
|
470
|
+
nobody checked — worse than having no reviewer. The asymmetry runs the other way too:
|
|
471
|
+
nothing ever annotates a PASS into looking suspect, because a PASS is the outcome that
|
|
472
|
+
gets merged and its reviewer's judgement is left alone.
|
|
473
|
+
|
|
474
|
+
Two questions, one request:
|
|
475
|
+
|
|
476
|
+
| | |
|
|
477
|
+
|---|---|
|
|
478
|
+
| **correctness** | does the review name a real bug, or only preferences about how the code is written? The QA prompt already says "style nits are not failures" |
|
|
479
|
+
| **citation** | do the files, lines and quoted code the review cites actually appear in this diff? |
|
|
480
|
+
|
|
481
|
+
Measured on three hand-written reviews against one diff:
|
|
482
|
+
|
|
483
|
+
| review | correctness | citation | flagged |
|
|
484
|
+
|---|---|---|---|
|
|
485
|
+
| style nits only ("`i` should be `item`") | 0.21 | 0.80 | yes — style |
|
|
486
|
+
| a real bug (discount below zero, with a triggering input) | 0.96 | 0.88 | no |
|
|
487
|
+
| cites `src/payment.py`, absent from the diff | 0.45 | 0.02 | yes — citation |
|
|
488
|
+
|
|
489
|
+
A low citation score suppresses the correctness reason rather than printing both. The
|
|
490
|
+
hallucinated review scored 0.45 on correctness and would have been reported as "style
|
|
491
|
+
preferences", which describes the wrong problem: nothing can be said about the
|
|
492
|
+
correctness of code that is not there.
|
|
493
|
+
|
|
494
|
+
## Wave coupling
|
|
495
|
+
|
|
496
|
+
`compute_waves` bounds parallelism structurally: two tasks share a wave only when their
|
|
497
|
+
declared file sets are disjoint. Disjointness cannot see the case where task A changes an
|
|
498
|
+
interface task B consumes — both diffs are individually correct, both pass review against
|
|
499
|
+
their own baseline, and the merge is broken. This asks about exactly that.
|
|
500
|
+
|
|
501
|
+
**It never fired once, on any plan, until it was measured.** It borrowed `gate_warn`
|
|
502
|
+
(0.60) while coupling probabilities live around 0.09–0.38. That was not a strict
|
|
503
|
+
threshold; it was an unreachable one.
|
|
504
|
+
|
|
505
|
+
Measured over 11 same-wave pairs from a real 8-task plan, five runs each, with ground
|
|
506
|
+
truth taken from the run itself — two of those pairs produced a defect that actually
|
|
507
|
+
shipped:
|
|
508
|
+
|
|
509
|
+
| | median |
|
|
510
|
+
|---|---|
|
|
511
|
+
| `events`+`fixtures` — fixtures reads an event's answers object | **0.27** |
|
|
512
|
+
| `events`+`page` — page renders normalised events | **0.25** |
|
|
513
|
+
| `page`+`stats` — page rendered a repeats shape stats never emitted *(shipped bug)* | **0.23** |
|
|
514
|
+
| `events`+`stats` — stats consumes `normalise()`'s output shape | **0.22** |
|
|
515
|
+
| `discover`+`readme` — unrelated | 0.18 |
|
|
516
|
+
| `discover`+`server`, `readme`+`fixtures` — unrelated | 0.13–0.14 |
|
|
517
|
+
| `discover`+`fixtures`, `fixtures`+`page`, `discover`+`page` — unrelated | 0.09–0.10 |
|
|
518
|
+
|
|
519
|
+
Two changes came out of it.
|
|
520
|
+
|
|
521
|
+
**The requirements now reach the rubric.** It used to see `id (title) files: …` and
|
|
522
|
+
nothing else, and in that state it had *no signal at all*: the pairs that produced real
|
|
523
|
+
defects scored lowest of everything, below pairs that were entirely unrelated. An
|
|
524
|
+
interface dependency between two tasks is written down in `prompt.md` and nowhere else —
|
|
525
|
+
certainly not in a one-line title. File *contents* are still not sent; diffs nobody has
|
|
526
|
+
written yet would be noise.
|
|
527
|
+
|
|
528
|
+
**`coupling_warn` is 0.20, and at most three pairs are reported.** The threshold decides
|
|
529
|
+
whether anything is worth saying; the ordering decides what gets said. Rank is the real
|
|
530
|
+
signal — the coupled pairs took four of the top five places — while the absolute scores
|
|
531
|
+
move with the plan: the same rubric sat at 0.09–0.27 in the measurement and 0.20–0.38 on
|
|
532
|
+
the live plan, where a bare 0.20 warned about 12 of 21 pairs. A gate that fires on more
|
|
533
|
+
than half of everything teaches people to ignore it.
|
|
534
|
+
|
|
535
|
+
On the live plan it now names three pairs, all genuinely coupled, `page`+`server` highest
|
|
536
|
+
— the page consumes the server's API contract. On a plan of eight unrelated tasks it says
|
|
537
|
+
nothing.
|
|
538
|
+
|
|
539
|
+
### The ordering half: `forward_dependency`
|
|
540
|
+
|
|
541
|
+
The other shipped defect was `readme` documenting a CLI interface that `cli` had not
|
|
542
|
+
built yet. Those two were in *different waves*, so they were never a candidate pair, and
|
|
543
|
+
no same-wave check can see that shape. It is a different failure from coupling, too:
|
|
544
|
+
not a broken merge between concurrent tasks, but a task reviewed, verified and merged
|
|
545
|
+
**before the thing it was written against exists** — so its own review cannot catch it,
|
|
546
|
+
and the error ships.
|
|
547
|
+
|
|
548
|
+
`forward_pairs` forms ordered `(earlier, later)` pairs across waves, skipping any pair
|
|
549
|
+
where LATER is named in EARLIER's declared deps — forge would not have placed EARLIER
|
|
550
|
+
first in that case, so the risk cannot arise. Cross-wave pairs get their own
|
|
551
|
+
`MAX_FORWARD_PAIRS` budget rather than sharing `MAX_PAIRS`, or one large first wave would
|
|
552
|
+
spend the whole allowance inside itself and never ask the ordering question at all.
|
|
553
|
+
|
|
554
|
+
Measured over that plan's seven cross-wave pairs, five runs each:
|
|
555
|
+
|
|
556
|
+
| pair | score | |
|
|
557
|
+
|---|---|---|
|
|
558
|
+
| `readme` → `cli` | **0.88–0.90** | documents the CLI built in the next wave *(shipped bug)* |
|
|
559
|
+
| `page` → `cli` | 0.13–0.15 | |
|
|
560
|
+
| the other five | 0.05–0.07 | |
|
|
561
|
+
|
|
562
|
+
A **0.73-wide gap**, the cleanest separation any rubric here has produced, so
|
|
563
|
+
`forward_warn` sits in the middle of it at 0.50 — 0.35 clear below, 0.38 above, against a
|
|
564
|
+
0.07 noise floor. Provisional in one specific way: the gap is enormous but rests on a
|
|
565
|
+
single positive example. A second plan could move the positive class; it is unlikely to
|
|
566
|
+
move it 0.38.
|
|
567
|
+
|
|
568
|
+
Both halves run from one `forge jev coupling` invocation, so a plan gets both warnings or
|
|
569
|
+
neither, and each row carries a trailing `kind` of `same-wave` or `forward`. On the real
|
|
570
|
+
plan the gate now names all three of its genuine problems: `page`+`server` and
|
|
571
|
+
`events`+`page` same-wave, and `readme`→`cli` at 0.88.
|
|
572
|
+
|
|
573
|
+
Still advisory in every mode: it prints, and never reorders a wave or blocks a dispatch.
|
|
574
|
+
|
|
575
|
+
## Memory curation
|
|
576
|
+
|
|
577
|
+
Guarded so `--no-memory`, `FORGE_MEMORY=off` and a disabled capability each yield today's
|
|
578
|
+
exact behaviour, verified against a pristine `HEAD` export.
|
|
579
|
+
|
|
580
|
+
The asymmetry that shapes every default here: `memory.md` is injected into every dispatch
|
|
581
|
+
of every future run, so a wrong fact is paid for forever, while `ledger.tsv` is
|
|
582
|
+
append-only and never injected. **Nothing recorded is ever lost — only injection is
|
|
583
|
+
gated.** A rejected line still lands in the ledger in full.
|
|
584
|
+
|
|
585
|
+
| At `record` | |
|
|
586
|
+
|---|---|
|
|
587
|
+
| **quality gate** | a one-off observation is recorded but not injected |
|
|
588
|
+
| **category correction** | refiles a fact under `verify`/`trap`/`finding`; `none` leaves it alone |
|
|
589
|
+
| **semantic dedup** | merges a paraphrase onto the existing key |
|
|
590
|
+
|
|
591
|
+
Dedup fixes a limitation `norm_key`'s own comment names: exact-match keys mean two runs
|
|
592
|
+
learning the same thing in different words count as two facts, so neither reaches the
|
|
593
|
+
two-distinct-runs promotion threshold for a `finding`. Live, four learnings produced:
|
|
594
|
+
a rename note recorded but not injected (durable 0.1); a `trap` refiled to `verify`; a
|
|
595
|
+
differently-worded restatement of it merged onto the same key; and a genuine new trap
|
|
596
|
+
kept.
|
|
597
|
+
|
|
598
|
+
**Category correction and dedup are two rubrics, and they have two thresholds.** They
|
|
599
|
+
shared one `memory_dedup` value at 0.80 until both were measured. Splitting them was
|
|
600
|
+
right; the first numbers put on them were not, and the way that went wrong is the useful
|
|
601
|
+
part.
|
|
602
|
+
|
|
603
|
+
The first pass fed existing entries as `verify | text` and read correct merges at
|
|
604
|
+
0.99–1.00, which set the bar at 0.90. Running the real CLI against a real `memory.md`
|
|
605
|
+
then returned **0.90 on a correct merge** — right on the threshold. Production strips
|
|
606
|
+
entries to bare text under a `## section` heading, with no category prefix; the state
|
|
607
|
+
was different, so the number was. Re-measured with the state
|
|
608
|
+
`cmd_memory_curate` actually builds, six repeats per boundary case:
|
|
609
|
+
|
|
610
|
+
| | wrong, must not act | right, must act | threshold |
|
|
611
|
+
|---|---|---|---|
|
|
612
|
+
| `memory_merge` | 0.56–0.72 | 0.91–1.00 | **0.85** |
|
|
613
|
+
| `memory_recategorize` | 0.56–0.72 | 0.92–1.00 | **0.80** |
|
|
614
|
+
|
|
615
|
+
Both rubrics separate in the same place. What sets the two numbers apart is therefore not
|
|
616
|
+
the scale but the cost: a wrong merge loses a fact permanently, so it takes the top of the
|
|
617
|
+
gap, while a wrong refile leaves the fact in place under the wrong heading and refusing a
|
|
618
|
+
correction is the commoner harm, so it takes the bottom. **0.80 was a defensible value for
|
|
619
|
+
one of the two all along** — the fault was never measuring it, and the first measurement
|
|
620
|
+
was worse than the guess.
|
|
621
|
+
|
|
622
|
+
The wrong merges are worth naming, because the threshold is the only thing that stops
|
|
623
|
+
them: `config/prod.yaml` is ciphertext / `config/prod.yaml` must not hold plaintext
|
|
624
|
+
secrets merged on 6 of 6 runs at 0.56–0.72. The rubric's `none` criterion already tells
|
|
625
|
+
the model to keep both on a partial match and it picks one anyway — at a confidence that
|
|
626
|
+
separates cleanly from a true paraphrase. That is the case for gating on confidence
|
|
627
|
+
rather than wording the criterion harder.
|
|
628
|
+
|
|
629
|
+
The refused corrections are facts that genuinely span two categories: a test that fails
|
|
630
|
+
without `alembic upgrade head`, and `make test` regenerating fixtures so a dirty tree is
|
|
631
|
+
expected. Both are verify and trap at once, which is exactly when the recorded category
|
|
632
|
+
should stand.
|
|
633
|
+
|
|
634
|
+
**A merged line is exempt from the quality gate.** The two judgments arrive in one
|
|
635
|
+
request and originally did not consult each other, which broke dedup in the case dedup
|
|
636
|
+
exists for. `rebuild` skips a non-injectable row *before* it counts anything, and a
|
|
637
|
+
`finding` needs two distinct runs to be promoted — so a restatement judged not durable
|
|
638
|
+
was dropped from the count of the fact it restates, and the two wordings still never
|
|
639
|
+
reached two.
|
|
640
|
+
|
|
641
|
+
Measured over paraphrases of facts already in memory: durability 0.44–0.83, against
|
|
642
|
+
0.82–0.83 for the same facts stated fresh. A restatement reads as less durable than the
|
|
643
|
+
thing it restates. That is the wrong question to ask of it: the entry it matched is
|
|
644
|
+
already in `memory.md`, so durability was settled when *that* entry was written, and a
|
|
645
|
+
line matching it is by definition not the one-off observation the gate is for. The
|
|
646
|
+
exemption is the merge itself — a match refused by `memory_merge` grants nothing.
|
|
647
|
+
|
|
648
|
+
The ledger gained an eleventh column for the injectable flag. `rebuild` treats a row
|
|
649
|
+
without it as injectable, so an existing ledger keeps behaving as it did.
|
|
650
|
+
|
|
651
|
+
| Elsewhere | |
|
|
652
|
+
|---|---|
|
|
653
|
+
| **semantic staleness** (`prune` only) | an entry whose anchor still exists but whose fact went false |
|
|
654
|
+
| **injection slicing** (`inject`) | narrows the role slice to the ~8 facts bearing on this task |
|
|
655
|
+
|
|
656
|
+
Staleness runs **only** on an explicit `forge-memory.sh prune`, never during `record`.
|
|
657
|
+
`rebuild` runs after every dispatch, so a request per entry per dispatch would be the
|
|
658
|
+
most expensive thing in a run.
|
|
659
|
+
|
|
660
|
+
Its threshold is `stale_drop` at 0.25, far below `gate_warn`. Measured over six facts
|
|
661
|
+
against real files: ones that had genuinely gone false scored 0.03–0.10, ones still true
|
|
662
|
+
scored 0.47–0.96. `gate_warn`'s 0.60 sits inside the true range and would have deleted a
|
|
663
|
+
fact the file still supports. Dropping a fact is irreversible and silent; keeping a stale
|
|
664
|
+
one costs a line until someone notices.
|
|
665
|
+
|
|
666
|
+
Slicing only removes lines — never adds, never reorders, and keeps the section heading
|
|
667
|
+
above each kept line. The 40-line/4KB cap and the role slice remain the outer bound. Live
|
|
668
|
+
on a 10-fact memory against a payments-retry task, it kept 8 and dropped the CSS build
|
|
669
|
+
ordering and the parser cache — while keeping the idempotency trap.
|
|
670
|
+
|
|
671
|
+
## Removed: effort sizing, spec parsing, Fractal re-rating
|
|
672
|
+
|
|
673
|
+
All three were removed on 2026-10-17, and none had ever acted.
|
|
674
|
+
|
|
675
|
+
- **Effort sizing** rode in the routing request and suggested a `low..ultra` level per
|
|
676
|
+
task, with QA effort sizing (`qa-effort`) doing the same for the reviewer. Over the run
|
|
677
|
+
dataset the dwarf suggestion separated first-pass outcomes at AUC 0.64 overall but
|
|
678
|
+
0.50–0.54 within a tier: all it knew was the tier. And 81% of its suggestions were
|
|
679
|
+
`xhigh` or higher. Effort is now measured like models are: pools that differ only in
|
|
680
|
+
effort (`--dwarf-low sol --dwarf-medium sol:high --dwarf-high sol:xhigh`) give
|
|
681
|
+
exploratory routing two arms per tier, and `forge dataset routing` reports whether the
|
|
682
|
+
higher effort passes more.
|
|
683
|
+
- **`forge jev parse-spec`** mapped a sentence onto `--dwarf-<tier>` flags. It could only
|
|
684
|
+
map a sentence that already named the aliases, since `registry.tsv` says nothing about
|
|
685
|
+
cost or strength, which made it no shorter than typing the flags.
|
|
686
|
+
- **Fractal child re-rating** could move a child between authorised pools, gated on the
|
|
687
|
+
same per-repo calibration as `--jev-act`, which no repo had. A child's difficulty is the
|
|
688
|
+
parent dwarf's own again.
|
|
689
|
+
|
|
690
|
+
## Early test feedback (what became of per-task subsetting)
|
|
691
|
+
|
|
692
|
+
Running tests inside a task worktree has no safe window: an artifact created before the
|
|
693
|
+
diff is captured reaches QA as if the dwarf wrote it, and one created after trips the
|
|
694
|
+
fingerprint re-checks in `do_task` and `merge_task`, which write `INVALIDATED` and block
|
|
695
|
+
the merge. So this exports the reviewed commit to a throwaway directory and runs there.
|
|
696
|
+
A test asserts the worktree's tree hash, file list and absence of `__pycache__` across a
|
|
697
|
+
real run.
|
|
698
|
+
|
|
699
|
+
**It is not a speed-up.** The full suite still runs at integration exactly as before, so
|
|
700
|
+
this *adds* a run rather than replacing one. The saving the plan described required
|
|
701
|
+
replacing verification, which the fingerprint rules forbid. What it buys instead is that
|
|
702
|
+
a reviewer learns "your diff breaks `test_x`" while it can still act on that.
|
|
703
|
+
|
|
704
|
+
### It is opt-in, and needs a template
|
|
705
|
+
|
|
706
|
+
`.forge/verify-subset` must exist and contain `{files}`:
|
|
707
|
+
|
|
708
|
+
```
|
|
709
|
+
pytest -q {files}
|
|
710
|
+
npm test -- {files}
|
|
711
|
+
python3 -m unittest {files}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
Deliberately a separate file from `.forge/verify`. There is no general way to restrict an
|
|
715
|
+
arbitrary command to a file list, and the first version assumed there was — it appended
|
|
716
|
+
paths to the verify command, which works for `pytest -q` and fails for `npm test`,
|
|
717
|
+
`go test ./...`, `bash tests/check.sh` and `python -m unittest discover`, each for a
|
|
718
|
+
different reason. Without the template this feature stays off.
|
|
719
|
+
|
|
720
|
+
### A failure is only reported when the base passes
|
|
721
|
+
|
|
722
|
+
The first run of this reported **"the likely-affected tests FAILED"** when the real
|
|
723
|
+
problem was that `pytest` was not installed in the export. An environment failure
|
|
724
|
+
presented to a reviewer as a broken diff is worse than silence, and pre-existing failures
|
|
725
|
+
have the same shape.
|
|
726
|
+
|
|
727
|
+
So on a failure the identical subset runs again against the commit the task started from,
|
|
728
|
+
and the result is reported **only if the base passes**. That one extra run — paid only
|
|
729
|
+
when something already failed — is what separates "this diff broke it" from "this was
|
|
730
|
+
already broken, or cannot run here".
|
|
731
|
+
|
|
732
|
+
## Cloudflare Clef (a second provider)
|
|
733
|
+
|
|
734
|
+
Cloudflare Workers AI hosts Clef, a decision model that takes Jev's request shape
|
|
735
|
+
(`{state, questions}`) and answers with a probability per option. Forge can send chosen
|
|
736
|
+
**sites** (the `site=` name of each call: `jev-drift`, `jev-memory-slice`, ...) to it.
|
|
737
|
+
Without a Cloudflare provider configured, every site asks TypeSafe exactly as before.
|
|
738
|
+
|
|
739
|
+
```sh
|
|
740
|
+
./forge jev setup --provider cloudflare --account-id <id> [--model clef|clef-flash] [--sites a,b]
|
|
741
|
+
./forge jev sites # which provider answers each site, and whether it is ready
|
|
742
|
+
./forge jev sites jev-drift=typesafe # move a site back
|
|
743
|
+
./forge jev doctor --live # probes every provider a site uses
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
- **Token:** a Cloudflare API token with **Account > Workers AI > Read**, in
|
|
747
|
+
`cloudflare.env` beside `jev.json` (`CLOUDFLARE_API_TOKEN=...`, mode `0600`) or in
|
|
748
|
+
`CLOUDFLARE_API_TOKEN`; the account id in config or `CLOUDFLARE_ACCOUNT_ID`. Never argv,
|
|
749
|
+
never a run dir, a prompt or a log: reasons are redacted before they are cut.
|
|
750
|
+
- **Privacy:** a mapped site sends Cloudflare what it would have sent TypeSafe: prompts,
|
|
751
|
+
diff hunks, file excerpts, memory entries. Cloudflare states it does not store or train on
|
|
752
|
+
these requests.
|
|
753
|
+
- **Off switches:** `FORGE_CLEF=off` sends every site back to TypeSafe and stops the
|
|
754
|
+
predictions below; `FORGE_JEV_PROVIDER=typesafe|cloudflare` forces one provider for every
|
|
755
|
+
site; `FORGE_JEV_OUTCOMES=off` stops only the predictions; `FORGE_JEV=off` still stops all.
|
|
756
|
+
- **Limits the client handles:** at most 64 questions per request (drift asks up to 120, so
|
|
757
|
+
it goes as two requests, concurrently, merged); a context of 64K tokens (`clef`) or 24K
|
|
758
|
+
(`clef-flash`) for state and questions together, beyond which Cloudflare cuts the state
|
|
759
|
+
silently, so the client refuses (`too-large`) rather than send it; `4006`/`3036` (allocation
|
|
760
|
+
used up) and `5035` (plan) are final, `3040`/`971` and 429 are retried within the deadline,
|
|
761
|
+
honoring `Retry-After`. Every requested answer must be present and in range, or the call
|
|
762
|
+
is `malformed`. A failure is "no judgment", as with TypeSafe.
|
|
763
|
+
- **Thresholds are per model.** A cutoff measured on Jev's probabilities is not one on
|
|
764
|
+
Clef's: `model_thresholds.<model>.<name>` overrides a threshold for that model's answers.
|
|
765
|
+
Drift now reads `drift_warn`, which falls back to `gate_warn` until a model has its own.
|
|
766
|
+
- **Cost and latency:** `clef` is $0.24 and `clef-flash` $0.038 per million input tokens,
|
|
767
|
+
output free. A drift request (two chunks) takes about 2.4 s at the median and 3.0 s at
|
|
768
|
+
p90, against Jev's 0.65 s and 10.8 s on the same live requests.
|
|
769
|
+
|
|
770
|
+
### Which sites move: a paired replay, not an assumption
|
|
771
|
+
|
|
772
|
+
`jev-drift` is the only site `setup` maps by default. Measured 2026-10-16 by replaying the
|
|
773
|
+
stored live requests (the run dataset keeps each one whole) through Clef, labeled by the
|
|
774
|
+
task's own outcome, older half of runs to calibrate, newer half to judge; a site moves only
|
|
775
|
+
if Clef is non-inferior (paired run-bootstrap lower bound above -0.02) on at least 30
|
|
776
|
+
labeled decisions from at least 8 held-out runs:
|
|
777
|
+
|
|
778
|
+
| site | labeled | held-out | Clef AUC | Jev AUC | paired diff [95%] | moves |
|
|
779
|
+
|---|---|---|---|---|---|---|
|
|
780
|
+
| `jev-drift` | 4560 decisions, 16 positive, 17 runs | 9 runs | 0.985 | 0.972 | +0.013 [0.000, +0.028] | yes |
|
|
781
|
+
| `jev-memory-slice` | 855 decisions, 144 positive, 10 runs | 5 runs | 0.951 | 0.953 | -0.003 [-0.064, +0.014] | no: too few runs |
|
|
782
|
+
|
|
783
|
+
`jev-review-triage` was not replayed: its `correctness` half has no label, and the export's
|
|
784
|
+
citation label (a review against its own diff or a substituted one) is not a live label.
|
|
785
|
+
Clef keeps drift's shared 0.60 cutoff: on the calibration half neither model warned
|
|
786
|
+
precisely (Jev flagged nothing at 0.60), so no Clef-specific value could be fitted there; at
|
|
787
|
+
0.60 on the held-out half Clef flagged 1 file (right) and Jev 2 (1 right). The other sites
|
|
788
|
+
stay on TypeSafe until a replay of their own justifies a move.
|
|
789
|
+
|
|
790
|
+
### Silent outcome predictions
|
|
791
|
+
|
|
792
|
+
With a Cloudflare provider configured, forge also asks Clef two questions forge has never
|
|
793
|
+
asked, and records the answers without acting on them:
|
|
794
|
+
|
|
795
|
+
- `clef-first-pass`, once per plan after the table rewrite that assigns every row its dwarf
|
|
796
|
+
and reviewer: will this task's first attempt pass review? Written to
|
|
797
|
+
`tasks/<id>/clef.first_pass` with the assignment it was about.
|
|
798
|
+
- `clef-retry-pass`, when a dwarf attempt above 1 begins (not on a QA-only resume): will this
|
|
799
|
+
retry pass, given the findings it was handed? Written to `tasks/<id>/clef.retry_pass.<n>`.
|
|
800
|
+
|
|
801
|
+
On forge's exported history Clef ranks these at AUC 0.73 (first pass, 253 cases) and 0.75
|
|
802
|
+
(retry, 300), against hosted Jev's composite at 0.69 and cheap features at 0.67 and 0.61, but
|
|
803
|
+
its raw probabilities are poorly calibrated (retry ECE 0.28) and the newest, largest tasks
|
|
804
|
+
were too few to test. So nothing is printed and nothing acts: no prompt, verdict, tasks table,
|
|
805
|
+
retry spec or findings ever sees a prediction. The export attaches them to the routing and
|
|
806
|
+
retry cases (`first_pass_clef`, with whether attempt 1 ran the predicted dwarf, and
|
|
807
|
+
`retry_pass_clef`), so their skill on new runs can be measured before anything relies on
|
|
808
|
+
them. One bounded command per plan or retry: one deadline, at most 4 requests in flight,
|
|
809
|
+
model resolution cached, an in-process watchdog; about $0.0025 a task with `clef`.
|
|
810
|
+
|
|
811
|
+
## Status
|
|
812
|
+
|
|
813
|
+
Every phase of the plan is implemented. What changed the picture since is that it was
|
|
814
|
+
finally **run**, rather than tested: the first real `forge-parallel.sh` invocation with
|
|
815
|
+
Jev enabled, on an 8-task plan, found twelve defects that 402 passing tests could not
|
|
816
|
+
see. Most shared one shape — the code was correct and nothing reached it.
|
|
817
|
+
|
|
818
|
+
- Every `--jev*` flag was unreachable: the option parser was sourced from inside
|
|
819
|
+
functions that had already walked their arguments, so `forge_jev_flag`,
|
|
820
|
+
`forge_jev_export`, `forge_jev_record` and `forge_jev_restore` had no callers at all.
|
|
821
|
+
- `prompt.md` never reached routing or the gates, which judged tasks from a one-line
|
|
822
|
+
title. Both text gates had therefore been calibrated on data production never produced.
|
|
823
|
+
- Both memory readers in `verify.py` parsed the *ledger* row format against the rendered
|
|
824
|
+
`memory.md`, so they were empty for every repo, always — which made the `known_flake`
|
|
825
|
+
re-run unreachable, since it requires a corroborating trap.
|
|
826
|
+
- Wave coupling borrowed `gate_warn` and had never fired on any plan.
|
|
827
|
+
- Every memory call was unlogged, and the review-triage flag printed where the default
|
|
828
|
+
output mode never shows it.
|
|
829
|
+
|
|
830
|
+
**What acts today:** the drift, prompt-adequacy and verifiability gates, verification,
|
|
831
|
+
early test feedback, memory curation, wave coupling (same-wave and cross-wave) and
|
|
832
|
+
review-triage annotation. All are advisory —
|
|
833
|
+
none blocks a dispatch, and nothing overturns a `FORGE_VERDICT`.
|
|
834
|
+
|
|
835
|
+
**What was removed (2026-10-17):** the tier rewrite behind `--jev-act`, `calibrate`,
|
|
836
|
+
effort and QA-effort sizing, `parse-spec` and Fractal re-rating. None had ever acted, and
|
|
837
|
+
the run dataset showed decision models cannot choose models or efforts; statistics over
|
|
838
|
+
randomized routing do that now. **Frozen:** wave coupling and forward-dependency scoring
|
|
839
|
+
keep running as they are, and the decision-model fine-tuning pipelines get no further
|
|
840
|
+
work. **Kept and maintained:** capture and replay, the provider client, drift and memory
|
|
841
|
+
curation, where decision models measurably read well.
|
|
842
|
+
|
|
843
|
+
**Known unsound, recorded rather than patched:**
|
|
844
|
+
|
|
845
|
+
- `independently_verifiable` still cannot separate a task that documents later work
|
|
846
|
+
(`readme`, 0.60–0.65) from a real one (`cli`, 0.65–0.75). Putting the plan in state
|
|
847
|
+
fixed the rubric's worst end — it now scores the clearest fragment at 0.26–0.34, where
|
|
848
|
+
it used to score 0.63–0.65 — but the right-hand side remains a precision gate whose
|
|
849
|
+
silence is not evidence.
|
|
850
|
+
- `tests_act` = 0.70 is stale. The semantics are fixed — the gate reads 1 − P(none)
|
|
851
|
+
rather than Choice confidence, so a tie between two correct commands no longer reads as
|
|
852
|
+
doubt — but the threshold behind it was calibrated before candidate evidence reached
|
|
853
|
+
the model, and the one confirmed wrong pick (`spankai` → `cargo test`) has risen from
|
|
854
|
+
0.55 to 0.77–0.81 and now clears the gate either way.
|
|
855
|
+
- Wave coupling's same-wave half rests on one plan with roughly 0.02 of margin. Its
|
|
856
|
+
cross-wave half (`forward_dependency`, gap 0.73) is far better separated but rests on a
|
|
857
|
+
single positive example.
|
|
858
|
+
|
|
859
|
+
The standing lesson, now learned seven separate times: a threshold named for one rubric
|
|
860
|
+
and reused for another is wrong, and a number measured against state the model is not
|
|
861
|
+
actually sent describes nothing. The sixth was `independently_verifiable`, which asked
|
|
862
|
+
whether a task needed "work declared in a different task" while no different task had
|
|
863
|
+
ever been in state — and the fix was to send the plan, not to reword the question. `jevscope` exists to make both visible — it reads the
|
|
864
|
+
run logs back and reports each rubric's distribution and the spread across byte-identical
|
|
865
|
+
repeated requests, which is the floor any threshold has to clear.
|