@giamme/forge 0.0.0-stage → 0.8.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1854 -2
  4. package/SKILL.md +122 -0
  5. package/VERSION +1 -0
  6. package/forge +74 -0
  7. package/package.json +26 -4
  8. package/references/dataset.md +329 -0
  9. package/references/decompose.md +667 -0
  10. package/references/fractal-dashboard.md +21 -0
  11. package/references/fractal.md +307 -0
  12. package/references/harnesses.md +353 -0
  13. package/references/jev.md +865 -0
  14. package/references/memory.md +228 -0
  15. package/references/planning.md +42 -0
  16. package/references/ripwire.md +47 -0
  17. package/references/runtime.md +64 -0
  18. package/references/solo.md +113 -0
  19. package/references/ui.md +112 -0
  20. package/registry.tsv +81 -0
  21. package/scripts/forge-artifact.sh +78 -0
  22. package/scripts/forge-contract.py +430 -0
  23. package/scripts/forge-dataset.py +6 -0
  24. package/scripts/forge-dispatch.sh +665 -0
  25. package/scripts/forge-fail-first.py +195 -0
  26. package/scripts/forge-fractal-dispatch.sh +4 -0
  27. package/scripts/forge-fractal-options.sh +54 -0
  28. package/scripts/forge-fractal.py +6 -0
  29. package/scripts/forge-guard.py +266 -0
  30. package/scripts/forge-install-fractal.sh +4 -0
  31. package/scripts/forge-install-ripwire.sh +52 -0
  32. package/scripts/forge-install.sh +135 -0
  33. package/scripts/forge-jev-options.sh +259 -0
  34. package/scripts/forge-jev.py +6 -0
  35. package/scripts/forge-lib-plans.sh +785 -0
  36. package/scripts/forge-lib-review.sh +494 -0
  37. package/scripts/forge-lib-task.sh +302 -0
  38. package/scripts/forge-lib-verify.sh +30 -0
  39. package/scripts/forge-memory.sh +575 -0
  40. package/scripts/forge-metrics.sh +39 -0
  41. package/scripts/forge-parallel-options.sh +76 -0
  42. package/scripts/forge-parallel.sh +1717 -0
  43. package/scripts/forge-plan-check.py +118 -0
  44. package/scripts/forge-prompt.py +45 -0
  45. package/scripts/forge-ripwire.py +136 -0
  46. package/scripts/forge-route.py +344 -0
  47. package/scripts/forge-runtime.py +533 -0
  48. package/scripts/forge-schedule.py +312 -0
  49. package/scripts/forge-shadow.py +305 -0
  50. package/scripts/forge-solo.sh +347 -0
  51. package/scripts/forge-ui-options.sh +42 -0
  52. package/scripts/forge-ui.py +435 -0
  53. package/scripts/forge-update.sh +71 -0
  54. package/scripts/forge-verify-coverage.py +956 -0
  55. package/scripts/forge-version.py +198 -0
  56. package/scripts/forge_dataset/__init__.py +439 -0
  57. package/scripts/forge_dataset/capture.py +330 -0
  58. package/scripts/forge_dataset/checks.py +109 -0
  59. package/scripts/forge_dataset/cli.py +233 -0
  60. package/scripts/forge_dataset/export.py +1009 -0
  61. package/scripts/forge_dataset/harvest.py +716 -0
  62. package/scripts/forge_dataset/importer.py +412 -0
  63. package/scripts/forge_dataset/outcomes.py +275 -0
  64. package/scripts/forge_dataset/routing.py +278 -0
  65. package/scripts/forge_fractal/__init__.py +83 -0
  66. package/scripts/forge_fractal/bridge.py +46 -0
  67. package/scripts/forge_fractal/cli.py +202 -0
  68. package/scripts/forge_fractal/dashboard.css +138 -0
  69. package/scripts/forge_fractal/dashboard.html +41 -0
  70. package/scripts/forge_fractal/dashboard.js +687 -0
  71. package/scripts/forge_fractal/decomposition.py +262 -0
  72. package/scripts/forge_fractal/execution.py +659 -0
  73. package/scripts/forge_fractal/inspection.py +261 -0
  74. package/scripts/forge_fractal/install.py +158 -0
  75. package/scripts/forge_fractal/invocation.py +94 -0
  76. package/scripts/forge_fractal/pipeline.py +124 -0
  77. package/scripts/forge_fractal/selection.py +179 -0
  78. package/scripts/forge_jev/__init__.py +376 -0
  79. package/scripts/forge_jev/backtest.py +322 -0
  80. package/scripts/forge_jev/cli.py +980 -0
  81. package/scripts/forge_jev/client.py +418 -0
  82. package/scripts/forge_jev/corpus.py +156 -0
  83. package/scripts/forge_jev/coupling.py +244 -0
  84. package/scripts/forge_jev/features.py +24 -0
  85. package/scripts/forge_jev/gates.py +100 -0
  86. package/scripts/forge_jev/memory.py +163 -0
  87. package/scripts/forge_jev/outcome.py +49 -0
  88. package/scripts/forge_jev/predict.py +204 -0
  89. package/scripts/forge_jev/questions.py +661 -0
  90. package/scripts/forge_jev/review.py +73 -0
  91. package/scripts/forge_jev/routing.py +271 -0
  92. package/scripts/forge_jev/subset.py +123 -0
  93. package/scripts/forge_jev/verify.py +453 -0
  94. package/scripts/forge_registry.py +140 -0
  95. package/ui/README.md +287 -0
  96. package/ui/THIRD_PARTY_NOTICES.md +69 -0
  97. package/ui/docs/api.md +888 -0
  98. package/ui/forge_ui/__init__.py +20 -0
  99. package/ui/forge_ui/__main__.py +135 -0
  100. package/ui/forge_ui/diffs.py +454 -0
  101. package/ui/forge_ui/executions.py +436 -0
  102. package/ui/forge_ui/export.py +204 -0
  103. package/ui/forge_ui/findings.py +132 -0
  104. package/ui/forge_ui/fractal.py +583 -0
  105. package/ui/forge_ui/models.py +216 -0
  106. package/ui/forge_ui/reader.py +1336 -0
  107. package/ui/forge_ui/server.py +303 -0
  108. package/ui/forge_ui/static/app.js +924 -0
  109. package/ui/forge_ui/static/cloud3d.js +646 -0
  110. package/ui/forge_ui/static/cloudmodel.js +375 -0
  111. package/ui/forge_ui/static/cloudui.js +394 -0
  112. package/ui/forge_ui/static/diffview.js +216 -0
  113. package/ui/forge_ui/static/favicon.svg +1 -0
  114. package/ui/forge_ui/static/files.js +776 -0
  115. package/ui/forge_ui/static/gantt.js +97 -0
  116. package/ui/forge_ui/static/graph.js +266 -0
  117. package/ui/forge_ui/static/index.html +55 -0
  118. package/ui/forge_ui/static/iso.js +765 -0
  119. package/ui/forge_ui/static/leaderboard.js +315 -0
  120. package/ui/forge_ui/static/map3d.js +45 -0
  121. package/ui/forge_ui/static/modelstats.js +249 -0
  122. package/ui/forge_ui/static/notify.js +179 -0
  123. package/ui/forge_ui/static/panel.js +526 -0
  124. package/ui/forge_ui/static/runmodel.js +651 -0
  125. package/ui/forge_ui/static/scene3d.js +996 -0
  126. package/ui/forge_ui/static/search.js +119 -0
  127. package/ui/forge_ui/static/style.css +618 -0
@@ -0,0 +1,228 @@
1
+ # Project memory
2
+
3
+ How forge remembers what it learned about a repository across runs: the `FORGE_LEARNING`
4
+ grammar, what gets promoted into a prompt and what only reaches the ledger, and how memory
5
+ stays small and true as the code moves under it.
6
+
7
+ Read this when memory looks empty, looks wrong, or has grown past its budget.
8
+
9
+ ## Contents
10
+
11
+ - [Why two files](#why-two-files)
12
+ - [Committing, ignoring, disabling](#committing-ignoring-disabling)
13
+ - [The sentinel](#the-sentinel)
14
+ - [Promotion](#promotion)
15
+ - [Dedup, and the limits of it](#dedup-and-the-limits-of-it)
16
+ - [Staleness and the cap](#staleness-and-the-cap)
17
+ - [Injection slices](#injection-slices)
18
+ - [The diff exclusion](#the-diff-exclusion)
19
+ - [Spend accounting](#spend-accounting)
20
+ - [Commands](#commands)
21
+ - [Failure modes](#failure-modes)
22
+
23
+ ## Why two files
24
+
25
+ ```
26
+ .forge/memory.md curated, hard-capped, injected into every prompt
27
+ .forge/ledger.tsv append-only, one row per dispatch, NEVER injected
28
+ ```
29
+
30
+ Injected context is paid for on every dispatch of every future run — with a decomposed run
31
+ that is 2N prompts, forever. So the two things a memory has to do pull in opposite
32
+ directions: remembering everything makes it big, and staying small means forgetting.
33
+
34
+ Splitting them resolves that. The ledger is unbounded and costs nothing, because nothing
35
+ reads it unless asked; it is what recurrence is counted from and what answers "has this been
36
+ tried here before". Only `memory.md` is in prompts, and it is capped hard.
37
+
38
+ Never inline the ledger into a prompt. That would undo the whole arrangement.
39
+
40
+ Both live in the repo so the memory travels with it, and both are written by forge but
41
+ committed by the user — forge does not commit on anyone's behalf.
42
+
43
+ ## Committing, ignoring, disabling
44
+
45
+ `.forge/` appears untracked after the first run. Three coherent choices:
46
+
47
+ | Intent | Action |
48
+ |---|---|
49
+ | share memory with the team | commit `.forge/` |
50
+ | keep it to this machine | `echo '.forge/' >> .gitignore` |
51
+ | no memory at all | `--no-memory` or `FORGE_MEMORY=off` |
52
+
53
+ Gitignoring does **not** disable anything: an ignored `.forge/` is still written and still
54
+ injected into every prompt. Only the third row turns the feature off.
55
+
56
+ **The two files must share a fate.** Because `memory.md` is rebuilt from `ledger.tsv` on
57
+ every write, committing the memory while ignoring the ledger destroys the shared facts the
58
+ first time anyone runs forge on a fresh clone — the rebuild regenerates `memory.md` from that
59
+ machine's own short ledger, and nothing warns:
60
+
61
+ ```
62
+ A commits memory.md only: - pytest -q runs the suite
63
+ - src/a.py is generated [src/a.py]
64
+ B clones, runs forge once: - b learned something new <- A's facts gone
65
+ ```
66
+
67
+ Committing both means `ledger.tsv` grows a line per dispatch and shows up in diffs. That is
68
+ the price of the recurrence counting; the alternative is memory that cannot tell a one-off
69
+ from a pattern.
70
+
71
+ For a repo you contribute to but do not own, ignore it — a PR carrying forge's notes to
72
+ itself is noise to a reviewer who does not use forge.
73
+
74
+ ## The sentinel
75
+
76
+ Extraction happens in-band, in the final message of the role that just did the work:
77
+
78
+ ```
79
+ FORGE_LEARNING: verify | pytest -q runs the suite; make test also lints and is 4x slower
80
+ FORGE_LEARNING: trap | test_api.py::test_timeout is flaky under -n auto [tests/test_api.py]
81
+ FORGE_LEARNING: finding | route added without updating openapi.yaml [src/routes]
82
+ ```
83
+
84
+ `<category> | <text> [optional anchor]`. Categories:
85
+
86
+ | Category | Is |
87
+ |---|---|
88
+ | `verify` | how this project is actually built, tested or checked |
89
+ | `trap` | a landmine: a flaky test, a generated file, a sharp edge |
90
+ | `finding` | a *mistake pattern* likely to recur — emitted by qa, not a description of one bug |
91
+
92
+ This costs no extra dispatch, which is the reason it works at all: a separate "librarian"
93
+ model would have to re-derive from a transcript what the working model already knew. The
94
+ role that hit the landmine is the one that writes it down.
95
+
96
+ `forge-memory.sh note <role>` produces the instruction. It is deliberately biased toward
97
+ silence — "most runs teach nothing durable; emitting no line is the normal, correct
98
+ outcome" — because a false fact is worse than a missing one. A missing fact costs one
99
+ rediscovery; a false one is injected into every future prompt until someone notices.
100
+ Measured behaviour matches: on a trivial task with nothing to learn, models emit nothing;
101
+ on a task with a real trap, they emit it.
102
+
103
+ ## Promotion
104
+
105
+ | Category | Enters `memory.md` |
106
+ |---|---|
107
+ | `verify`, `trap` | on first sighting — these are facts, true the first time |
108
+ | `finding` | only after **two distinct runs** |
109
+
110
+ Everything reaches the ledger immediately regardless; promotion governs only what gets
111
+ injected. The threshold is the main guard against a junk drawer: one QA noticing one bug is
112
+ an incident, and putting it in every future prompt would be noise. Two separate runs finding
113
+ the same thing is a pattern worth telling the next dwarf about.
114
+
115
+ Recurrence counts **distinct runs**, not lines — one run emitting the same learning twice is
116
+ still one occurrence. Promoted findings display their count (`3x:`), which is also how a
117
+ human spots a systemic problem worth fixing properly.
118
+
119
+ ## Dedup, and the limits of it
120
+
121
+ The key is the text lowercased with every run of non-alphanumerics collapsed to one space.
122
+ So `Route added without updating openapi.yaml` and `route added, without updating
123
+ openapi.yaml!` are the same key; a genuinely different phrasing is not.
124
+
125
+ This is exact matching, not fuzzy, and pretending otherwise would be worse than stating it.
126
+ What makes it work in practice is a feedback loop rather than cleverness: memory is *in the
127
+ prompt*, and `note` asks explicitly to reuse the wording of anything already listed. A
128
+ reviewer that can see `2x: route added without updating openapi.yaml` tends to re-emit that
129
+ line rather than invent a synonym, so repeats converge on one phrasing.
130
+
131
+ `memory.md` is rebuilt from the whole ledger on every write rather than edited in place.
132
+ Rebuilding is idempotent and order-independent, so a crashed or concurrent run cannot leave
133
+ memory half-updated — at worst the ledger is one row short.
134
+
135
+ ## Staleness and the cap
136
+
137
+ An entry ending in `[path]` is anchored. When that path stops existing the entry is dropped:
138
+ the code it describes is gone, so the fact is at best useless and at worst actively
139
+ misleading. This is what keeps memory true as the codebase moves, and it is why `note` asks
140
+ for an anchor wherever one makes sense.
141
+
142
+ Caps are 40 lines and 4 KB (`FORGE_MEMORY_MAX_LINES`, `FORGE_MEMORY_MAX_BYTES`). Over cap,
143
+ entries are trimmed from the bottom — lowest recurrence count first, then least recent.
144
+ A single entry is clamped to 180 characters (`FORGE_MEMORY_MAX_FACT_CHARS`), truncated at a
145
+ word boundary with its anchor preserved, so one discursive model cannot spend the whole
146
+ budget on one line.
147
+
148
+ ## Injection slices
149
+
150
+ | Role | Gets |
151
+ |---|---|
152
+ | planner | Verify + Known traps + Recurring findings |
153
+ | dwarf | Verify + Known traps + Recurring findings |
154
+ | qa | Known traps + Recurring findings |
155
+
156
+ QA is not told the build command because it is not building anything. It *is* told the
157
+ recurring findings, so it can notice this is the fourth time the same bug shipped, and the
158
+ traps, so it does not report a known-generated file as a defect.
159
+
160
+ ## The diff exclusion
161
+
162
+ `.forge/` must be excluded from every diff capture:
163
+
164
+ ```bash
165
+ git diff -- . ':(exclude).forge'
166
+ git add -A -- . ':(exclude).forge' && git diff --cached -- . ':(exclude).forge'
167
+ ```
168
+
169
+ Two distinct failures otherwise, both quiet:
170
+
171
+ - memory is rewritten in the working tree at the end of every run, so the *next* run's
172
+ capture hands it to qa as if a dwarf had written it — an unrelated file in the diff, which
173
+ a good reviewer will correctly flag as scope creep;
174
+ - if `.forge/` ever entered a task's `files`, every task would overlap every other task and
175
+ the wave planner would serialize the entire run while reporting it as deferrals.
176
+
177
+ ## Spend accounting
178
+
179
+ `forge-dispatch.sh` times every dispatch and `record` stores it, so the ledger answers what
180
+ forge has cost this repo without any new bookkeeping and without a byte of prompt context:
181
+
182
+ ```bash
183
+ forge-memory.sh spend <repo> [--run <run-id>]
184
+ ```
185
+
186
+ Wall-clock and dispatch counts grouped by model and by role — **not** tokens and not money.
187
+ The five CLIs expose usage differently or not at all, so a token number would be a number
188
+ forge cannot actually measure, which is worse than none.
189
+
190
+ Two things the output states rather than leaves the reader to work out: durations *sum*
191
+ concurrent work, so a `--max-parallel 3` run reports more model time than it took in
192
+ wall-clock; and dispatches recorded before `duration_s` existed are counted but not timed.
193
+
194
+ `duration_s` is the ledger's tenth column, appended at the end on purpose. A ledger written
195
+ by an older forge — and possibly already committed and shared — has nine fields, and awk
196
+ yields `""` for the missing one instead of shifting every column, so old rows stay readable.
197
+
198
+ Counting is by dispatch, not by row: one dispatch that emits two learnings writes two rows,
199
+ and every row from a single dispatch shares its timestamp, which is what the counter keys on.
200
+
201
+ ## Commands
202
+
203
+ ```bash
204
+ forge-memory.sh inject <repo> <planner|dwarf|qa> # block to prepend; empty if no memory
205
+ forge-memory.sh note <planner|dwarf|qa> # the instruction to append
206
+ forge-memory.sh record <repo> --last <file> --role <r> [--run-id X] [--task T]
207
+ [--model M] [--verdict V] [--duration S]
208
+ forge-memory.sh show <repo>
209
+ forge-memory.sh prune <repo> # re-apply staleness and cap now
210
+ forge-memory.sh spend <repo> [--run <run-id>] # time and dispatch counts
211
+ ```
212
+
213
+ `--no-memory`, or `FORGE_MEMORY=off` in the environment, disables all of it: nothing is
214
+ injected, nothing is recorded, and no `.forge/` is created.
215
+
216
+ ## Failure modes
217
+
218
+ | Symptom | Cause |
219
+ |---|---|
220
+ | memory stays empty | nothing durable was learned — the common and correct case. Check the ledger: rows with `-` categories mean dispatches ran and emitted nothing |
221
+ | a fact you wanted is missing | it was a `finding` seen once; it is in the ledger and will promote on its second run |
222
+ | the same fact appears twice, worded differently | dedup is exact-after-normalization; delete the weaker line from `memory.md`, or let the cap evict it |
223
+ | a fact is wrong and keeps coming back | it is in the ledger. Editing `memory.md` alone will not hold — the next rebuild regenerates from the ledger; remove the ledger rows |
224
+ | memory vanished after a refactor | its entries were anchored to paths that no longer exist and were dropped as stale. Working as intended |
225
+ | `.forge/` shows up in a review | a diff capture is missing the exclude pathspec |
226
+ | memory in a worktree is stale | inject reads the **main repo**, not the worktree; on a repo's first run `.forge/` is not committed, so a worktree branched from the base commit does not have it |
227
+ | a teammate's clone lost every shared fact | `memory.md` was committed but `ledger.tsv` was not; the first `record` there rebuilt memory from an empty ledger. Commit both, or neither |
228
+ | `.forge/` keeps appearing in `git status` | expected — forge never commits it. Commit it to share, or gitignore it to keep it local |
@@ -0,0 +1,42 @@
1
+ ## Planning
2
+
3
+ Fractal selection applies to the later implementation stage. It does not turn a planner
4
+ dispatch or `forge-parallel.sh plan` into nested execution, install a runtime during
5
+ planning, or bypass the task/model approval table. Pass `--fractal` or `--no-fractal` to
6
+ the eventual solo runner or decomposed `run`. See [Fractal](fractal.md).
7
+
8
+ For automatic recursive decisions during execution, use `--fractal-auto-decompose`.
9
+ Its `--fractal-planner` selects the per-node decision model, independently of the
10
+ top-level `--planner` that designs the whole run. Without an override it reuses that
11
+ planner when configured, then each task's initial root model. Child planning must
12
+ preserve the approved interfaces and ownership established here.
13
+
14
+ By default **you** plan: before dispatching, decide the approach and put it in the prompt,
15
+ so the dwarf builds the agreed thing rather than inventing a shape nobody has seen. In a
16
+ decomposed run that matters more, because the capsule prevents two dwarves from touching
17
+ one *file* but nothing stops them inventing incompatible *interfaces* at a seam they share.
18
+
19
+ `--planner <spec>` hands that stage to a named model instead — one dispatch for the whole
20
+ run, never one per task. A single planner is the point: one mind designs both sides of every
21
+ seam, where N independent planners would recreate the problem. Effort defaults to `xhigh`,
22
+ since a bad plan is executed at full price by every dwarf downstream of it.
23
+
24
+ Offer optional installation once before a direct planner dispatch with
25
+ `bash <skill_dir>/scripts/forge-install-ripwire.sh`. Propagate `FORGE_RIPWIRE=off`
26
+ when the user opted out. Supply `--ripwire-query-file` when requirements are separate
27
+ from the assembled planner prompt; see [Ripwire context](ripwire.md).
28
+
29
+ ```bash
30
+ bash <skill_dir>/scripts/forge-dispatch.sh planner <spec> \
31
+ --repo "$REPO" --run-dir "$FORGE_RUN" --prompt-file "$FORGE_RUN/planner.prompt"
32
+ ```
33
+
34
+ The planner is instructed to read only and uses QA permission flags. These flags alone do not enforce filesystem immutability.
35
+ Ask it for the approach only: in a decomposed run, `tasks.tsv` rows plus a few lines of
36
+ approach per task; in a single-task run, just the approach. Ask too for tasks small enough to
37
+ pass on a first attempt, each ending its prompt with an `## Acceptance criteria` section of
38
+ checks the reviewer can verify; see [task size](decompose.md#task-size-and-acceptance-criteria). Write each task's approach to
39
+ `tasks/<id>/approach.md`, where the runner picks it up for both the dwarf and qa.
40
+
41
+ Show the approach before dispatching and let the user change it. That gate is the last
42
+ moment the plan is free; after it, changing the plan costs a whole run.
@@ -0,0 +1,47 @@
1
+ ## Optional Ripwire context
2
+
3
+ Forge automatically uses Ripwire **0.4.0** when found on `PATH`, then in
4
+ `~/.local/bin`. Other versions are preserved but skipped until compatibility is verified.
5
+ All six harnesses use the same preparation step. No provider calls are needed for it.
6
+
7
+ Use `--no-ripwire` on dispatch, solo, or parallel `plan`, `run`, and `retry`, or set
8
+ `export FORGE_RIPWIRE=off` in your shell configuration. Opt-out is resolved before
9
+ probing or offering installation. Parallel plans remember opt-out in `no_ripwire`
10
+ for later resumes and retries; remove that marker to enable it again.
11
+
12
+ Forge setup and solo/parallel run entrypoints offer installation once before agents
13
+ start. The helper shows v0.4.0 and `~/.local/bin/ripwire`, then asks
14
+ “Ripwire is missing. Install it now? [Y/n]”. Enter accepts; no or EOF continues.
15
+ It reads the controlling terminal separately from task stdin. Without a terminal it
16
+ prints the helper command and continues. Dispatch itself never prompts, so background
17
+ workers cannot wait for installation input.
18
+
19
+ ```bash
20
+ bash <skill_dir>/scripts/forge-install-ripwire.sh # offer installation
21
+ bash <skill_dir>/scripts/forge-install-ripwire.sh --dry-run # preview, no downloads
22
+ bash <skill_dir>/scripts/forge-install-ripwire.sh --yes # explicit unattended consent
23
+ ```
24
+
25
+ The helper pins the [upstream v0.4.0 installer](https://github.com/redhat-et/ripwire/blob/v0.4.0/scripts/install.sh)
26
+ and its SHA-256, retains upstream archive checksum and binary version checks, and sets
27
+ `RIPWIRE_NO_ACTIVATE=1`. Existing installations are preserved. Failed installation
28
+ reports its cause; Forge runners continue without requiring Ripwire.
29
+
30
+ Planner/dwarf use `--for` in their own repository or worktree. Runners pass the
31
+ requirements via `--ripwire-query-file`; direct dispatch defaults to the original
32
+ prompt. Only this search excerpt is limited to 2,048 characters. QA uses
33
+ `--pr-context=<review-base>` against the disposable review snapshot; direct QA without
34
+ a baseline uses task context. See the [pinned command reference](https://github.com/redhat-et/ripwire/blob/v0.4.0/docs/COMMANDS.md).
35
+
36
+ The full prompt remains intact, followed by one marked advisory section. Requirements,
37
+ ownership, retry findings and the actual diff remain authoritative; suggested tests
38
+ supplement required verification. Each attempt prepares fresh context with `--no-cache`,
39
+ excluding `.forge` and Git internal paths. Temporary preparation files live with the
40
+ external attempt artifacts. Preparation has a shared 30-second deadline, a 4,096 estimated
41
+ token budget and a 16 KiB subprocess output ceiling. Failed, empty, oversized, incompatible
42
+ or timed-out results are skipped with a reason; timeout cleanup kills the process group.
43
+
44
+ Each `attempts/<role>-*/ripwire.json` records the command, binary version, repository,
45
+ baseline, duration, status and delivered section (or skip reason). Dry runs and preflight
46
+ skip preparation. Doctor reports availability without installation. Native Codex review
47
+ skips context because its dispatch cannot accept an additional prompt.
@@ -0,0 +1,64 @@
1
+ # Runtime helper decision
2
+
3
+ The approved optimization plan permits Python 3 standard-library helpers while retaining
4
+ shell entrypoints and all existing model, retry and integration boundaries.
5
+
6
+ A complete Python runner would simplify process ownership, but would also rewrite artifact
7
+ capture, retry, merge, permission and setup behavior. The chosen design keeps those shell
8
+ contracts and adds focused helpers: `forge-prompt.py` assembles context without summarizing
9
+ requirements, `forge-schedule.py` coordinates eligible tasks (including opt-in in-run retries and
10
+ infrastructure pauses), `forge-runtime.py` handles capability caching, attempt telemetry and
11
+ infrastructure-failure classification, `forge-contract.py` owns the shared QA/dwarf prompt
12
+ contract and the severity gate, `forge-guard.py` the implementer guards, and
13
+ `forge-verify-coverage.py` the verify-coverage warnings. `forge-metrics.sh` connects pipeline timing to
14
+ existing shell stages. Python unit tests retain the repository's standard-library unittest
15
+ convention; no new dependency manager or test framework is required.
16
+
17
+ Tasks execute through `_task` and merge through `_merge`. The coordinator pins baselines
18
+ before launching tasks, excludes overlapping active ownership paths, and serializes merges.
19
+ Each merge still checks dependency status, reviewed commit, source fingerprint and accepted
20
+ integration revision. A plan lock spans run/retry/integrate operations. Saved passing work
21
+ can resume without a new dispatch; ordinary failed and interrupted work requires explicit
22
+ retry (or an opt-in `run --retry-failed N`), while tasks paused on an infrastructure failure
23
+ resume on the next `run` and an unjudged review re-runs QA only. Fractal has the additional
24
+ recovery checkpoints described below.
25
+
26
+ Review repositories import self-contained tree object packs and create two commits before
27
+ checking out the final tree. No alternates or source repository access is required afterward.
28
+ Both exact tree comparisons and the existing fingerprint checks remain mandatory.
29
+
30
+ Telemetry is independent of project memory. Dispatch and pipeline attempt records are nested;
31
+ their total durations must not be added together. Unsupported usage remains unknown. See
32
+ [the evaluation](../tests/benchmark-2026-09-07.md) for observed improvements and regressions.
33
+
34
+ ## Optional Fractal adapter decision
35
+
36
+ Fractal adds a Python adapter around the existing shell pipeline instead of replacing
37
+ Forge's dispatcher, model registry, QA or integration rules. This keeps provider effort,
38
+ permission and attempt-record behavior in one place. The isolated runtime pins Fractal
39
+ 1.2.0 and registers `ForgeAgent` through upstream `agents.py`; no Fractal fork is required.
40
+ Ordinary execution and inspection do not require that runtime to be installed.
41
+
42
+ `forge-fractal-options.sh` records the per-run choice and selects the bridge only when
43
+ enabled. `forge_fractal/selection.py` freezes routing and limits; `execution.py` manages
44
+ bounded children, shared model slots, process ownership and candidate import checks;
45
+ `pipeline.py` preserves Forge checkpoints for resume and explicit retry. Work steps use
46
+ fresh dispatcher invocations rather than provider conversation continuation.
47
+
48
+ Each task separates a Fractal control repository from product snapshot repositories.
49
+ That separation prevents wiki/configuration/ledger initialization from entering the product
50
+ diff. Solo imports check source fingerprints and preserve the user's index. Decomposed
51
+ candidates return to the task's existing pinned review and integration boundary. Explicit
52
+ retry retains prior work and the original Forge QA baseline while recording a new attempt.
53
+
54
+ Runtime and durable run records live under
55
+ `${XDG_STATE_HOME:-$HOME/.local/state}/forge/fractal/`, outside the product repository.
56
+ A single task coordinator and a run-wide invocation limiter bound nested work and QA;
57
+ setup and shutdown count toward the task deadline, explicit pauses do not. Fractal
58
+ execution status, Forge acceptance and repository verification remain separate records.
59
+
60
+ `inspection.py` reads curated managed artifacts and the pinned SQLite ledger without
61
+ constructing lifecycle-reconciling Fractal nodes or marking messages read. The browser
62
+ server is read-only and localhost-bound; portable HTML embeds captured data and assets.
63
+ Unsupported tokens/cost/tool details stay unknown, and the bridge rejects dollar caps.
64
+ See [operating instructions](fractal.md) and [verification evidence](../tests/fractal-verification.md).
@@ -0,0 +1,113 @@
1
+ # Solo runs
2
+
3
+ Create a run directory outside the repository, write the full implementation requirements to
4
+ `prompt.md`, and optionally add `goal.txt` and an approach file. State the selected models,
5
+ efforts, harnesses and any explicit bypass flags before dispatch.
6
+
7
+ ```bash
8
+ bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
9
+ --dwarf <spec> [--qa <spec>] [--approach <file>] \
10
+ [--yolo-dwarf] [--yolo-qa] [--timeout <seconds>] [--no-memory] [--dry-run] \
11
+ [--fractal | --no-fractal] [--fractal-concurrency <n>] [--fractal-deadline <seconds>] \
12
+ [--ui | --no-ui]
13
+ ```
14
+
15
+ `--ui` opens the run in [forge-ui](ui.md) before the first dispatch and prints a
16
+ `forge-ui: <url>` line to relay. It is fail-open (a UI that cannot start never changes the
17
+ exit code), is not remembered, and is ignored with `--dry-run`. Ask once for a new run; do
18
+ not ask on a re-run or `--retry`.
19
+
20
+ Besides the artifacts above, the runner writes these for forge-ui and for you:
21
+
22
+ | File | Meaning |
23
+ |---|---|
24
+ | `status` | `RUNNING` while it works, then this invocation's outcome: a verdict word (`PASS`, `FAIL`, `UNKNOWN`, `NOCHANGES`, `INVALIDATED`) or `TIMEOUT`, `INFRA`, `INTERRUPTED`, `ERROR` |
25
+ | `dwarf.spec`, `qa.spec` | the specs this run was given, one line each |
26
+ | `attempts.tsv` | one row per invocation: epoch, attempt number, status, infrastructure class, dwarf spec |
27
+
28
+ Both roles receive offline preflight before implementation starts. The runner records a
29
+ starting tree with a private index, then captures the net filesystem changes since that
30
+ snapshot, including staged, unstaged, binary and untracked files. It does not alter the
31
+ user's staging choices. `existing.diff` distinguishes pre-existing work from `changes.diff`.
32
+ Ignored files and Forge memory are outside the implementation diff. Concurrent writers
33
+ cannot be attributed reliably: keep one implementer per source tree.
34
+
35
+ QA receives the full implementation input, approach and actual diff in a disposable Git
36
+ repository. Source and review fingerprints are checked afterwards; mutations invalidate QA.
37
+ This detects mutations; it is not an operating-system security sandbox for unrestricted Bash.
38
+
39
+ Read `dwarf.last`, `qa.last`, `changes.diff` and `verdict`. Preserve CONFIRMED/PLAUSIBLE labels
40
+ when relaying findings. Report scope drift and unsupported implementation claims. Do not
41
+ automatically retry. In ordinary execution the run is one implementation followed by
42
+ one review; Fractal may use multiple bounded work steps before that independent review.
43
+
44
+ Statuses: PASS, FAIL, UNKNOWN, NOCHANGES, INVALIDATED. Exit codes: 0 completed review
45
+ (inspect verdict), 2 usage, 3 precondition, 4 dispatch failure, 5 no changes or invalidated
46
+ review, 7 timeout, 8 infrastructure failure (quota, auth, rate limit, network, empty output:
47
+ nothing was judged — fix the cause and run again; a dwarf that returns nothing *and* changes
48
+ nothing is reported as 8, not 5). The default dispatch timeout is 2700 seconds, scaled up for
49
+ `xhigh`/`max` effort (so the default QA/planner effort gets 3600), overridable per model in the
50
+ registry; 0 disables that dispatch timeout, but an enabled Fractal task still has its own attempt
51
+ deadline. See [dispatch timeouts](../README.md#scriptsforge-dispatchsh).
52
+
53
+ The dwarf prompt carries implementer rules (no commits, no background processes, finish every
54
+ measurement before the last message). forge records what it observes: if the dwarf committed
55
+ anyway the run still reviews its changes, says so, and prints `git reset --soft <old HEAD>` —
56
+ it never resets anything itself; leftover processes (stopped) and promises of later work appear
57
+ under "implementer guard notes". The QA prompt asks for `P0..P3`-labelled findings; solo has no
58
+ threshold gate, so any CONFIRMED finding still fails.
59
+
60
+ `--native-review` is an explicit Codex-only alternative: native review cannot accept the
61
+ custom requirements prompt or verdict instruction. Its result is informational and UNKNOWN;
62
+ it cannot establish requirements coverage. The disposable repository supplies the recorded
63
+ baseline via `--review-base`.
64
+
65
+ `--fail-first` runs the change's new tests against the code without its non-test changes
66
+ first and gives the reviewer the result (`failfirst.json`); see
67
+ [fail-first evidence](decompose.md#fail-first-evidence-opt-in).
68
+
69
+ Changes remain in the source working tree. Forge does not commit or integrate solo work.
70
+
71
+ Solo defaults to `--output summary`; `--output full` includes raw CLI output. Read the two
72
+ `.last` files once, then inspect logs only as needed. `forge-dispatch.sh report <run-dir>`
73
+ reports immutable attempt metrics and native usage independently of project memory. Shared
74
+ context is assembled from original requirements and approach; QA does not embed dwarf
75
+ execution instructions. Review snapshots use self-contained object packs and one checkout.
76
+
77
+ Optional [Ripwire context](ripwire.md) is enabled by default. Use `--no-ripwire`
78
+ or `FORGE_RIPWIRE=off` to disable preparation and installation offers.
79
+
80
+ ## Fractal execution and recovery
81
+
82
+ [Fractal](fractal.md) is separately opt-in. It starts from the complete snapshot
83
+ above, runs in a managed isolated repository, and imports the candidate only after
84
+ checking source drift. Resume preserves that snapshot. `--retry` starts an explicit
85
+ new Fractal attempt using preserved work and the original QA baseline. A saved PASS
86
+ checkpoint is reused only while its source fingerprint remains unchanged.
87
+
88
+ Ask `Use Fractal for this run? [y/N]` once in the conversational workflow and pass
89
+ `--fractal` or `--no-fractal` explicitly to the runner. A direct interactive runner
90
+ asks if neither flag is given; unattended execution defaults off. Installation is a
91
+ separate choice. Do not ask again when resuming or retrying a recorded run.
92
+
93
+ ```bash
94
+ # First run, with optional child routing; the run directory already contains prompt.md.
95
+ bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
96
+ --dwarf sol:high --qa opus --dwarf-low luna:medium --fractal
97
+
98
+ # Discover the managed run ID, then inspect or resume from another terminal.
99
+ <skill_dir>/forge fractal runs --repo "$REPO" --json
100
+ <skill_dir>/forge fractal tree RUN_ID --json
101
+ <skill_dir>/forge fractal resume RUN_ID
102
+
103
+ # Start a new attempt only when explicitly requested, using the same frozen settings.
104
+ bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" \
105
+ --dwarf sol:high --qa opus --dwarf-low luna:medium --fractal --retry
106
+ ```
107
+
108
+ The solo directory's `fractal-selection.json` also records the managed run ID.
109
+ Per-node workspaces, preserved responses and raw attempts are under the managed run;
110
+ the solo directory still supplies `changes.diff`, `qa.last` and `verdict` for delivery.
111
+ Parent invocations yield while their children run. QA sees their combined task diff.
112
+ Use [Fractal controls and reports](fractal.md#inspection-and-controls) to inspect partial
113
+ work; a completed execution node does not imply a PASS verdict or repository verification.
@@ -0,0 +1,112 @@
1
+ # forge-ui
2
+
3
+ forge-ui is a read-only local web page that shows a forge run live: a decomposed plan or a
4
+ solo run. It is opt-in per run. The orchestrator asks "Open forge-ui to watch this run?
5
+ [y/N]" once at the start of every new interactive run and passes the answer as `--ui` or
6
+ `--no-ui`. Nothing about the run depends on it: a UI that cannot start prints a note and the
7
+ run goes on, with the same exit code.
8
+
9
+ ## What it shows
10
+
11
+ - **Map of every run:** every run forge has registered, one district per repo and one block per
12
+ run with a lamp per task, filterable by state, repo, kind, model, age and review verdict.
13
+ - **Run cloud:** every run at once, full screen in 3D, grouped by project, in one of five layouts
14
+ (helix, sphere, galaxy, timeline, constellation) as blocks or orbs. Filters narrow it to exactly
15
+ the runs wanted (project, state, difficulty, tasks per run, models and more), and runs can be
16
+ dragged anywhere; where they were dropped is remembered in that browser.
17
+ - **Files heatmap:** which tasks planned, changed, drifted into or conflicted on which files,
18
+ with same-wave overlaps marked, and each file's diff a click away.
19
+ - **Model leaderboard:** first-attempt pass rates with confidence intervals, retries, escalations,
20
+ timeouts, time and tokens per implementer, and verdicts, findings and shadow agreement per
21
+ reviewer, filterable by project, difficulty and period.
22
+ - **Diffs with findings in place:** any task diff, the exact diff each reviewer read, or a final
23
+ review's combined diff, with every P0–P3 finding shown under the line it names.
24
+ - **Fractal child tasks:** the tree a Fractal task was split into, in the inspector and above
25
+ the opened task in 3D, each node with its decision, cost, output and candidate diff.
26
+ - **Export a run:** one HTML file to share that opens without forge-ui; prompts, diffs, reports
27
+ and local paths stay out unless chosen.
28
+ - **Quick jump and notifications:** Ctrl K (⌘K) or `/` searches every run, task, project and
29
+ model; the bell tells you when a run finishes or fails, a task fails or a final review gives its
30
+ verdict, and can raise browser notifications while the tab is in the background.
31
+ - **Plan run:** tasks in their planned waves with dependency cables, then the finish line:
32
+ integration, verification and the whole-run final review, each a node with its status. The
33
+ *Execution* view shows the same run on wall-clock time, one block per attempt in the lane it
34
+ ran in, with projected blocks for what has not started and a strip of busy slots against
35
+ `--max-parallel`.
36
+ - **Opening a node** shows its attempts, dwarves and reviews, and an inspector with the prompt
37
+ that launched each one, the model, timings by phase, tokens, verdicts and the P0–P3 findings
38
+ of every review (the final review's included).
39
+ - **Solo run:** one task: its attempts, specs, prompts and the final messages.
40
+ - It refreshes every two seconds while something runs (five otherwise) while the tab is
41
+ visible, so a run can be watched as it goes. A run can also be replayed from its record.
42
+
43
+ The 3D view loads three.js from cdn.jsdelivr.net when first used; offline, the page shows the
44
+ same run in 2D. The full page description is in [ui/README.md](../ui/README.md).
45
+
46
+ ## Commands
47
+
48
+ ```bash
49
+ bash <skill_dir>/scripts/forge-parallel.sh plan "$PLAN" --repo "$REPO" ... --ui
50
+ bash <skill_dir>/scripts/forge-solo.sh "$FORGE_RUN" --repo "$REPO" --dwarf <spec> --ui
51
+ <skill_dir>/forge ui open [DIR] [--no-browser] [--port N]
52
+ <skill_dir>/forge ui status [--json]
53
+ <skill_dir>/forge ui stop
54
+ ```
55
+
56
+ - `--ui` starts (or reuses) the server for this run and prints a `forge-ui: <url>` line;
57
+ relay that URL to the user. `--no-ui` is the explicit "no". `plan --ui` prints the link
58
+ under the task table. `forge-solo.sh --ui` is ignored with `--dry-run`.
59
+ - `forge ui open DIR` does the same for an existing plan or solo run dir; without `DIR` it
60
+ opens the run list. `--no-browser` prints the link only. The browser opens the first time a
61
+ run is opened, not on every `open`.
62
+ - `forge ui status` exits 0 when the server runs and 1 when not; `--json` prints
63
+ `{"running": ...}` with pid, port, url, version, build, started and log.
64
+ - `forge ui stop` stops it; it exits 0 when nothing was running.
65
+
66
+ | Variable | Meaning |
67
+ |---|---|
68
+ | `FORGE_UI=off` | `--ui` on a runner starts nothing (a note is printed); `forge ui open` still starts the server |
69
+ | `FORGE_UI_BROWSER=off` | print the link, never open a browser |
70
+ | `FORGE_UI_PORT` | preferred port, default `8765`; `--port` wins; a taken port falls back to a free one |
71
+ | `FORGE_UI_IDLE_S` | idle seconds before the server exits, default `14400`; `0` never |
72
+ | `FORGE_RUN_REGISTRY=off` | runs stop registering themselves (`--ui` and `forge ui open DIR` still register the run they open) |
73
+
74
+ ## One shared server
75
+
76
+ All runs share one detached server on `127.0.0.1`. A second `forge ui open` reuses it.
77
+ It is replaced when it runs old code: `open` compares the server's build (a hash of the
78
+ `forge_ui` Python files, reported by `/api/health`) with the checkout's, and when they differ,
79
+ or the registry directory changed, stops the old server and starts a new one. It exits by
80
+ itself after `FORGE_UI_IDLE_S` seconds without a request; the next `open` starts it again.
81
+ Concurrent launchers are serialised by a lock, so they never start two servers.
82
+
83
+ ## State and logs
84
+
85
+ ```text
86
+ ${XDG_STATE_HOME:-~/.local/state}/forge/ui/
87
+ server.json pid, port, build, registry and the runs a browser was opened for
88
+ server.log the server's output; read it when the server did not start
89
+ lock serialises launchers
90
+ ```
91
+
92
+ Every plan, run and solo run registers its run dir in forge's run registry
93
+ (`${XDG_CONFIG_HOME:-~/.config}/forge/runs/`), whether or not the UI was asked for: that is
94
+ how the map shows all of a repo's runs. `forge-parallel.sh run` also records the
95
+ `--max-parallel` it was given in `$PLAN/max_parallel`.
96
+
97
+ ## Privacy
98
+
99
+ The server binds only `127.0.0.1` and serves read-only. It has **no token**: any process on
100
+ the machine, under any user who can reach loopback, can read every run it knows, including
101
+ prompts, diffs and review text from private repos. A running server keeps serving them,
102
+ including runs that were later rerun with `--no-ui`.
103
+
104
+ Every run is registered, so a running server shows every run on the machine, not only those
105
+ opened with `--ui`. The 3D view fetches three.js from cdn.jsdelivr.net (no run data is sent;
106
+ choose 2D in the page to never contact it).
107
+
108
+ `--no-ui` and `FORGE_UI=off` only stop a *runner* from launching the server. They do not stop a
109
+ server that is already running, do not unregister runs, and do not block `forge ui open`. To
110
+ close the exposure, run `forge ui stop`. Do not forward the server's port.
111
+
112
+ The API contract is [ui/docs/api.md](../ui/docs/api.md).