@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,302 @@
1
+ #!/usr/bin/env bash
2
+ # Per-task helpers for forge-parallel.sh: attempt accounting, infrastructure stops,
3
+ # QA-only resume, retry preparation, implementer guards, the severity gate and the
4
+ # report blocks printed under the results table.
5
+ #
6
+ # Sourced by forge-parallel.sh, which provides die/note/field/all_ids/task_status and
7
+ # SKILL_DIR. Functions only; nothing runs at source time.
8
+
9
+ # --- attempt log ----------------------------------------------------------------
10
+ # One row per do_task entry: epoch, attempt number, resulting status, infra class, and the
11
+ # implementer spec the attempt ran (the escalation policy counts failures per model, and
12
+ # the dwarf column is rewritten before the next attempt begins).
13
+ # `attempt` itself only counts attempts that were really spent (an infrastructure stop
14
+ # refunds it), so this file is the audit trail of what happened, including the refunds.
15
+ record_attempt() { # record_attempt <plan> <id>
16
+ local tdir="$1/tasks/$2" cls=""
17
+ [ "$(task_status "$1" "$2")" = BLOCKED ] && return 0 # never started: nothing to audit
18
+ [ "$(task_status "$1" "$2")" = INFRA ] && cls="$(sed -n 's/^class=//p' "$tdir/infra.txt" 2>/dev/null | head -1)"
19
+ printf '%s\t%s\t%s\t%s\t%s\n' "$(date +%s)" "$(cat "$tdir/attempt" 2>/dev/null || echo 0)" \
20
+ "$(task_status "$1" "$2")" "$cls" "$(field "$1/tasks.tsv" "$2" dwarf)" >> "$tdir/attempts.tsv"
21
+ }
22
+
23
+ # --- infrastructure stops -------------------------------------------------------
24
+ # A dispatch that failed because of quota, auth, a rate limit, the network or empty
25
+ # output says nothing about the work. The task is marked INFRA and the attempt is not
26
+ # spent. A dwarf-stage stop refunds the counter; a QA-stage stop leaves the dwarf's
27
+ # committed work intact, so a `resume` marker makes the next entry re-run QA only.
28
+ infra_stop() { # infra_stop <tdir> <id> <role> <attempt>
29
+ local tdir="$1" id="$2" role="$3" attempt="$4" cls
30
+ cp "$tdir/$role.infra" "$tdir/infra.txt" 2>/dev/null || : > "$tdir/infra.txt"
31
+ printf 'stage=%s\n' "$role" >> "$tdir/infra.txt"
32
+ cls="$(sed -n 's/^class=//p' "$tdir/infra.txt" | head -1)"
33
+ echo INFRA > "$tdir/status"
34
+ if [ "$role" = dwarf ]; then
35
+ [ "$attempt" -gt 0 ] && echo $((attempt - 1)) > "$tdir/attempt"
36
+ rm -f "$tdir/resume"
37
+ else
38
+ echo qa > "$tdir/resume"
39
+ fi
40
+ note "$id: $role dispatch hit an infrastructure failure (${cls:-unknown}) — no attempt spent; re-run to resume"
41
+ }
42
+
43
+ # A QA-only resume is trusted only while the worktree is still exactly what was
44
+ # reviewed. Anything else clears the marker and the full path runs.
45
+ resume_valid() { # resume_valid <tdir> <wt>
46
+ local tdir="$1" wt="$2"
47
+ [ "$(cat "$tdir/resume" 2>/dev/null)" = qa ] || return 1
48
+ [ -s "$tdir/reviewed.commit" ] && [ -s "$tdir/source.fingerprint" ] && [ -s "$tdir/changes.diff" ] || return 1
49
+ [ -e "$wt/.git" ] || return 1
50
+ [ "$(git -C "$wt" rev-parse HEAD 2>/dev/null)" = "$(cat "$tdir/reviewed.commit")" ] || return 1
51
+ [ "$(forge_fingerprint "$wt")" = "$(cat "$tdir/source.fingerprint")" ]
52
+ }
53
+
54
+ # --- retry preparation ----------------------------------------------------------
55
+ # Shared by `retry` (a human asked) and the scheduler's automatic retries. The findings
56
+ # are the whole point of retrying rather than re-running: the dwarf is told what was
57
+ # wrong with its own previous attempt.
58
+ prepare_retry() { # prepare_retry <plan> <id> [new-dwarf]
59
+ local PLAN="$1" id="$2" newdwarf="${3:-}" T="$1/tasks.tsv" tdir="$1/tasks/$2" st verdict
60
+ st="$(task_status "$PLAN" "$id")"
61
+ verdict="$(forge_verdict "$tdir/qa.last" 2>/dev/null)"
62
+ if [ "$st" = INFRA ]; then
63
+ # Nothing was judged, so there is nothing new to carry forward; the stop already
64
+ # recorded where to resume (a `resume` marker for a QA-stage stop).
65
+ note "$id: resuming after an infrastructure stop"
66
+ elif [ "$st" = UNKNOWN ] && [ -s "$tdir/reviewed.commit" ]; then
67
+ # The reviewer never reached a verdict, so the code was not rejected. Re-asking QA is
68
+ # cheaper and more honest than paying an implementer to rewrite unjudged work; the
69
+ # marker is revalidated against the worktree before it is believed.
70
+ echo qa > "$tdir/resume"
71
+ note "$id: the reviewer gave no verdict — re-running QA on the unchanged work"
72
+ # Only a real review replaces the findings. The runner also writes explanatory text
73
+ # into qa.last when it fails a task without dispatching QA ("produced no changes at
74
+ # all"), and copying that over would throw away the actual findings the previous
75
+ # reviewer gave — the one thing a retry exists to carry forward.
76
+ elif [ "$verdict" = PASS ] || [ "$verdict" = FAIL ]; then
77
+ cp "$tdir/qa.last" "$tdir/retry_findings.md"
78
+ elif grep -q 'FORGE_VERDICT' "$tdir/qa.last" 2>/dev/null; then
79
+ cp "$tdir/qa.last" "$tdir/retry_findings.md"
80
+ elif [ -s "$tdir/retry_findings.md" ]; then
81
+ note "$id: no new review since the last retry — reusing the previous findings"
82
+ else
83
+ note "$id: no previous QA findings to pass on (last status was $st)"
84
+ : > "$tdir/retry_findings.md"
85
+ fi
86
+ # The retry policy (forge-route.py escalation): after escalate_after failed attempts on
87
+ # one model (default 3), the next attempt goes a tier up in the user's own pools instead
88
+ # of repeating the dispatch whose fourth try passes least often, or the task stops with a
89
+ # split suggestion. An explicit --dwarf is the human's call and skips the policy.
90
+ local cur next rc
91
+ cur="$(field "$T" "$id" dwarf)"
92
+ if [ -z "$newdwarf" ]; then
93
+ next="$(python3 "$SKILL_DIR/scripts/forge-route.py" escalation --plan "$PLAN" --task "$id")"; rc=$?
94
+ if [ "$rc" = 3 ]; then
95
+ note "$(cat "$tdir/split.suggested" 2>/dev/null)"
96
+ return 3
97
+ fi
98
+ if [ -n "$next" ]; then
99
+ note "$id: escalating from $cur to $next after $(cat "$PLAN/escalate_after" 2>/dev/null || echo 3) failed attempts on one model (--escalate-after 0 turns this off)"
100
+ newdwarf="$next"
101
+ fi
102
+ else
103
+ rm -f "$tdir/escalation.stop" "$tdir/split.suggested" "$tdir/escalation.next"
104
+ fi
105
+ if [ "$newdwarf" = "$cur" ]; then newdwarf=""; fi # the same spec again is no escalation
106
+ if [ -n "$newdwarf" ]; then
107
+ # Written into tasks.tsv rather than kept in a side file: the table then shows the
108
+ # model that will actually be spent, and plan's rule that an explicit column value
109
+ # survives a re-plan protects the escalation for free. Rewritten first, so a failed
110
+ # rewrite is never recorded as an escalation that happened.
111
+ awk -F'\t' -v OFS='\t' -v id="$id" -v dw="$newdwarf" \
112
+ '$0 !~ /^#/ && $1==id { $5=dw } { print }' "$T" > "$PLAN/.tasks.tsv.retry" \
113
+ && mv "$PLAN/.tasks.tsv.retry" "$T" \
114
+ || { note "$id: could not write $newdwarf into tasks.tsv; nothing was retried"; return 1; }
115
+ # The rewrite loses which model failed first, and an escalation is exactly the record
116
+ # of "this tier was too weak for this task".
117
+ printf '%s\t%s\t%s\t%s\n' "$(date +%s)" "$(cat "$tdir/attempt" 2>/dev/null || echo 0)" \
118
+ "$cur" "$newdwarf" >> "$tdir/escalations.tsv"
119
+ # Recorded at the rewrite itself: it happens before the retried attempt begins, so no
120
+ # attempt capture would ever see it as something that attempt produced.
121
+ forge_dataset_event escalation --dir "$PLAN" --task "$id" --set "from=$cur" \
122
+ --set "to=$newdwarf" --set "after_attempt=$(cat "$tdir/attempt" 2>/dev/null || echo 0)"
123
+ rm -f "$tdir/escalation.next"
124
+ fi
125
+ rm -f "$tdir/noretry"
126
+ }
127
+
128
+ # The scheduler's `_prepare_retry` target: prepare, then clear the status so the task
129
+ # reads as PENDING again. `retry` clears it itself, after its own preflight.
130
+ retry_for_scheduler() { # retry_for_scheduler <plan> <id>
131
+ prepare_retry "$1" "$2" || return $?
132
+ rm -f "$1/tasks/$2/status"
133
+ }
134
+
135
+ # --- implementer guards ---------------------------------------------------------
136
+ # Observed facts, never self-reports. They warn and are recorded in guard.txt; they do
137
+ # not change a task's status, because the diff and the reviewer still decide that.
138
+ guard_before_dwarf() { # guard_before_dwarf <wt> <tdir>
139
+ rm -f "$2/guard.txt"
140
+ git -C "$1" rev-parse HEAD > "$2/dwarf.head.before" 2>/dev/null || rm -f "$2/dwarf.head.before"
141
+ git -C "$1" symbolic-ref -q HEAD > "$2/dwarf.ref.before" 2>/dev/null || echo detached > "$2/dwarf.ref.before"
142
+ }
143
+
144
+ guard_after_dwarf() { # guard_after_dwarf <wt> <tdir> <id>
145
+ local wt="$1" tdir="$2" id="$3" h0 h1 r0 r1 n g="$2/guard.txt" line
146
+ : > "$g"
147
+ h0="$(cat "$tdir/dwarf.head.before" 2>/dev/null)"; h1="$(git -C "$wt" rev-parse HEAD 2>/dev/null)"
148
+ r0="$(cat "$tdir/dwarf.ref.before" 2>/dev/null)"; r1="$(git -C "$wt" symbolic-ref -q HEAD 2>/dev/null || echo detached)"
149
+ if [ -n "$h0" ] && [ -n "$h1" ] && [ "$h0" != "$h1" ]; then
150
+ n="$(git -C "$wt" rev-list --count "$h0..$h1" 2>/dev/null || echo '?')"
151
+ printf 'self-commit: the implementer committed (%s new commit(s)) although it was told not to; the cumulative diff against the task base is still what gets reviewed\n' "$n" >> "$g"
152
+ fi
153
+ if [ -n "$r0" ] && [ "$r0" != "$r1" ]; then
154
+ printf 'branch switch: the implementer changed the checked-out ref from %s to %s; the work is rejected as INVALIDATED unless it ends on the task branch\n' "$r0" "$r1" >> "$g"
155
+ fi
156
+ if [ -s "$tdir/dwarf.orphans" ]; then
157
+ printf 'orphan processes: %s process(es) the implementer left running were stopped (listed in %s)\n' \
158
+ "$(wc -l < "$tdir/dwarf.orphans" | tr -d ' ')" "$tdir/dwarf.orphans" >> "$g"
159
+ fi
160
+ if [ -s "$tdir/dwarf.last" ]; then
161
+ python3 "$SKILL_DIR/scripts/forge-guard.py" promises "$tdir/dwarf.last" 2>/dev/null |
162
+ while IFS= read -r line; do
163
+ printf 'promised later work: %s\n' "$line"
164
+ done >> "$g"
165
+ fi
166
+ if [ -s "$g" ]; then
167
+ while IFS= read -r line; do note "$id: guard — $line"; done < "$g"
168
+ else
169
+ rm -f "$g"
170
+ fi
171
+ return 0
172
+ }
173
+
174
+ # --- QA severity gate -----------------------------------------------------------
175
+ qa_threshold_for() { # per-task file wins over the plan-level default; prints nothing if unset
176
+ local f
177
+ for f in "$1/tasks/$2/qa_threshold" "$1/qa_threshold"; do
178
+ [ -s "$f" ] && { head -1 "$f" | tr -d ' \r'; return 0; }
179
+ done
180
+ return 0
181
+ }
182
+
183
+ # With an explicit threshold, a reviewer's FAIL whose confirmed findings are all labelled
184
+ # and less severe than the threshold is accepted as PASS-with-known-issues. The literal
185
+ # status stays PASS, so the merge path, the scheduler and Fractal need no new vocabulary.
186
+ # Anything unlabelled stays blocking: a finding the gate cannot place is not tolerated.
187
+ qa_gate() { # qa_gate <plan> <id> <tdir> <verdict>
188
+ local plan="$1" id="$2" tdir="$3" verdict="$4" thr line
189
+ rm -f "$tdir/qa.gate" "$tdir/known_issues.md"
190
+ thr="$(qa_threshold_for "$plan" "$id")"
191
+ [ -n "$thr" ] || return 0
192
+ line="$(python3 "$SKILL_DIR/scripts/forge-contract.py" gate --verdict "${verdict:-UNKNOWN}" \
193
+ --qa-last "$tdir/qa.last" --threshold "$thr" --known-issues "$tdir/known_issues.md" 2>/dev/null)" || return 0
194
+ [ -n "$line" ] || return 0
195
+ printf '%s\t%s\n' "$thr" "$line" > "$tdir/qa.gate"
196
+ if [ "$(printf '%s' "$line" | awk -F'\t' '{print $1}')" = accept ]; then
197
+ echo PASS > "$tdir/status"
198
+ note "$id: the reviewer said FAIL, but every confirmed finding is less severe than the $thr threshold — accepted with known issues ($tdir/known_issues.md)"
199
+ fi
200
+ return 0
201
+ }
202
+
203
+ # --- report blocks ----------------------------------------------------------------
204
+ report_infra() { # report_infra <plan>
205
+ local plan="$1" id n=0 cls
206
+ for id in $(all_ids "$plan/tasks.tsv"); do
207
+ [ "$(task_status "$plan" "$id")" = INFRA ] || continue
208
+ [ "$n" = 0 ] && echo "paused on an infrastructure failure (not a task failure, no attempt spent):"
209
+ n=$((n+1))
210
+ cls="$(sed -n 's/^class=//p' "$plan/tasks/$id/infra.txt" 2>/dev/null | head -1)"
211
+ printf ' %-14s %s (%s stage)\n' "$id" "${cls:-unknown}" "$(sed -n 's/^stage=//p' "$plan/tasks/$id/infra.txt" 2>/dev/null | tail -1)"
212
+ done
213
+ if [ "$n" -gt 0 ]; then
214
+ echo " fix the cause (quota, login, network), then re-run: forge-parallel.sh run $plan"
215
+ echo
216
+ fi
217
+ return 0
218
+ }
219
+
220
+ report_known_issues() { # report_known_issues <plan>
221
+ local plan="$1" id n=0
222
+ for id in $(all_ids "$plan/tasks.tsv"); do
223
+ [ -s "$plan/tasks/$id/known_issues.md" ] || continue
224
+ [ "$n" = 0 ] && echo "known issues accepted (findings below the QA threshold, or accepted by a human):"
225
+ n=$((n+1))
226
+ printf ' %-14s %s finding(s) %s\n' "$id" "$(grep -c '^- ' "$plan/tasks/$id/known_issues.md" 2>/dev/null || echo 0)" "$plan/tasks/$id/known_issues.md"
227
+ done
228
+ [ "$n" -gt 0 ] && echo
229
+ return 0
230
+ }
231
+
232
+ report_guard_notes() { # report_guard_notes <plan>
233
+ local plan="$1" id n=0 line
234
+ for id in $(all_ids "$plan/tasks.tsv"); do
235
+ [ -s "$plan/tasks/$id/guard.txt" ] || continue
236
+ [ "$n" = 0 ] && echo "implementer guard notes (observed by forge, not self-reported):"
237
+ n=$((n+1))
238
+ while IFS= read -r line; do printf ' %-14s %s\n' "$id" "$line"; done < "$plan/tasks/$id/guard.txt"
239
+ done
240
+ [ "$n" -gt 0 ] && echo
241
+ return 0
242
+ }
243
+
244
+ report_verify_notes() { # report_verify_notes <dir holding verification.*>
245
+ local d="$1"
246
+ if [ -s "$d/verification.flaky" ]; then
247
+ echo "FLAKY verification: the command failed once and passed on rerun — read $d/verification.log"
248
+ echo " before trusting this result; an intermittent failure can be a real bug."
249
+ echo
250
+ fi
251
+ if [ -s "$d/verification.coverage.txt" ]; then
252
+ echo "verification coverage (suites the project defines that the verify command does not run):"
253
+ sed 's/^/ /' "$d/verification.coverage.txt"
254
+ echo
255
+ fi
256
+ return 0
257
+ }
258
+
259
+ # Everything worth saying under the results table, in one call.
260
+ # Advisory notes under the approval table: tasks large enough that their first attempt
261
+ # usually fails review, and prompts with no acceptance criteria (forge-plan-check.py).
262
+ report_size() { # report_size <plan>
263
+ python3 "$SKILL_DIR/scripts/forge-plan-check.py" size --plan "$1" 2>/dev/null || true
264
+ }
265
+
266
+ # The retry policy's verdicts: a task that stopped with a split suggestion, or whose next
267
+ # retry would go a tier up. Decided after every attempt, so a spent budget still says it.
268
+ report_escalations() { # report_escalations <plan>
269
+ local plan="$1" id n=0 head
270
+ head="retry policy (after $(cat "$plan/escalate_after" 2>/dev/null || echo 3) failed attempts on one model):"
271
+ for id in $(all_ids "$plan/tasks.tsv"); do
272
+ if [ -s "$plan/tasks/$id/split.suggested" ]; then
273
+ [ "$n" = 0 ] && echo "$head"
274
+ n=$((n+1)); printf ' %-14s split suggested: %s\n' "$id" "$(cat "$plan/tasks/$id/split.suggested")"
275
+ elif [ -s "$plan/tasks/$id/escalation.next" ] && [ "$(task_status "$plan" "$id")" != MERGED ]; then
276
+ [ "$n" = 0 ] && echo "$head"
277
+ n=$((n+1)); printf ' %-14s its next retry goes to %s\n' "$id" "$(cat "$plan/tasks/$id/escalation.next")"
278
+ fi
279
+ done
280
+ [ "$n" -gt 0 ] && echo
281
+ return 0
282
+ }
283
+
284
+ run_report_extras() { # run_report_extras <plan> [<verification dir>]
285
+ report_infra "$1"
286
+ report_escalations "$1"
287
+ report_known_issues "$1"
288
+ report_guard_notes "$1"
289
+ report_verify_notes "${2:-$1}"
290
+ }
291
+
292
+ # Integrating a partial run is allowed (a human may want what passed), but never silently:
293
+ # say which tasks are NOT in the result.
294
+ report_unmerged() { # report_unmerged <plan>
295
+ local plan="$1" id st list=""
296
+ for id in $(all_ids "$plan/tasks.tsv"); do
297
+ st="$(task_status "$plan" "$id")"
298
+ [ "$st" = MERGED ] || list="$list $id($st)"
299
+ done
300
+ [ -z "$list" ] || note "WARNING: not merged, so NOT part of this integration:$list"
301
+ return 0
302
+ }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ # Verification helpers for forge-parallel.sh. Sourced; functions only.
3
+
4
+ # The verify command a plan would run: --verify (persisted in the plan) first, then the
5
+ # repository's own .forge/verify. Prints nothing when neither is configured.
6
+ verify_command_for() { # verify_command_for <plan> <repo>
7
+ if [ -s "$1/verify_cmd" ]; then cat "$1/verify_cmd"
8
+ elif [ -s "$2/.forge/verify" ]; then cat "$2/.forge/verify"
9
+ fi
10
+ return 0
11
+ }
12
+
13
+ # "Verify is whatever you hand it": a gate that skips a suite the project defines lets a
14
+ # regression in that suite reach the branch with no warning. Read the project's own test
15
+ # configuration and say which suites the command does not run. Advisory by design — it
16
+ # prints and records, it never changes an outcome, and a failure of the checker is silent.
17
+ verify_coverage_report() { # verify_coverage_report <checkout> <command> <out-file>
18
+ local dir="$1" cmd="$2" out="$3" line
19
+ [ -f "$SKILL_DIR/scripts/forge-verify-coverage.py" ] || return 0
20
+ rm -f "$out"
21
+ if [ -n "$cmd" ]; then
22
+ python3 "$SKILL_DIR/scripts/forge-verify-coverage.py" check --repo "$dir" --command="$cmd" --out "$out" >/dev/null 2>&1
23
+ else
24
+ python3 "$SKILL_DIR/scripts/forge-verify-coverage.py" check --repo "$dir" --none --out "$out" >/dev/null 2>&1
25
+ fi
26
+ if [ -s "$out" ]; then
27
+ while IFS= read -r line; do note "$line"; done < "$out"
28
+ fi
29
+ return 0
30
+ }