@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.
- package/CHANGELOG.md +181 -0
- package/LICENSE +21 -0
- package/README.md +1854 -2
- package/SKILL.md +122 -0
- package/VERSION +1 -0
- package/forge +74 -0
- package/package.json +26 -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 +353 -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 +665 -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-runtime.py +533 -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 +198 -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 +83 -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 +287 -0
- package/ui/THIRD_PARTY_NOTICES.md +69 -0
- package/ui/docs/api.md +888 -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 +204 -0
- package/ui/forge_ui/findings.py +132 -0
- package/ui/forge_ui/fractal.py +583 -0
- package/ui/forge_ui/models.py +216 -0
- package/ui/forge_ui/reader.py +1336 -0
- package/ui/forge_ui/server.py +303 -0
- package/ui/forge_ui/static/app.js +924 -0
- package/ui/forge_ui/static/cloud3d.js +646 -0
- package/ui/forge_ui/static/cloudmodel.js +375 -0
- package/ui/forge_ui/static/cloudui.js +394 -0
- package/ui/forge_ui/static/diffview.js +216 -0
- package/ui/forge_ui/static/favicon.svg +1 -0
- package/ui/forge_ui/static/files.js +776 -0
- package/ui/forge_ui/static/gantt.js +97 -0
- package/ui/forge_ui/static/graph.js +266 -0
- package/ui/forge_ui/static/index.html +55 -0
- package/ui/forge_ui/static/iso.js +765 -0
- package/ui/forge_ui/static/leaderboard.js +315 -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 +526 -0
- package/ui/forge_ui/static/runmodel.js +651 -0
- package/ui/forge_ui/static/scene3d.js +996 -0
- package/ui/forge_ui/static/search.js +119 -0
- package/ui/forge_ui/static/style.css +618 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# forge-solo.sh — the single-task forge run: one dwarf implements in the working
|
|
3
|
+
# tree, one QA reviews the diff it actually produced.
|
|
4
|
+
#
|
|
5
|
+
# This exists for the same reason forge-dispatch.sh does. The pipeline is a dozen
|
|
6
|
+
# steps — memory injection, prompt assembly, dispatch, diff capture, review prompt,
|
|
7
|
+
# a second dispatch, two ledger writes — and it used to live in SKILL.md as shell
|
|
8
|
+
# for the orchestrating model to retype on every invocation, from whichever of five
|
|
9
|
+
# harnesses was driving. Several of those steps are silently wrong rather than
|
|
10
|
+
# loudly broken when mistyped: drop ':(exclude).forge' from the capture and forge's
|
|
11
|
+
# own memory goes in front of the reviewer as if the dwarf had written it.
|
|
12
|
+
# Decomposed runs never had this problem because forge-parallel.sh owns the
|
|
13
|
+
# plumbing. Solo runs should not either.
|
|
14
|
+
#
|
|
15
|
+
# What stays with the orchestrator is the judgment: it writes prompt.md, it reads
|
|
16
|
+
# dwarf.last and qa.last, and it reports. Only the plumbing moved.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# forge-solo.sh <run-dir> --repo <dir> --dwarf <spec> [--qa <spec>]
|
|
20
|
+
# [--approach <file>] [--yolo-dwarf] [--yolo-qa]
|
|
21
|
+
# [--native-review] [--no-memory] [--timeout <s>] [--dry-run] [--ui]
|
|
22
|
+
# [--fail-first]
|
|
23
|
+
#
|
|
24
|
+
# Reads <run-dir>/prompt.md the implementation instruction (required)
|
|
25
|
+
# <run-dir>/goal.txt one line, what the user asked for (optional)
|
|
26
|
+
# Writes <run-dir>/dwarf.{input,last,log,resolved}
|
|
27
|
+
# <run-dir>/changes.diff
|
|
28
|
+
# <run-dir>/qa.{input,last,log,resolved}
|
|
29
|
+
# <run-dir>/verdict PASS | FAIL | UNKNOWN, when qa emitted one
|
|
30
|
+
# <run-dir>/failfirst.json with --fail-first: do the change's tests fail without it?
|
|
31
|
+
# <run-dir>/status RUNNING, then this invocation's outcome
|
|
32
|
+
# <run-dir>/dwarf.spec, qa.spec and attempts.tsv
|
|
33
|
+
#
|
|
34
|
+
# Exit codes: 0 ok | 2 usage | 3 precondition | 4 a dispatch failed
|
|
35
|
+
# 5 the dwarf produced no changes | 7 a dispatch timed out
|
|
36
|
+
# 8 a dispatch hit an infrastructure failure (quota, auth, rate limit, network,
|
|
37
|
+
# empty output): nothing about the work was judged, fix the cause and re-run
|
|
38
|
+
set -uo pipefail
|
|
39
|
+
|
|
40
|
+
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
41
|
+
DISPATCH="$SKILL_DIR/scripts/forge-dispatch.sh"
|
|
42
|
+
MEMORY="$SKILL_DIR/scripts/forge-memory.sh"
|
|
43
|
+
source "$SKILL_DIR/scripts/forge-artifact.sh"
|
|
44
|
+
source "$SKILL_DIR/scripts/forge-metrics.sh"
|
|
45
|
+
source "$SKILL_DIR/scripts/forge-fractal-options.sh"
|
|
46
|
+
source "$SKILL_DIR/scripts/forge-ui-options.sh"
|
|
47
|
+
source "$SKILL_DIR/scripts/forge-lib-task.sh"
|
|
48
|
+
JEV_OPTS=0
|
|
49
|
+
[ -f "$SKILL_DIR/scripts/forge-jev-options.sh" ] \
|
|
50
|
+
&& source "$SKILL_DIR/scripts/forge-jev-options.sh" 2>/dev/null && JEV_OPTS=1
|
|
51
|
+
ORIGINAL_ARGS=("$@")
|
|
52
|
+
|
|
53
|
+
die() { printf 'forge: %s\n' "$1" >&2; exit "${2:-2}"; }
|
|
54
|
+
note() { printf 'forge: %s\n' "$*" >&2; }
|
|
55
|
+
|
|
56
|
+
# forge_metric_begin's EXIT trap calls this, before runtime finish and Fractal's
|
|
57
|
+
# _outcome read status. Only this script defines it. It must not change the exit code.
|
|
58
|
+
forge_metric_on_exit() {
|
|
59
|
+
local rc="${1:-}" status="" word="" cls="" n=0 pick=""
|
|
60
|
+
[ -n "${RUN:-}" ] || return 0
|
|
61
|
+
if [ -f "$RUN/verdict" ]; then
|
|
62
|
+
word=$(awk 'NF { print $1; exit }' "$RUN/verdict" 2>/dev/null || true)
|
|
63
|
+
case "$word" in
|
|
64
|
+
PASS|FAIL|UNKNOWN|NOCHANGES|INVALIDATED) status=$word ;;
|
|
65
|
+
esac
|
|
66
|
+
fi
|
|
67
|
+
if [ -z "$status" ]; then
|
|
68
|
+
case "$rc" in
|
|
69
|
+
7) status=TIMEOUT ;;
|
|
70
|
+
8) status=INFRA ;;
|
|
71
|
+
130|143) status=INTERRUPTED ;;
|
|
72
|
+
*) status=ERROR ;;
|
|
73
|
+
esac
|
|
74
|
+
fi
|
|
75
|
+
printf '%s\n' "$status" > "$RUN/status" 2>/dev/null || true
|
|
76
|
+
# The run start cleared both infra files, so any file here is this invocation's.
|
|
77
|
+
# QA only runs once the dwarf has passed, which makes qa.infra the later stage; no
|
|
78
|
+
# timestamps are compared (macOS mtimes can tie at whole-second resolution).
|
|
79
|
+
if [ "$status" = INFRA ]; then
|
|
80
|
+
for pick in "$RUN/qa.infra" "$RUN/dwarf.infra"; do
|
|
81
|
+
[ -f "$pick" ] || continue
|
|
82
|
+
cls=$(awk '/^class=/ { sub(/^class=/, ""); print; exit }' "$pick" 2>/dev/null || true)
|
|
83
|
+
break
|
|
84
|
+
done
|
|
85
|
+
fi
|
|
86
|
+
n=0
|
|
87
|
+
if [ -f "$RUN/attempts.tsv" ]; then
|
|
88
|
+
n=$(grep -c . "$RUN/attempts.tsv" 2>/dev/null) || n=0
|
|
89
|
+
fi
|
|
90
|
+
n=$(( ${n:-0} + 1 ))
|
|
91
|
+
printf '%s\t%s\t%s\t%s\t%s\n' "$(date +%s)" "$n" "$status" "$cls" "${DWARF:-}" >> "$RUN/attempts.tsv" 2>/dev/null || true
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if [ "${1:-}" = --help ] || [ "${1:-}" = -h ]; then
|
|
96
|
+
sed -n '18,33s/^# *//p' "$0"
|
|
97
|
+
forge_fractal_help
|
|
98
|
+
echo 'Explicit Fractal retry: repeat the original command with --retry.'
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
RUN="${1:-}"; [ -n "$RUN" ] || die "usage: forge-solo.sh <run-dir> --repo <dir> --dwarf <spec> [--qa <spec>]"
|
|
102
|
+
case "$RUN" in -*) die "first argument must be the run directory, got '$RUN'" ;; esac
|
|
103
|
+
shift
|
|
104
|
+
|
|
105
|
+
OUTPUT=summary
|
|
106
|
+
REPO="$PWD"; DWARF=""; QA="opus"; APPROACH=""; YD=""; YQ=""; NATIVE=""; TIMEOUT=""; DRY=0; FAIL_FIRST=0
|
|
107
|
+
while [ $# -gt 0 ]; do
|
|
108
|
+
case "$1" in
|
|
109
|
+
--fractal) forge_fractal_flag on || exit $?; shift ;;
|
|
110
|
+
--fractal-auto-decompose) forge_fractal_flag on || exit $?; FRACTAL_ARGS+=("$1"); shift ;;
|
|
111
|
+
--no-fractal) forge_fractal_flag off || exit $?; shift ;;
|
|
112
|
+
--retry) export FORGE_FRACTAL_RETRY=1; shift ;;
|
|
113
|
+
--fractal-*|--dwarf-low|--dwarf-medium|--dwarf-high) FRACTAL_ARGS+=("$1" "${2:?}"); shift 2 ;;
|
|
114
|
+
--output) OUTPUT="${2:?}"; shift 2 ;;
|
|
115
|
+
--repo) REPO="${2:?--repo needs a value}"; shift 2 ;;
|
|
116
|
+
--dwarf) DWARF="${2:?--dwarf needs a spec}"; shift 2 ;;
|
|
117
|
+
--qa) QA="${2:?--qa needs a spec}"; shift 2 ;;
|
|
118
|
+
--approach) APPROACH="${2:?--approach needs a file}"; shift 2 ;;
|
|
119
|
+
--yolo-dwarf) YD="--yolo"; shift ;;
|
|
120
|
+
--yolo-qa) YQ="--yolo"; shift ;;
|
|
121
|
+
--native-review) NATIVE="--native-review"; shift ;;
|
|
122
|
+
--no-ripwire) export FORGE_RIPWIRE=off; shift ;;
|
|
123
|
+
--no-memory) export FORGE_MEMORY=off; shift ;;
|
|
124
|
+
--timeout) TIMEOUT="${2:?--timeout needs seconds}"; shift 2 ;;
|
|
125
|
+
--dry-run) DRY=1; shift ;;
|
|
126
|
+
--fail-first) FAIL_FIRST=1; shift ;;
|
|
127
|
+
--ui|--no-ui) forge_ui_flag "$1"; shift ;;
|
|
128
|
+
*)
|
|
129
|
+
# `case` is not a loop, so `continue` here belongs to the enclosing while.
|
|
130
|
+
if [ "$JEV_OPTS" = 1 ]; then
|
|
131
|
+
forge_jev_flag "$1"; jev_rc=$?
|
|
132
|
+
case "$jev_rc" in
|
|
133
|
+
0) shift "$JEV_SHIFT"; continue ;;
|
|
134
|
+
2) exit 2 ;;
|
|
135
|
+
esac
|
|
136
|
+
fi
|
|
137
|
+
die "unknown option '$1'" ;;
|
|
138
|
+
esac
|
|
139
|
+
done
|
|
140
|
+
# A solo run has one run dir and no plan, so the selection is frozen there: an
|
|
141
|
+
# explicit retry of the same run dir must not change its mind about Jev.
|
|
142
|
+
[ "$JEV_OPTS" = 1 ] && forge_jev_export
|
|
143
|
+
|
|
144
|
+
case "$OUTPUT" in summary|full) ;; *) die "--output must be summary or full" ;; esac
|
|
145
|
+
|
|
146
|
+
# Forge never picks the model: every dispatch spends the user's quota, and choosing
|
|
147
|
+
# for them is not the tool's call.
|
|
148
|
+
[ -n "$DWARF" ] || die "--dwarf is required"
|
|
149
|
+
mkdir -p "$RUN" || die "cannot create run dir '$RUN'"
|
|
150
|
+
[ -s "$RUN/prompt.md" ] || die "no prompt.md in '$RUN' — write the implementation instruction there first"
|
|
151
|
+
[ -d "$REPO" ] || die "--repo '$REPO' is not a directory"
|
|
152
|
+
(cd "$REPO" && git rev-parse --git-dir >/dev/null 2>&1) \
|
|
153
|
+
|| die "'$REPO' is not a git repository — there is no way to capture a diff to review" 3
|
|
154
|
+
|
|
155
|
+
# The run dir must live outside the working tree: anything forge writes inside the
|
|
156
|
+
# repo turns up in the dwarf's own diff and reaches QA as if the dwarf wrote it.
|
|
157
|
+
case "$(cd "$RUN" && pwd)/" in
|
|
158
|
+
"$(cd "$REPO" && pwd)"/*) die "run dir '$RUN' is inside the repo; put it somewhere else or it lands in the diff" ;;
|
|
159
|
+
esac
|
|
160
|
+
|
|
161
|
+
RUN="$(cd "$RUN" && pwd)"
|
|
162
|
+
# Freeze the selection now that the run dir is real and absolute. A retry of this
|
|
163
|
+
# same run dir restores it rather than re-reading whatever flags are passed then.
|
|
164
|
+
[ "$JEV_OPTS" = 1 ] && { forge_jev_restore "$RUN" || true; forge_jev_record "$RUN"; }
|
|
165
|
+
REPO="$(cd "$REPO" && pwd)"
|
|
166
|
+
if [ "$DRY" != 1 ] && [ "${FORGE_SOLO_LOCK:-}" != "$RUN" ]; then
|
|
167
|
+
exec python3 "$SKILL_DIR/scripts/forge-fractal.py" _lock "$RUN" "$0" "${ORIGINAL_ARGS[@]}"
|
|
168
|
+
fi
|
|
169
|
+
RUN_ID="$(basename "$RUN")"
|
|
170
|
+
export FORGE_CAPABILITY_CACHE="$RUN/capabilities"
|
|
171
|
+
TFLAG=""; [ -n "$TIMEOUT" ] && TFLAG="--timeout $TIMEOUT"
|
|
172
|
+
GOAL="$(head -1 "$RUN/goal.txt" 2>/dev/null || true)"
|
|
173
|
+
|
|
174
|
+
FFLAGS=()
|
|
175
|
+
[ -z "$YD" ] || FFLAGS+=(--yolo-dwarf)
|
|
176
|
+
[ -z "$YQ" ] || FFLAGS+=(--yolo-qa)
|
|
177
|
+
forge_fractal_select "$RUN" "$REPO" solo "$DRY" --dwarf "$DWARF" --qa "$QA" ${FFLAGS[@]+"${FFLAGS[@]}"} || exit $?
|
|
178
|
+
if [ -n "${FORGE_FRACTAL_RUN:-}" ]; then
|
|
179
|
+
# The UI choice is per invocation: a tmux resume replays these args and must not open it again.
|
|
180
|
+
REMEMBER_ARGS=()
|
|
181
|
+
for arg in "${ORIGINAL_ARGS[@]}"; do
|
|
182
|
+
case "$arg" in --ui|--no-ui) ;; *) REMEMBER_ARGS+=("$arg") ;; esac
|
|
183
|
+
done
|
|
184
|
+
python3 "$SKILL_DIR/scripts/forge-fractal.py" _remember "$0" ${REMEMBER_ARGS[@]+"${REMEMBER_ARGS[@]}"} || exit $?
|
|
185
|
+
fi
|
|
186
|
+
if [ -n "${FORGE_FRACTAL_RUN:-}" ] && [ "$DRY" != 1 ] && [ "${FORGE_FRACTAL_RETRY:-}" != 1 ]; then
|
|
187
|
+
python3 "$SKILL_DIR/scripts/forge-fractal.py" _checkpoint "$REPO"
|
|
188
|
+
checkpoint_rc=$?
|
|
189
|
+
[ "$checkpoint_rc" != 0 ] || exit 0
|
|
190
|
+
[ "$checkpoint_rc" = 1 ] || exit "$checkpoint_rc"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# The dispatches see a review snapshot, not the repo, so the run dir names it for the dataset.
|
|
194
|
+
[ "$DRY" = 1 ] || printf '%s\n' "$REPO" > "$RUN/repo"
|
|
195
|
+
# Every real run, --ui or not: forge-ui's view of this repo's runs reads the registry.
|
|
196
|
+
[ "$DRY" = 1 ] || forge_ui_register "$RUN"
|
|
197
|
+
# Recorded before any dispatch: forge-ui opens this dir immediately, and a re-run
|
|
198
|
+
# that dies early must not keep showing the previous invocation's verdict.
|
|
199
|
+
if [ "$DRY" != 1 ]; then
|
|
200
|
+
forge_metric_begin "$RUN" solo
|
|
201
|
+
printf '%s\n' RUNNING > "$RUN/status"
|
|
202
|
+
printf '%s\n' "$DWARF" > "$RUN/dwarf.spec"
|
|
203
|
+
printf '%s\n' "$QA" > "$RUN/qa.spec"
|
|
204
|
+
rm -f "$RUN/verdict" "$RUN/dwarf.infra" "$RUN/qa.infra"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# --- dwarf --------------------------------------------------------------------
|
|
208
|
+
/bin/bash "$MEMORY" inject "$REPO" dwarf --run-dir "$RUN" > "$RUN/dwarf.memory"
|
|
209
|
+
python3 "$SKILL_DIR/scripts/forge-prompt.py" --requirements "$RUN/prompt.md" --approach "$APPROACH" --memory "$RUN/dwarf.memory" > "$RUN/context.md" || exit 3
|
|
210
|
+
{
|
|
211
|
+
cat "$RUN/context.md"
|
|
212
|
+
cat <<'EOF'
|
|
213
|
+
|
|
214
|
+
Edit the files in this repository directly. When you are done, run whatever tests
|
|
215
|
+
or checks the project already has and report the result.
|
|
216
|
+
|
|
217
|
+
You are running headless: nobody is at the other end and no one can answer a
|
|
218
|
+
question. If something is ambiguous, pick the most reasonable interpretation,
|
|
219
|
+
proceed, and state the assumption in your final message.
|
|
220
|
+
EOF
|
|
221
|
+
echo; python3 "$SKILL_DIR/scripts/forge-contract.py" dwarf
|
|
222
|
+
echo; /bin/bash "$MEMORY" note dwarf
|
|
223
|
+
} > "$RUN/dwarf.input"
|
|
224
|
+
|
|
225
|
+
if [ "$DRY" = 1 ]; then
|
|
226
|
+
echo "--- dry run ---"
|
|
227
|
+
[ "$FORGE_UI_WANTED" != 1 ] || note "--ui ignored: a dry run starts no UI"
|
|
228
|
+
/bin/bash "$DISPATCH" dwarf "$DWARF" --repo "$REPO" --run-dir "$RUN" \
|
|
229
|
+
--ripwire-query-file "$RUN/prompt.md" --prompt-file "$RUN/dwarf.input" $YD $TFLAG --dry-run
|
|
230
|
+
echo
|
|
231
|
+
echo "qa would be: $QA $NATIVE $YQ"
|
|
232
|
+
echo "dwarf prompt: $RUN/dwarf.input ($(wc -l < "$RUN/dwarf.input" | tr -d ' ') lines)"
|
|
233
|
+
exit 0
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
forge_metric_phase preflight
|
|
237
|
+
/bin/bash "$DISPATCH" doctor --spec "$DWARF" --role dwarf $YD $TFLAG || exit $?
|
|
238
|
+
/bin/bash "$DISPATCH" doctor --spec "$QA" --role qa $YQ $NATIVE $TFLAG || exit $?
|
|
239
|
+
/bin/bash "$SKILL_DIR/scripts/forge-install-ripwire.sh" || true
|
|
240
|
+
# After the _lock re-exec and the Fractal checkpoint exit, so it runs once per run.
|
|
241
|
+
forge_ui_launch "$RUN"
|
|
242
|
+
forge_metric_phase snapshot
|
|
243
|
+
if [ -n "${FORGE_FRACTAL_RUN:-}" ] && [ -s "$RUN/start.tree" ]; then
|
|
244
|
+
START="$(cat "$RUN/start.tree")"
|
|
245
|
+
else
|
|
246
|
+
START="$(forge_tree "$REPO")" || die "cannot record starting snapshot" 3
|
|
247
|
+
fi
|
|
248
|
+
echo "$START" > "$RUN/start.tree"
|
|
249
|
+
git -C "$REPO" diff --binary HEAD "$START" -- . ':(exclude).forge' > "$RUN/existing.diff"
|
|
250
|
+
forge_metric_phase dispatch
|
|
251
|
+
note "dwarf $DWARF in $REPO"
|
|
252
|
+
guard_before_dwarf "$REPO" "$RUN"
|
|
253
|
+
/bin/bash "$DISPATCH" dwarf "$DWARF" --repo "$REPO" --run-dir "$RUN" \
|
|
254
|
+
--ripwire-query-file "$RUN/prompt.md" --prompt-file "$RUN/dwarf.input" $YD $TFLAG --output "$OUTPUT"
|
|
255
|
+
rc=$?
|
|
256
|
+
dur_dwarf="$(sed -n 's/^duration_s=//p' "$RUN/dwarf.resolved" 2>/dev/null | tail -1)"
|
|
257
|
+
/bin/bash "$MEMORY" record "$REPO" --last "$RUN/dwarf.last" --role dwarf \
|
|
258
|
+
--run-id "$RUN_ID" --model "$DWARF" --run-dir "$RUN" \
|
|
259
|
+
--duration "${dur_dwarf:--}" >/dev/null 2>&1
|
|
260
|
+
[ "$rc" = 7 ] && die "the dwarf exceeded its timeout — see $RUN/dwarf.log" 7
|
|
261
|
+
[ "$rc" = 8 ] && die "the dwarf hit an infrastructure failure ($(sed -n 's/^class=//p' "$RUN/dwarf.infra" 2>/dev/null | head -1)) — nothing was reviewed; fix the cause and run again (see $RUN/dwarf.infra)" 8
|
|
262
|
+
[ "$rc" -ne 0 ] && die "the dwarf dispatch failed — see $RUN/dwarf.log" 4
|
|
263
|
+
guard_after_dwarf "$REPO" "$RUN" solo
|
|
264
|
+
|
|
265
|
+
forge_metric_phase snapshot
|
|
266
|
+
# --- the real diff ------------------------------------------------------------
|
|
267
|
+
# Diff complete filesystem snapshots; the private index preserves user staging.
|
|
268
|
+
END="$(forge_tree "$REPO")" || die "cannot capture implementation" 3
|
|
269
|
+
echo "$END" > "$RUN/reviewed.tree"
|
|
270
|
+
git -C "$REPO" diff --binary "$START" "$END" -- . ':(exclude).forge' > "$RUN/changes.diff" || die "cannot create diff" 3
|
|
271
|
+
|
|
272
|
+
if [ ! -s "$RUN/changes.diff" ]; then
|
|
273
|
+
# An exit-0 harness with an empty final message and no changes never really ran.
|
|
274
|
+
grep -qx 'class=empty' "$RUN/dwarf.infra" 2>/dev/null && die "the dwarf returned nothing (empty output, no changes) — an infrastructure failure, not a refusal; fix the cause and run again (see $RUN/dwarf.infra)" 8
|
|
275
|
+
note "the dwarf produced no changes at all — see $RUN/dwarf.last for what it said"
|
|
276
|
+
echo NOCHANGES > "$RUN/verdict"
|
|
277
|
+
exit 5
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
REVIEW="$RUN/review-$(date +%s)-$$"
|
|
281
|
+
forge_review_snapshot "$REPO" "$START" "$END" "$REVIEW" || die "cannot create review snapshot" 3
|
|
282
|
+
SOURCE_FP="$(forge_fingerprint "$REPO")" || exit 3
|
|
283
|
+
[ "$(forge_tree "$REPO")" = "$END" ] || { echo INVALIDATED > "$RUN/verdict"; die "source changed while preparing QA" 5; }
|
|
284
|
+
REVIEW_FP="$(forge_fingerprint "$REVIEW")" || exit 3
|
|
285
|
+
printf '%s\n' "$SOURCE_FP" > "$RUN/source.fingerprint"
|
|
286
|
+
printf '%s\n' "$REVIEW_FP" > "$RUN/review.fingerprint"
|
|
287
|
+
forge_metric_phase preparation
|
|
288
|
+
# --- qa -----------------------------------------------------------------------
|
|
289
|
+
rm -f "$RUN/failfirst.json" "$RUN/failfirst.txt"
|
|
290
|
+
if [ "$FAIL_FIRST" = 1 ]; then
|
|
291
|
+
# Opt-in and advisory: the reviewer reads the evidence; nothing here changes a verdict.
|
|
292
|
+
forge_fail_first "$REVIEW" "$RUN" "$REPO"
|
|
293
|
+
note "fail-first evidence for the reviewer: $RUN/failfirst.json"
|
|
294
|
+
fi
|
|
295
|
+
/bin/bash "$MEMORY" inject "$REPO" qa --run-dir "$RUN" > "$RUN/qa.memory"
|
|
296
|
+
{
|
|
297
|
+
python3 "$SKILL_DIR/scripts/forge-prompt.py" --requirements "$RUN/prompt.md" --approach "$APPROACH" --goal "$RUN/goal.txt" --memory "$RUN/dwarf.memory" --memory "$RUN/qa.memory"
|
|
298
|
+
python3 "$SKILL_DIR/scripts/forge-contract.py" qa --part head --kind solo
|
|
299
|
+
echo
|
|
300
|
+
if [ -s "$RUN/failfirst.txt" ]; then cat "$RUN/failfirst.txt"; echo; fi
|
|
301
|
+
python3 "$SKILL_DIR/scripts/forge-contract.py" qa --part tail --kind solo
|
|
302
|
+
echo
|
|
303
|
+
echo '```diff'; cat "$RUN/changes.diff"; echo '```'
|
|
304
|
+
echo; /bin/bash "$MEMORY" note qa
|
|
305
|
+
echo "Place any learning notes before the final FORGE_VERDICT: PASS or FORGE_VERDICT: FAIL line."
|
|
306
|
+
} > "$RUN/qa.input"
|
|
307
|
+
|
|
308
|
+
forge_metric_phase dispatch
|
|
309
|
+
note "qa $QA on $(wc -l < "$RUN/changes.diff" | tr -d ' ') diff lines"
|
|
310
|
+
/bin/bash "$DISPATCH" qa "$QA" --repo "$REVIEW" --review-base "$(cat "$RUN/review.base")" --run-dir "$RUN" \
|
|
311
|
+
--ripwire-query-file "$RUN/prompt.md" --prompt-file "$RUN/qa.input" $YQ $NATIVE $TFLAG --output "$OUTPUT"
|
|
312
|
+
rc=$?
|
|
313
|
+
forge_metric_phase verification
|
|
314
|
+
verdict="$(forge_verdict "$RUN/qa.last")"
|
|
315
|
+
[ -n "$NATIVE" ] && verdict=UNKNOWN
|
|
316
|
+
if [ "$(forge_fingerprint "$REPO")" != "$SOURCE_FP" ] || [ "$(forge_fingerprint "$REVIEW")" != "$REVIEW_FP" ]; then
|
|
317
|
+
echo INVALIDATED > "$RUN/verdict"
|
|
318
|
+
die "source or review artifact changed during QA; review invalidated" 5
|
|
319
|
+
fi
|
|
320
|
+
dur_qa="$(sed -n 's/^duration_s=//p' "$RUN/qa.resolved" 2>/dev/null | tail -1)"
|
|
321
|
+
/bin/bash "$MEMORY" record "$REPO" --last "$RUN/qa.last" --role qa \
|
|
322
|
+
--run-id "$RUN_ID" --model "$QA" --verdict "${verdict:-UNKNOWN}" --run-dir "$RUN" \
|
|
323
|
+
--duration "${dur_qa:--}" >/dev/null 2>&1
|
|
324
|
+
[ "$rc" = 7 ] && die "qa exceeded its timeout — the dwarf's changes are still in the tree; see $RUN/qa.log" 7
|
|
325
|
+
[ "$rc" = 8 ] && die "qa hit an infrastructure failure ($(sed -n 's/^class=//p' "$RUN/qa.infra" 2>/dev/null | head -1)) — the dwarf's changes are still in the tree and unreviewed; fix the cause and review again (see $RUN/qa.infra)" 8
|
|
326
|
+
[ "$rc" -ne 0 ] && die "the qa dispatch failed — the dwarf's changes are still in the tree; see $RUN/qa.log" 4
|
|
327
|
+
|
|
328
|
+
# --native-review is codex's own reviewer, which refuses a custom prompt alongside
|
|
329
|
+
# its scope flags — so it never sees the verdict instruction and cannot emit one.
|
|
330
|
+
echo "${verdict:-UNKNOWN}" > "$RUN/verdict"
|
|
331
|
+
|
|
332
|
+
echo
|
|
333
|
+
echo "dwarf: $DWARF ${dur_dwarf:-?}s"
|
|
334
|
+
echo "qa: $QA ${dur_qa:-?}s verdict: ${verdict:-UNKNOWN}"
|
|
335
|
+
echo "diff: $RUN/changes.diff"
|
|
336
|
+
echo "read: $RUN/dwarf.last and $RUN/qa.last"
|
|
337
|
+
echo
|
|
338
|
+
if grep -q '^self-commit' "$RUN/guard.txt" 2>/dev/null; then
|
|
339
|
+
echo "The dwarf COMMITTED in $REPO although it was told not to. The review above covers its"
|
|
340
|
+
echo "changes either way. To turn its commit(s) back into uncommitted changes:"
|
|
341
|
+
echo " git -C $REPO reset --soft $(cat "$RUN/dwarf.head.before" 2>/dev/null)"
|
|
342
|
+
echo "Forge itself committed, pushed and reset nothing."
|
|
343
|
+
else
|
|
344
|
+
echo "The changes are UNCOMMITTED in $REPO. Nothing was committed, pushed or reset."
|
|
345
|
+
fi
|
|
346
|
+
[ ! -s "$RUN/guard.txt" ] || { echo; echo "implementer guard notes:"; sed 's/^/ /' "$RUN/guard.txt"; }
|
|
347
|
+
forge_update_notice
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Source in runners (forge-solo.sh, forge-parallel.sh plan and run). Compatible with macOS Bash 3.2.
|
|
3
|
+
#
|
|
4
|
+
# forge-ui is opt-in per run: the orchestrator asks the user and passes the answer as
|
|
5
|
+
# --ui. The flag is never persisted, and launching never decides whether the run goes on.
|
|
6
|
+
# Registering a run (forge_ui_register) is not part of that choice: it happens on every
|
|
7
|
+
# run, so forge-ui can later show all of a repo's runs, including those never opened in it.
|
|
8
|
+
FORGE_UI_WANTED=0
|
|
9
|
+
|
|
10
|
+
# forge_ui_flag ARG — returns 0 and records the choice for --ui / --no-ui, 1 for anything else.
|
|
11
|
+
forge_ui_flag() {
|
|
12
|
+
case "${1:-}" in
|
|
13
|
+
--ui) FORGE_UI_WANTED=1 ;;
|
|
14
|
+
--no-ui) FORGE_UI_WANTED=0 ;;
|
|
15
|
+
*) return 1 ;;
|
|
16
|
+
esac
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# forge_ui_launch DIR — open DIR in the shared forge-ui server when --ui was given.
|
|
20
|
+
# Always returns 0: a UI that cannot start must not change the run's exit code. The
|
|
21
|
+
# launcher's stdin is /dev/null so it can never wait for a terminal.
|
|
22
|
+
forge_ui_launch() {
|
|
23
|
+
[ "${FORGE_UI_WANTED:-0}" = 1 ] || return 0
|
|
24
|
+
if [ "${FORGE_UI:-}" = off ]; then
|
|
25
|
+
printf 'forge: FORGE_UI=off; not starting forge-ui\n' >&2
|
|
26
|
+
return 0
|
|
27
|
+
fi
|
|
28
|
+
python3 "$SKILL_DIR/scripts/forge-ui.py" open "$1" </dev/null \
|
|
29
|
+
|| printf 'forge: forge-ui did not start; the run goes on (later: %s/forge ui open %s)\n' "$SKILL_DIR" "$1" >&2
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# forge_ui_register DIR — note DIR in forge's run registry ($XDG_CONFIG_HOME/forge/runs),
|
|
34
|
+
# under the repo named in DIR/repo. Called on every run, whatever --ui and FORGE_UI say,
|
|
35
|
+
# because forge-ui's view of a repo's runs reads that registry. Always returns 0 and prints
|
|
36
|
+
# nothing: the registry can be unwritable or the repo file odd, and neither may change a
|
|
37
|
+
# run's output or exit code. -I keeps a PYTHON* variable or a stray module in the current
|
|
38
|
+
# directory from changing what runs. FORGE_RUN_REGISTRY=off turns it off.
|
|
39
|
+
forge_ui_register() {
|
|
40
|
+
python3 -I "$SKILL_DIR/scripts/forge_registry.py" register "$1" </dev/null >/dev/null 2>&1 || true
|
|
41
|
+
return 0
|
|
42
|
+
}
|