@giamme/forge 0.0.0-stage → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +205 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/SKILL.md +123 -0
- package/VERSION +1 -0
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +69 -0
- package/docs/commands.md +315 -0
- package/docs/dataset.md +20 -0
- package/docs/decomposed-runs.md +268 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +92 -0
- package/docs/fractal.md +181 -0
- package/docs/install.md +160 -0
- package/docs/memory.md +156 -0
- package/docs/models.md +118 -0
- package/docs/reviews.md +231 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +79 -0
- package/package.json +27 -4
- package/references/dataset.md +329 -0
- package/references/decompose.md +667 -0
- package/references/fractal-dashboard.md +21 -0
- package/references/fractal.md +307 -0
- package/references/harnesses.md +365 -0
- package/references/jev.md +865 -0
- package/references/memory.md +228 -0
- package/references/planning.md +42 -0
- package/references/ripwire.md +47 -0
- package/references/runtime.md +64 -0
- package/references/solo.md +113 -0
- package/references/ui.md +112 -0
- package/registry.tsv +81 -0
- package/scripts/forge-artifact.sh +78 -0
- package/scripts/forge-contract.py +430 -0
- package/scripts/forge-dataset.py +6 -0
- package/scripts/forge-dispatch.sh +671 -0
- package/scripts/forge-fail-first.py +195 -0
- package/scripts/forge-fractal-dispatch.sh +4 -0
- package/scripts/forge-fractal-options.sh +54 -0
- package/scripts/forge-fractal.py +6 -0
- package/scripts/forge-guard.py +266 -0
- package/scripts/forge-install-fractal.sh +4 -0
- package/scripts/forge-install-ripwire.sh +52 -0
- package/scripts/forge-install.sh +135 -0
- package/scripts/forge-jev-options.sh +259 -0
- package/scripts/forge-jev.py +6 -0
- package/scripts/forge-lib-plans.sh +785 -0
- package/scripts/forge-lib-review.sh +494 -0
- package/scripts/forge-lib-task.sh +302 -0
- package/scripts/forge-lib-verify.sh +30 -0
- package/scripts/forge-memory.sh +575 -0
- package/scripts/forge-metrics.sh +39 -0
- package/scripts/forge-parallel-options.sh +76 -0
- package/scripts/forge-parallel.sh +1717 -0
- package/scripts/forge-plan-check.py +118 -0
- package/scripts/forge-prompt.py +45 -0
- package/scripts/forge-ripwire.py +136 -0
- package/scripts/forge-route.py +344 -0
- package/scripts/forge-runs.py +121 -0
- package/scripts/forge-runtime.py +586 -0
- package/scripts/forge-schedule.py +312 -0
- package/scripts/forge-shadow.py +305 -0
- package/scripts/forge-solo.sh +347 -0
- package/scripts/forge-ui-options.sh +42 -0
- package/scripts/forge-ui.py +435 -0
- package/scripts/forge-update.sh +71 -0
- package/scripts/forge-verify-coverage.py +956 -0
- package/scripts/forge-version.py +240 -0
- package/scripts/forge_dataset/__init__.py +439 -0
- package/scripts/forge_dataset/capture.py +330 -0
- package/scripts/forge_dataset/checks.py +109 -0
- package/scripts/forge_dataset/cli.py +233 -0
- package/scripts/forge_dataset/export.py +1009 -0
- package/scripts/forge_dataset/harvest.py +716 -0
- package/scripts/forge_dataset/importer.py +412 -0
- package/scripts/forge_dataset/outcomes.py +275 -0
- package/scripts/forge_dataset/routing.py +278 -0
- package/scripts/forge_fractal/__init__.py +84 -0
- package/scripts/forge_fractal/bridge.py +46 -0
- package/scripts/forge_fractal/cli.py +202 -0
- package/scripts/forge_fractal/dashboard.css +138 -0
- package/scripts/forge_fractal/dashboard.html +41 -0
- package/scripts/forge_fractal/dashboard.js +687 -0
- package/scripts/forge_fractal/decomposition.py +262 -0
- package/scripts/forge_fractal/execution.py +659 -0
- package/scripts/forge_fractal/inspection.py +261 -0
- package/scripts/forge_fractal/install.py +158 -0
- package/scripts/forge_fractal/invocation.py +94 -0
- package/scripts/forge_fractal/pipeline.py +124 -0
- package/scripts/forge_fractal/selection.py +179 -0
- package/scripts/forge_jev/__init__.py +376 -0
- package/scripts/forge_jev/backtest.py +322 -0
- package/scripts/forge_jev/cli.py +980 -0
- package/scripts/forge_jev/client.py +418 -0
- package/scripts/forge_jev/corpus.py +156 -0
- package/scripts/forge_jev/coupling.py +244 -0
- package/scripts/forge_jev/features.py +24 -0
- package/scripts/forge_jev/gates.py +100 -0
- package/scripts/forge_jev/memory.py +163 -0
- package/scripts/forge_jev/outcome.py +49 -0
- package/scripts/forge_jev/predict.py +204 -0
- package/scripts/forge_jev/questions.py +661 -0
- package/scripts/forge_jev/review.py +73 -0
- package/scripts/forge_jev/routing.py +271 -0
- package/scripts/forge_jev/subset.py +123 -0
- package/scripts/forge_jev/verify.py +453 -0
- package/scripts/forge_registry.py +140 -0
- package/ui/README.md +347 -0
- package/ui/THIRD_PARTY_NOTICES.md +177 -0
- package/ui/docs/api.md +970 -0
- package/ui/forge_ui/__init__.py +20 -0
- package/ui/forge_ui/__main__.py +135 -0
- package/ui/forge_ui/diffs.py +454 -0
- package/ui/forge_ui/executions.py +436 -0
- package/ui/forge_ui/export.py +214 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +582 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1424 -0
- package/ui/forge_ui/server.py +312 -0
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +1079 -0
- package/ui/forge_ui/static/cloud3d.js +647 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +397 -0
- package/ui/forge_ui/static/convo.js +295 -0
- package/ui/forge_ui/static/datatable.js +232 -0
- package/ui/forge_ui/static/diffview.js +235 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +67 -0
- package/ui/forge_ui/static/iso.js +816 -0
- package/ui/forge_ui/static/leaderboard.js +311 -0
- package/ui/forge_ui/static/map3d.js +45 -0
- package/ui/forge_ui/static/modelstats.js +249 -0
- package/ui/forge_ui/static/notify.js +179 -0
- package/ui/forge_ui/static/panel.js +562 -0
- package/ui/forge_ui/static/runmodel.js +667 -0
- package/ui/forge_ui/static/scene3d.js +1471 -0
- package/ui/forge_ui/static/search.js +120 -0
- package/ui/forge_ui/static/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +1073 -0
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
"""Question builders for the TypeSafe wire format, plus a trivial liveness probe.
|
|
2
|
+
|
|
3
|
+
Every rubric Jev is ever asked lives in this one file, not scattered at each call site.
|
|
4
|
+
That is deliberate: a human auditing what gets sent to an external API, and what
|
|
5
|
+
probability threshold decides anything, should be able to read one file top to bottom
|
|
6
|
+
instead of hunting through backtest.py, client.py and cli.py for scattered prompts.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def Noul(instructions: str, *, true: str | None = None, false: str | None = None) -> dict:
|
|
12
|
+
question = dict(type='noul', instructions=instructions)
|
|
13
|
+
if true is not None or false is not None:
|
|
14
|
+
question['criteria'] = {'true': true, 'false': false}
|
|
15
|
+
return question
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def Choice(instructions: str, criteria: dict) -> dict:
|
|
19
|
+
if len(criteria) < 2:
|
|
20
|
+
raise ValueError('Choice needs at least 2 options')
|
|
21
|
+
return dict(type='choice', instructions=instructions, criteria=criteria)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def Score(instructions: str, criteria: list) -> dict:
|
|
25
|
+
if not 2 <= len(criteria) <= 10:
|
|
26
|
+
raise ValueError('Score needs between 2 and 10 levels')
|
|
27
|
+
return dict(type='score', instructions=instructions, criteria=criteria)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Used by `setup`/`doctor --live` to confirm a key actually works, with no ambiguity
|
|
31
|
+
# about the expected answer.
|
|
32
|
+
PROBE_STATE = 'yes'
|
|
33
|
+
PROBE = {'probe': Noul('Is the state the word "yes"?')}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_relevance(test_path: str, header: str = '') -> dict:
|
|
37
|
+
"""Could this test plausibly be broken by the change under review?
|
|
38
|
+
|
|
39
|
+
Asked once per candidate test, with the task description and the changed source
|
|
40
|
+
files supplied as state. Used both by backtest (scored against which tests a human
|
|
41
|
+
actually touched in the same commit) and, eventually, by live test selection.
|
|
42
|
+
|
|
43
|
+
The true/false split is deliberately asymmetric: a missed test is a missed
|
|
44
|
+
regression (the change ships broken and nobody notices until later), while an
|
|
45
|
+
extra test only costs a few seconds of runtime. So "true" is written to capture
|
|
46
|
+
genuine uncertainty, not just confident matches — a test that touches the same
|
|
47
|
+
module, calls the same function, or exercises adjacent behavior should land on the
|
|
48
|
+
"true" side even without a slam-dunk import-graph connection. Calibration should
|
|
49
|
+
reflect that a borderline-relevant test is still worth running.
|
|
50
|
+
"""
|
|
51
|
+
instructions = (
|
|
52
|
+
'A change is being made to a codebase. Given the task description and the '
|
|
53
|
+
f'source files it changed (in state), could the test at {test_path!r} '
|
|
54
|
+
'plausibly be broken by that change?'
|
|
55
|
+
)
|
|
56
|
+
if header:
|
|
57
|
+
instructions += f'\n\nFirst lines of that test file:\n{header}'
|
|
58
|
+
return Noul(
|
|
59
|
+
instructions,
|
|
60
|
+
true=(
|
|
61
|
+
'The test exercises the same module, function, class, route, or behavior '
|
|
62
|
+
'that the change touches, directly or through a dependency; the test name '
|
|
63
|
+
'or file path names the same feature area as the change; or the '
|
|
64
|
+
'relationship is genuinely uncertain rather than clearly absent. Missing a '
|
|
65
|
+
'test that should have run is a missed regression, so borderline cases '
|
|
66
|
+
'belong here.'
|
|
67
|
+
),
|
|
68
|
+
false=(
|
|
69
|
+
'The test exercises an unrelated module, feature, or layer with no '
|
|
70
|
+
'plausible dependency on the changed files — for example a test for the '
|
|
71
|
+
'CLI when only a database migration changed, or a test fixture that is '
|
|
72
|
+
'unaffected by the change. Only mark false when the lack of relevance is '
|
|
73
|
+
'clear, since an unnecessary extra test only costs runtime.'
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def verify_selection(candidates: list[str]) -> dict:
|
|
79
|
+
"""Which existing command, if any, actually verifies this repo by running its tests?
|
|
80
|
+
|
|
81
|
+
Asked once per discover() call, over every candidate enumerated from real files in
|
|
82
|
+
the repo (see verify.candidates) plus a 'none' escape hatch. Jev may only choose
|
|
83
|
+
among commands that demonstrably exist -- this rubric selects, it never generates,
|
|
84
|
+
so it cannot invent 'make test' for a repo with no Makefile.
|
|
85
|
+
"""
|
|
86
|
+
criteria = {
|
|
87
|
+
command: (
|
|
88
|
+
f'{command!r} runs this project\'s actual test suite end to end -- the same '
|
|
89
|
+
'checks a human would run before trusting a change, not a narrow slice of '
|
|
90
|
+
'it and not an unrelated build, lint, format, or deploy step.'
|
|
91
|
+
)
|
|
92
|
+
for command in candidates
|
|
93
|
+
}
|
|
94
|
+
criteria['none'] = (
|
|
95
|
+
'No candidate above actually runs this project\'s test suite. Every one either '
|
|
96
|
+
'lints, type-checks, builds, formats, or covers only a small, unrelated slice of '
|
|
97
|
+
'the project. Pick this rather than force a bad guess: a wrong pick here reports '
|
|
98
|
+
'an unverified change as verified.'
|
|
99
|
+
)
|
|
100
|
+
instructions = (
|
|
101
|
+
'Given this repo\'s top-level files and any known verify facts (in state), which '
|
|
102
|
+
'of these existing commands actually verifies a change to this project by '
|
|
103
|
+
'running its test suite? Choose \'none\' if nothing listed truly does.'
|
|
104
|
+
)
|
|
105
|
+
return Choice(instructions, criteria)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def runs_tests(command: str) -> dict:
|
|
109
|
+
"""Does this command execute tests, as opposed to only build/lint/typecheck/format?
|
|
110
|
+
|
|
111
|
+
Asked once per candidate alongside verify_selection, so a command that only builds
|
|
112
|
+
can be rejected even if the Choice rubric picked it confidently -- a build that
|
|
113
|
+
passes is not a verified change.
|
|
114
|
+
"""
|
|
115
|
+
instructions = (
|
|
116
|
+
f'Does the command {command!r} execute this project\'s tests, as opposed to '
|
|
117
|
+
'only linting, type-checking, building, formatting, or deploying it?'
|
|
118
|
+
)
|
|
119
|
+
return Noul(
|
|
120
|
+
instructions,
|
|
121
|
+
true=(
|
|
122
|
+
'The command invokes a test runner or test framework (for example pytest, '
|
|
123
|
+
'go test, cargo test, npm test, a test shell script, tox) that exercises the '
|
|
124
|
+
'project\'s actual behavior and can fail when that behavior is wrong.'
|
|
125
|
+
),
|
|
126
|
+
false=(
|
|
127
|
+
'The command only compiles, bundles, lints, type-checks, formats, or deploys '
|
|
128
|
+
'-- it can succeed while the project\'s behavior is broken. A build or lint '
|
|
129
|
+
'that passes is not a verified change, even if its name contains the word '
|
|
130
|
+
'"check" or "verify".'
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def verify_runtime(command: str) -> dict:
|
|
136
|
+
"""How long does this command typically take? Ordered levels, cheapest first.
|
|
137
|
+
|
|
138
|
+
Mirrors the kind of fact Forge's own `verify` memory category already records
|
|
139
|
+
("pytest -q runs the suite; make test also lints and is 4x slower") so the levels
|
|
140
|
+
describe situations exactly like that one.
|
|
141
|
+
"""
|
|
142
|
+
instructions = (
|
|
143
|
+
f'How long does {command!r} typically take to run against this project, based '
|
|
144
|
+
'on what its name, scope, and any known verify facts (in state) imply?'
|
|
145
|
+
)
|
|
146
|
+
criteria = [
|
|
147
|
+
'seconds -- a small, focused check: a single test file or a fast unit-test slice.',
|
|
148
|
+
'under a minute -- a normal unit test run with no heavy setup or external services.',
|
|
149
|
+
'minutes -- a fuller suite, integration tests, or a build-then-test pipeline.',
|
|
150
|
+
'very slow -- end-to-end or browser tests, a full CI pipeline, or a command that '
|
|
151
|
+
'bundles lint, typecheck, build and test together (e.g. "make test also lints and '
|
|
152
|
+
'is 4x slower" than a plain test runner).',
|
|
153
|
+
]
|
|
154
|
+
return Score(instructions, criteria)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def failure_triage(traps: list[str]) -> dict:
|
|
158
|
+
"""Why did verification fail: a real regression, a known flake, environment, or drift?
|
|
159
|
+
|
|
160
|
+
Asked once per verification failure. Getting this wrong ships a regression, so
|
|
161
|
+
'real_regression' is written as the default reading and 'known_flake' is written to
|
|
162
|
+
require positive evidence -- a named trap, or an error signature characteristic of
|
|
163
|
+
flakiness (timeout, ordering, port in use, a race) -- never just "it failed and a
|
|
164
|
+
rerun might pass."
|
|
165
|
+
"""
|
|
166
|
+
named = '; '.join(traps) if traps else '(none recorded)'
|
|
167
|
+
instructions = (
|
|
168
|
+
'Verification just failed. Given the command that was run, the tail of its '
|
|
169
|
+
f'output, and this repo\'s known traps (in state -- recorded traps: {named}), '
|
|
170
|
+
'classify why it failed.'
|
|
171
|
+
)
|
|
172
|
+
criteria = {
|
|
173
|
+
'real_regression': (
|
|
174
|
+
'DEFAULT reading. The failure looks like the change under test actually '
|
|
175
|
+
'broke something: a specific assertion, a new error, or a changed output '
|
|
176
|
+
'tied to the kind of change being verified. Pick this whenever the failure '
|
|
177
|
+
'is not clearly explained by one of the other options -- an ambiguous '
|
|
178
|
+
'failure is a regression until proven otherwise.'
|
|
179
|
+
),
|
|
180
|
+
'known_flake': (
|
|
181
|
+
'Requires positive evidence, not just an unwanted failure: the failing test '
|
|
182
|
+
'or command is named among the recorded traps above, OR the error signature '
|
|
183
|
+
'is characteristic of flakiness with no link to the change under test -- a '
|
|
184
|
+
'timeout, a test-ordering or isolation failure, "address already in use" / '
|
|
185
|
+
'port in use, or an evident race condition.'
|
|
186
|
+
),
|
|
187
|
+
'environment': (
|
|
188
|
+
'The failure is about the environment, not the code or a flaky test: a '
|
|
189
|
+
'missing binary, dependency, credential, network resource, or a disk or '
|
|
190
|
+
'permission error unrelated to test logic.'
|
|
191
|
+
),
|
|
192
|
+
'generated_drift': (
|
|
193
|
+
'The failure is a diff against a generated or derived artifact (a lockfile, '
|
|
194
|
+
'a snapshot, generated bindings or code) that is expected to regenerate, not '
|
|
195
|
+
'a behavioral failure.'
|
|
196
|
+
),
|
|
197
|
+
}
|
|
198
|
+
return Choice(instructions, criteria)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def drift_prediction(file_path: str) -> dict:
|
|
202
|
+
"""Is this file likely to need editing to complete the described task?
|
|
203
|
+
|
|
204
|
+
Asked once per file a Forge task's `files` column does NOT declare, with the task
|
|
205
|
+
description supplied as state. Used to warn when a task's declared touch set
|
|
206
|
+
understates what it will actually touch (see references/decompose.md, "files is a
|
|
207
|
+
promise, not a prediction"). A false positive here is a warning nobody asked for on
|
|
208
|
+
a file that was only ever read; a false negative silently lets real drift through.
|
|
209
|
+
Because the warning itself has a cost (noise erodes trust in every future warning),
|
|
210
|
+
the middle case — a file that might be read or referenced but not edited — is
|
|
211
|
+
written to fall on the false side rather than the true side.
|
|
212
|
+
"""
|
|
213
|
+
instructions = (
|
|
214
|
+
'A Forge task is being carried out. Given the task description (in state), is '
|
|
215
|
+
f'the file {file_path!r} likely to need editing to complete this task, as '
|
|
216
|
+
'opposed to merely being read, imported, or referenced?'
|
|
217
|
+
)
|
|
218
|
+
return Noul(
|
|
219
|
+
instructions,
|
|
220
|
+
true=(
|
|
221
|
+
'The task description names this file directly, or names a behavior, '
|
|
222
|
+
'function, route, schema, or config that only this file defines and that '
|
|
223
|
+
'must change for the task to be complete — the task cannot plausibly be '
|
|
224
|
+
'finished without an edit landing here.'
|
|
225
|
+
),
|
|
226
|
+
false=(
|
|
227
|
+
'The file would only be read, imported, or called into during the task '
|
|
228
|
+
'without itself changing (a dependency, a shared utility invoked but not '
|
|
229
|
+
'modified, a config merely consulted), or the file has no plausible '
|
|
230
|
+
'connection to the task at all. Treat "might be touched incidentally" as '
|
|
231
|
+
'false — this rubric exists to warn on files that will need an edit, not '
|
|
232
|
+
'on every file in the change\'s vicinity, since a warning that fires on '
|
|
233
|
+
'files nobody edits trains people to ignore it.'
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# --- routing (Phase 3 rubrics, recorded in shadow mode before they decide anything) ---
|
|
238
|
+
#
|
|
239
|
+
# Five Scores rather than one Choice asking for the tier directly. Two reasons. First,
|
|
240
|
+
# `decompose.md` already defines difficulty as a conjunction of distinct properties
|
|
241
|
+
# ("design judgment ... cross-cutting ... subtle edge cases or state/concurrency ... the
|
|
242
|
+
# right approach is not obvious"), and asking about each separately is what lets code,
|
|
243
|
+
# not the model, decide how to weigh them. Second, the composite is then computed here,
|
|
244
|
+
# so re-tuning weights against accrued outcomes costs nothing -- no re-inference.
|
|
245
|
+
#
|
|
246
|
+
# The levels below are quoted from decompose.md's difficulty table wherever it says the
|
|
247
|
+
# same thing, so that the rubric and the human-facing spec cannot drift apart.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def design_judgment() -> dict:
|
|
251
|
+
"""How much design judgment does this task demand? (decompose.md's primary axis.)"""
|
|
252
|
+
instructions = (
|
|
253
|
+
'Given the task and its context (in state), how much design judgment does '
|
|
254
|
+
'carrying it out demand? Rate what the task demands of whoever does it, NOT how '
|
|
255
|
+
'much code it produces: a five-hundred-line mechanical rename demands little, a '
|
|
256
|
+
'ten-line concurrency fix demands a lot.'
|
|
257
|
+
)
|
|
258
|
+
return Score(instructions, [
|
|
259
|
+
'none -- mechanical and local: a rename, a move, a docs edit, or a test for '
|
|
260
|
+
'behaviour that already exists. A mistake would be obvious.',
|
|
261
|
+
'some -- a self-contained implementation against a clear spec. A few choices to '
|
|
262
|
+
'make, all of them local and reversible.',
|
|
263
|
+
'substantial -- several defensible approaches exist and the choice between them '
|
|
264
|
+
'changes the shape of the result.',
|
|
265
|
+
'the approach must be invented -- the right way to do this is not obvious from '
|
|
266
|
+
'the goal, and getting it wrong means the work is wasted rather than merely '
|
|
267
|
+
'imperfect.',
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def blast_radius() -> dict:
|
|
272
|
+
"""How far can a mistake in this task reach?"""
|
|
273
|
+
instructions = (
|
|
274
|
+
'If this task were done wrong, how far would the damage reach through the rest '
|
|
275
|
+
'of the codebase (state describes the repo and the files the task declares)?'
|
|
276
|
+
)
|
|
277
|
+
return Score(instructions, [
|
|
278
|
+
'local -- confined to the lines the task edits; nothing else observes the change.',
|
|
279
|
+
'one module -- other code in the same module or package is affected, but the '
|
|
280
|
+
'boundary holds.',
|
|
281
|
+
'cross-module -- behaviour other parts of the project depend on changes, so a '
|
|
282
|
+
'mistake surfaces somewhere the task never touched.',
|
|
283
|
+
'a public contract -- an API, schema, wire format, CLI surface or file format '
|
|
284
|
+
'that callers outside this repo, or already-stored data, depend on.',
|
|
285
|
+
])
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def state_subtlety() -> dict:
|
|
289
|
+
"""Concurrency and state -- called out by name in decompose.md's `high` row."""
|
|
290
|
+
instructions = (
|
|
291
|
+
'How much subtle state or concurrency does this task involve? decompose.md '
|
|
292
|
+
'names this on its own because it is the property most often underrated: a very '
|
|
293
|
+
'small diff can still be the hardest kind of change.'
|
|
294
|
+
)
|
|
295
|
+
return Score(instructions, [
|
|
296
|
+
'none -- pure or straight-line code; no shared state, no ordering, no time.',
|
|
297
|
+
'sequential state -- state is read and written, but in one place, in a '
|
|
298
|
+
'predictable order, by one caller at a time.',
|
|
299
|
+
'shared or asynchronous state -- concurrency, locks, retries, caching, '
|
|
300
|
+
'cross-process coordination, or ordering that is not obvious from reading the '
|
|
301
|
+
'code, where a wrong interleaving produces a bug that does not reproduce.',
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def spec_clarity() -> dict:
|
|
306
|
+
"""Is the approach given, or must it be worked out? Note the inverted direction."""
|
|
307
|
+
instructions = (
|
|
308
|
+
'How clearly does the task (and its approach.md, if state carries one) state '
|
|
309
|
+
'HOW the work should be done, as opposed to only what outcome is wanted?'
|
|
310
|
+
)
|
|
311
|
+
return Score(instructions, [
|
|
312
|
+
'the approach is stated -- the task says which files change and what to do in '
|
|
313
|
+
'them; carrying it out is following instructions.',
|
|
314
|
+
'the approach is implied -- the outcome is clear and there is an obvious way to '
|
|
315
|
+
'reach it, but nobody has written it down.',
|
|
316
|
+
'the approach must be invented -- the task states a goal and leaves the means '
|
|
317
|
+
'open, so the first real work is deciding what to build.',
|
|
318
|
+
])
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_coverage() -> dict:
|
|
322
|
+
"""Would a mistake here be caught? Coverage is why a wrong `low` is survivable."""
|
|
323
|
+
instructions = (
|
|
324
|
+
'How well is the behaviour this task touches already covered by tests in this '
|
|
325
|
+
'repo (state lists the repo\'s files and the files the task declares)?'
|
|
326
|
+
)
|
|
327
|
+
return Score(instructions, [
|
|
328
|
+
'none -- nothing exercises this behaviour; a regression ships silently.',
|
|
329
|
+
'partial -- some tests touch the area, but not the specific behaviour being '
|
|
330
|
+
'changed.',
|
|
331
|
+
'well covered -- the behaviour has direct tests that would fail on a mistake.',
|
|
332
|
+
])
|
|
333
|
+
|
|
334
|
+
# --- pre-dispatch gates (Phase 4) --------------------------------------------------
|
|
335
|
+
#
|
|
336
|
+
# These ride in the SAME request as the routing rubrics. The state a gate needs -- goal,
|
|
337
|
+
# task, declared files, approach -- is exactly the state routing already sends, and
|
|
338
|
+
# questions in one request evaluate in parallel, so asking them costs no extra round
|
|
339
|
+
# trip. They warn and never block: a gate that can stop a dispatch is a gate that will
|
|
340
|
+
# eventually stop a good one.
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def prompt_adequacy() -> dict:
|
|
344
|
+
"""Could a reviewer judge correctness against this prompt at all?
|
|
345
|
+
|
|
346
|
+
SKILL.md already requires this of a decomposition ("short titles are insufficient
|
|
347
|
+
for QA") and nothing enforces it. The failure it prevents is expensive and silent:
|
|
348
|
+
a dwarf produces something plausible, and QA has no stated requirement to check it
|
|
349
|
+
against, so a PASS means only "nothing looked wrong".
|
|
350
|
+
"""
|
|
351
|
+
instructions = (
|
|
352
|
+
'A Forge task is about to be dispatched to a model, and its diff will then be '
|
|
353
|
+
'reviewed by a separate reviewer who sees this same task text. Does the task '
|
|
354
|
+
'(in state) state requirements specific enough that the reviewer could judge '
|
|
355
|
+
'whether the work is correct?'
|
|
356
|
+
)
|
|
357
|
+
return Noul(
|
|
358
|
+
instructions,
|
|
359
|
+
true=(
|
|
360
|
+
'The task names the behaviour that must hold when it is done -- what should '
|
|
361
|
+
'happen, to what, under which conditions -- specifically enough that a '
|
|
362
|
+
'reviewer reading only this text could point at a diff and say whether it '
|
|
363
|
+
'meets them.'
|
|
364
|
+
),
|
|
365
|
+
false=(
|
|
366
|
+
'The task states an area, a file, or an intention rather than a '
|
|
367
|
+
'requirement -- "improve error handling", "refactor the parser", "add '
|
|
368
|
+
'tests" -- so a reviewer could only judge whether the diff looks reasonable, '
|
|
369
|
+
'not whether it is correct. A title with no body belongs here.'
|
|
370
|
+
),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def independently_verifiable() -> dict:
|
|
375
|
+
"""decompose.md's hard rule, checked: can this task be verified on its own?"""
|
|
376
|
+
instructions = (
|
|
377
|
+
'Forge runs this task in its own worktree and verifies it alone, before any '
|
|
378
|
+
'other task in the plan is merged. state.plan_tasks lists the other tasks in '
|
|
379
|
+
'the plan with their file scopes; state.task.deps names the ones that are '
|
|
380
|
+
'guaranteed to land BEFORE this one. Judge against that list, not against '
|
|
381
|
+
'imagined work: can this task be verified on its own?'
|
|
382
|
+
)
|
|
383
|
+
return Noul(
|
|
384
|
+
instructions,
|
|
385
|
+
true=(
|
|
386
|
+
'Something observable proves this task once it is done -- a test that can '
|
|
387
|
+
'run, a command that behaves differently, a check that passes -- using only '
|
|
388
|
+
'this task\'s own files, what already exists in the repo, and anything '
|
|
389
|
+
'named in state.task.deps. A task that needs nothing from plan_tasks is '
|
|
390
|
+
'verifiable; so is one whose only needs are already its declared deps.'
|
|
391
|
+
),
|
|
392
|
+
false=(
|
|
393
|
+
'Verifying this task requires a specific OTHER task in state.plan_tasks '
|
|
394
|
+
'that is not among its declared deps -- name it to yourself before '
|
|
395
|
+
'answering. This is the fragment case: one half of a rename, a caller whose '
|
|
396
|
+
'callee is a sibling task, an interface whose only implementation is a '
|
|
397
|
+
'sibling task. decompose.md forbids splitting this finely for exactly this '
|
|
398
|
+
'reason. Do not answer false merely because the task is small, or because '
|
|
399
|
+
'its value is only visible once the whole plan lands.'
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# --- post-review annotation (Phase 4, items 5 and 6) -------------------------------
|
|
404
|
+
#
|
|
405
|
+
# These read a FAIL and say whether it looks sound. They NEVER overturn it. Forge's
|
|
406
|
+
# second invariant is reviewer independence: a model that can talk another model out of
|
|
407
|
+
# a FAIL is worth less than no reviewer at all, because it produces confident PASSes on
|
|
408
|
+
# code nobody checked. So the only output here is an annotation a human reads.
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def failure_is_correctness() -> dict:
|
|
412
|
+
"""Does this FAIL rest on a correctness bug, or on taste?
|
|
413
|
+
|
|
414
|
+
The QA prompt is explicit that "style nits are not failures", and a reviewer that
|
|
415
|
+
fails a diff over naming or structure costs a whole retry cycle for nothing.
|
|
416
|
+
"""
|
|
417
|
+
instructions = (
|
|
418
|
+
'A reviewer failed a code change. Given the review text and the diff it '
|
|
419
|
+
'reviewed (in state), does the review identify at least one genuine correctness '
|
|
420
|
+
'problem -- behaviour that is wrong, an edge case that breaks, a requirement not '
|
|
421
|
+
'met -- as opposed to only preferences about how the code is written?'
|
|
422
|
+
)
|
|
423
|
+
return Noul(
|
|
424
|
+
instructions,
|
|
425
|
+
true=(
|
|
426
|
+
'At least one finding describes something that would produce a wrong result, '
|
|
427
|
+
'a crash, data loss, a security hole, or a plain failure to do what the task '
|
|
428
|
+
'asked. It would still be a bug if the code were formatted perfectly.'
|
|
429
|
+
),
|
|
430
|
+
false=(
|
|
431
|
+
'Every finding is about style, naming, structure, duplication, comments, '
|
|
432
|
+
'idiom, or a preference for a different approach that would work no better. '
|
|
433
|
+
'A reader could apply none of them and the code would still behave correctly.'
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def findings_cite_the_diff() -> dict:
|
|
439
|
+
"""Citation check: is the review talking about code that is actually there?
|
|
440
|
+
|
|
441
|
+
A review that cites a line the diff does not contain is describing something else --
|
|
442
|
+
a stale read, a hallucinated hunk, or the wrong file -- and its verdict is worth
|
|
443
|
+
nothing regardless of how confident it sounds.
|
|
444
|
+
"""
|
|
445
|
+
instructions = (
|
|
446
|
+
'Given the review text and the diff it reviewed (in state), do the specific '
|
|
447
|
+
'places the review cites -- files, line numbers, function names, quoted code -- '
|
|
448
|
+
'actually appear in that diff, saying what the review claims they say?'
|
|
449
|
+
)
|
|
450
|
+
return Noul(
|
|
451
|
+
instructions,
|
|
452
|
+
true=(
|
|
453
|
+
'The code the review quotes or points at is present in the diff and matches '
|
|
454
|
+
'the description given of it. Minor paraphrasing or an off-by-a-line '
|
|
455
|
+
'reference is fine as long as the cited code is really there.'
|
|
456
|
+
),
|
|
457
|
+
false=(
|
|
458
|
+
'The review cites a file, function, or line that the diff does not contain, '
|
|
459
|
+
'or quotes code that does not appear in it, or describes the cited code as '
|
|
460
|
+
'doing something it plainly does not do.'
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# --- semantic wave coupling (Phase 4, item 4) --------------------------------------
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def forward_dependency(earlier: str, later: str) -> dict:
|
|
468
|
+
"""Is an earlier-wave task written against something a LATER task has not built yet?
|
|
469
|
+
|
|
470
|
+
Distinct from wave_coupling, which asks about two tasks running at the same time and
|
|
471
|
+
breaking on merge. This asks about ordering: a task that runs in wave 1 is reviewed,
|
|
472
|
+
verified and merged before wave 2 exists, so if it was written against wave 2's work
|
|
473
|
+
it is wrong at the moment anyone checks it -- and its own review cannot tell, because
|
|
474
|
+
the thing it is wrong about is not in the tree yet.
|
|
475
|
+
|
|
476
|
+
The concrete case: a `readme` task in wave 1 documenting a CLI built in wave 2. It
|
|
477
|
+
passed review and shipped describing an interface that was never built that way.
|
|
478
|
+
"""
|
|
479
|
+
instructions = (
|
|
480
|
+
f'Forge runs plan tasks in waves. Task EARLIER: {earlier!r} runs first and is '
|
|
481
|
+
f'reviewed, verified and merged before task LATER: {later!r} has started. '
|
|
482
|
+
'LATER is not a declared dependency of EARLIER, so nothing forces that order to '
|
|
483
|
+
'be safe. Does EARLIER depend on what LATER will build -- describing it, calling '
|
|
484
|
+
'it, or assuming its shape -- so that EARLIER cannot be confirmed correct until '
|
|
485
|
+
'LATER lands?'
|
|
486
|
+
)
|
|
487
|
+
return Noul(
|
|
488
|
+
instructions,
|
|
489
|
+
true=(
|
|
490
|
+
'EARLIER is written against something LATER produces: it documents a command '
|
|
491
|
+
'or interface LATER defines, calls an API LATER adds, or assumes a shape only '
|
|
492
|
+
'LATER fixes. When EARLIER is reviewed that thing does not exist yet, so the '
|
|
493
|
+
'review cannot catch a mismatch and the error ships.'
|
|
494
|
+
),
|
|
495
|
+
false=(
|
|
496
|
+
'EARLIER stands on its own. It depends only on what already exists in the '
|
|
497
|
+
'repo or on its own files, and nothing in LATER can make it wrong. LATER '
|
|
498
|
+
'building on EARLIER is the normal, safe direction and is not this.'
|
|
499
|
+
),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def wave_coupling(first: str, second: str) -> dict:
|
|
504
|
+
"""Would running these two tasks concurrently produce an integration conflict?
|
|
505
|
+
|
|
506
|
+
`compute_waves` bounds parallelism structurally: two tasks share a wave only when
|
|
507
|
+
their file sets are disjoint. Disjointness cannot see the conflict where task A
|
|
508
|
+
changes an interface that task B consumes -- both diffs are clean, both pass review
|
|
509
|
+
against their own baseline, and the merge is broken. This asks about exactly that.
|
|
510
|
+
"""
|
|
511
|
+
instructions = (
|
|
512
|
+
f'Two Forge tasks are about to run at the same time, in separate worktrees '
|
|
513
|
+
f'branched from the same commit, each reviewed only against its own baseline. '
|
|
514
|
+
f'Task A: {first!r}. Task B: {second!r}. Their declared files do not overlap. '
|
|
515
|
+
'Would running them concurrently still produce a broken result once both are '
|
|
516
|
+
'merged?'
|
|
517
|
+
)
|
|
518
|
+
return Noul(
|
|
519
|
+
instructions,
|
|
520
|
+
true=(
|
|
521
|
+
'One task changes something the other depends on -- a function signature, a '
|
|
522
|
+
'return type, a schema, a config key, an interface, a shared constant -- so '
|
|
523
|
+
'the second task is written against a version of the code that will no '
|
|
524
|
+
'longer exist. Both diffs can be individually correct and the merge still '
|
|
525
|
+
'broken.'
|
|
526
|
+
),
|
|
527
|
+
false=(
|
|
528
|
+
'The two tasks touch genuinely independent behaviour. Neither one changes '
|
|
529
|
+
'anything the other reads, calls, or relies on, so the order they land in '
|
|
530
|
+
'does not matter.'
|
|
531
|
+
),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# --- memory curation (Phase 5, items 1-3) ------------------------------------------
|
|
536
|
+
#
|
|
537
|
+
# The stakes here are asymmetric and worth stating. memory.md is injected into EVERY
|
|
538
|
+
# dispatch of EVERY future run, so a wrong fact is paid for forever and is corrected
|
|
539
|
+
# only when a human happens to read it. The ledger is append-only and never injected, so
|
|
540
|
+
# nothing recorded is ever lost -- only injection is gated.
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def memory_same_fact(text: str, existing: list) -> dict:
|
|
544
|
+
"""Which recorded fact, if any, is this one restating?
|
|
545
|
+
|
|
546
|
+
`memory.md` counts recurrence by exact normalised key, which the docs already name
|
|
547
|
+
as a limitation: two runs learning the same thing in different words count as two
|
|
548
|
+
separate facts and neither reaches the promotion threshold. Matching paraphrases to
|
|
549
|
+
the existing key is what makes that threshold work.
|
|
550
|
+
"""
|
|
551
|
+
criteria = {
|
|
552
|
+
entry: f'The new note states the same fact as {entry!r}, in different words.'
|
|
553
|
+
for entry in existing
|
|
554
|
+
}
|
|
555
|
+
criteria['none'] = (
|
|
556
|
+
'The new note states something none of the entries above says. Pick this '
|
|
557
|
+
'whenever the match is partial -- merging two facts that only look alike loses '
|
|
558
|
+
'one of them permanently, and a duplicate is merely untidy.'
|
|
559
|
+
)
|
|
560
|
+
return Choice(
|
|
561
|
+
f'A model working in this repo recorded: {text!r}. Which of the facts already '
|
|
562
|
+
'recorded, if any, states that same thing?', criteria)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def memory_durable(text: str) -> dict:
|
|
566
|
+
"""A durable project fact, or a one-off observation about today's task?"""
|
|
567
|
+
return Noul(
|
|
568
|
+
f'A model working in this repo recorded: {text!r}. Is this a durable fact about '
|
|
569
|
+
'the project that would still be useful to a different model, working on a '
|
|
570
|
+
'different task, months from now?',
|
|
571
|
+
true=(
|
|
572
|
+
'It states something stable about the project itself -- how it is built, '
|
|
573
|
+
'tested or verified, a trap that will catch the next person, a constraint '
|
|
574
|
+
'that holds across tasks. It would read as true and useful out of context.'
|
|
575
|
+
),
|
|
576
|
+
false=(
|
|
577
|
+
'It is about this particular change, task, or moment -- what was just '
|
|
578
|
+
'edited, what the model did, a status report, a restatement of the task, or '
|
|
579
|
+
'an observation that is only meaningful next to that diff. Injecting it '
|
|
580
|
+
'into unrelated future work would be noise at best and misleading at worst.'
|
|
581
|
+
),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def memory_category(text: str) -> dict:
|
|
586
|
+
"""verify | trap | finding | none -- the categories references/memory.md defines."""
|
|
587
|
+
criteria = {
|
|
588
|
+
'verify': 'How this project is checked: the command that runs its tests, what '
|
|
589
|
+
'that command covers, how long it takes, what it needs set up first.',
|
|
590
|
+
'trap': 'A sharp edge that will catch the next person: a flaky test, a generated '
|
|
591
|
+
'file that must not be hand-edited, a surprising dependency, a thing '
|
|
592
|
+
'that looks wrong but is deliberate.',
|
|
593
|
+
'finding': 'A recurring correctness problem in this codebase -- a mistake that '
|
|
594
|
+
'has been made before and is likely to be made again.',
|
|
595
|
+
'none': 'None of the above fits. Prefer this over forcing a bad fit: a fact '
|
|
596
|
+
'filed under the wrong category is injected into the wrong role\'s '
|
|
597
|
+
'prompt, where it is noise.',
|
|
598
|
+
}
|
|
599
|
+
return Choice(
|
|
600
|
+
f'A model working in this repo recorded: {text!r}. Which kind of fact is it?',
|
|
601
|
+
criteria)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def memory_still_true(text: str, file_content: str) -> dict:
|
|
605
|
+
"""Has this fact quietly gone false while its anchor file still exists?"""
|
|
606
|
+
excerpt = (file_content or '')[:4000]
|
|
607
|
+
return Noul(
|
|
608
|
+
f'A fact recorded about this project says: {text!r}. The file it refers to now '
|
|
609
|
+
f'contains:\n\n{excerpt}\n\nIs the recorded fact still true?',
|
|
610
|
+
true='The file still behaves the way the fact describes, or the fact is about '
|
|
611
|
+
'something the file does not contradict.',
|
|
612
|
+
false='The file has changed in a way that makes the fact wrong -- the command, '
|
|
613
|
+
'behaviour, constraint or trap it describes is no longer there.',
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def memory_relevant(text: str, task: str) -> dict:
|
|
618
|
+
"""Is this fact worth spending prompt space on for THIS task?"""
|
|
619
|
+
return Noul(
|
|
620
|
+
f'A model is about to work on this task: {task!r}. A fact recorded about the '
|
|
621
|
+
f'project says: {text!r}. Is that fact worth including in the prompt for this '
|
|
622
|
+
'particular task?',
|
|
623
|
+
true='The fact bears on what this task touches -- the same area, the same '
|
|
624
|
+
'command, a trap the task could plausibly hit, or a mistake it could '
|
|
625
|
+
'plausibly repeat.',
|
|
626
|
+
false='The fact is true but unrelated to this task. Every line included costs '
|
|
627
|
+
'context on every dispatch, so a fact that will not change what the model '
|
|
628
|
+
'does here should be left out.',
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# --- outcome rubrics: asked live by the clef-first-pass and clef-retry-pass sites, and by
|
|
632
|
+
# the export on rebuilt states (forge_jev/outcome.py builds both). Text is frozen: it is
|
|
633
|
+
# part of every exported case's body and of what Clef was measured answering.
|
|
634
|
+
|
|
635
|
+
def first_pass() -> dict:
|
|
636
|
+
return dict(type='noul', instructions=(
|
|
637
|
+
'This Forge task is dispatched to the model in state.dwarf, at the effort shown, in its '
|
|
638
|
+
'own worktree. An independent model (state.reviewer) then reviews the resulting diff '
|
|
639
|
+
'against the task. Will that FIRST attempt pass review, with no retry needed?'),
|
|
640
|
+
criteria={
|
|
641
|
+
'true': 'The task is within reach of this model at this effort on the first try: the '
|
|
642
|
+
'requirements are clear, the change is contained, and nothing in it is likely '
|
|
643
|
+
'to be missed or subtly wrong.',
|
|
644
|
+
'false': 'The first attempt is likely to fail review: the task has edge cases, '
|
|
645
|
+
'cross-cutting effects, unclear requirements or subtle state that a first '
|
|
646
|
+
'attempt by this model at this effort typically gets wrong, so a retry would '
|
|
647
|
+
'be needed.'})
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def retry_pass() -> dict:
|
|
651
|
+
return dict(type='noul', instructions=(
|
|
652
|
+
'An earlier attempt at this Forge task failed review. The task is now dispatched again, to '
|
|
653
|
+
'the model in state.dwarf at the effort shown, with that review\'s findings '
|
|
654
|
+
'(state.previous_review) to fix; state.attempt is which try this is, and the model in '
|
|
655
|
+
'state.reviewer reviews the result. Will this attempt pass review?'),
|
|
656
|
+
criteria={
|
|
657
|
+
'true': 'The findings are specific and fixable, and this model at this effort can address '
|
|
658
|
+
'them without breaking something else: the retry is likely to pass.',
|
|
659
|
+
'false': 'The findings point at a design problem, an unclear requirement or a recurring '
|
|
660
|
+
'mistake that another attempt by this model at this effort is likely to repeat: '
|
|
661
|
+
'the retry is likely to fail again and should be escalated or rethought.'})
|