forgedock 1.7.1 → 1.8.1
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/bin/engine/admission.mjs +242 -7
- package/bin/engine/admission.test.mjs +183 -2
- package/bin/engine/phases.mjs +31 -22
- package/bin/engine/resolve.mjs +8 -1
- package/bin/engine/resolve.test.mjs +5 -0
- package/bin/engine.mjs +26 -17
- package/bin/forgedock.mjs +15 -2
- package/bin/hooks/pre-tool-use.mjs +573 -2
- package/bin/journey.mjs +11 -7
- package/bin/labels.json +5 -0
- package/bin/opencode-adapter.mjs +47 -13
- package/bin/orchestrate-preflight.mjs +434 -0
- package/bin/runner.mjs +30 -4
- package/commands/audit-agents.md +75 -26
- package/commands/autopilot.md +4 -4
- package/commands/cleanup.md +10 -5
- package/commands/issue.md +95 -35
- package/commands/orchestrate/phase-1-resolve.md +70 -32
- package/commands/orchestrate/phase-3-dependency.md +141 -13
- package/commands/orchestrate/phase-4-execution.md +660 -70
- package/commands/orchestrate/phase-5-cleanup.md +5 -11
- package/commands/orchestrate/phase-6-report.md +27 -1
- package/commands/orchestrate/safety.md +1 -1
- package/commands/orchestrate.md +39 -1
- package/commands/quality-gate.md +30 -10
- package/commands/review-pr-agents/protocols.md +13 -0
- package/commands/review-pr-agents.md +1 -1
- package/commands/review-pr-staging.md +100 -18
- package/commands/review-pr.md +78 -24
- package/commands/upgrade-deps.md +1 -1
- package/commands/work-on/build/validate.md +25 -4
- package/commands/work-on/close.md +40 -10
- package/commands/work-on/decompose.md +33 -1
- package/commands/work-on/investigate.md +33 -0
- package/commands/work-on/remediate.md +40 -5
- package/commands/work-on/review.md +27 -0
- package/commands/work-on.md +48 -5
- package/docs/CONFIG.md +45 -10
- package/docs/OPENCODE.md +21 -6
- package/docs/spec/review-protocol.md +7 -0
- package/package.json +1 -1
- package/scripts/check-admission-parity.mjs +1 -0
- package/scripts/check-command-side-effects.sh +14 -13
- package/scripts/check-shell-portability.sh +36 -0
- package/scripts/check-spec-markers.sh +21 -11
- package/scripts/checkbox-sections.test.sh +67 -0
- package/scripts/extract-affected-files.sh +85 -5
- package/scripts/extract-affected-files.test.sh +154 -2
|
@@ -49,16 +49,11 @@ Include the audit summary in the final report (Phase 6). Key metrics to surface:
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
if [ -n "${COORD_ISSUE_NUMBER:-}" ]; then
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# which also captures each claim's `files` into an object alongside `holder`.
|
|
58
|
-
ACTIVE_CLAIMS=$(gh api repos/{GH_REPO}/issues/${COORD_ISSUE_NUMBER}/comments \
|
|
59
|
-
--jq '[.[] | select(.body | contains("<!-- FORGE:CLAIM -->")) |
|
|
60
|
-
select(.body | contains("<!-- FORGE:CLAIM_RELEASED -->") | not)] |
|
|
61
|
-
map(.body | capture("\\*\\*Holder\\*\\*: (?P<h>[^\\n]+)").h // "unknown")' 2>/dev/null || echo '[]')
|
|
52
|
+
# Use Step 4A's read_active_claims() helper (re-declare it verbatim here when this
|
|
53
|
+
# phase runs in a fresh context). Releases are separate later comments, so checking
|
|
54
|
+
# for CLAIM_RELEASED inside the claim body would retain every released claim.
|
|
55
|
+
ACTIVE_CLAIMS=$(read_active_claims "$COORD_ISSUE_NUMBER" 2>/dev/null \
|
|
56
|
+
| jq '[.[].holder // "unknown"]' 2>/dev/null || echo '[]')
|
|
62
57
|
ACTIVE_CLAIMS_COUNT=$(echo "$ACTIVE_CLAIMS" | jq 'length' 2>/dev/null || echo 0)
|
|
63
58
|
|
|
64
59
|
if [ "$ACTIVE_CLAIMS_COUNT" -gt 0 ] 2>/dev/null; then
|
|
@@ -87,4 +82,3 @@ fi
|
|
|
87
82
|
Include the cleanup summary in the final report (Phase 6), including whether the coordination issue was closed this run (see Step 5C). If cleanup found problems, call them out — they indicate agent pipeline failures that may need investigation.
|
|
88
83
|
|
|
89
84
|
---
|
|
90
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
|
|
2
3
|
install: core
|
|
3
4
|
---
|
|
4
5
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
@@ -208,6 +209,7 @@ Sweep (Step 4F.2.5) once the batch is no longer fully gated.
|
|
|
208
209
|
| #{F} | {title} | original | ⏸ Awaiting Merge | #{PR} | staging |
|
|
209
210
|
| #{E} | {title} | original | ⏭ Skipped (dep) | — | — |
|
|
210
211
|
| #{G} | {title} | original | 🔗 Blocked-on-Merge (gated by #{PRED}) | — | — |
|
|
212
|
+
| #{H} | {title} | original | ⚠ Review Panel Degraded | #{PR} | {target} |
|
|
211
213
|
|
|
212
214
|
`⏸ Awaiting Merge` (`workflow:awaiting-merge`) is structurally distinct from `⚠ Blocked`
|
|
213
215
|
(`needs-human`): a Blocked row means the pipeline hit something it cannot resolve on its own
|
|
@@ -223,6 +225,17 @@ was abandoned. A Blocked-on-Merge row means the work is queued and will auto-dis
|
|
|
223
225
|
the gating predecessor's PR merges — no manual `/orchestrate` re-run required. Never render a
|
|
224
226
|
blocked-on-human-merge dependent as `⏭ Skipped (dep)`.
|
|
225
227
|
|
|
228
|
+
### Degraded Review Panels
|
|
229
|
+
|
|
230
|
+
{IF any PR reviewed during this batch has the `review-degraded` label:}
|
|
231
|
+
| Issue | PR | Target | Required action |
|
|
232
|
+
|-------|----|--------|-----------------|
|
|
233
|
+
| #{NUM} | #{PR} | {target} | Re-run the complete isolated review panel in a fresh session |
|
|
234
|
+
|
|
235
|
+
These PRs are not eligible to merge or deploy. A dispatch-pool failure produced an incomplete panel, so no inline or partial-panel verdict is accepted.
|
|
236
|
+
|
|
237
|
+
{ELSE: omit this section entirely.}
|
|
238
|
+
|
|
226
239
|
{IF `MERGE_READY_PRS` (Step 6A.5) is non-empty:}
|
|
227
240
|
|
|
228
241
|
### Merge-Ready — {N} PR(s) awaiting only a human merge
|
|
@@ -286,12 +299,14 @@ current session's live wake pick them up automatically if it is still running).
|
|
|
286
299
|
{list of cosmetic deferred issue numbers and titles}
|
|
287
300
|
|
|
288
301
|
### Summary
|
|
302
|
+
- **Knowledge Gists**: {IF FORGE_GIST_CAPABLE == "false": "Skipped - unavailable for the batch authentication (informational)." ELSE: "Available."}
|
|
289
303
|
- **Investigations**: {N} completed, spawned {M} new issues
|
|
290
304
|
- **Review findings**: {N} spawned, {M} resolved in-batch, {K} swept, {J} deferred (cosmetic), {L} deferred (gen2), {IDLE_POLICY_DEFERRED_COUNT} deferred (idle policy, forge#1814)
|
|
291
305
|
- **Succeeded**: {N} issues resolved (implementation + sweep)
|
|
292
306
|
- **Failed**: {N} issues need attention
|
|
293
307
|
- **Merge-ready**: {#MERGE_READY_PRS[@]:-0} PRs awaiting only a human merge (see "Merge-Ready" section above)
|
|
294
308
|
- **Blocked-on-merge**: {#BLOCKED_ON_MERGE[@]:-0} issues queued behind a human-gated predecessor, will auto-dispatch on merge (see "Blocked-on-Merge" section above)
|
|
309
|
+
- **Degraded review panels**: {N} PRs require a fresh full-panel re-review before merge/deploy
|
|
295
310
|
- **Skipped**: {N} issues (dependency failures)
|
|
296
311
|
|
|
297
312
|
### Post-Batch Cleanup
|
|
@@ -325,6 +340,11 @@ current session's live wake pick them up automatically if it is still running).
|
|
|
325
340
|
| Findings deferred — idle policy (forge#1814) | {IDLE_POLICY_DEFERRED_COUNT} (batch fully human-gated at defer time — see Step 4B item 6.7) |
|
|
326
341
|
| P3 findings clubbed (surface-area batching, forge#1818) | {SURFACE_BATCH_COUNT:-0} batch(es) covering {#SURFACE_BATCHED_FINDINGS[@]:-0} finding(s) — see Step 4C |
|
|
327
342
|
| Findings deferred — token budget (forge#1858) | {#TOKEN_DEFERRED[@]:-0} (batch spend {BATCH_TOKEN_SPEND:-0}/{TOKEN_BUDGET:-uncapped} tokens — see Step 4C) |
|
|
343
|
+
| Full-repo intake (`policy: all`) | {FULL_REPO_SWEEP_COUNT:-0} sweep completion mark; {#FULL_REPO_INTAKE[@]:-0} newly-seen actionable issues admitted |
|
|
344
|
+
| Automated alerts deduplicated | {AUTOMATED_DUPLICATES_CLOSED:-0} closed; canonical issue retained and linked for each |
|
|
345
|
+
| **Cascade amplification** | **{FINDINGS_SPAWNED:-0}/{MERGED_UNITS:-0} = {AMPLIFICATION_RATIO:-0} findings per merged unit** |
|
|
346
|
+
| Amplification composition | {#REFINEMENT_FINDINGS[@]:-0} same-lineage refinements; {#NEW_SURFACE_FINDINGS[@]:-0} new-surface findings |
|
|
347
|
+
| Amplification bound | {CASCADE_MAX_AMPLIFICATION:-off}; {#AMPLIFICATION_DEFERRED[@]:-0} same-lineage refinements deferred to completion sweep |
|
|
328
348
|
| Competing recommendations reconciled (Phase 2.5) | {RECONCILED_COUNT} (investigation plans arbitrated in place + serialized) |
|
|
329
349
|
| Findings validated | {N} |
|
|
330
350
|
| False positives | {N} ({%}) |
|
|
@@ -334,12 +354,19 @@ current session's live wake pick them up automatically if it is still running).
|
|
|
334
354
|
| Actual spend (best-effort telemetry) | ${ACTUAL_SPEND:-—} |
|
|
335
355
|
| Issues deferred (budget ceiling) | {#DEFERRED_BUDGET_ISSUES[@]:-0} |
|
|
336
356
|
| ε-reserve issued (no-prior dispatch) | {EPSILON_DISPATCHED:-no} |
|
|
357
|
+
| Top-level workers dispatched | ${TOP_LEVEL_DISPATCH_TOTAL:-0} |
|
|
358
|
+
| Subagent spawns planned (8 per worker) | ${PLANNED_SUBAGENT_SPAWNS:-0} |
|
|
359
|
+
| Subagent spawns observed | ${OBSERVED_SUBAGENT_SPAWNS:-unavailable} |
|
|
337
360
|
| **Value-weighted throughput** | **{VALUE_THROUGHPUT:-—} value-pts/USD** |
|
|
338
361
|
|
|
339
362
|
`value-weighted throughput` = Σ(priority_weight × danger_zone_weight) for **merged** issues ÷ actual spend (USD). A higher value means more high-priority issues were resolved per dollar. Trend this across runs via `/pipeline-health` to measure the economic scheduling ROI. <!-- Added: forge#1743 -->
|
|
340
363
|
|
|
341
364
|
`P3 findings clubbed` and `Findings deferred — token budget` are a DIFFERENT mechanism from the `$`-denominated `Budget limit`/`Projected spend`/`Actual spend`/`Issues deferred (budget ceiling)` rows above (forge#1743, opt-in `--budget N` flag, gates the *original* issue dispatch order). The token-budget row reads the always-on, token-denominated ceiling that scopes ONLY to Step 4C's review-finding cascade dispatch (forge#1858); `SURFACE_BATCH_COUNT` and `SURFACE_BATCHED_FINDINGS` are populated by the same Step 4C surface-area-batching block (forge#1818) that clubs P3 findings sharing a file/leaf-directory into one dispatched pipeline. Both degrade gracefully to `0`/`uncapped` when the batch had no review findings or ran with defaults. <!-- Added: forge#1858 -->
|
|
342
365
|
|
|
366
|
+
**Cascade amplification detail**: render one row per `FINDINGS_BY_SOURCE_PR` entry: `| PR #{source} | {finding count} | {refinement count} | {new-surface count} |`. A ratio at or above 1.0 means the batch is not reducing its open-work count through merges alone; it does **not** mean the findings are low-value. Include any convergence warning and state that the default `max_amplification: off` never defers findings. When an opt-in bound deferred a refinement, list it under Completion Sweep as re-evaluable rather than silently omitting it.
|
|
367
|
+
|
|
368
|
+
For `policy: all`, include the full-repo intake counts and list each automated duplicate with its canonical issue. A sweep measures all new open issues, including CI alerts without `review-finding`; it must not claim that the cascade ratio alone measures total queue growth.
|
|
369
|
+
|
|
343
370
|
**Compute value-weighted throughput** (populate before rendering the table):
|
|
344
371
|
|
|
345
372
|
```bash
|
|
@@ -433,4 +460,3 @@ the Summary section above. `BATCH_ELAPSED` is the wall-clock duration of the orc
|
|
|
433
460
|
(Step 6B `Duration`), not the sum of per-issue elapsed times.
|
|
434
461
|
|
|
435
462
|
---
|
|
436
|
-
|
|
@@ -11,7 +11,7 @@ install: core
|
|
|
11
11
|
1. **Every agent MUST invoke `/work-on` via the Skill tool.** No custom prompts. No manual implementation. Copy the Phase 4A template, fill in variables, done. (See HARD RULES at top of file.)
|
|
12
12
|
2. **NEVER merge anything to `main`** — agents merge to `staging` or `milestone/{slug}` only
|
|
13
13
|
3. **Concurrency is capped by default (12 in-flight agents)** <!-- Updated: forge#1912 --> — file overlap and dependencies still determine which issues are *eligible* to run in parallel, but Phase 4 never dispatches more than `MAX_CONCURRENT` at once (default 12); the rest queue and dispatch as running agents complete. Set `forge.yaml → orchestration.max_concurrent` to raise or lower the cap (see Engine mode § Concurrency model, and `phase-4-execution.md` Step 4A-pre.0.2).
|
|
14
|
-
4. **Always confirm with user before launching** — Step 3E is the mandatory checkpoint
|
|
14
|
+
4. **Always confirm with user before launching** — Step 3E is the mandatory checkpoint. Headless callers must pass the explicit `--auto` or `--confirm` authorization; OpenCode must not infer confirmation from a completed preflight.
|
|
15
15
|
5. **No retries** — if an agent fails, report it and move on
|
|
16
16
|
6. **Respect existing work** — skip issues already being worked on (`workflow:building`, `workflow:in-review`)
|
|
17
17
|
7. **Dependency failures cascade** — if A fails and B depends on A, all transitive dependents of A are skipped (not attempted)
|
package/commands/orchestrate.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Orchestrate parallel work on multiple issues or an entire milestone — spawns sub-agents that each run the full /work-on pipeline
|
|
3
|
-
argument-hint: "[milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0]"
|
|
3
|
+
argument-hint: "[milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0] [--auto|--confirm]"
|
|
4
4
|
---
|
|
5
5
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
6
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -9,8 +9,46 @@ argument-hint: "[milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P
|
|
|
9
9
|
|
|
10
10
|
**Input**: $ARGUMENTS
|
|
11
11
|
|
|
12
|
+
`--auto` and `--confirm` authorize the dispatch checkpoint; they are control
|
|
13
|
+
flags, not part of the issue-set query. `--deep-plan` requests the full analysis
|
|
14
|
+
path, and `--max-concurrent N` only tunes the dispatch cap.
|
|
15
|
+
|
|
12
16
|
This file is the slim dispatcher. Detailed phase content lives in `commands/orchestrate/`.
|
|
13
17
|
|
|
18
|
+
## OpenCode Preflight
|
|
19
|
+
|
|
20
|
+
When `FORGE_RUNTIME=opencode` (or an OpenCode runtime marker is present), run the
|
|
21
|
+
deterministic preflight at `$FORGE_HOME/bin/orchestrate-preflight.mjs` immediately,
|
|
22
|
+
before reading `orchestrate/config.md`, this workflow, or any phase spec. The helper
|
|
23
|
+
resolves the repository from `forge.yaml`, including a parent config when the target
|
|
24
|
+
is a nested Git worktree:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
node "$FORGE_HOME/bin/orchestrate-preflight.mjs" \
|
|
28
|
+
--repo "$GH_REPO" \
|
|
29
|
+
--args "$ARGUMENTS"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The preflight is a compact mechanical adapter for issue resolution, eligibility,
|
|
33
|
+
explicit dependencies, scoped issue-body file overlap, database serialization, and
|
|
34
|
+
the initial ready queue. If it returns a supported plan with `requiresDeepPlan: false`
|
|
35
|
+
and `confirmed: true`, launch `dispatchNow` with native background `task` calls
|
|
36
|
+
immediately. Without an explicit `--auto` or `--confirm` argument, present the
|
|
37
|
+
compact plan and ask for one confirmation; after the user confirms, launch the
|
|
38
|
+
plan's ready queue without re-reading the large phase files. Do not load the full
|
|
39
|
+
Phase 3 or Phase 4 prose just to ask that question.
|
|
40
|
+
|
|
41
|
+
Continue through the phase files when the plan says `requiresDeepPlan`, the input is
|
|
42
|
+
unsupported, preflight fails, or a task-result event requires recovery. This adapter
|
|
43
|
+
never closes, deduplicates, or edits issues; the full shared workflow remains the
|
|
44
|
+
authority for investigations, review-finding cascade handling, recovery, cleanup,
|
|
45
|
+
and reporting.
|
|
46
|
+
|
|
47
|
+
For a supported compact OpenCode plan, stop after the fast-path dispatch. The phase
|
|
48
|
+
execution order below is the fallback path for deep plans, unsupported inputs,
|
|
49
|
+
preflight failures, and task-result recovery; do not read it merely to confirm or
|
|
50
|
+
dispatch a compact plan.
|
|
51
|
+
|
|
14
52
|
## Execution Order
|
|
15
53
|
|
|
16
54
|
Read and execute phases in sequence. Each phase file is self-contained.
|
package/commands/quality-gate.md
CHANGED
|
@@ -246,6 +246,12 @@ echo "Applicable domains: $DOMAINS"
|
|
|
246
246
|
|
|
247
247
|
Run ONLY the checks whose domain was identified in Step 1.5. Skip checks for domains not present in the `DOMAINS` list.
|
|
248
248
|
|
|
249
|
+
**Execution budget**: Set `FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS` to configure the
|
|
250
|
+
per-check budget (default: 30 seconds). Set `FORGEDOCK_VERIFICATION_TIMEOUT_SECONDS`
|
|
251
|
+
to configure formatter, typecheck, and build commands (default: 120 seconds). Both
|
|
252
|
+
values must be positive integers. A timeout emits a structured HIGH finding and fails
|
|
253
|
+
the gate rather than leaving the workflow blocked.
|
|
254
|
+
|
|
249
255
|
- **2A (Security)**: Run if `SECURITY` in DOMAINS *(always — included for all code files)*
|
|
250
256
|
- **2B (Auth)**: Run if `AUTH` in DOMAINS
|
|
251
257
|
- **2C (Deploy chain)**: Run if `DEPLOY` in DOMAINS
|
|
@@ -715,9 +721,9 @@ Do NOT suppress the finding by making the check less strict or adding fake no-op
|
|
|
715
721
|
```bash
|
|
716
722
|
# 2G.9: Danger-zone recurrence check
|
|
717
723
|
# Step 1: Read injected danger-zone cards from FORGE:CONTEXT comment on the issue
|
|
718
|
-
CONTEXT_COMMENT=$(gh api repos/{GH_REPO}/issues/{ISSUE_NUMBER}/comments \
|
|
724
|
+
CONTEXT_COMMENT=$(timeout 10 gh api repos/{GH_REPO}/issues/{ISSUE_NUMBER}/comments \
|
|
719
725
|
--jq '[.[] | select(.body | contains("<!-- FORGE:CONTEXT -->"))] | last | .body // ""' 2>/dev/null \
|
|
720
|
-
|
|
726
|
+
|| echo '')
|
|
721
727
|
|
|
722
728
|
# Extract the Danger-Zone Rule Cards section
|
|
723
729
|
DZ_SECTION=$(echo "$CONTEXT_COMMENT" \
|
|
@@ -1302,6 +1308,11 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
|
1302
1308
|
```bash
|
|
1303
1309
|
REGISTRY_FILE="{WORKTREE_PATH}/scripts/check-registry/manifest.json"
|
|
1304
1310
|
REGISTRY_FINDINGS=""
|
|
1311
|
+
QUALITY_GATE_TIMEOUT_SECONDS="${FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS:-30}"
|
|
1312
|
+
if ! [[ "$QUALITY_GATE_TIMEOUT_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
|
|
1313
|
+
echo "QUALITY-GATE-CONFIG | HIGH | timeout | FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS must be a positive integer (got '$QUALITY_GATE_TIMEOUT_SECONDS')"
|
|
1314
|
+
QUALITY_GATE_TIMEOUT_SECONDS=30
|
|
1315
|
+
fi
|
|
1305
1316
|
|
|
1306
1317
|
if [ ! -f "$REGISTRY_FILE" ]; then
|
|
1307
1318
|
echo "2R: No check registry found at $REGISTRY_FILE — skipping registry checks."
|
|
@@ -1332,11 +1343,15 @@ else
|
|
|
1332
1343
|
continue
|
|
1333
1344
|
fi
|
|
1334
1345
|
|
|
1335
|
-
#
|
|
1336
|
-
CHECK_OUTPUT=$("$SCRIPT_PATH" "{CHANGED_FILES}" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1346
|
+
# Bound every third-party check. Exit 124 is timeout's documented code.
|
|
1347
|
+
CHECK_OUTPUT=$(timeout "$QUALITY_GATE_TIMEOUT_SECONDS" "$SCRIPT_PATH" "{CHANGED_FILES}" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1337
1348
|
CHECK_EXIT=$?
|
|
1338
1349
|
|
|
1339
|
-
if [ "$CHECK_EXIT" -eq
|
|
1350
|
+
if [ "$CHECK_EXIT" -eq 124 ]; then
|
|
1351
|
+
REGISTRY_FINDINGS="${REGISTRY_FINDINGS}
|
|
1352
|
+
REGISTRY-${SLUG}-timeout | HIGH | (check-registry) | Registry check '$SLUG' timed out after ${QUALITY_GATE_TIMEOUT_SECONDS}s — failing closed. Investigate and optimize the script before proceeding."
|
|
1353
|
+
echo "2R: TIMEOUT — $SLUG (${QUALITY_GATE_TIMEOUT_SECONDS}s) — fail-closed HIGH finding added"
|
|
1354
|
+
elif [ "$CHECK_EXIT" -eq 1 ]; then
|
|
1340
1355
|
# Check fired — append to findings
|
|
1341
1356
|
while IFS= read -r line; do
|
|
1342
1357
|
[ -z "$line" ] && continue
|
|
@@ -1383,6 +1398,11 @@ if [[ "$ADAPTIVE_DIR_NORM" != "${WORKTREE_NORM}/"* ]]; then
|
|
|
1383
1398
|
fi
|
|
1384
1399
|
|
|
1385
1400
|
GATED_FINDINGS=""
|
|
1401
|
+
QUALITY_GATE_TIMEOUT_SECONDS="${FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS:-30}"
|
|
1402
|
+
if ! [[ "$QUALITY_GATE_TIMEOUT_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
|
|
1403
|
+
echo "QUALITY-GATE-CONFIG | HIGH | timeout | FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS must be a positive integer (got '$QUALITY_GATE_TIMEOUT_SECONDS')"
|
|
1404
|
+
QUALITY_GATE_TIMEOUT_SECONDS=30
|
|
1405
|
+
fi
|
|
1386
1406
|
|
|
1387
1407
|
if [ "$ADAPTIVE_ENABLED" != "false" ]; then
|
|
1388
1408
|
GATE_D_DIR="${ADAPTIVE_DIR}/gate.d"
|
|
@@ -1411,17 +1431,17 @@ if [ "$ADAPTIVE_ENABLED" != "false" ]; then
|
|
|
1411
1431
|
continue
|
|
1412
1432
|
fi
|
|
1413
1433
|
|
|
1414
|
-
# Run with per-script
|
|
1434
|
+
# Run with the configured per-script timeout; pass diff path and worktree root
|
|
1415
1435
|
# timeout exit code 124 = timed out; treat as fail-closed (HIGH finding)
|
|
1416
|
-
GATE_OUTPUT=$(timeout
|
|
1436
|
+
GATE_OUTPUT=$(timeout "$QUALITY_GATE_TIMEOUT_SECONDS" bash "$script" "$DIFF_PATH" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1417
1437
|
GATE_EXIT=$?
|
|
1418
1438
|
|
|
1419
1439
|
if [ "$GATE_EXIT" -eq 124 ]; then
|
|
1420
1440
|
# Timeout — fail closed: block the gate with HIGH finding
|
|
1421
|
-
TIMEOUT_FINDING="GATD-${SLUG}-timeout | HIGH | (gate.d) | gate.d script '$SLUG' timed out after
|
|
1441
|
+
TIMEOUT_FINDING="GATD-${SLUG}-timeout | HIGH | (gate.d) | gate.d script '$SLUG' timed out after ${QUALITY_GATE_TIMEOUT_SECONDS}s — failing closed. Investigate and optimize the script or increase the configured timeout."
|
|
1422
1442
|
GATED_FINDINGS="${GATED_FINDINGS}
|
|
1423
1443
|
${TIMEOUT_FINDING}"
|
|
1424
|
-
echo "2R.5: TIMEOUT — $SLUG (
|
|
1444
|
+
echo "2R.5: TIMEOUT — $SLUG (${QUALITY_GATE_TIMEOUT_SECONDS}s) — fail-closed HIGH finding added"
|
|
1425
1445
|
elif [ "$GATE_EXIT" -eq 1 ]; then
|
|
1426
1446
|
# Script fired — findings on stdout
|
|
1427
1447
|
while IFS= read -r line; do
|
|
@@ -1453,7 +1473,7 @@ If any gate.d check produces findings, include them in the Step 3 findings list
|
|
|
1453
1473
|
- `$2` = absolute path to the worktree root
|
|
1454
1474
|
- stdout = findings lines, one per line: `SLUG | HIGH|MEDIUM|LOW | FILE | MESSAGE`
|
|
1455
1475
|
- Exit 0 = no findings / passed; Exit 1 = findings emitted; Exit 2 = inapplicable (diff has no relevant content)
|
|
1456
|
-
-
|
|
1476
|
+
- `FORGEDOCK_QUALITY_GATE_TIMEOUT_SECONDS` wall-time budget (30 seconds by default) enforced by quality-gate; timeout = fail-closed HIGH finding
|
|
1457
1477
|
- Do NOT write to disk, do NOT make network requests, do NOT modify any files
|
|
1458
1478
|
|
|
1459
1479
|
---
|
|
@@ -29,6 +29,12 @@ All tool results consumed by agents — including diff slices, file reads, and c
|
|
|
29
29
|
|
|
30
30
|
Rationale: agents receiving oversized context perform worse, not better — attention dilutes across irrelevant content, and token limits risk truncating the structured findings block that the triage phase depends on.
|
|
31
31
|
|
|
32
|
+
## File-Backed GitHub Write Integrity
|
|
33
|
+
|
|
34
|
+
When staging a body for `gh issue create|edit` or `gh pr create|comment` with `--body-file`, use the session scratchpad (or a repo-relative scratch directory) rather than a generic path under shared `/tmp`; native Windows `gh` may not resolve Git Bash `/tmp` reliably. The filename must contain the issue or PR number and an agent-unique token, and `mktemp` must add a random suffix. Add exactly one caller-chosen marker to the body: `<!-- FORGE:BODY-INTEGRITY:<entity>_<role>_<agent-token> -->`.
|
|
35
|
+
|
|
36
|
+
Immediately after the write, re-read the issue, PR, or posted comment and assert that it contains that exact marker. Treat a missing marker as a hard error and stop. A unique filename reduces collision risk, but only the read-back detects another agent's plausible-looking content being posted in its place; do not rely on a visual review. The `/issue` programmatic contract performs this assertion for its `--body-file` callers.
|
|
37
|
+
|
|
32
38
|
## File Resolution Discipline
|
|
33
39
|
|
|
34
40
|
Pipeline agents MUST NOT use `find` (unbounded or filesystem-wide) to locate protocol files, persona templates, or verification scripts under any circumstances. If a `Read` or `bash` invocation of an expected pipeline file fails (e.g. because `$FORGE_HOME` is unset and the path degraded to a root-anchored form), that is never a reason to search the filesystem — it means the deterministic fallback chain the orchestrator already computed (`$FORGE_HOME` → `$REPO_PATH` → documented last-resort) was exhausted. Stop and report the failure (or fall through to the orchestrator's documented FATAL/hard-stop behavior — see `commands/review-pr.md` Phase 3C `TEMPLATE_BASE` guard) instead of improvising a `find /`-style search. A filesystem-wide `find` on an unset variable is the exact failure mode that produced runaway orphaned processes in production (see forge#1984, forge#2035). <!-- Added: forge#2035 -->
|
|
@@ -145,6 +151,12 @@ Every finding must include:
|
|
|
145
151
|
|
|
146
152
|
**All review agents MUST include a machine-readable findings block at the end of their PR comment.** This is NON-OPTIONAL. Without structured findings, the review system cannot create GitHub issues, and findings die as unread PR comments. Every finding that doesn't become a GitHub issue is a finding that will never be addressed.
|
|
147
153
|
|
|
154
|
+
### Persist Before Post
|
|
155
|
+
|
|
156
|
+
GitHub is a delivery channel, not the sole record of a review. Before the first `gh pr comment` attempt, write the finalized complete review body, including its `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker and structured findings block, to a uniquely named durable file such as `${TMPDIR:-/tmp}/forge-review-${PR_NUMBER}-${DOMAIN}-$$.md`. Use `gh pr comment --body-file "$REVIEW_BODY_PATH"`; do not construct a retry loop around a failed write.
|
|
157
|
+
|
|
158
|
+
Every agent MUST return its verdict, finding count, and one line per finding to the orchestrator even when posting succeeds. If the post fails, return the durable body path and the same finding summary, then stop. A 403 or other write failure is a failed delivery, not a clean review and not a reason to retry in the background.
|
|
159
|
+
|
|
148
160
|
### Format
|
|
149
161
|
|
|
150
162
|
Append this block at the very end of your comment (after the `---` footer line, still inside the EOF heredoc). It uses HTML comments so it's invisible in rendered markdown:
|
|
@@ -163,6 +175,7 @@ Append this block at the very end of your comment (after the `---` footer line,
|
|
|
163
175
|
6. **Summary**: Concise one-line description (no pipe `|` characters in summary)
|
|
164
176
|
7. **Empty block**: If no findings at all, include just the START/END markers
|
|
165
177
|
8. **HTML comments**: The block is invisible in rendered markdown but parseable by the review system
|
|
178
|
+
9. **Agent marker**: Include exactly one `<!-- FORGE:REVIEW-AGENT:{domain} -->` marker in the persisted body, where `{domain}` is the lowercase dispatched domain.
|
|
166
179
|
|
|
167
180
|
### Domain Prefixes
|
|
168
181
|
|
|
@@ -21,7 +21,7 @@ directly (`gh pr comment`) rather than relaying them through the orchestrator's
|
|
|
21
21
|
|
|
22
22
|
**OpenCode override**: When `FORGE_RUNTIME=opencode` or an equivalent OpenCode marker is present, the
|
|
23
23
|
orchestrator must set `DISPATCH_TOOL=task` before checking literal Claude tool names. A native task
|
|
24
|
-
argument is shaped as `{ description: "...", prompt: "...", subagent_type: "general"|"explore", background:
|
|
24
|
+
argument is shaped as `{ description: "...", prompt: "...", subagent_type: "general"|"explore", background: false }`:
|
|
25
25
|
use `general` for implementation/review and `explore` for read-only discovery. The `Neither tool is
|
|
26
26
|
available` hard stop applies only when lowercase native `task` is genuinely absent; it must not be
|
|
27
27
|
triggered by the absence of `Task` and `Agent` names in an OpenCode session. If native `task` is absent,
|
|
@@ -39,11 +39,12 @@ if [ "${FORGE_RUNTIME:-}" = "opencode" ] ||
|
|
|
39
39
|
fi
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
When `IS_OPENCODE_RUNTIME=true`, lowercase native `task` is the preferred isolated dispatch tool. Do not enter the `Neither tool is available` branch merely because Claude's literal `Task` and `Agent` names are absent. Every native task call must use a top-level `subagent_type`: `general` for implementation/review work and `explore` for read-only discovery (Claude `general-purpose` and `codebase-explorer` map to those values).
|
|
42
|
+
When `IS_OPENCODE_RUNTIME=true`, lowercase native `task` is the preferred isolated dispatch tool. Do not enter the `Neither tool is available` branch merely because Claude's literal `Task` and `Agent` names are absent. Every native task call must use a top-level `subagent_type`: `general` for implementation/review work and `explore` for read-only discovery (Claude `general-purpose` and `codebase-explorer` map to those values). Review tasks are foreground: wait for their completed result before synthesis. If the native `task` capability itself is absent from the current tool registry, use the existing hard-stop path and post `FORGE:REVIEW_BLOCKED`; never substitute inline review.
|
|
43
43
|
|
|
44
44
|
1. **If `Task` is available in the current environment**: set `{DISPATCH_TOOL} = Task`. This is the preferred tool — tightest `allowed-tools` scoping. (Identical resolution logic to `/review-pr` Phase 3C — do not diverge.)
|
|
45
45
|
2. **Else if `Agent` is available**: set `{DISPATCH_TOOL} = Agent`. This is the documented fallback, not a degraded path — use it exactly as you would `Task`: one call per selected agent (Bug Hunters in Phase 3, Code Quality in Phase 4, domain agents in Phase 5), same prompt template, `subagent_type: "general-purpose"` (or the closest equivalent the environment offers), same requirement that each agent posts its own findings directly to the PR via `gh pr comment`. Isolation and fresh-context review are preserved either way.
|
|
46
46
|
3. **Neither tool is available**: this is a genuine setup defect, not a routing decision — HARD STOP, post a PR/issue comment explaining that no sub-agent dispatch tool is available, add `needs-human`, and exit without posting a verdict. Do NOT fall back to reviewing inline in the orchestrator's own context.
|
|
47
|
+
4. **Dispatch pool exhausted or dispatch call fails**: this is not tool absence. If any Bug Hunter, quality, domain, material-change, or regression reviewer cannot be launched because the sub-agent pool/session limit is exhausted (or any dispatch call fails), HARD STOP. Do not substitute inline review or silently continue with a partial panel. Mark the PR `review-degraded`, post `<!-- FORGE:GATE_FAILURE:TYPE=review-panel-integrity -->` with the selected and completed counts, and exit without a deploy verdict. A fresh session must re-run the full panel.
|
|
47
48
|
|
|
48
49
|
**Do not halt to ask the operator which tool to use.** Steps 1–2 are deterministic and fully resolve the common case; only step 3 (both absent) requires a stop, and even then the action is HARD STOP + `needs-human`, not a question back to the operator.
|
|
49
50
|
|
|
@@ -198,9 +199,17 @@ ALL_PR_NUMBERS=$(echo "$BUNDLE_PRS $MERGE_PRS" | tr ' ' '\n' | sort -u | grep -E
|
|
|
198
199
|
|
|
199
200
|
echo "PRs in staging→main bundle: $(echo $ALL_PR_NUMBERS | tr '\n' ' ')"
|
|
200
201
|
|
|
201
|
-
# Step 2: For each PR in the bundle, check for open review-finding issues
|
|
202
|
+
# Step 2: For each PR in the bundle, check for open review-finding issues and degraded panels
|
|
202
203
|
BLOCKING_FINDINGS=""
|
|
204
|
+
DEGRADED_REVIEWS=""
|
|
203
205
|
for pr_num in $ALL_PR_NUMBERS; do
|
|
206
|
+
IS_REVIEW_DEGRADED=$(gh pr view "$pr_num" -R {GH_REPO} --json labels \
|
|
207
|
+
--jq '[.labels[].name] | any(. == "review-degraded")' 2>/dev/null || echo "false")
|
|
208
|
+
if [ "$IS_REVIEW_DEGRADED" = "true" ]; then
|
|
209
|
+
DEGRADED_REVIEWS="${DEGRADED_REVIEWS}
|
|
210
|
+
**PR #${pr_num}** has a \`review-degraded\` label and requires a full fresh-context re-review."
|
|
211
|
+
fi
|
|
212
|
+
|
|
204
213
|
# Search for open review-finding issues that reference this PR
|
|
205
214
|
OPEN_FINDINGS=$(gh issue list -R {GH_REPO} \
|
|
206
215
|
--label "review-finding" \
|
|
@@ -217,6 +226,27 @@ ${OPEN_FINDINGS}"
|
|
|
217
226
|
fi
|
|
218
227
|
done
|
|
219
228
|
|
|
229
|
+
# A degraded panel is a review-integrity failure, not a finding that an override may waive.
|
|
230
|
+
if [ -n "$DEGRADED_REVIEWS" ]; then
|
|
231
|
+
echo "⛔ DEPLOY BLOCKED — An included PR has an incomplete isolated review panel."
|
|
232
|
+
echo "$DEGRADED_REVIEWS"
|
|
233
|
+
if [ -n "$PR_NUMBER" ]; then
|
|
234
|
+
gh pr comment "$PR_NUMBER" -R {GH_REPO} --body "<!-- FORGE:GATE_FAILURE -->
|
|
235
|
+
## Deploy Gate: BLOCKED
|
|
236
|
+
|
|
237
|
+
**Gate**: review-panel-integrity
|
|
238
|
+
|
|
239
|
+
### Degraded Review Panels
|
|
240
|
+
|
|
241
|
+
${DEGRADED_REVIEWS}
|
|
242
|
+
|
|
243
|
+
Re-run the complete review panel in a fresh session before deployment.
|
|
244
|
+
|
|
245
|
+
<!-- FORGE:GATE_FAILURE:TYPE=review-panel-integrity -->" 2>/dev/null || true
|
|
246
|
+
fi
|
|
247
|
+
exit 1
|
|
248
|
+
fi
|
|
249
|
+
|
|
220
250
|
# Step 3: Block deploy if open findings exist (unless human override present)
|
|
221
251
|
if [ -n "$BLOCKING_FINDINGS" ]; then
|
|
222
252
|
# Check for human override comment on the staging→main PR
|
|
@@ -403,7 +433,7 @@ For fixable failures: checkout staging, apply fix, verify locally, commit as `fi
|
|
|
403
433
|
|
|
404
434
|
Launch agent (model: {SUBAGENT_MODEL}) to analyze all commits since last deploy. Categorize as: NEW FEATURE, ENHANCEMENT, BUG FIX, REFACTOR, SECURITY, PERFORMANCE, INFRASTRUCTURE, DEPENDENCY. Separate user-facing vs internal. Document breaking changes and required pre-deploy actions.
|
|
405
435
|
|
|
406
|
-
**MANDATORY — post findings as PR comment**: After completing analysis, the agent
|
|
436
|
+
**MANDATORY — post findings as PR comment**: Before its first post attempt, the agent MUST persist the final report to a uniquely named durable file, then post it with `--body-file`. It MUST also return verdict, finding count, and one line per finding to the orchestrator whether the post succeeds or fails; on failure, return the file path and stop without retrying. After completing analysis, the agent posts its full report directly to the PR:
|
|
407
437
|
```bash
|
|
408
438
|
gh pr comment ${PR_NUMBER} -R ${GH_REPO} --body "<!-- FORGE:REVIEW-AGENT:material-change -->
|
|
409
439
|
## Material Change Analysis
|
|
@@ -428,7 +458,7 @@ Launch Bug Hunter agents for each service with changes:
|
|
|
428
458
|
|
|
429
459
|
**Web Bug Hunter** (web/src/): React issues (keys, closures, hydration), data fetching, security (XSS), UX, build-breaking patterns, type issues. Prefix: FE.
|
|
430
460
|
|
|
431
|
-
Each reads the service diff, hunts for bugs, traces context across imports,
|
|
461
|
+
Each reads the service diff, hunts for bugs, traces context across imports, persists its finalized report before posting, and returns its verdict and findings independently of GitHub delivery.
|
|
432
462
|
|
|
433
463
|
**MANDATORY — each Bug Hunter agent MUST post its findings directly to the PR immediately upon completion** (do not wait for the orchestrator to batch-post):
|
|
434
464
|
```bash
|
|
@@ -449,7 +479,7 @@ Where `{service}` is `api`, `worker`, or `web`. Post one comment per service age
|
|
|
449
479
|
|
|
450
480
|
Agent hunts for: dead code, duplicate logic, complexity (>50 line functions), naming issues, missing abstractions, logging quality, magic numbers. Prefix: QA.
|
|
451
481
|
|
|
452
|
-
**MANDATORY — post findings as PR comment**: After completing analysis, the agent
|
|
482
|
+
**MANDATORY — post findings as PR comment**: Before its first post attempt, the agent MUST persist the final report to a uniquely named durable file, then post it with `--body-file`. It MUST also return verdict, finding count, and one line per finding to the orchestrator whether the post succeeds or fails; on failure, return the file path and stop without retrying. After completing analysis, the agent posts its full report directly to the PR:
|
|
453
483
|
```bash
|
|
454
484
|
gh pr comment ${PR_NUMBER} -R ${GH_REPO} --body "<!-- FORGE:REVIEW-AGENT:code-quality -->
|
|
455
485
|
## Code Quality Review
|
|
@@ -506,7 +536,7 @@ fi
|
|
|
506
536
|
|
|
507
537
|
Launch domain-specific agents based on which domains have changes. Substitute PR diff commands with staging diff commands. Agents: General Security (always), Auth, Billing, Concurrency, Scraper, API Design, Database, Infrastructure.
|
|
508
538
|
|
|
509
|
-
**MANDATORY — each domain agent MUST
|
|
539
|
+
**MANDATORY — each domain agent MUST persist its finalized body before posting its findings directly to the PR immediately upon completion** (not batched by the orchestrator). It MUST return verdict, finding count, and one line per finding to the orchestrator independently of delivery; if posting fails, return the durable file path and stop without retrying:
|
|
510
540
|
```bash
|
|
511
541
|
gh pr comment ${PR_NUMBER} -R ${GH_REPO} --body "<!-- FORGE:REVIEW-AGENT:{domain} -->
|
|
512
542
|
## {Domain} Review
|
|
@@ -525,7 +555,7 @@ Where `{domain}` is `security`, `auth`, `billing`, `concurrency`, `scraper`, `ap
|
|
|
525
555
|
|
|
526
556
|
Agent maps dependencies, assesses integration points (service boundaries, env vars, Docker changes, workflow sibling drift between ci.yml and deploy-production.yml), evaluates rollback difficulty (easy/hard/destructive/state-dependent), checks test coverage. Posts risk matrix with rollback plan. Prefix: REG.
|
|
527
557
|
|
|
528
|
-
**MANDATORY — post findings as PR comment**: After completing analysis, the agent
|
|
558
|
+
**MANDATORY — post findings as PR comment**: Before its first post attempt, the agent MUST persist the final report to a uniquely named durable file, then post it with `--body-file`. It MUST also return verdict, finding count, and one line per finding to the orchestrator whether the post succeeds or fails; on failure, return the file path and stop without retrying. After completing analysis, the agent posts its full report directly to the PR:
|
|
529
559
|
```bash
|
|
530
560
|
gh pr comment ${PR_NUMBER} -R ${GH_REPO} --body "<!-- FORGE:REVIEW-AGENT:regression-risk -->
|
|
531
561
|
## Regression Risk Assessment
|
|
@@ -682,8 +712,51 @@ If the gate exits with `RESULT: BLOCK DEPLOY` → **STOP**. A `<!-- FORGE:GATE_F
|
|
|
682
712
|
|
|
683
713
|
---
|
|
684
714
|
|
|
715
|
+
**Incomplete-panel guard (MANDATORY):** Increment `SELECTED_AGENT_COUNT` for every reviewer selected across Phases 2–6 before launching it. If any dispatch fails, set `DISPATCH_FAILED=true`. Before Phase 7, count unique `FORGE:REVIEW-AGENT` markers. If the completed count is lower than selected, follow the same hard-stop path as pool exhaustion: create `review-degraded` if needed, label the PR, post `FORGE:GATE_FAILURE`, and exit. Do not create a deploy verdict from partial reviewer output.
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
ACTUAL_AGENT_COUNT=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
|
|
719
|
+
--jq '[.[].body | scan("<!-- FORGE:REVIEW-AGENT:([a-z-]+) -->") | .[0]] | unique | length' 2>/dev/null || echo 0)
|
|
720
|
+
if [ "$DISPATCH_FAILED" = "true" ] || [ "$ACTUAL_AGENT_COUNT" -lt "$SELECTED_AGENT_COUNT" ]; then
|
|
721
|
+
gh label create "review-degraded" --color "E4E669" --description "PR review panel was incomplete; re-review required before deployment. Managed by ForgeDock." --force -R "$GH_REPO" 2>/dev/null || true
|
|
722
|
+
gh pr edit "$PR_NUMBER" -R "$GH_REPO" --add-label "review-degraded" --add-label "needs-human" 2>/dev/null || true # allowlist:check-command-side-effects
|
|
723
|
+
gh pr comment "$PR_NUMBER" -R "$GH_REPO" --body "<!-- FORGE:GATE_FAILURE:TYPE=review-panel-integrity -->
|
|
724
|
+
## Deploy Review Blocked: Incomplete Isolated Review Panel
|
|
725
|
+
|
|
726
|
+
**Selected isolated reviewers**: ${SELECTED_AGENT_COUNT}
|
|
727
|
+
**Completed isolated reviewers**: ${ACTUAL_AGENT_COUNT}
|
|
728
|
+
|
|
729
|
+
Re-run the full staging review in a fresh session before deployment."
|
|
730
|
+
exit 1
|
|
731
|
+
fi
|
|
732
|
+
```
|
|
733
|
+
|
|
685
734
|
## Phase 7: Finding Triage & Issue Creation
|
|
686
735
|
|
|
736
|
+
### Phase 6.75: Verify Agent Delivery
|
|
737
|
+
|
|
738
|
+
Before launching every Phase 2-6 review agent, append its lowercase marker domain (for example, `security`, `bug-hunter-api`, or `regression-risk`) to `LAUNCHED_REVIEW_AGENTS`. A completed agent is accounted for only when its corresponding marker comment is present on the PR.
|
|
739
|
+
|
|
740
|
+
```bash
|
|
741
|
+
MISSING_AGENT_COMMENTS=""
|
|
742
|
+
for AGENT_DOMAIN in $LAUNCHED_REVIEW_AGENTS; do
|
|
743
|
+
AGENT_COMMENT_COUNT=$(gh api "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
|
|
744
|
+
--jq "[.[] | select(.body | contains(\"<!-- FORGE:REVIEW-AGENT:${AGENT_DOMAIN} -->\"))] | length" \
|
|
745
|
+
2>/dev/null || printf '0')
|
|
746
|
+
if [ "${AGENT_COMMENT_COUNT:-0}" -lt 1 ]; then
|
|
747
|
+
MISSING_AGENT_COMMENTS="${MISSING_AGENT_COMMENTS} ${AGENT_DOMAIN}"
|
|
748
|
+
fi
|
|
749
|
+
done
|
|
750
|
+
|
|
751
|
+
if [ -n "$MISSING_AGENT_COMMENTS" ]; then
|
|
752
|
+
echo "REVIEW DELIVERY FAILURE: missing findings comment(s) for:${MISSING_AGENT_COMMENTS}"
|
|
753
|
+
echo "Recover each agent's returned verdict, findings, and durable body path before rerunning review."
|
|
754
|
+
exit 1
|
|
755
|
+
fi
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Missing comments are an explicit failure, never evidence of no findings. Do not triage or synthesize a deploy verdict while any launched agent is unaccounted for.
|
|
759
|
+
|
|
687
760
|
### 7A: Extract Findings
|
|
688
761
|
From PR comments, extract structured findings (`<!-- FINDING:... -->`). If none found, scan for unstructured findings. If still 0 → skip to Phase 8.
|
|
689
762
|
|
|
@@ -734,7 +807,11 @@ STAGING_FINDING_TITLE="chore: [summary] (staging review — PR #${PR_NUMBER})"
|
|
|
734
807
|
# this is no longer required for safety — but strip it anyway so the raw title
|
|
735
808
|
# stays readable if it round-trips through any other eval-based consumer.
|
|
736
809
|
STAGING_FINDING_TITLE=$(printf '%s' "$STAGING_FINDING_TITLE" | tr '`' "'" | sed 's/\$(/$ (/g')
|
|
737
|
-
|
|
810
|
+
SCRATCHPAD="${FORGE_SCRATCHPAD:-$PWD/.forge-scratch}"
|
|
811
|
+
REVIEW_AGENT_TOKEN="${AGENT_ID:-${HOSTNAME:-reviewer}-$$}"
|
|
812
|
+
mkdir -p "$SCRATCHPAD"
|
|
813
|
+
STAGING_FINDING_BODY_MARKER="FORGE:BODY-INTEGRITY:${PR_NUMBER}_staging-review_${REVIEW_AGENT_TOKEN}"
|
|
814
|
+
STAGING_FINDING_BODY_FILE=$(mktemp "$SCRATCHPAD/${PR_NUMBER}_staging-review_${REVIEW_AGENT_TOKEN}.XXXXXX.md")
|
|
738
815
|
cat <<'ISSUE_EOF' > "$STAGING_FINDING_BODY_FILE"
|
|
739
816
|
## Problem
|
|
740
817
|
|
|
@@ -768,6 +845,7 @@ Files that need changes:
|
|
|
768
845
|
- [ ] Finding validated: VALIDATED / FALSE_POSITIVE / INCONCLUSIVE
|
|
769
846
|
- [ ] If VALIDATED: fix implemented and tested on correct branch
|
|
770
847
|
ISSUE_EOF
|
|
848
|
+
printf '\n<!-- %s -->\n' "$STAGING_FINDING_BODY_MARKER" >> "$STAGING_FINDING_BODY_FILE"
|
|
771
849
|
|
|
772
850
|
# STAGING_FINDING_SEVERITY is extracted from the finding's own **Severity**
|
|
773
851
|
# body field (set above in the heredoc) — example assignment shown here for
|
|
@@ -794,18 +872,22 @@ else
|
|
|
794
872
|
# --label is repeatable (not comma-joined) per the /issue programmatic contract.
|
|
795
873
|
# ${MILESTONE_FLAG} carries the Phase 7D derivation through — empty string is
|
|
796
874
|
# a no-op arg when the reviewed branch has no milestone (plain staging→main).
|
|
797
|
-
Skill(skill="issue", args="--title \"$STAGING_FINDING_TITLE\" --body-file \"$STAGING_FINDING_BODY_FILE\" --label review-finding --label needs-validation --label staging-review --label \"$STAGING_FINDING_PRIORITY\" ${MILESTONE_FLAG}")
|
|
875
|
+
ISSUE_SKILL_OUTPUT=$(Skill(skill="issue", args="--title \"$STAGING_FINDING_TITLE\" --body-file \"$STAGING_FINDING_BODY_FILE\" --label review-finding --label needs-validation --label staging-review --label \"$STAGING_FINDING_PRIORITY\" ${MILESTONE_FLAG}"))
|
|
876
|
+
# /issue re-reads the created issue and hard-fails unless this exact marker is present.
|
|
798
877
|
rm -f "$STAGING_FINDING_BODY_FILE"
|
|
799
878
|
|
|
800
|
-
# /issue
|
|
801
|
-
#
|
|
802
|
-
#
|
|
803
|
-
ISSUE_NUM
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
879
|
+
# /issue succeeds only after its API create-token read-back (Phase 4B). Its
|
|
880
|
+
# explicit result marker distinguishes a verified create from an intentional
|
|
881
|
+
# dedup STOP; do not use title search to mask a swallowed 403.
|
|
882
|
+
ISSUE_NUM=$(printf '%s\n' "$ISSUE_SKILL_OUTPUT" | sed -n 's/.*ISSUE_CREATE_RESULT:CREATED number=\([0-9][0-9]*\).*/\1/p' | head -1)
|
|
883
|
+
DEDUP_NUMBER=$(printf '%s\n' "$ISSUE_SKILL_OUTPUT" | sed -n 's/.*ISSUE_CREATE_RESULT:DEDUP number=\([0-9][0-9]*\).*/\1/p' | head -1)
|
|
884
|
+
if [ -n "$DEDUP_NUMBER" ]; then
|
|
885
|
+
ISSUE_NUM="$DEDUP_NUMBER"
|
|
886
|
+
echo "Staging review finding deduped against existing issue #${ISSUE_NUM}."
|
|
887
|
+
elif [ -z "$ISSUE_NUM" ]; then
|
|
888
|
+
echo "ERROR: /issue did not report a verified staging review-finding number; stopping review instead of silently dropping the finding." >&2
|
|
889
|
+
exit 1
|
|
890
|
+
fi
|
|
809
891
|
fi
|
|
810
892
|
```
|
|
811
893
|
|