@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
package/docs/safety.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Safety
|
|
2
|
+
|
|
3
|
+
[← README](../README.md) · [All docs](README.md)
|
|
4
|
+
|
|
5
|
+
What forge never does to your branch and working tree, what it refuses to run on, and how it keeps reviewed work and merged work the same.
|
|
6
|
+
|
|
7
|
+
## Safety guarantees
|
|
8
|
+
|
|
9
|
+
- Solo runs leave source changes uncommitted. Forge creates private review commits, but
|
|
10
|
+
does not commit on the solo source branch, push, or reset the user tree.
|
|
11
|
+
- In a decomposed run, commits and merges happen **only** on the `forge/<run-id>/*` task
|
|
12
|
+
branches and the `forge/<run-id>-integration` branch — branches forge created for itself.
|
|
13
|
+
Never your branch, never a push. Your branch and working tree are untouched for the whole
|
|
14
|
+
run.
|
|
15
|
+
- Bypass flags are never added on forge's own initiative. `--yolo-dwarf` / `--yolo-qa` are the
|
|
16
|
+
only route to an unsandboxed run, so that choice is always explicit and visible.
|
|
17
|
+
- **One dwarf per working tree, always.** Two agents editing one tree interleave their edits
|
|
18
|
+
into a diff neither of them wrote and qa cannot untangle. Parallelism comes from separate
|
|
19
|
+
worktrees, which is why worktrees are mandatory there rather than an optimization.
|
|
20
|
+
- A failed task's branch and worktree are **never** deleted — they're the only record of what
|
|
21
|
+
went wrong and the only thing to retry from. A successful task's worktree is removed only
|
|
22
|
+
after `results.tsv` is written and read back.
|
|
23
|
+
- QA runs in a disposable repository. Mutations to the source or review snapshot invalidate
|
|
24
|
+
acceptance. Disabled editing tools alone do not prevent writes through Bash.
|
|
25
|
+
|
|
26
|
+
## Preconditions
|
|
27
|
+
|
|
28
|
+
- Not a git repo → decomposed runs refuse. Worktrees are impossible and there's no honest
|
|
29
|
+
fallback.
|
|
30
|
+
- Uncommitted changes → **warned prominently.** Worktrees branch from a commit, so your local
|
|
31
|
+
edits are invisible to every dwarf. This is the likeliest route to a confusingly wrong
|
|
32
|
+
result.
|
|
33
|
+
|
|
34
|
+
## Execution integrity
|
|
35
|
+
|
|
36
|
+
Forge now captures solo changes against a recorded starting snapshot, including staged edits,
|
|
37
|
+
without altering the user's index. Existing work is recorded separately in `existing.diff`.
|
|
38
|
+
QA receives the full implementation requirements and reviews a disposable repository; source
|
|
39
|
+
or snapshot changes invalidate the result. Only the recorded reviewed task commit can merge.
|
|
40
|
+
|
|
41
|
+
Parallel execution rechecks dependencies before each task and retry. Failed prerequisites leave
|
|
42
|
+
consumers BLOCKED. Directory and child file declarations are serialized.
|
|
43
|
+
|
|
44
|
+
`forge-dispatch.sh doctor --spec sol --role dwarf` validates a selected model recipe using local
|
|
45
|
+
CLI help. Solo and parallel runners preflight the whole selected pipeline before implementation.
|
|
46
|
+
These checks do not establish authentication, billing or live model availability.
|
|
47
|
+
|
|
48
|
+
Use `forge-parallel.sh run <plan> --verify 'your check command'` or put the command in
|
|
49
|
+
`.forge/verify` to verify the combined result. Output and exit status are saved with the plan.
|
|
50
|
+
Integration checks a candidate containing the current user branch before updating it. A missing
|
|
51
|
+
command is reported as UNVERIFIED; a failing configured check blocks integration. The project's
|
|
52
|
+
own test suites that the command does not run are named as warnings (advisory), and a failing
|
|
53
|
+
command is rerun once so a flaky lane is reported rather than failing the run — see
|
|
54
|
+
[verification coverage and flaky reruns](reviews.md#verification-coverage-and-flaky-reruns).
|
|
55
|
+
`integrate` also names any task that is not merged and therefore not part of what it delivers.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
[← README](../README.md) · [All docs](README.md)
|
|
4
|
+
|
|
5
|
+
Symptoms, causes and fixes, from a skill a harness cannot see to a task that will not merge.
|
|
6
|
+
|
|
7
|
+
| Symptom | Cause / fix |
|
|
8
|
+
|---|---|
|
|
9
|
+
| skill missing in **codex** only | frontmatter isn't strict YAML. Codex refuses to load and logs `invalid YAML`; the other four are lenient and load it fine. Note codex's line numbers exclude the opening `---`, so its "line 3" is the file's line 4 |
|
|
10
|
+
| skill missing in **antigravity** | `agy` ignores `~/.gemini/skills` entirely — skills load only inside a plugin. Re-run `forge-install.sh` |
|
|
11
|
+
| antigravity runs an **old version** | `agy plugin install` copies. Re-run `forge-install.sh` after every edit |
|
|
12
|
+
| duplicate entry in **opencode** | opencode auto-scans `~/.claude/`; a `~/.config/opencode/command/forge.md` on top is redundant |
|
|
13
|
+
| agy: `Find command timed out` | missing `--add-dir <repo>` — forge passes it; a hand-built command must too |
|
|
14
|
+
| agy dwarf ends with an error and an empty diff | it tried to run a shell command without yolo. A single denial aborts the run — use `--yolo-dwarf` |
|
|
15
|
+
| dwarf produced no changes | usually an ambiguous prompt it couldn't resolve headlessly. Nobody can answer a question mid-run |
|
|
16
|
+
| task status `ERROR` | worktree creation or a dispatch failed — read `tasks/<id>/dwarf.out` / `qa.out` |
|
|
17
|
+
| task status `UNKNOWN` | QA never emitted a verdict line; read `tasks/<id>/qa.last`. `retry` (or `run --retry-failed N`) asks QA again on the unchanged work — no dwarf runs, no attempt is spent |
|
|
18
|
+
| task status `INFRA`, `run` exits `8` | a quota/usage limit, auth problem, rate limit, network error or empty answer stopped a dispatch — not a task failure, and no attempt was spent. Read `tasks/<id>/infra.txt` (`class=`, `retry_after=`), fix the cause, run the same command again. `run --infra-retries M` waits it out instead. Dependents stay `PENDING`, never `BLOCKED` |
|
|
19
|
+
| a task merged although its reviewer said FAIL | `--qa-threshold` accepted it: every CONFIRMED finding was labelled and milder than the threshold. See `tasks/<id>/known_issues.md` and `qa.gate`; nothing is accepted without an explicit threshold |
|
|
20
|
+
| `accept` exits `3` "changed since it was reviewed" | the branch or worktree moved after QA, so accepting would sign off on unreviewed code. `retry` for a fresh review |
|
|
21
|
+
| `combine` / `split` refuses | the message names why: unmerged task left behind, duplicate ids, locked or dirty plan, a Fractal-backed plan, or a selection that strands a dependent. Nothing was changed |
|
|
22
|
+
| `integrate --final-review` stops with exit `5` | the whole-run reviewer found a blocking defect (or never reached a verdict); findings are in `<plan>/…/final-review/qa.last`. Your branch is untouched. Exit `8` means the reviewer hit a quota/auth stop: run it again |
|
|
23
|
+
| `FLAKY` in the summary, verification `PASS` | the verify command failed once and passed on rerun. Read `verification.log` (the first run) — an intermittent failure can be a real bug. `--verify-retries 0` restores the strict single run |
|
|
24
|
+
| `verify coverage: suite '…' is not run by the verify command` | the project defines a suite your `--verify` skips. Advisory only; add it to the command (or `.forge/verify`) if it should gate the merge |
|
|
25
|
+
| implementer guard notes in the summary | the dwarf committed, switched branch, left processes running, or promised later work. Recorded in `tasks/<id>/guard.txt`; the diff and the reviewer still decide. Leftover processes were stopped |
|
|
26
|
+
| task status `CONFLICT` | QA passed but the merge conflicted — `files` was understated somewhere. Check the run's scope-drift list: it usually names the file |
|
|
27
|
+
| task status `TIMEOUT` | that role exceeded the dispatch timeout and was killed. Raise `--timeout`, or `retry` — the partial edit is still in the worktree |
|
|
28
|
+
| a dispatch never returns | it can't any more: every backend is killed at its timeout (`--timeout`, else `FORGE_TIMEOUT`, else the registry's `timeout` column, else 45 min — 60 min for the default `xhigh` QA/planner effort) |
|
|
29
|
+
| a task won't re-run: "already exists" | a worktree directory was deleted by hand. `run` and `retry` prune stale registrations first, so re-run rather than deleting the branch |
|
|
30
|
+
| a failed task is expensive to redo | it isn't — `forge-parallel.sh retry <dir> <id> [--dwarf <spec>]` re-runs that one task with its reviewer's findings, in the worktree it already has |
|
|
31
|
+
| an interrupted run seems lost | run `run` again; merged tasks are skipped, not re-dispatched |
|
|
32
|
+
| every wave has exactly one task | `files` sets overlap across most tasks; the decomposition isn't actually parallel |
|
|
33
|
+
| dwarves reimplement already-merged work | `goal.txt` or `files` missing, so the capsule carries no useful status |
|
|
34
|
+
| several tasks report "produced no changes" | the decomposition made tasks that weren't independently actionable — use a coarser `--decompose-level`, not more retries |
|
|
35
|
+
| `retry` says "produced no new changes" | the dwarf returned a byte-identical diff, so no reviewer was paid to read the same code twice. Escalate with `--dwarf`, or fix the prompt |
|
|
36
|
+
| a new file's code never reached QA | fixed: untracked files are diffed against `/dev/null`. A hand-built capture using only `git status --porcelain` shows the reviewer a filename and no content |
|
|
37
|
+
| claude dwarf ends "I need your permission to edit" | a permission mode that prompts for Edit. forge uses `acceptEdits` plus an explicit Bash allowance, because `auto` allows shell but prompts for Edit, and `acceptEdits` alone allows Edit but prompts for real shell commands |
|
|
38
|
+
| `.forge/` turns up in a review | a diff capture is missing `':(exclude).forge'` |
|
|
39
|
+
| memory stays empty | nothing durable was learned — the common, correct case. The ledger still has a row per dispatch |
|
|
40
|
+
| a wrong fact keeps reappearing | `memory.md` is rebuilt from the ledger; delete the ledger rows, not the memory line |
|
|
41
|
+
| dwarves build incompatible interfaces | the seam was never agreed — plan the approach, or use `--planner` |
|
|
42
|
+
| Fractal was installed but a new unattended run uses ordinary execution | installation never activates it; pass `--fractal` explicitly |
|
|
43
|
+
| `Fractal selected but unavailable` | run `forge fractal doctor`, then the suggested managed install; resume retains the selected backend |
|
|
44
|
+
| existing run rejects a new backend, pool or limit | selection and bounds are recorded; create a new run directory for changed settings |
|
|
45
|
+
| Fractal reports completion but Forge has not accepted the work | inspect QA, verdict and verification; execution completion does not establish acceptance |
|
|
46
|
+
| Fractal import stops with `source_drift` | the source changed after its snapshot; preserve both versions and inspect the candidate instead of forcing import |
|
|
47
|
+
| Fractal work was paused or its coordinator exited | inspect `status` and `logs`, then use explicit `resume RUN_ID`; retry starts a new attempt |
|
|
48
|
+
| browser dashboard closed while work is running | execution is independent; reopen with `forge fractal open RUN_ID` |
|
|
49
|
+
| costs are unknown or a dollar cap is rejected | the bridge only reports available usage; v1 cannot enforce a spending cap |
|
|
50
|
+
|
|
51
|
+
For per-harness invocation details, effort ladders and known CLI failure modes, see
|
|
52
|
+
[`references/harnesses.md`](../references/harnesses.md). For decomposition mechanics, see
|
|
53
|
+
[`references/decompose.md`](../references/decompose.md). For memory internals, see
|
|
54
|
+
[`references/memory.md`](../references/memory.md).
|
package/forge
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# npm runs this file through a symlink in its bin dir, usually a relative one. Follow the
|
|
4
|
+
# links so ROOT is the package and not the bin dir; older macOS has no readlink -f.
|
|
5
|
+
SELF="${BASH_SOURCE[0]}"
|
|
6
|
+
while [ -L "$SELF" ]; do
|
|
7
|
+
LINK="$(readlink "$SELF")"
|
|
8
|
+
case "$LINK" in
|
|
9
|
+
/*) SELF="$LINK" ;;
|
|
10
|
+
*) SELF="$(dirname "$SELF")/$LINK" ;;
|
|
11
|
+
esac
|
|
12
|
+
done
|
|
13
|
+
ROOT="$(cd -P "$(dirname "$SELF")" && pwd)"
|
|
14
|
+
|
|
15
|
+
usage() {
|
|
16
|
+
printf '%s\n' \
|
|
17
|
+
'usage: forge <command> [args]' \
|
|
18
|
+
'' \
|
|
19
|
+
'commands:' \
|
|
20
|
+
' install link forge into each agent harness on this machine; --dry-run previews' \
|
|
21
|
+
' fractal opt-in Fractal execution: install, doctor, runs, status, tree, logs, open, report, ...' \
|
|
22
|
+
' jev optional Jev advice: setup, status, doctor, enable, disable, backtest' \
|
|
23
|
+
' dataset every run'"'"'s inputs and outcomes: status, dump, import, export, outcomes, ...' \
|
|
24
|
+
' ui forge-ui, the run viewer: open [DIR], status, stop' \
|
|
25
|
+
' runs this repo'"'"'s recent runs in the terminal: state, verdicts and run dirs' \
|
|
26
|
+
' update fast-forward this checkout and refresh every harness installation' \
|
|
27
|
+
' version the installed version, and whether a newer release is out' \
|
|
28
|
+
'' \
|
|
29
|
+
'Run `forge <command> --help` for its options.' \
|
|
30
|
+
'Runs start from your agent harness (/forge "<goal>" --dwarf <model>); see README.md.'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case "${1:-}" in
|
|
34
|
+
install)
|
|
35
|
+
shift
|
|
36
|
+
exec /bin/bash "$ROOT/scripts/forge-install.sh" "$@"
|
|
37
|
+
;;
|
|
38
|
+
fractal)
|
|
39
|
+
shift
|
|
40
|
+
exec python3 "$ROOT/scripts/forge-fractal.py" "$@"
|
|
41
|
+
;;
|
|
42
|
+
jev)
|
|
43
|
+
shift
|
|
44
|
+
exec python3 "$ROOT/scripts/forge-jev.py" "$@"
|
|
45
|
+
;;
|
|
46
|
+
dataset)
|
|
47
|
+
shift
|
|
48
|
+
exec python3 "$ROOT/scripts/forge-dataset.py" "$@"
|
|
49
|
+
;;
|
|
50
|
+
ui)
|
|
51
|
+
shift
|
|
52
|
+
exec python3 "$ROOT/scripts/forge-ui.py" "$@"
|
|
53
|
+
;;
|
|
54
|
+
runs)
|
|
55
|
+
shift
|
|
56
|
+
exec python3 "$ROOT/scripts/forge-runs.py" "$@"
|
|
57
|
+
;;
|
|
58
|
+
update)
|
|
59
|
+
shift
|
|
60
|
+
exec /bin/bash "$ROOT/scripts/forge-update.sh" "$@"
|
|
61
|
+
;;
|
|
62
|
+
version)
|
|
63
|
+
shift
|
|
64
|
+
exec python3 "$ROOT/scripts/forge-version.py" "$@"
|
|
65
|
+
;;
|
|
66
|
+
-h|--help|help)
|
|
67
|
+
usage
|
|
68
|
+
exit 0
|
|
69
|
+
;;
|
|
70
|
+
'')
|
|
71
|
+
usage >&2
|
|
72
|
+
exit 2
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
printf 'forge: unknown command: %s\n\n' "$1" >&2
|
|
76
|
+
usage >&2
|
|
77
|
+
exit 2
|
|
78
|
+
;;
|
|
79
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giamme/forge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.9.0",
|
|
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
|
+
"keywords": ["claude-code", "codex", "opencode", "agent-skill", "code-review", "multi-model"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Giamme",
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/Giamme/forge.git" },
|
|
9
|
+
"bin": { "forge": "forge" },
|
|
10
|
+
"os": ["darwin", "linux"],
|
|
11
|
+
"files": [
|
|
12
|
+
"forge",
|
|
13
|
+
"SKILL.md",
|
|
14
|
+
"VERSION",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"README.md",
|
|
17
|
+
"docs/",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"registry.tsv",
|
|
20
|
+
"references/",
|
|
21
|
+
"scripts/",
|
|
22
|
+
"ui/forge_ui/",
|
|
23
|
+
"ui/README.md",
|
|
24
|
+
"ui/THIRD_PARTY_NOTICES.md",
|
|
25
|
+
"ui/docs/api.md",
|
|
26
|
+
"!**/__pycache__"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": { "access": "public" }
|
|
29
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Run dataset
|
|
2
|
+
|
|
3
|
+
Every forge run records its inputs and outcomes in one global store outside every repo,
|
|
4
|
+
so a decision model (Jev, Laya or any other) can later be trained or calibrated on what
|
|
5
|
+
forge actually did. Run and plan dirs hold nearly all of this, but they live in `/tmp`
|
|
6
|
+
and scratchpads and get deleted. `.forge/ledger.tsv` survives, but it keeps only spec
|
|
7
|
+
strings and verdicts. It has no inputs, no resolved models, no attempt numbers and no
|
|
8
|
+
tokens.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
${FORGE_DATA_HOME:-${XDG_DATA_HOME:-~/.local/share}/forge}/dataset/
|
|
12
|
+
dataset.sqlite events (rid-keyed, append-only) and repos
|
|
13
|
+
blobs/<aa>/<sha>.gz prompts, diffs, messages, logs: capped, gzipped, deduplicated
|
|
14
|
+
errors.log capture failures
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Capture is on by default and costs a few milliseconds per execution. `FORGE_DATASET=off`
|
|
18
|
+
turns it off for one invocation.
|
|
19
|
+
|
|
20
|
+
## Raw events, not training examples
|
|
21
|
+
|
|
22
|
+
The store holds **what happened, when it happened**: the state each decision saw and the
|
|
23
|
+
outcomes that followed. It never holds (state, question, label) rows. Those are derived
|
|
24
|
+
per decision by an exporter, so a question nobody has asked yet can still be answered
|
|
25
|
+
from old runs without re-running anything. Labels come from folding a run's events in
|
|
26
|
+
time order: an attempt that PASSed, then a merge that CONFLICTed, then a human `accept`.
|
|
27
|
+
|
|
28
|
+
| kind | one per | what it holds |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `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. Blobs: the delivered prompt and the final message |
|
|
32
|
+
| `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
|
+
| `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` |
|
|
35
|
+
| `run_end` | `run` | results per task, the accepted integration commit, `memory.md` |
|
|
36
|
+
| `invocation` | user-facing `forge-parallel.sh` subcommand | argv, exit, duration, plus final review, accept, integrate and split/combine payloads |
|
|
37
|
+
| `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` |
|
|
38
|
+
| `event` `escalation` | `retry --dwarf` | the model a retry escalated from and to, recorded at the rewrite, before the retried attempt begins |
|
|
39
|
+
| `event` `shadow_review` | shadow review | how an opt-in second review ended (`done`, `invalid`, `overtime`, `orphaned`, ...), with the reviewed commit and both specs. Its dispatch is recorded with role `qa_shadow` |
|
|
40
|
+
| `jev` | Jev request | site, answers, usage, latency, mode. Blob: the **full request** (state and questions). Its original sha is `jev.jsonl`'s `key` |
|
|
41
|
+
| `fractal_step` / `fractal_node` | Fractal step, node state | decomposition decision, iteration, resolved model, cost, tokens, candidate diff |
|
|
42
|
+
| `ledger` | ledger row | imported `.forge/ledger.tsv` rows |
|
|
43
|
+
| `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 |
|
|
44
|
+
|
|
45
|
+
Each blob reference carries `sha` (the stored bytes), `original_sha`, `n` (the original
|
|
46
|
+
size), `kept`, and `cut`. A cut keeps the head and the tail, because a QA prompt ends with
|
|
47
|
+
the diff and a log ends with the failure. Caps: prompt 1 MiB, diff 2 MiB, message 256 KiB,
|
|
48
|
+
log 272 KiB. Transcripts (`*.log`) and `.cmd` (which some harnesses use to carry the
|
|
49
|
+
prompt in argv) are never stored. A harness without structured output has its final
|
|
50
|
+
message copied from the whole log, so for those harnesses only the tail is kept, flagged
|
|
51
|
+
`final_source: log_copy`.
|
|
52
|
+
|
|
53
|
+
## How it is captured
|
|
54
|
+
|
|
55
|
+
All capture goes through a handful of hooks. Every one of them is fail-open: errors go
|
|
56
|
+
to `errors.log`, nothing is printed, and nothing changes an exit code.
|
|
57
|
+
|
|
58
|
+
- **`forge-runtime.py begin()/finish()`.** Every dispatch, task attempt, solo run,
|
|
59
|
+
verification and parallel run already passes through these. `finish()` captures from a
|
|
60
|
+
`finally:` block, so an extraction error cannot lose the record. This is the only
|
|
61
|
+
hook that sees every exit path: an empty diff, INVALIDATED, a timeout, an infra stop
|
|
62
|
+
or a `die`.
|
|
63
|
+
- **Provenance travels with the execution.** `begin()` writes
|
|
64
|
+
`attempts/<role>-<uuid>/dataset.json` with the run id, task, starting attempt counter,
|
|
65
|
+
trigger and a signature (inode, mtime, size) of every task file. `finish()` credits a
|
|
66
|
+
file to this execution only when its signature changed. A dwarf that fails returns
|
|
67
|
+
before `changes.diff` is refreshed, and without this check it would inherit the
|
|
68
|
+
previous attempt's diff and verdict.
|
|
69
|
+
- **Point events** (`forge_dataset_event`) record facts that disappear before any EXIT
|
|
70
|
+
trap could read them: a merge that gets aborted, a combine that rolls back, a user
|
|
71
|
+
branch that moves just before a cleanup that may still fail.
|
|
72
|
+
- **An EXIT trap in `forge-parallel.sh`** records every user-facing subcommand, `die`
|
|
73
|
+
included.
|
|
74
|
+
- **`forge_jev/client.py`** keeps the canonical request bytes of every live Jev call,
|
|
75
|
+
including calls made without a run dir. It skips fixture replays, the setup probe and
|
|
76
|
+
backtests.
|
|
77
|
+
|
|
78
|
+
Run identity is a uuid in `<plan-or-run-dir>/dataset.uid`, created once (`O_EXCL`) by
|
|
79
|
+
`plan` or by the first `begin()`. Split and combine copy attempt dirs unchanged, so a
|
|
80
|
+
copied execution keeps its original identity and collapses to one event by rid.
|
|
81
|
+
|
|
82
|
+
## Which forge produced an event
|
|
83
|
+
|
|
84
|
+
Outcomes depend on the prompts, contracts and registry forge ran with, and the skill is
|
|
85
|
+
often a live dev checkout that changes between releases. So every event carries four
|
|
86
|
+
provenance columns:
|
|
87
|
+
|
|
88
|
+
| column | what it identifies |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `forge_version` | the release in `VERSION` |
|
|
91
|
+
| `registry_sha` | the effective `registry.tsv` (or `FORGE_REGISTRY`) |
|
|
92
|
+
| `code_sha` | the content of every file that shapes a run: `scripts/`, `references/`, `SKILL.md`, `VERSION`, `forge` and the effective registry. Two runs share it only if they ran the same code, uncommitted edits included |
|
|
93
|
+
| `forge_rev` | the skill checkout's `HEAD` (`git rev-parse`, read-only), or null for a copied skill dir |
|
|
94
|
+
|
|
95
|
+
An execution is stamped with what `begin()` saw, because its prompt was built then.
|
|
96
|
+
`finish()` runs in another process; if the code changed in between, the event carries
|
|
97
|
+
`code_changed: true` and can be left out of any per-regime analysis.
|
|
98
|
+
|
|
99
|
+
An imported run was produced by whatever forge ran it, so the importer stamps nothing,
|
|
100
|
+
unless a live `begin()` left its provenance in the attempt dir. Rows imported before
|
|
101
|
+
schema 2 carry the importer's version by mistake, so readers treat `forge_version` and
|
|
102
|
+
`registry_sha` on `src = 'import'` rows without a `code_sha` as unknown. The exporter
|
|
103
|
+
gives those a `forge_rev_estimate`: the last forge commit before the run started, which is a
|
|
104
|
+
guess and is labeled as one. Schema 2 added `code_sha` and `forge_rev`. The first
|
|
105
|
+
capture after an upgrade adds them in one `BEGIN IMMEDIATE` transaction, and an older
|
|
106
|
+
forge still running keeps writing.
|
|
107
|
+
|
|
108
|
+
## Commands
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
./forge dataset status [-v] [--json] # where, how much, and what looks incomplete
|
|
112
|
+
./forge dataset dump [--kind K] [--run R] [--task T] [--since UNIX] # events as JSON lines
|
|
113
|
+
./forge dataset blob <sha> # print one stored blob
|
|
114
|
+
./forge dataset import <dir|archive|legacy-dir>... [--registry] [--fractal] [--ledger REPO] [--dry-run]
|
|
115
|
+
./forge dataset export [--slice S]... [--limit N] [--seed N] [--out DIR] # labeled decision cases
|
|
116
|
+
./forge dataset outcomes [--horizon-days 14] [--repo PATH] [--dry-run] # what integrated work became
|
|
117
|
+
./forge dataset routing [--repo PATH] [--since DAYS] [--json] # pass rate and runtime per tier and model
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`status` runs a different completeness check for each mode, because each mode
|
|
121
|
+
legitimately lacks different things. A solo run has no separate verification, and a
|
|
122
|
+
final review's QA has no task attempt. The checks are:
|
|
123
|
+
|
|
124
|
+
- executions that started but never ended
|
|
125
|
+
- task QA dispatches with no task attempt
|
|
126
|
+
- parallel PASSes with no merge outcome in a finished run
|
|
127
|
+
- parallel runs with no end
|
|
128
|
+
- blob references with no stored blob
|
|
129
|
+
|
|
130
|
+
## Exporting labeled decisions
|
|
131
|
+
|
|
132
|
+
`export` folds the events into cases in the System One wire format (one JSON object per
|
|
133
|
+
line with `id`, `state`, `questions` and `expected`), the format decision-model
|
|
134
|
+
benchmarks and fine-tuning recipes take. Each case is a decision Jev makes in forge,
|
|
135
|
+
with its state built the way `forge_jev` builds it for that site and its label taken
|
|
136
|
+
from what happened afterwards:
|
|
137
|
+
|
|
138
|
+
| slice | question | label |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `routing` | will the first attempt at this task pass review? (state adds the dwarf and reviewer models) | the final status of the first real attempt (see below) |
|
|
141
|
+
| `routing` | which difficulty tier is this task? | the tier the orchestrator or planner declared. This is agreement, not an outcome |
|
|
142
|
+
| `retry` | will this retry pass, given the review findings it was handed? (state adds the attempt number and the previous review) | the final status of that attempt (see below) |
|
|
143
|
+
| `review` | will the independent reviewer pass this diff? (state names the reviewer's model, effort and harness) | the QA verdict. A diff whose reviews disagreed is dropped |
|
|
144
|
+
| `review` | will most of the lines this diff adds stay in the code, unrewritten and unreverted? | from `outcomes`: at least 80% of the added lines still blamed to the task's commits at the horizon, and no revert. Only the review of the diff that landed gets it |
|
|
145
|
+
| `review` | what is the most severe problem the reviewer will find? (`none`, `P3` to `P0`, the contract's scale) | the worst CONFIRMED finding, only when every CONFIRMED finding carries a severity and none is UNSTATED. A review whose findings cannot be fully read gets no label rather than a wrong `none` |
|
|
146
|
+
| `drift` | is this undeclared file likely to need editing? | whether the passing attempt changed it. Negatives are undeclared files near the task, plus random ones |
|
|
147
|
+
| `citation` | do the places this review cites appear in this diff? | the review paired with its own diff, or with another task's diff from the same repo that shares no file |
|
|
148
|
+
| `memory` | is this recorded fact worth including for this task? | the fact's anchor is a file the task edited (true), or lies in an area it never touched (false). Anything in between is left out |
|
|
149
|
+
|
|
150
|
+
The reviewer is part of the review state because strictness differs by reviewer: the
|
|
151
|
+
same kind of diff passes far more often under one reviewer than another, so a label
|
|
152
|
+
means little without knowing who gave it. Routing and retry states also carry a `size`
|
|
153
|
+
block (`prompt_chars`, `declared_files`, `plan_tasks`, `deps`, `decompose_level`, from
|
|
154
|
+
`forge_jev/features.py`), since task size moves the first-pass rate more than anything
|
|
155
|
+
else in the history.
|
|
156
|
+
|
|
157
|
+
An attempt is labeled by the last execution that carries its number, because a QA-only
|
|
158
|
+
rerun keeps the number. Refunded and infra-stopped executions spent no attempt. PASS is
|
|
159
|
+
true and FAIL is false. TIMEOUT is false only when the implementer's own dispatch ran out
|
|
160
|
+
of time. Everything else says nothing about the implementer and is left out, counted
|
|
161
|
+
under `censored` in `manifest.json`:
|
|
162
|
+
- a reviewer's TIMEOUT or ERROR
|
|
163
|
+
- a harness crash
|
|
164
|
+
- forge's own failures between dispatches
|
|
165
|
+
- UNKNOWN, INVALIDATED, INTERRUPTED
|
|
166
|
+
|
|
167
|
+
A later success is never labeled as the first attempt's.
|
|
168
|
+
|
|
169
|
+
Decisions with no outcome to label them are left out rather than labeled by opinion.
|
|
170
|
+
These are QA effort sizing (recorded until 2026-10-17), the five routing rubrics, failure triage, and coupling, since
|
|
171
|
+
no merge has conflicted yet.
|
|
172
|
+
|
|
173
|
+
Routing cases of tasks routed with `plan --explore` add `route` to their metadata: the
|
|
174
|
+
candidates, the pool's own pick, the choice, its propensity, and `followed`, which says
|
|
175
|
+
whether attempt 1 really ran the chosen spec (an approval-gate edit or `retry --dwarf` can
|
|
176
|
+
replace it). `assignments.jsonl` lists every such decision with its first attempt's outcome
|
|
177
|
+
or censoring reason, including tasks no case was exported for. It is the randomized cohort,
|
|
178
|
+
free of the export's own filters.
|
|
179
|
+
|
|
180
|
+
Review cases whose attempt also had a shadow review (`plan --shadow-qa`) add `shadow` (its
|
|
181
|
+
verdict, reviewer and worst finding) and `consensus` to their metadata. Only a shadow that
|
|
182
|
+
finished on an untouched snapshot and reviewed the same commit counts. The primary
|
|
183
|
+
labels never change, because `qa_shadow` dispatches are not reviews of record.
|
|
184
|
+
`./forge dataset status` prints the agreement rate, overall and per model pair.
|
|
185
|
+
|
|
186
|
+
`meta.jsonl` records each case's slice, run, repo and label source. It also holds hosted
|
|
187
|
+
Jev's own answers where forge recorded them, as baselines that are never part of a case.
|
|
188
|
+
Routing and retry cases add:
|
|
189
|
+
- `decision_provenance` (the plan's) and `outcome_provenance` (the attempt's)
|
|
190
|
+
- `code_changed`
|
|
191
|
+
- the plan `options`
|
|
192
|
+
- `started`
|
|
193
|
+
|
|
194
|
+
Routing cases also add how the task went:
|
|
195
|
+
- `attempts_observed`
|
|
196
|
+
- `raw_verdict`
|
|
197
|
+
- `gate_pass`, a reviewer FAIL the severity gate accepted
|
|
198
|
+
- `first_raw_pass_attempt`
|
|
199
|
+
- `human_accept`
|
|
200
|
+
- `stop_reason`: `passed`, `accepted`, `noretry`, `interrupted` or `no_pass_observed`
|
|
201
|
+
|
|
202
|
+
A final FAIL is not proof a task cannot pass: the retry budget ended the observation.
|
|
203
|
+
The output defaults to `<data>/exports/forge-decisions/` (`0700`, files `0600`). It holds
|
|
204
|
+
prompts, diffs and reviews from your repos, so keep it out of public repos and pass it
|
|
205
|
+
only to local models. Builds are deterministic for a given store and `--seed`.
|
|
206
|
+
|
|
207
|
+
## Routing
|
|
208
|
+
|
|
209
|
+
`forge dataset routing` answers "which model should this tier use?" from the store, in two
|
|
210
|
+
tables kept apart because they answer different questions.
|
|
211
|
+
|
|
212
|
+
**Observational.** Every task's first attempt, by declared tier and the model and effort that
|
|
213
|
+
ran it (resolved, so `sol` and `gpt-6.1-sol:medium:codex` share a row):
|
|
214
|
+
- first-pass rate with a Wilson 95% interval
|
|
215
|
+
- the share that passed review at some attempt, or that a human accepted
|
|
216
|
+
- unfinished tasks
|
|
217
|
+
- attempts per task
|
|
218
|
+
- the implementer's median runtime
|
|
219
|
+
|
|
220
|
+
Routing chose who ran what, so a model sent the hard tasks looks worse here than it is.
|
|
221
|
+
Fractal runs are left out, since their dispatches have identities of their own.
|
|
222
|
+
|
|
223
|
+
**Randomized.** The `plan --explore` draws, one row per (tier, pool pick, neighbour) pair.
|
|
224
|
+
Round-robin pools make the pair differ between rows, and a model has no chance at all
|
|
225
|
+
outside its own pairs, so pairs are never pooled. For each arm:
|
|
226
|
+
- assigned tasks
|
|
227
|
+
- first-pass as a Hajek (self-normalized inverse-propensity) mean
|
|
228
|
+
- Kish's effective n
|
|
229
|
+
- attrition: tasks not run as assigned, and tasks with no outcome
|
|
230
|
+
- runtime
|
|
231
|
+
|
|
232
|
+
Every task stays in the arm it was *assigned* (intent to treat), whatever the approval gate
|
|
233
|
+
or a retry did afterwards. A task is bound to the last draw recorded before its first attempt
|
|
234
|
+
started, so a later re-plan never relabels it. Draws from before the two-candidate rule
|
|
235
|
+
(a pick among every spec of the plan) have no pair and are counted, not compared.
|
|
236
|
+
|
|
237
|
+
**The decision rule** is fixed in advance and only advises. A pair is judged once, at the
|
|
238
|
+
first of 30, 60 or 120 neighbour assignments it reaches, on exactly the assignments up to
|
|
239
|
+
that point. The interval is a run-cluster bootstrap, shown only with at least 10 neighbour
|
|
240
|
+
assignments across 3 runs.
|
|
241
|
+
- For a **low** pair the neighbour is the stronger model: switching is advised only when the
|
|
242
|
+
interval is above 0.
|
|
243
|
+
- For **medium** and **high** pairs it is the cheaper one: it is called non-inferior when the
|
|
244
|
+
interval's lower end is above -0.10 and its runtime is lower.
|
|
245
|
+
|
|
246
|
+
At the default `--explore 0.2` a row changes model 10% of the time, so a pair needs weeks of
|
|
247
|
+
normal use to reach its first window.
|
|
248
|
+
|
|
249
|
+
## Outcomes after merge
|
|
250
|
+
|
|
251
|
+
Every other label comes from a reviewer. `forge dataset outcomes` adds one that does not,
|
|
252
|
+
read from the repo's history after the fact. `integrate` merges the run branch with
|
|
253
|
+
`--no-ff` and fast-forwards your branch to that merge, never squashing, so each task's
|
|
254
|
+
attempt commits land as they are and `git blame` still attributes a task's lines to them.
|
|
255
|
+
For every integration, and every task it brought in, the harvester records:
|
|
256
|
+
|
|
257
|
+
- **Survival:** of the text lines the task added (`base_ref..reviewed_commit`), how many
|
|
258
|
+
are still attributed to the task's commits at the horizon, per file. A deleted file
|
|
259
|
+
counts as none surviving. Binary files are skipped, and a task that only deleted lines
|
|
260
|
+
is `no_additions`.
|
|
261
|
+
- **Reverts:** later commits whose message says `This reverts commit <sha>` for one of the
|
|
262
|
+
task's commits, its merge or the integration.
|
|
263
|
+
- **The horizon:** the last commit on your branch's first-parent chain, after the
|
|
264
|
+
integration, dated before `integrate` + N days. The branch is resolved once, and the
|
|
265
|
+
chain is walked by position, so commit dates out of order cannot skip a commit.
|
|
266
|
+
|
|
267
|
+
Two cases leave a task without a survival count:
|
|
268
|
+
- **Pending:** the horizon has not been reached and no later commit has passed it yet.
|
|
269
|
+
Nothing is written, and the next harvest tries again.
|
|
270
|
+
- **Lost:** a rebase, squash or reset took the integration off the branch. This means
|
|
271
|
+
unobservable, not failed.
|
|
272
|
+
|
|
273
|
+
The rid names the integration, the reviewed commit and the horizon, so harvesting again
|
|
274
|
+
adds nothing, and two horizons are two outcomes. Survival describes the diff that landed,
|
|
275
|
+
with every retry in it, not the first model the task was routed to. That is why the
|
|
276
|
+
exporter attaches it to that diff's review.
|
|
277
|
+
|
|
278
|
+
Each verification event also records the commit it verified (`verification.commit`, named
|
|
279
|
+
before any early return), so a FAIL or UNVERIFIED result can be joined to its code too.
|
|
280
|
+
|
|
281
|
+
## Importing old runs
|
|
282
|
+
|
|
283
|
+
`import` reads run dirs that predate live capture through the same readers. An execution
|
|
284
|
+
seen both live and imported collapses to one event. Old dirs have no begin-time snapshot,
|
|
285
|
+
so the importer will not guess which execution wrote a mutable file:
|
|
286
|
+
|
|
287
|
+
- The **newest** task attempt gets the task dir's current files, flagged `legacy`.
|
|
288
|
+
- **Earlier** attempts get only what is immutable:
|
|
289
|
+
- their own dispatch dirs
|
|
290
|
+
- the diff QA was shown (the last `` ```diff `` block of the QA prompt)
|
|
291
|
+
- the status that `attempts.tsv` logged, or failing that, the QA verdict, with
|
|
292
|
+
`status_source` saying which
|
|
293
|
+
- Unfinished executions (still running, or killed) are skipped. The first write of a rid
|
|
294
|
+
wins, so importing one now would block the real outcome that live capture records when
|
|
295
|
+
it ends. Re-import the dir afterwards to fill anything live capture missed.
|
|
296
|
+
- Repos under temp dirs (test runs) are skipped unless `--include-temp` is given.
|
|
297
|
+
- Archives are extracted to a temp dir and mapped back to their original path. The legacy
|
|
298
|
+
run id hashes that original path, so the archive and the original dir agree on it.
|
|
299
|
+
|
|
300
|
+
`~/.local/share/forge/legacy/` holds archives of run dirs taken before live capture, with a
|
|
301
|
+
`MANIFEST.tsv`. Passing that dir to `import` reads every archive, plus the ledger copies
|
|
302
|
+
listed in `ledgers/REPOS.tsv`.
|
|
303
|
+
|
|
304
|
+
## Invariants
|
|
305
|
+
|
|
306
|
+
- **Never inside the repo.** Capture refuses, silently and before any write, when the store
|
|
307
|
+
resolves inside the run's repo or a forge worktree. `integrate` refuses a dirty tree.
|
|
308
|
+
- **Never read by a prompt builder.** Memory inject, the capsule and Ripwire must not read
|
|
309
|
+
the store. A reviewer that could see earlier runs' verdicts would not be independent.
|
|
310
|
+
`tests/test_dataset.py` enforces that outside its own package, the dataset is only
|
|
311
|
+
imported for capture.
|
|
312
|
+
- **Read-only in your repos.** Capture and the harvester run only object-store git queries
|
|
313
|
+
in a repo: `rev-parse`, `rev-list`, `log`, `show`, `ls-tree`, `cat-file`, `diff` between
|
|
314
|
+
commits and `blame` at a commit. All run with `GIT_OPTIONAL_LOCKS=0`. None takes the
|
|
315
|
+
working tree or refreshes the index. `outcomes` reads the store through a read-only
|
|
316
|
+
connection and writes only what it found.
|
|
317
|
+
- **Private.** Prompts, diffs and logs can contain secrets. The dir is `0700` and its files
|
|
318
|
+
`0600`. Do not sync it. Redaction belongs to whatever exports a training set.
|
|
319
|
+
|
|
320
|
+
## Not captured
|
|
321
|
+
|
|
322
|
+
- **Token counts for openclaude, opencode and antigravity.** Those harnesses expose none.
|
|
323
|
+
They are recorded as null.
|
|
324
|
+
- **Dispatches refused before `begin()`** (an unknown alias, bad flags). No model ran.
|
|
325
|
+
- **The orchestrator's own reasoning.** Only its outputs are recorded: the task table,
|
|
326
|
+
`request.md` and the approval edits visible between the planner's message and the
|
|
327
|
+
declared table.
|
|
328
|
+
- **Counterfactuals.** Each task runs on one model, so nothing here says whether another
|
|
329
|
+
model would have done better. That needs deliberate head-to-head runs.
|