forgedock 1.13.1 → 1.15.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 (45) hide show
  1. package/bin/engine/phases-args.test.mjs +11 -0
  2. package/bin/engine/phases.mjs +268 -25
  3. package/bin/engine/phases.test.mjs +24 -0
  4. package/bin/engine-cli.mjs +18 -5
  5. package/bin/engine.mjs +149 -12
  6. package/bin/forgedock.mjs +4 -1
  7. package/commands/issue.md +17 -2
  8. package/commands/orchestrate/phase-3-dependency.md +5 -2
  9. package/commands/orchestrate/phase-4-execution.md +154 -27
  10. package/commands/pipeline-health/phase-2-metrics.md +5 -3
  11. package/commands/review-pr-staging.md +554 -54
  12. package/commands/review-pr.md +662 -51
  13. package/commands/validate.md +11 -3
  14. package/commands/work-on/build/architect.md +11 -8
  15. package/commands/work-on/build/implement.md +15 -1
  16. package/commands/work-on/build.md +34 -10
  17. package/commands/work-on/close.md +5 -4
  18. package/commands/work-on/decompose.md +7 -2
  19. package/commands/work-on/investigate.md +13 -0
  20. package/commands/work-on/remediate.md +332 -91
  21. package/commands/work-on/review.md +3 -3
  22. package/docs/CONFIG.md +14 -1
  23. package/docs/RELEASE-NOTES.md +20 -0
  24. package/package.json +1 -1
  25. package/packages/protocol/src/phases.js +6 -0
  26. package/scripts/architect-resume-match.test.sh +53 -0
  27. package/scripts/check-spec-bash.sh +73 -3
  28. package/scripts/check-spec-bash.test.sh +50 -0
  29. package/scripts/check-spec-markers.sh +2 -0
  30. package/scripts/checkbox-sections.test.sh +19 -0
  31. package/scripts/classify-finding.sh +103 -1
  32. package/scripts/classify-finding.test.sh +56 -0
  33. package/scripts/forge-root.test.sh +86 -2
  34. package/scripts/orchestrate-soft-edge.test.sh +58 -0
  35. package/scripts/remediate-approved-head.test.sh +43 -0
  36. package/scripts/remediate-basesync.test.sh +62 -3
  37. package/scripts/remediation-marker-match.test.sh +6 -0
  38. package/scripts/resolve-deploy-gate.sh +51 -0
  39. package/scripts/resolve-deploy-gate.test.sh +59 -0
  40. package/scripts/review-delta.sh +3 -2
  41. package/scripts/review-delta.test.sh +1 -1
  42. package/scripts/review-pr-delta-wiring.test.sh +274 -0
  43. package/scripts/review-pr-domain-map.test.sh +10 -0
  44. package/scripts/review-pr-staging-scope.test.sh +159 -0
  45. package/scripts/trusted-comments.test.sh +11 -0
@@ -1520,7 +1520,7 @@ Each agent receives only its domain-relevant diff slice rather than the full cha
1520
1520
  # Domain file patterns — used in Phase 3C to scope each agent's input
1521
1521
  DOMAIN_FILES_AUTH=$(echo "$FILES" | grep -iE "auth|session|login|logout|jwt|oauth|token|user|permission|middleware" || echo "")
1522
1522
  DOMAIN_FILES_BILLING=$(echo "$FILES" | grep -iE "billing|credit|payment|charge|refund|subscription|pricing|tier" || echo "")
1523
- DOMAIN_FILES_CONCURRENCY=$(echo "$FILES" | grep -iE "lock|queue|atomic|transaction|worker|job|task|celery|redis" || echo "")
1523
+ DOMAIN_FILES_CONCURRENCY=$(echo "$FILES" | grep -iE "lock|queue|atomic|transaction|worker|job|task|celery|redis|claim|lease|engine-state" || echo "")
1524
1524
  DOMAIN_FILES_DATABASE=$(echo "$FILES" | grep -iE "migration|\.sql$|model|schema|db|database|alembic" || echo "")
1525
1525
  DOMAIN_FILES_INFRA=$(echo "$FILES" | grep -iE "docker|nginx|traefik|\.github|Makefile|deploy|infra" || echo "")
1526
1526
  DOMAIN_FILES_SCRAPING=$(echo "$FILES" | grep -iE "scrape|crawler|stealth|browser.*pool|headless|anti.bot|detection.*keyword|captcha" || echo "")
@@ -1528,14 +1528,225 @@ DOMAIN_FILES_FRONTEND=$(echo "$FILES" | grep -iE "^web/|\.tsx?$|\.jsx?$|componen
1528
1528
  DOMAIN_FILES_API=$(echo "$FILES" | grep -iE "router|route|endpoint|openapi|sdk|api" || echo "")
1529
1529
 
1530
1530
  # Fallback: if a domain has no specific files matched, give it the full file list
1531
- # (happens for Security — which reviews everything — and for edge cases)
1531
+ # (a domain can be on the roster without a pattern hit, e.g. via delta_roster or Security's full-diff scope)
1532
1532
  [ -z "$DOMAIN_FILES_AUTH" ] && DOMAIN_FILES_AUTH="$FILES"
1533
1533
  [ -z "$DOMAIN_FILES_BILLING" ] && DOMAIN_FILES_BILLING="$FILES"
1534
+ [ -z "$DOMAIN_FILES_CONCURRENCY" ] && DOMAIN_FILES_CONCURRENCY="$FILES"
1534
1535
  [ -z "$DOMAIN_FILES_DATABASE" ] && DOMAIN_FILES_DATABASE="$FILES"
1536
+ [ -z "$DOMAIN_FILES_INFRA" ] && DOMAIN_FILES_INFRA="$FILES"
1537
+ [ -z "$DOMAIN_FILES_SCRAPING" ] && DOMAIN_FILES_SCRAPING="$FILES"
1538
+ [ -z "$DOMAIN_FILES_FRONTEND" ] && DOMAIN_FILES_FRONTEND="$FILES"
1539
+ [ -z "$DOMAIN_FILES_API" ] && DOMAIN_FILES_API="$FILES"
1535
1540
  ```
1536
1541
 
1537
1542
  ### 3C: Load Agent Templates & Launch
1538
1543
 
1544
+ #### Review mode: FULL, DELTA or BASE_SYNC_ONLY (run first, before loading any template)
1545
+
1546
+ Re-reviewing a head that only moved since the last fully reviewed head does not need the whole panel over the whole diff. `scripts/review-delta.sh` (resolved from the trusted install, never the working directory or the PR checkout) classifies the move from GitHub state and git history. It is fail closed: every error, an unresolvable script or trust script, unreadable output or an unvalidated SHA means **FULL**, which is the review this spec ran before the resolver existed.
1547
+
1548
+ | Mode | Meaning | What changes |
1549
+ |------|---------|--------------|
1550
+ | `FULL` | First review, anything the resolver cannot prove, `--thorough`, milestone-to-staging, or a same-head re-entry | Nothing. The 3B roster reviews the full PR diff. |
1551
+ | `DELTA` | Head moved by ordinary commits since the last fully reviewed head | Reviewers get `git diff <reviewed>..<head>` as their slice (`FULL_FILES` stays as context). The 3B roster stands while `FULL_ROUNDS < 2`; from the third review on the roster is Security plus the domains whose trigger paths the delta touches. Findings of the earlier reviewed head are read again in Phase 6A. |
1552
+ | `BASE_SYNC_ONLY` | Head moved only by clean merges of `origin/<base>` with the PR's own change byte-identical | Zero reviewers. A trusted `FORGE:REVIEW-CARRIED` record stands in for the per-domain markers in Phases 4, 5, 6A and 9. |
1553
+
1554
+ Phase 2 checks, the §6B.4 provenance gates (against the full PR diff at the current head) and the Phase 7B/8 gates run unchanged in every mode.
1555
+
1556
+ ```bash
1557
+ # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
1558
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; SELECTED_AGENTS="{SELECTED_AGENTS}"
1559
+ THOROUGH="{THOROUGH}"; IS_MILESTONE_TO_STAGING="{IS_MILESTONE_TO_STAGING}"
1560
+ # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
1561
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1562
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1563
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1564
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
1565
+ done
1566
+ TRUSTED_SCRIPT=""
1567
+ while IFS= read -r _c; do
1568
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1569
+ done <<< "$_tc"
1570
+ # HELPER_SCRIPT resolver (REVIEW_DELTA_SCRIPT; trusted-install tiers only, never the working directory or the PR checkout, #3400/#3483/#3596): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. A PR that shipped its own resolver could otherwise decide that its own head needs no review. The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
1571
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1572
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1573
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1574
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
1575
+ done
1576
+ REVIEW_DELTA_SCRIPT=""
1577
+ while IFS= read -r _c; do
1578
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
1579
+ done <<< "$_tc"
1580
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1581
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1582
+ # BEGIN review-pr-delta-mode
1583
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1584
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
1585
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
1586
+ review_delta_mode() {
1587
+ local out l1 l2 kind arg from to rounds=0 top
1588
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
1589
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
1590
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
1591
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
1592
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
1593
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
1594
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
1595
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
1596
+ kind="${l1%% *}"; arg="${l1#* }"
1597
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
1598
+ if [ "$kind" = "DELTA" ]; then
1599
+ from="${arg%%..*}"; to="${arg##*..}"
1600
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
1601
+ fi
1602
+ echo "FULL - $rounds"
1603
+ }
1604
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
1605
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
1606
+ carry_verified_from() {
1607
+ local kind from rounds n
1608
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
1609
+ read -r kind from rounds <<< "$(review_delta_mode)"
1610
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
1611
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
1612
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
1613
+ [ "$n" -ge 1 ] && printf '%s' "$from"
1614
+ return 0
1615
+ }
1616
+ # END review-pr-delta-mode
1617
+ # BEGIN review-pr-delta-roster
1618
+ # Effective roster for a DELTA head. While fewer than two full panels have run (FULL_ROUNDS < 2) the 3B roster stands;
1619
+ # from the third review on it narrows to Security plus the domains whose trigger paths the delta touches.
1620
+ # spec-cli is internal-only (not shipped to consumers): it is added only when the persona file exists in the repo under review.
1621
+ # Args: FULL_ROUNDS, the canonical 3B roster (space separated), the delta file list (one path per line).
1622
+ delta_roster() {
1623
+ local rounds="${1}" full="${2}" files="${3}" out="security" root
1624
+ root="${REPO_PATH:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
1625
+ case "$rounds" in ''|*[!0-9]*) printf '%s' "$full"; return 0 ;; esac
1626
+ if [ "$rounds" -lt 2 ]; then printf '%s' "$full"; return 0; fi
1627
+ printf '%s\n' "$files" | grep -qiE "auth|session|login|logout|jwt|oauth|token|user|permission|middleware" && out="$out auth"
1628
+ printf '%s\n' "$files" | grep -qiE "billing|credit|payment|charge|refund|subscription|pricing|tier" && out="$out billing"
1629
+ printf '%s\n' "$files" | grep -qiE "lock|queue|atomic|transaction|worker|job|task|celery|redis|claim|lease|engine-state" && out="$out concurrency"
1630
+ printf '%s\n' "$files" | grep -qiE "migration|\.sql$|model|schema|db|database|alembic" && out="$out database"
1631
+ printf '%s\n' "$files" | grep -qiE "docker|nginx|traefik|\.github|Makefile|deploy|infra" && out="$out infra"
1632
+ printf '%s\n' "$files" | grep -qiE "scrape|crawler|stealth|browser.*pool|headless|anti.bot|detection.*keyword|captcha" && out="$out scraper"
1633
+ printf '%s\n' "$files" | grep -qiE "^web/|\.tsx?$|\.jsx?$|component|page|layout|style|css" && out="$out frontend"
1634
+ printf '%s\n' "$files" | grep -qiE "router|route|endpoint|openapi|sdk|api" && out="$out api"
1635
+ printf '%s\n' "$files" | grep -qE "^(commands|bin)/" && [ -f "$root/commands/review-pr-agents/spec-cli.md" ] && out="$out spec-cli"
1636
+ printf '%s' "$out"
1637
+ }
1638
+ # END review-pr-delta-roster
1639
+ REVIEW_MODE="FULL"; DELTA_FROM=""; CARRIED_FROM=""; FULL_ROUNDS=0
1640
+ if [ "$THOROUGH" != "true" ] && [ "$IS_MILESTONE_TO_STAGING" != "true" ]; then
1641
+ read -r RD_KIND RD_ARG RD_ROUNDS <<< "$(review_delta_mode)"
1642
+ FULL_ROUNDS="${RD_ROUNDS:-0}"
1643
+ case "$RD_KIND" in
1644
+ DELTA) REVIEW_MODE="DELTA"; DELTA_FROM="$RD_ARG" ;;
1645
+ BASE_SYNC_ONLY) REVIEW_MODE="BASE_SYNC_ONLY"; CARRIED_FROM="$RD_ARG" ;;
1646
+ esac
1647
+ fi
1648
+ if [ "$REVIEW_MODE" = "DELTA" ]; then
1649
+ RD_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
1650
+ DELTA_FILES=$(git -C "$RD_TOP" -c core.quotepath=off diff --name-only --no-renames "${DELTA_FROM}" "${REVIEW_SHA}" 2>/dev/null) || DELTA_FILES=""
1651
+ if [ -z "$DELTA_FILES" ]; then
1652
+ REVIEW_MODE="FULL"; DELTA_FROM="" # unreadable or empty delta: review the full diff
1653
+ else
1654
+ SELECTED_AGENTS=$(delta_roster "$FULL_ROUNDS" "$SELECTED_AGENTS" "$DELTA_FILES")
1655
+ fi
1656
+ fi
1657
+ [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ] && SELECTED_AGENTS=""
1658
+ echo "REVIEW_MODE: ${REVIEW_MODE}"
1659
+ echo "DELTA_FROM: ${DELTA_FROM}"
1660
+ echo "CARRIED_FROM: ${CARRIED_FROM}"
1661
+ echo "FULL_ROUNDS: ${FULL_ROUNDS}"
1662
+ echo "SELECTED_AGENTS: ${SELECTED_AGENTS}"
1663
+ ```
1664
+
1665
+ From here on substitute the printed `REVIEW_MODE`, `DELTA_FROM`, `CARRIED_FROM` and `SELECTED_AGENTS` into every later block as `{REVIEW_MODE}`, `{DELTA_FROM}`, `{CARRIED_FROM}` and `{SELECTED_AGENTS}`. The printed `SELECTED_AGENTS` is the **effective roster**: the panel guard here, the Phase 4 count and the Phase 9 count compare against what is actually dispatched, never against a stale full roster. A block that does not receive these values behaves as FULL (never as a skipped review). A printed `REVIEW_MODE` is advisory: Phases 4, 5, 6A and 9 re-verify `BASE_SYNC_ONLY` themselves before honouring it.
1666
+
1667
+ **If `REVIEW_MODE` is `BASE_SYNC_ONLY`**: dispatch zero reviewers. Skip the template guard, config load, code index, diff slicing and the dispatch below, post the carry record, then go straight to Phase 4:
1668
+
1669
+ ```bash
1670
+ # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
1671
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"
1672
+ # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
1673
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1674
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1675
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1676
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
1677
+ done
1678
+ TRUSTED_SCRIPT=""
1679
+ while IFS= read -r _c; do
1680
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1681
+ done <<< "$_tc"
1682
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
1683
+ REVIEW_DELTA_SCRIPT=""
1684
+ while IFS= read -r _c; do
1685
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
1686
+ done <<< "$_tc"
1687
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1688
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1689
+ # BEGIN review-pr-delta-mode
1690
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1691
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
1692
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
1693
+ review_delta_mode() {
1694
+ local out l1 l2 kind arg from to rounds=0 top
1695
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
1696
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
1697
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
1698
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
1699
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
1700
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
1701
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
1702
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
1703
+ kind="${l1%% *}"; arg="${l1#* }"
1704
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
1705
+ if [ "$kind" = "DELTA" ]; then
1706
+ from="${arg%%..*}"; to="${arg##*..}"
1707
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
1708
+ fi
1709
+ echo "FULL - $rounds"
1710
+ }
1711
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
1712
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
1713
+ carry_verified_from() {
1714
+ local kind from rounds n
1715
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
1716
+ read -r kind from rounds <<< "$(review_delta_mode)"
1717
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
1718
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
1719
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
1720
+ [ "$n" -ge 1 ] && printf '%s' "$from"
1721
+ return 0
1722
+ }
1723
+ # END review-pr-delta-mode
1724
+ read -r RD_KIND RD_FROM RD_ROUNDS <<< "$(review_delta_mode)"
1725
+ if [ "$RD_KIND" != "BASE_SYNC_ONLY" ]; then
1726
+ echo "CARRY_RECORD: NOT_APPLICABLE (resolver says ${RD_KIND}); run the FULL path"; exit 0
1727
+ fi
1728
+ if [ -n "$(carry_verified_from)" ]; then
1729
+ echo "CARRY_RECORD: PRESENT (verified from ${RD_FROM})"; exit 0 # re-entry: never post a second record
1730
+ fi
1731
+ gh pr comment "$PR_NUMBER" -R "$REPO" --body "<!-- FORGE:REVIEW-CARRIED -->
1732
+ Reviewed-SHA: ${REVIEW_SHA}
1733
+ Carried-From-SHA: ${RD_FROM}
1734
+
1735
+ ## Review carried forward
1736
+
1737
+ This head only merged \`origin/${BASE_BRANCH}\` into the PR (the PR's own change is byte-identical to the fully reviewed commit \`${RD_FROM}\`), so no reviewer was dispatched. The panel result and findings of \`${RD_FROM}\` stand; the merge gates still run on this head."
1738
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1739
+ if [ -n "$(carry_verified_from)" ]; then
1740
+ echo "CARRY_RECORD: POSTED (verified from ${RD_FROM})"
1741
+ else
1742
+ echo "CARRY_RECORD: UNVERIFIED (the record is not trusted or could not be read back); run the FULL path"
1743
+ fi
1744
+ ```
1745
+
1746
+ `CARRY_RECORD: PRESENT` or `POSTED` proceeds to Phase 4 with an empty roster. `NOT_APPLICABLE` or `UNVERIFIED` falls back to `REVIEW_MODE=FULL` with the 3B roster: set `{REVIEW_MODE}` back to `FULL`, restore the 3B `{SELECTED_AGENTS}` and continue with the template guard below. A record that cannot be trusted (for example the posting identity is not a trusted author) must never be the reason a head goes unreviewed.
1747
+
1748
+ **Otherwise (`FULL` or `DELTA`)** continue below. Under `DELTA`, append to every reviewer prompt: "Delta review: your diff slice is the change since the fully reviewed commit `{DELTA_FROM}`. The file list is context only. Findings of that earlier commit are carried separately; do not re-report code this slice does not show." <!-- Added: forge#3637 -->
1749
+
1539
1750
  **>>> MANDATORY TEMPLATE-RESOLUTION GUARD — run BEFORE any Read of persona templates:**
1540
1751
 
1541
1752
  Missing persona templates are a fatal setup error, not permission to skip multi-agent review. Resolve the template source through this ordered chain and STOP if none resolve — never fall through to reviewing inline in the main context:
@@ -1596,7 +1807,7 @@ Read: $MONOLITHIC_CATALOG
1596
1807
  ```
1597
1808
  Then extract the shared protocols section and each selected persona's section from within that single file — same content, different packaging.
1598
1809
 
1599
- Persona file names: `security.md` (always), `auth.md`, `billing.md`, `concurrency.md`, `scraper.md`, `frontend.md`, `api.md`, `database.md`, `infra.md`.
1810
+ Persona file names: `security.md` (always), `auth.md`, `billing.md`, `concurrency.md`, `scraper.md`, `frontend.md`, `api.md`, `database.md`, `infra.md`, and `spec-cli.md` (marker domain `spec-cli`, only ever on a `DELTA` roster whose delta touches `commands/**` or `bin/**` AND whose repo contains `commands/review-pr-agents/spec-cli.md`, i.e. ForgeDock itself; consumer repos never get it).
1600
1811
  See `review-pr-agents.md` for the full routing table mapping domains → persona files.
1601
1812
 
1602
1813
  (`$FORGE_HOME` defaults to `~/.claude`)
@@ -1628,7 +1839,17 @@ Before launching agents, query the code index to build a domain-scoped file list
1628
1839
 
1629
1840
  ```bash
1630
1841
  REPO_PATH=$(yq '.paths.root' "$FORGE_YAML" 2>/dev/null || git rev-parse --show-toplevel 2>/dev/null || pwd)
1631
- CODE_INDEX_SCRIPT="${REPO_PATH}/scripts/code-index.sh"
1842
+ # HELPER_SCRIPT resolver (CODE_INDEX_SCRIPT; trusted-install tiers only, never the working directory or the target repo, #3400/#3483): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
1843
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1844
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1845
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1846
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
1847
+ done
1848
+ CODE_INDEX_SCRIPT=""
1849
+ while IFS= read -r _c; do
1850
+ case "$_c" in /*) [ -z "$CODE_INDEX_SCRIPT" ] && [ -f "$_c/scripts/code-index.sh" ] && CODE_INDEX_SCRIPT="$_c/scripts/code-index.sh" ;; esac
1851
+ done <<< "$_tc"
1852
+ # REPO_PATH is the PR checkout (author-controlled): it is only the index target, never the script source. Unresolved: skip the index.
1632
1853
 
1633
1854
  # Ensure index is current (cache-hit on unchanged HEAD — instant if already built)
1634
1855
  if [[ -x "$CODE_INDEX_SCRIPT" ]]; then
@@ -1665,10 +1886,22 @@ Each domain agent receives only the diff slice relevant to its domain, not the f
1665
1886
 
1666
1887
  ```bash
1667
1888
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
1668
- PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"
1889
+ PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"; DELTA_FROM="{DELTA_FROM}"
1669
1890
  # Full diff fetched once — agents do NOT re-fetch it
1670
1891
  FULL_DIFF=$(gh pr diff "$PR_NUMBER" -R "$REPO")
1671
1892
  FULL_FILES=$(gh pr diff "$PR_NUMBER" -R "$REPO" --name-only)
1893
+ # DELTA (review-delta mode): the slice source becomes the range diff since the last fully reviewed head, and FULL_FILES stays as
1894
+ # context. Every failure (bad SHA, commit missing from the checkout, unreadable or empty range) keeps the full PR diff above.
1895
+ # The variable keeps its FULL_DIFF name so every awk slice below is unchanged.
1896
+ if [ "$REVIEW_MODE" = "DELTA" ]; then
1897
+ is_hex() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1898
+ RD_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
1899
+ if is_hex "$DELTA_FROM" && is_hex "$REVIEW_SHA" && [ -n "$RD_TOP" ] \
1900
+ && git -C "$RD_TOP" cat-file -e "${DELTA_FROM}^{commit}" 2>/dev/null && git -C "$RD_TOP" cat-file -e "${REVIEW_SHA}^{commit}" 2>/dev/null; then
1901
+ DELTA_DIFF=$(git -C "$RD_TOP" -c core.quotepath=off diff --no-color --no-ext-diff "${DELTA_FROM}..${REVIEW_SHA}" 2>/dev/null) || DELTA_DIFF=""
1902
+ [ -n "$DELTA_DIFF" ] && FULL_DIFF="$DELTA_DIFF"
1903
+ fi
1904
+ fi
1672
1905
 
1673
1906
  # --- Security agent: always receives the full diff (cross-cutting domain) ---
1674
1907
  DIFF_SLICE_SECURITY="$FULL_DIFF"
@@ -1736,6 +1969,14 @@ DIFF_SLICE_SCRAPER=$(echo "$FULL_DIFF" | awk '
1736
1969
  in_block { print }
1737
1970
  ')
1738
1971
  [ -z "$DIFF_SLICE_SCRAPER" ] && DIFF_SLICE_SCRAPER="$FULL_DIFF"
1972
+
1973
+ # --- Spec/CLI agent (DELTA rosters only): commands/** and bin/** files ---
1974
+ DIFF_SLICE_SPEC_CLI=$(echo "$FULL_DIFF" | awk '
1975
+ /^diff --git/ { in_block=0 }
1976
+ /^diff --git a\/(commands|bin)\// { in_block=1 }
1977
+ in_block { print }
1978
+ ')
1979
+ [ -z "$DIFF_SLICE_SPEC_CLI" ] && DIFF_SLICE_SPEC_CLI="$FULL_DIFF"
1739
1980
  ```
1740
1981
 
1741
1982
  **Tool-result truncation discipline**: All `gh pr diff` and file-read outputs piped to agents MUST be capped at ~100K characters. This mirrors the runner's built-in 100K-char tool-result cap (`bin/runner.mjs`). Any diff slice exceeding 100K chars must be truncated before substitution — agents that receive oversized context perform worse, not better.
@@ -1761,6 +2002,7 @@ DIFF_SLICE_FRONTEND=$(truncate_slice "$DIFF_SLICE_FRONTEND")
1761
2002
  DIFF_SLICE_API=$(truncate_slice "$DIFF_SLICE_API")
1762
2003
  DIFF_SLICE_INFRA=$(truncate_slice "$DIFF_SLICE_INFRA")
1763
2004
  DIFF_SLICE_SCRAPER=$(truncate_slice "$DIFF_SLICE_SCRAPER")
2005
+ DIFF_SLICE_SPEC_CLI=$(truncate_slice "$DIFF_SLICE_SPEC_CLI")
1764
2006
  ```
1765
2007
 
1766
2008
  The `protocols.md` file contains the Evidence-Based Review Protocol, Structured Findings Protocol, Per-Agent Input Scoping rules, and Tool-Result Truncation Discipline. Each persona file contains that agent's full prompt template. For each agent in `$SELECTED_AGENTS` (computed in Phase 3B):
@@ -1777,7 +2019,7 @@ The `protocols.md` file contains the Evidence-Based Review Protocol, Structured
1777
2019
 
1778
2020
  **Reviewer-comment trust and shape (applies to every check below and in Phase 4)**: a reviewer comment counts only when it is from a trusted author (`scripts/trusted-comments.sh`, the same predicate as the in-PR fix gate and `verify-phase-trail.sh`: trusted association, Bot account, or `FORGE_TRAIL_TRUSTED_LOGINS`), its body **starts with** `<!-- FORGE:REVIEW-AGENT:{domain} -->`, and it carries a line `Reviewed-SHA: ${REVIEW_SHA}` for the current head. The head SHA is public, so the SHA line alone proves nothing: an untrusted commenter must never be able to suppress dispatch or satisfy the panel guard. If the trust script cannot be resolved, or its count is not an integer, the reviewer is treated as **not posted** (fail closed: re-dispatch in 3C, missing in Phase 4).
1779
2021
 
1780
- **Idempotent re-entry (before launching)**: a re-invoked review must reuse reviewers that already posted for the current head instead of re-dispatching the full panel. For each domain in `$SELECTED_AGENTS`, check for an existing trusted comment of the shape above; launch only the domains without one (possibly none), then continue to Phase 4. Comments for a different SHA never count. The selected roster for the panel guard stays the full `$SELECTED_AGENTS` set.
2022
+ **Idempotent re-entry (before launching)**: a re-invoked review must reuse reviewers that already posted for the current head instead of re-dispatching the full panel. For each domain in `$SELECTED_AGENTS`, check for an existing trusted comment of the shape above; launch only the domains without one (possibly none), then continue to Phase 4. Comments for a different SHA never count. The selected roster for the panel guard stays the full effective `$SELECTED_AGENTS` set (the one printed by the review-mode block above, not the 3B roster when `DELTA` narrowed it).
1781
2023
 
1782
2024
  ```bash
1783
2025
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
@@ -1900,7 +2142,7 @@ Reviewer completion is read from GitHub, never from notifications (see the "Revi
1900
2142
 
1901
2143
  ```bash
1902
2144
  # Each Bash call is a fresh shell: re-declare state with the orchestrator's values (never rely on Phase 3C variables).
1903
- REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; SELECTED_AGENTS="{SELECTED_AGENTS}"
2145
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; SELECTED_AGENTS="{SELECTED_AGENTS}"; REVIEW_MODE="{REVIEW_MODE}"
1904
2146
  DISPATCH_TOOL="{DISPATCH_TOOL}" # Task | Agent | task (OpenCode)
1905
2147
  case "$REVIEW_SHA" in *[!0-9a-f]*|"") REVIEW_SHA_OK=false ;; *) [ "${#REVIEW_SHA}" -eq 40 ] || [ "${#REVIEW_SHA}" -eq 64 ] && REVIEW_SHA_OK=true || REVIEW_SHA_OK=false ;; esac
1906
2148
  if [ "$REVIEW_SHA_OK" != "true" ]; then
@@ -1951,9 +2193,58 @@ TRUSTED_SCRIPT=""
1951
2193
  while IFS= read -r _c; do
1952
2194
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1953
2195
  done <<< "$_tc"
2196
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2197
+ REVIEW_DELTA_SCRIPT=""
2198
+ while IFS= read -r _c; do
2199
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2200
+ done <<< "$_tc"
2201
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1954
2202
  COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1955
2203
  MISSING_AGENT_COMMENTS=""
1956
2204
  ACTUAL_AGENT_COUNT=0
2205
+ # BEGIN review-pr-delta-mode
2206
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2207
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2208
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
2209
+ review_delta_mode() {
2210
+ local out l1 l2 kind arg from to rounds=0 top
2211
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2212
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2213
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2214
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2215
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
2216
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2217
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2218
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2219
+ kind="${l1%% *}"; arg="${l1#* }"
2220
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2221
+ if [ "$kind" = "DELTA" ]; then
2222
+ from="${arg%%..*}"; to="${arg##*..}"
2223
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2224
+ fi
2225
+ echo "FULL - $rounds"
2226
+ }
2227
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2228
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2229
+ carry_verified_from() {
2230
+ local kind from rounds n
2231
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2232
+ read -r kind from rounds <<< "$(review_delta_mode)"
2233
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2234
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
2235
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2236
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2237
+ return 0
2238
+ }
2239
+ # END review-pr-delta-mode
2240
+ # A zero-reviewer panel is acceptable only for a BASE_SYNC_ONLY head whose carry record is trusted AND re-verified by the
2241
+ # resolver right now. An empty roster with no verified record is a shortfall, never a clean panel (fail closed).
2242
+ CARRIED_FROM=""
2243
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then CARRIED_FROM=$(carry_verified_from); fi
2244
+ EMPTY_UNVERIFIED=false
2245
+ if [ "$SELECTED_AGENT_COUNT" -eq 0 ] && [ -z "$CARRIED_FROM" ]; then
2246
+ EMPTY_UNVERIFIED=true; MISSING_AGENT_COMMENTS=" (empty roster without a verified carried record)"
2247
+ fi
1957
2248
  # BEGIN review-pr-agent-domain
1958
2249
  agent_domain() {
1959
2250
  case "$(printf '%s' "${1}" | tr '[:upper:]' '[:lower:]')" in
@@ -1981,8 +2272,9 @@ for AGENT in $SELECTED_AGENTS; do
1981
2272
  done
1982
2273
 
1983
2274
  if [ -z "$MISSING_AGENT_COMMENTS" ] && [ "$ACTUAL_AGENT_COUNT" -ge "$SELECTED_AGENT_COUNT" ]; then
1984
- rm -f "$DEADLINE_FILE"; echo "PANEL_STATUS: COMPLETE (${ACTUAL_AGENT_COUNT}/${SELECTED_AGENT_COUNT})"
1985
- elif [ "$JOINED" = "true" ] || [ "$(date +%s)" -ge "$DEADLINE" ]; then
2275
+ rm -f "$DEADLINE_FILE"
2276
+ if [ -n "$CARRIED_FROM" ]; then echo "PANEL_STATUS: COMPLETE (carried from ${CARRIED_FROM}; no reviewers dispatched)"; else echo "PANEL_STATUS: COMPLETE (${ACTUAL_AGENT_COUNT}/${SELECTED_AGENT_COUNT})"; fi
2277
+ elif [ "$JOINED" = "true" ] || [ "$EMPTY_UNVERIFIED" = "true" ] || [ "$(date +%s)" -ge "$DEADLINE" ]; then
1986
2278
  rm -f "$DEADLINE_FILE"
1987
2279
  echo "REVIEW DELIVERY FAILURE: missing trusted current-SHA findings comment(s) for:${MISSING_AGENT_COMMENTS}"
1988
2280
  echo "Use each agent's returned verdict, findings, and durable body path to recover the review."
@@ -1995,17 +2287,19 @@ fi
1995
2287
 
1996
2288
  On `PANEL_STATUS: STOP`, return the printed `REVIEW_RESULT` and do nothing else (no hard stop, no verdict, no label change: the panel is intact, only the head moved).
1997
2289
 
2290
+ A `BASE_SYNC_ONLY` head has no reviewer comments to wait for. The block above accepts it only through a trusted `FORGE:REVIEW-CARRIED` record for this head whose `Carried-From-SHA` equals the reviewed SHA the resolver names right now; a missing, untrusted, forged or stale record falls into the existing hard stop (`ACTUAL_AGENT_COUNT=0`). The record never substitutes for reviewers on a `FULL` or `DELTA` head: those still need every selected reviewer's trusted current-SHA marker.
2291
+
1998
2292
  **Do NOT proceed until ALL selected agents' current-SHA comments are visible on the PR.** Under OpenCode, each foreground `task(..., background: false)` returns only when that reviewer is complete; verify its corresponding structured PR comment before continuing. A missing comment after the wait is an explicit delivery failure: do not synthesize a verdict or treat it as a clean result. Do not infer a clean review from a missing comment: a reviewer's return text may contain findings that could not be posted because GitHub writes were throttled.
1999
2293
 
2000
2294
  ---
2001
2295
 
2002
2296
  ## Phase 5: Synthesis (Multi-Agent Arbitration)
2003
2297
 
2004
- **Skip if**: Only 1 agent OR total findings ≤ 3.
2298
+ **Skip if**: Only 1 agent OR total findings ≤ 3, or `REVIEW_MODE` is `BASE_SYNC_ONLY` (no reviewer ran for this head; the carried head's synthesis, if any, stands and Phase 6A reads it). Under `DELTA` this phase synthesizes the current head's reviewers only; Phase 6A adds the earlier reviewed head's findings.
2005
2299
 
2006
2300
  ```bash
2007
2301
  # Each Bash call is a fresh shell: re-declare state with the orchestrator's values.
2008
- REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"
2302
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"
2009
2303
  case "$REVIEW_SHA" in *[!0-9a-f]*|"") REVIEW_SHA_OK=false ;; *) [ "${#REVIEW_SHA}" -eq 40 ] || [ "${#REVIEW_SHA}" -eq 64 ] && REVIEW_SHA_OK=true || REVIEW_SHA_OK=false ;; esac
2010
2304
  [ "$REVIEW_SHA_OK" = "true" ] || { echo "REVIEW_RESULT: status: BLOCKED, blocker: REVIEW_SHA is not a commit id (an empty SHA would count every head's findings)"; exit 0; }
2011
2305
  # Extract structured finding IDs from FINDING HTML comments.
@@ -2024,8 +2318,56 @@ TRUSTED_SCRIPT=""
2024
2318
  while IFS= read -r _c; do
2025
2319
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2026
2320
  done <<< "$_tc"
2321
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2322
+ REVIEW_DELTA_SCRIPT=""
2323
+ while IFS= read -r _c; do
2324
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2325
+ done <<< "$_tc"
2326
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
2027
2327
  COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
2028
- AGENT_RE="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${REVIEW_SHA}(\\r?\\n|$)"
2328
+ # BEGIN review-pr-delta-mode
2329
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2330
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2331
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
2332
+ review_delta_mode() {
2333
+ local out l1 l2 kind arg from to rounds=0 top
2334
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2335
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2336
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2337
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2338
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
2339
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2340
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2341
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2342
+ kind="${l1%% *}"; arg="${l1#* }"
2343
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2344
+ if [ "$kind" = "DELTA" ]; then
2345
+ from="${arg%%..*}"; to="${arg##*..}"
2346
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2347
+ fi
2348
+ echo "FULL - $rounds"
2349
+ }
2350
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2351
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2352
+ carry_verified_from() {
2353
+ local kind from rounds n
2354
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2355
+ read -r kind from rounds <<< "$(review_delta_mode)"
2356
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2357
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
2358
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2359
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2360
+ return 0
2361
+ }
2362
+ # END review-pr-delta-mode
2363
+ # BASE_SYNC_ONLY: no reviewer posted for this head, so the findings are the carried head's. Only a verified carry record
2364
+ # switches the SHA (the SHA comes from the validated record, never from untrusted text); an unverified claim keeps the
2365
+ # current head, finds no reviewer bodies and blocks below (fail closed).
2366
+ FINDINGS_SHA="$REVIEW_SHA"
2367
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then
2368
+ CARRIED_FROM=$(carry_verified_from); [ -n "$CARRIED_FROM" ] && FINDINGS_SHA="$CARRIED_FROM"
2369
+ fi
2370
+ AGENT_RE="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${FINDINGS_SHA}(\\r?\\n|$)"
2029
2371
  if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then
2030
2372
  echo "REVIEW_RESULT: status: BLOCKED, blocker: scripts/trusted-comments.sh unresolvable or PR comments unreadable; refusing to read reviewer findings (fail closed)"; exit 0
2031
2373
  fi
@@ -2033,7 +2375,7 @@ AGENT_BODIES=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$AG
2033
2375
  || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted reviewer comments unreadable (fail closed)"; exit 0; }
2034
2376
  ALL_FINDINGS=$(printf '%s\n' "$AGENT_BODIES" | jq -r 'scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null)
2035
2377
  # Agent bodies start with the REVIEW-AGENT marker and carry REVIEW-FINDINGS-START inside, so count it with a lookahead.
2036
- AGENT_COUNT=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "^(?=[\\s\\S]*<!-- REVIEW-FINDINGS-START -->)<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${REVIEW_SHA}(\\r?\\n|$)" 2>/dev/null || echo "")
2378
+ AGENT_COUNT=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "^(?=[\\s\\S]*<!-- REVIEW-FINDINGS-START -->)<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${FINDINGS_SHA}(\\r?\\n|$)" 2>/dev/null || echo "")
2037
2379
  case "$AGENT_COUNT" in ''|*[!0-9]*) AGENT_COUNT=0 ;; esac # fail closed
2038
2380
  FINDING_COUNT=$(echo "$ALL_FINDINGS" | grep -c '.' || echo 0)
2039
2381
  ```
@@ -2057,10 +2399,10 @@ If synthesis needed, launch a `general-purpose` Task (model: `"{SUBAGENT_MODEL}"
2057
2399
 
2058
2400
  ```bash
2059
2401
  # Each Bash call is a fresh shell: re-declare state with the orchestrator's values.
2060
- REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"
2402
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"; DELTA_FROM="{DELTA_FROM}"
2061
2403
  case "$REVIEW_SHA" in *[!0-9a-f]*|"") REVIEW_SHA_OK=false ;; *) [ "${#REVIEW_SHA}" -eq 40 ] || [ "${#REVIEW_SHA}" -eq 64 ] && REVIEW_SHA_OK=true || REVIEW_SHA_OK=false ;; esac
2062
2404
  [ "$REVIEW_SHA_OK" = "true" ] || { echo "REVIEW_RESULT: status: BLOCKED, blocker: REVIEW_SHA is not a commit id (an empty SHA would count every head's findings)"; exit 0; }
2063
- # Only TRUSTED bodies for the CURRENT head count (scripts/trusted-comments.sh + anchored marker + Reviewed-SHA line):
2405
+ # Only TRUSTED bodies for the CURRENT head count (BASE_SYNC_ONLY and DELTA add the carried / earlier reviewed head, see below) (scripts/trusted-comments.sh + anchored marker + Reviewed-SHA line):
2064
2406
  # findings from earlier heads are already fixed or re-reviewed, and an untrusted commenter must never be able to inject
2065
2407
  # findings or post a forged synthesis block that makes the review read clean (the head SHA is public).
2066
2408
  # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
@@ -2073,25 +2415,82 @@ TRUSTED_SCRIPT=""
2073
2415
  while IFS= read -r _c; do
2074
2416
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2075
2417
  done <<< "$_tc"
2418
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2419
+ REVIEW_DELTA_SCRIPT=""
2420
+ while IFS= read -r _c; do
2421
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2422
+ done <<< "$_tc"
2423
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
2076
2424
  COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
2077
2425
  if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then
2078
2426
  echo "REVIEW_RESULT: status: BLOCKED, blocker: scripts/trusted-comments.sh unresolvable or PR comments unreadable; refusing to read clean (fail closed)"; exit 0
2079
2427
  fi
2080
- SYNTH_RE="^<!-- REVIEW-FINDINGS-SYNTHESIZED-START -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${REVIEW_SHA}(\\r?\\n|$)"
2081
- AGENT_RE="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${REVIEW_SHA}(\\r?\\n|$)"
2082
- HAS_SYNTHESIS=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "$SYNTH_RE" 2>/dev/null || echo "")
2083
- case "$HAS_SYNTHESIS" in ''|*[!0-9]*) echo "REVIEW_RESULT: status: BLOCKED, blocker: synthesis count unreadable (fail closed)"; exit 0 ;; esac
2084
-
2085
- if [ "$HAS_SYNTHESIS" -gt 0 ]; then
2086
- # Extract finding IDs from the trusted current-head synthesized block using jq scan() (no PCRE grep needed)
2087
- SOURCE_BODIES=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$SYNTH_RE" 2>/dev/null) \
2088
- || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted synthesis unreadable (fail closed)"; exit 0; }
2089
- else
2090
- # Extract finding IDs from the trusted current-head agent comments using jq scan() (portable, no PCRE grep)
2091
- SOURCE_BODIES=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$AGENT_RE" 2>/dev/null) \
2092
- || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted reviewer comments unreadable (fail closed)"; exit 0; }
2093
- # The panel guard guarantees at least one trusted reviewer body for this head; none means a misread, never "clean".
2094
- [ -n "$SOURCE_BODIES" ] || { echo "REVIEW_RESULT: status: BLOCKED, blocker: no trusted reviewer comments for head ${REVIEW_SHA} (fail closed)"; exit 0; }
2428
+ # BEGIN review-pr-delta-mode
2429
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2430
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2431
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
2432
+ review_delta_mode() {
2433
+ local out l1 l2 kind arg from to rounds=0 top
2434
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2435
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2436
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2437
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2438
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
2439
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2440
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2441
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2442
+ kind="${l1%% *}"; arg="${l1#* }"
2443
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2444
+ if [ "$kind" = "DELTA" ]; then
2445
+ from="${arg%%..*}"; to="${arg##*..}"
2446
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2447
+ fi
2448
+ echo "FULL - $rounds"
2449
+ }
2450
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2451
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2452
+ carry_verified_from() {
2453
+ local kind from rounds n
2454
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2455
+ read -r kind from rounds <<< "$(review_delta_mode)"
2456
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2457
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
2458
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2459
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2460
+ return 0
2461
+ }
2462
+ # END review-pr-delta-mode
2463
+ # Trusted synthesized block for one head when present, else that head's trusted agent bodies (the same predicate as before,
2464
+ # now per head). Returns non-zero when the trust script cannot count or read.
2465
+ bodies_at() {
2466
+ local sha="${1}" synth agent has
2467
+ synth="^<!-- REVIEW-FINDINGS-SYNTHESIZED-START -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${sha}(\\r?\\n|$)"
2468
+ agent="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${sha}(\\r?\\n|$)"
2469
+ has=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "$synth" 2>/dev/null || echo "")
2470
+ case "$has" in ''|*[!0-9]*) return 1 ;; esac
2471
+ if [ "$has" -gt 0 ]; then
2472
+ printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$synth" 2>/dev/null || return 1
2473
+ else
2474
+ printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$agent" 2>/dev/null || return 1
2475
+ fi
2476
+ }
2477
+ # FULL: the current head's bodies, exactly as before. BASE_SYNC_ONLY: the head has no reviewers, so the findings are the
2478
+ # carried head's, and only a VERIFIED carry record (trusted, same head, resolver agrees) selects that SHA. DELTA: reviewers
2479
+ # saw only the delta, so the earlier fully reviewed head's findings are read too (they must still reach the Phase 8 gates);
2480
+ # the §6B.4 head-SHA and provenance gates then drop any whose cited line is gone from the current head.
2481
+ PRIMARY_SHA="$REVIEW_SHA"; EARLIER_SHA=""
2482
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then
2483
+ CARRIED_FROM=$(carry_verified_from); [ -n "$CARRIED_FROM" ] && PRIMARY_SHA="$CARRIED_FROM"
2484
+ fi
2485
+ if [ "$REVIEW_MODE" = "DELTA" ] && is_sha "$DELTA_FROM"; then EARLIER_SHA="$DELTA_FROM"; fi
2486
+ SOURCE_BODIES=$(bodies_at "$PRIMARY_SHA") \
2487
+ || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted reviewer comments unreadable (fail closed)"; exit 0; }
2488
+ # The panel guard guarantees at least one trusted reviewer body for this head; none means a misread, never "clean".
2489
+ [ -n "$SOURCE_BODIES" ] || { echo "REVIEW_RESULT: status: BLOCKED, blocker: no trusted reviewer comments for head ${PRIMARY_SHA} (fail closed)"; exit 0; }
2490
+ if [ -n "$EARLIER_SHA" ]; then
2491
+ EARLIER_BODIES=$(bodies_at "$EARLIER_SHA") && [ -n "$EARLIER_BODIES" ] \
2492
+ || { echo "REVIEW_RESULT: status: BLOCKED, blocker: earlier reviewed head ${EARLIER_SHA} findings unreadable (fail closed)"; exit 0; }
2493
+ SOURCE_BODIES=$(printf '%s\n%s' "$SOURCE_BODIES" "$EARLIER_BODIES")
2095
2494
  fi
2096
2495
  FINDINGS=$(printf '%s\n' "$SOURCE_BODIES" | jq -r 'scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null)
2097
2496
  ```
@@ -2161,7 +2560,8 @@ Filing a standalone `review-finding` issue for every LOW/POSSIBLE reviewer note
2161
2560
  - **Safety exemption is content-based** (it bypasses only this severity damper, never the §6B.4 provenance gate: a pre-existing auth gap is flagged through the pre-existing route, not attributed to this PR): a LOW or POSSIBLE finding is still filed when its file path / title / body matches the security/billing keyword set — a LOW finding additionally needs `**Confidence**: CONFIRMED`, so LOW LIKELY/POSSIBLE with a keyword is a NOTE (`security|auth|authz|authn|billing|payment|stripe|charge|invoice|injection|xss|csrf|ssrf|idor|secret(s)|credential(s)|permission(s)|sql|token|password|redact`, whole words, `_`/`-`/`/` separate words — so `auth_service` matches but `author`/`tokenizer` do not), or when it came from a dedicated, signal-selected domain agent (Auth, Billing, Concurrency, Database) AND is MEDIUM — a domain agent rescues MEDIUM only, and a LOW finding stays a NOTE whichever reviewer raised it, CONFIRMED or not. **Origin from the always-on General Security & Quality agent alone does NOT exempt a finding**: that agent runs on every PR, so origin-based exemption filed nearly every LOW note it raised (the 2026-10-08 cascade audit: 50 of 60 would-be notes in one batch were filed only for that reason).
2162
2561
  - **Review-finding lineage** (the PR's linked issue `MERGE_ISSUE` carries the `review-finding` label, any priority — this PR is itself a fix for a finding): only MEDIUM findings that are CONFIRMED, or LIKELY and safety-exempt, become issues; LOW and POSSIBLE are always NOTEs. A fix for a finding must not mint a new generation of findings.
2163
2562
  - **Otherwise**: `**Severity**: LOW`, or `**Confidence**: POSSIBLE` below HIGH, is a NOTE unless safety-exempt (LOW: keyword AND CONFIRMED; MEDIUM: keyword at any confidence, or a domain agent); everything else (MEDIUM CONFIRMED/LIKELY) is an ISSUE.
2164
- - **Contract-declared scope** <!-- Added: forge#3447 -->: the trusted, latest FORGE:CONTRACT of `MERGE_ISSUE` lists out-of-scope items as `deferred → #N`, `not-affected:` or `accepted-risk:` (parsed by `scripts/check-contract-scope.sh list`). A finding whose file equals, or sits under, a `deferred` item whose issue is verified becomes `NOTE contract-deferred #N` and is commented on that issue instead of being filed again. A finding under an `accepted-risk` item becomes `NOTE contract-accepted-risk`. A deferred issue is verified only when it is OPEN, is an issue (not a PR), is not `MERGE_ISSUE` itself, and its body carries `FORGE:DEFERRED_FROM: #<MERGE_ISSUE>` and the deferred path; an unverified item is dropped from the scope list. Never demoted: HIGH/CRITICAL, `not-affected` items (a contradicting finding means the contract was wrong), safety-exempt findings under either disposition, findings on a file this PR changed, and anything when the contract is missing, untrusted, unparseable or the deferred issue is closed, unverified or unreadable.
2563
+ - **Cross-head re-flag** <!-- Added: forge#3641 -->: a finding that an earlier head's review already posted, or that an earlier `FORGE:NOTE_DISPOSITION` list recorded, is not filed or noted a second time after its line moved. The caller builds a prior-findings TSV (file, line-start, line-end, title, prior-disposition, issue#) from trusted prior-head reviewer comments (`Reviewed-SHA` other than the current head) and trusted `FORGE:NOTE_DISPOSITION` records carrying a different `reviewed_sha=`, all read through `scripts/trusted-comments.sh`, and passes it as `--prior-findings`. A finding re-flags a row when it is the same file AND (line IoU >= 0.6 after a best-effort `git diff` hunk-offset shift AND >= 1 shared non-stopword title token, OR >= 3 shared title tokens of which >= 2 are non-stopword; generic stopwords such as `the`, `with`, `before`, `write` never count as topical similarity) and becomes `NOTE reflag-filed #N` (the prior row's issue is still open) or `NOTE reflag-note`. Never demoted: HIGH/CRITICAL and unparseable severity (the classifier exits on them before this gate, so they keep blocking via §7B), and any row whose linked issue is closed (a regressed defect is classified normally). Prior rows with no issue link are passed as `note` (recorded NOTE_DISPOSITION) or `unknown` (reviewer-body finding whose linkage failed; the classifier never demotes a MEDIUM CONFIRMED/LIKELY re-flag against an `unknown` row); an unreadable source only shrinks the TSV, it never blocks.
2564
+ - **Contract-declared scope** <!-- Added: forge#3447 -->: the trusted, latest FORGE:CONTRACT of `MERGE_ISSUE` lists out-of-scope items as `deferred → #N`, `not-affected:` or `accepted-risk:` (parsed by `scripts/check-contract-scope.sh list`). A finding whose file equals, or sits under, a `deferred` item whose issue is verified becomes `NOTE contract-deferred #N` and is commented on that issue instead of being filed again. A finding under an `accepted-risk` item becomes `NOTE contract-accepted-risk`. A deferred issue is verified only when it is OPEN, is an issue (not a PR), is not `MERGE_ISSUE` itself, and its body carries `FORGE:DEFERRED_FROM: #<MERGE_ISSUE>` and the deferred path, and it is authored by a trusted author (`author_association` OWNER/MEMBER/COLLABORATOR, or `user.type` Bot); an unverified item is dropped from the scope list. Never demoted: HIGH/CRITICAL, `not-affected` items (a contradicting finding means the contract was wrong), safety-exempt findings under either disposition, findings on a file this PR changed, and anything when the contract is missing, untrusted, unparseable or the deferred issue is closed, unverified or unreadable.
2165
2565
 
2166
2566
  ```bash
2167
2567
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
@@ -2212,7 +2612,7 @@ fi
2212
2612
  # issue still open) or `accepted-risk:` become NOTEs. `not-affected` items never demote. Fail toward
2213
2613
  # filing: any missing input (no linked issue, no trusted contract, unparseable section, unreadable
2214
2614
  # PR file list) leaves CONTRACT_SCOPE_FILE empty and nothing demotes. A deferred row whose issue fails
2215
- # verification (closed, unreadable, a PR, MERGE_ISSUE itself, or no DEFERRED_FROM marker + path) is dropped.
2615
+ # verification (closed, unreadable, a PR, MERGE_ISSUE itself, no DEFERRED_FROM marker + path, or not authored by a trusted author) is dropped.
2216
2616
  CONTRACT_SCOPE_FILE=""; CONTRACT_OPEN_FILE=""; PR_FILES_FILE=""; CONTRACT_DEMOTED=0
2217
2617
  if [ -n "${MERGE_ISSUE:-}" ]; then
2218
2618
  # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
@@ -2242,7 +2642,9 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2242
2642
  && [ -s "$CONTRACT_SCOPE_FILE" ] \
2243
2643
  && gh pr diff {PR_NUMBER} -R {GH_REPO} --name-only > "$PR_FILES_FILE" 2>/dev/null; then
2244
2644
  # A deferred row counts only if issue N is OPEN, is an issue (not a PR), is not MERGE_ISSUE, and its body
2245
- # carries `FORGE:DEFERRED_FROM: #<MERGE_ISSUE>` plus the row's path. Unverified rows are dropped.
2645
+ # carries `FORGE:DEFERRED_FROM: #<MERGE_ISSUE>` plus the row's path, and is authored by a trusted author
2646
+ # (OWNER/MEMBER/COLLABORATOR or Bot; same predicate as trusted-comments.sh; missing fields fail closed).
2647
+ # Unverified rows are dropped.
2246
2648
  _verified=$(mktemp "$_scratch/{PR_NUMBER}_contract-verified.XXXXXX")
2247
2649
  while IFS="$(printf '\t')" read -r _disp _ipath _n; do
2248
2650
  if [ "$_disp" = "deferred" ]; then
@@ -2250,7 +2652,7 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2250
2652
  [ "$_n" = "${MERGE_ISSUE#\#}" ] && continue
2251
2653
  gh api "repos/{GH_REPO}/issues/$_n" 2>/dev/null \
2252
2654
  | jq -e --arg m "FORGE:DEFERRED_FROM: #${MERGE_ISSUE#\#} -->" --arg p "$_ipath" \
2253
- '.state == "open" and .pull_request == null and ((.body // "") | contains($m) and contains($p))' >/dev/null 2>&1 \
2655
+ '.state == "open" and .pull_request == null and ((.body // "") | contains($m) and contains($p)) and (((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR")) or ((.user.type // "") == "Bot"))' >/dev/null 2>&1 \
2254
2656
  || continue
2255
2657
  grep -Fxq -- "$_n" "$CONTRACT_OPEN_FILE" || echo "$_n" >> "$CONTRACT_OPEN_FILE"
2256
2658
  fi
@@ -2263,18 +2665,113 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2263
2665
  rm -f "$CONTRACT_BODY_FILE"
2264
2666
  fi
2265
2667
  fi
2668
+ PRIOR_FILE=""; DIFF_MAP_FILE=""; NOTES_REFLAG=0
2669
+ _pscr="${FORGE_SCRATCHPAD:-$PWD/.forge-scratch}"; mkdir -p "$_pscr"
2670
+ REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"
2671
+ if [ -z "${TRUSTED_SCRIPT:-}" ]; then
2672
+ # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
2673
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
2674
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
2675
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
2676
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
2677
+ done
2678
+ TRUSTED_SCRIPT=""
2679
+ while IFS= read -r _c; do
2680
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2681
+ done <<< "$_tc"
2682
+ fi
2683
+ # Cross-head re-flag input (#3641). Fail soft: every unreadable source just leaves the TSV smaller, never blocks the review.
2684
+ # PF_SKIP="<sha or empty>" PF_PRS="<pr numbers>" prior_findings_tsv prints file<TAB>line-start<TAB>line-end<TAB>title<TAB>disposition<TAB>issue# rows
2685
+ # (disposition: filed = linked issue OPEN, filed-closed = linked issue CLOSED, note = a recorded NOTE_DISPOSITION with no linked issue, unknown = a reviewer-body finding whose issue linkage failed, so the classifier must not let it demote a MEDIUM CONFIRMED/LIKELY re-flag). Every comment is read through
2686
+ # scripts/trusted-comments.sh, so an untrusted commenter cannot pre-post a finding to suppress a real one. With PF_SKIP set, reviewer
2687
+ # bodies for that head are skipped and only NOTE_DISPOSITION records carrying a different `reviewed_sha=` count (this head's own record,
2688
+ # from an earlier run of this same review, must never swallow its findings).
2689
+ pf_split_loc() { # PF_LOC="file[:N[-M]]" -> file<TAB>N<TAB>M (`-` sentinel when a line column is absent: a tab IFS read collapses empty fields)
2690
+ local _loc="$PF_LOC" _file _r _a="" _b=""; _file="$_loc"
2691
+ case "$_loc" in *:[0-9]*) _r="${_loc##*:}"; case "$_r" in *[!0-9-]*|"") ;; *) _file="${_loc%:*}"; _a="${_r%%-*}"; _b="${_r#*-}"; [ "$_b" != "$_r" ] || _b="$_a" ;; esac ;; esac
2692
+ printf '%s\t%s\t%s\n' "$_file" "${_a:--}" "${_b:--}"
2693
+ }
2694
+ prior_findings_tsv() {
2695
+ local pr cj rows issues _id _c _s _loc _t _p _hit _f _a _b _src _dflt
2696
+ [ -n "${TRUSTED_SCRIPT:-}" ] || return 0
2697
+ rows=$(mktemp "${_pscr}/prior-rows.XXXXXX"); issues=$(mktemp "${_pscr}/prior-issues.XXXXXX")
2698
+ for pr in $PF_PRS; do
2699
+ case "$pr" in ''|*[!0-9]*) continue ;; esac
2700
+ cj=$(gh api --paginate "repos/${REPO}/issues/${pr}/comments" 2>/dev/null) || continue
2701
+ [ -n "$cj" ] || continue
2702
+ printf '%s' "$cj" | bash "$TRUSTED_SCRIPT" bodies '^<!-- (FORGE:REVIEW-AGENT:[a-z-]+|REVIEW-FINDINGS-SYNTHESIZED-START) -->[\s\S]*(^|\n)Reviewed-SHA: [0-9a-f]{40,64}' 2>/dev/null \
2703
+ | jq -r --arg skip "${PF_SKIP:-}" 'select($skip == "" or (test("(^|\\n)Reviewed-SHA: " + $skip + "\\r?(\\n|$)") | not)) | scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null \
2704
+ | while IFS='|' read -r _id _c _s _loc _t; do
2705
+ [ -n "$_t" ] || continue
2706
+ _p=$(PF_LOC="$_loc" pf_split_loc); printf 'F\t%s\t%s\n' "$_p" "$(printf '%s' "$_t" | tr '\t' ' ')"
2707
+ done >> "$rows"
2708
+ printf '%s' "$cj" | bash "$TRUSTED_SCRIPT" bodies '^<!-- FORGE:NOTE_DISPOSITION' 2>/dev/null \
2709
+ | jq -r --arg skip "${PF_SKIP:-}" 'select($skip == "" or ((capture("^<!-- FORGE:NOTE_DISPOSITION:[^>]*reviewed_sha=(?<s>[0-9a-f]{40,64})") | .s) as $s | $s != $skip)) | split("\n")[] | select(startswith("- "))' 2>/dev/null \
2710
+ | sed -n 's/^- [^ ]* \([^ ]*\) \[[A-Za-z?]*\] \(.*\): [^:]*$/\1\t\2/p' \
2711
+ | while IFS="$(printf '\t')" read -r _loc _t; do
2712
+ [ "$_loc" != "?" ] || continue
2713
+ _p=$(PF_LOC="$_loc" pf_split_loc); printf 'N\t%s\t%s\n' "$_p" "$_t"
2714
+ done >> "$rows"
2715
+ gh issue list -R "$REPO" --label review-finding --state all --search "${pr} in:body" --limit 100 --json number,state,title,body 2>/dev/null \
2716
+ | jq -c '.[]' >> "$issues" 2>/dev/null || true
2717
+ done
2718
+ # Linkage: a filed issue whose body names the file and whose title shares >= 3 tokens with the finding (open preferred).
2719
+ # Rows carry a leading source column (F = reviewer FINDING marker, N = NOTE_DISPOSITION record), stripped on output. An unlinked F row may
2720
+ # have been filed under a reworded title, so it is `unknown` (never demotes MEDIUM CONFIRMED/LIKELY); an unlinked N row is a true `note`.
2721
+ sort -u "$rows" | while IFS="$(printf '\t')" read -r _src _f _a _b _t; do
2722
+ [ -n "$_f" ] || continue
2723
+ [ "$_a" != "-" ] || _a=""; [ "$_b" != "-" ] || _b=""
2724
+ case "$_src" in N) _dflt=note ;; *) _dflt=unknown ;; esac
2725
+ _hit=$(jq -rs --arg f "$_f" --arg t "$_t" 'def toks: ascii_downcase | [scan("[a-z0-9]{3,}")] | unique;
2726
+ ($t | toks) as $a | [ .[] | select((.body // "") | contains($f))
2727
+ | select((((.title // "") | toks) as $b | [$a[] | select(. as $x | $b | index($x))] | length) >= 3)
2728
+ | {n: .number, s: (.state | ascii_downcase)} ]
2729
+ | (map(select(.s == "open")) + map(select(.s != "open"))) | .[0] // empty | "\(.s)\t\(.n)"' "$issues" 2>/dev/null) || _hit=""
2730
+ case "$_hit" in
2731
+ open*) printf '%s\t%s\t%s\t%s\tfiled\t%s\n' "$_f" "$_a" "$_b" "$_t" "${_hit#* }" ;;
2732
+ closed*) printf '%s\t%s\t%s\t%s\tfiled-closed\t%s\n' "$_f" "$_a" "$_b" "$_t" "${_hit#* }" ;;
2733
+ *) printf '%s\t%s\t%s\t%s\t%s\t\n' "$_f" "$_a" "$_b" "$_t" "$_dflt" ;;
2734
+ esac
2735
+ done
2736
+ rm -f "$rows" "$issues"
2737
+ }
2738
+ PRIOR_ROWS=$(PF_SKIP="$REVIEW_SHA" PF_PRS="{PR_NUMBER}" prior_findings_tsv)
2739
+ if [ -n "$PRIOR_ROWS" ]; then
2740
+ PRIOR_FILE=$(mktemp "$_pscr/{PR_NUMBER}_prior-findings.XXXXXX"); printf '%s\n' "$PRIOR_ROWS" > "$PRIOR_FILE"
2741
+ # Best-effort hunk map from the most recent prior reviewed head to this one, so a moved line still overlaps. Any failure leaves the unshifted ranges.
2742
+ PRIOR_SHA=$(gh api --paginate "repos/{GH_REPO}/issues/{PR_NUMBER}/comments" 2>/dev/null \
2743
+ | bash "$TRUSTED_SCRIPT" bodies '^<!-- (FORGE:REVIEW-AGENT:[a-z-]+|REVIEW-FINDINGS-SYNTHESIZED-START) -->[\s\S]*(^|\n)Reviewed-SHA: [0-9a-f]{40,64}' 2>/dev/null \
2744
+ | jq -r 'scan("(^|\\n)Reviewed-SHA: ([0-9a-f]{40,64})") | .[1]' 2>/dev/null | grep -vx "$REVIEW_SHA" | tail -n 1)
2745
+ _top=$(git rev-parse --show-toplevel 2>/dev/null) || _top=""
2746
+ if [ -n "$PRIOR_SHA" ] && [ -n "$_top" ]; then
2747
+ DIFF_MAP_FILE=$(mktemp "$_pscr/{PR_NUMBER}_prior-diffmap.XXXXXX")
2748
+ { git -C "$_top" diff -U0 --no-color --no-renames "$PRIOR_SHA" "$REVIEW_SHA" 2>/dev/null || true; } | while IFS= read -r _l; do
2749
+ case "$_l" in
2750
+ "+++ b/"*) _f="${_l#+++ b/}" ;;
2751
+ "@@ "*) _h="${_l#@@ -}"; _o="${_h%% *}"; _n="${_h#* +}"; _n="${_n%% *}"
2752
+ case "$_o" in *,*) _oc="${_o#*,}" ;; *) _oc=1 ;; esac; case "$_n" in *,*) _nc="${_n#*,}" ;; *) _nc=1 ;; esac
2753
+ printf '%s\t%s\t%s\t%s\t%s\n' "$_f" "${_o%%,*}" "$_oc" "${_n%%,*}" "$_nc" ;;
2754
+ esac
2755
+ done > "$DIFF_MAP_FILE" || : > "$DIFF_MAP_FILE"
2756
+ fi
2757
+ fi
2266
2758
  # Per deduped finding (FINDING_TEXT_FILE = mktemp file holding title + body + affected paths;
2267
2759
  # FINDING_FILE = the finding's file path, file:line accepted):
2268
2760
  # DISPOSITION=$(bash "$CLASSIFY_SCRIPT" --severity "$FINDING_SEVERITY" --confidence "$FINDING_CONFIDENCE" \
2269
2761
  # --agent "$FINDING_AGENT" --lineage "$FINDING_LINEAGE" --text-file "$FINDING_TEXT_FILE" \
2270
2762
  # ${FINDING_FILE:+--file "$FINDING_FILE"} ${INPR_DIFF_FILE:+--inpr-diff "$INPR_DIFF_FILE"} \
2271
- # ${CONTRACT_SCOPE_FILE:+--contract-scope "$CONTRACT_SCOPE_FILE" --contract-open "$CONTRACT_OPEN_FILE" --pr-files "$PR_FILES_FILE" --merge-issue "$MERGE_ISSUE"})
2763
+ # ${CONTRACT_SCOPE_FILE:+--contract-scope "$CONTRACT_SCOPE_FILE" --contract-open "$CONTRACT_OPEN_FILE" --pr-files "$PR_FILES_FILE" --merge-issue "$MERGE_ISSUE"} \
2764
+ # ${PRIOR_FILE:+--prior-findings "$PRIOR_FILE" --title "$FINDING_TITLE" ${DIFF_MAP_FILE:+--diff-map "$DIFF_MAP_FILE"}} \
2765
+ # ${FINDING_LINE_START:+--line-start "$FINDING_LINE_START" --line-end "${FINDING_LINE_END:-$FINDING_LINE_START}"})
2272
2766
  # case "$DISPOSITION" in ISSUE*) ;; NOTE*) ;; INPR_FIX*) ;; esac # first word is the class, the rest is the reason
2273
2767
  # Contract-matched NOTE (`NOTE contract-deferred #N`): comment the finding (file:line + title) on the open
2274
2768
  # deferred issue instead of filing a new one, and count it:
2275
2769
  # gh issue comment N -R {GH_REPO} --body "Review of #{PR_NUMBER} also found this on a path declared deferred here: <file:line> <title>" # allowlist:check-command-side-effects
2276
2770
  # CONTRACT_DEMOTED=$((CONTRACT_DEMOTED + 1))
2277
2771
  # `NOTE contract-accepted-risk` is counted in CONTRACT_DEMOTED too (no comment; the contract already records the reason).
2772
+ # Re-flag NOTE (`NOTE reflag-filed #N` / `NOTE reflag-note`): an earlier head already raised or recorded this finding. List it in the NOTE list
2773
+ # (`tracked on #N` or `already noted on an earlier head`), file nothing, and count it ONLY in notes_reflag (not notes_listed):
2774
+ # NOTES_REFLAG=$((NOTES_REFLAG + 1))
2278
2775
  ```
2279
2776
 
2280
2777
  If the guard printed `REVIEW_RESULT: status: BLOCKED` (`classifier unavailable`), STOP the whole review: `exit 0` only ends that Bash block, so do not continue to 6C or any later phase, file no issues, post no `FORGE:NOTE_DISPOSITION`, and do not merge (Phase 8 never runs, `--auto-merge` included). The caller treats it like any other `BLOCKED` result.
@@ -2286,14 +2783,15 @@ Each NOTE gets exactly one disposition, in this preference order:
2286
2783
 
2287
2784
  **Metrics note (forge#3106)**: because NOTEs are not filed as issues, `review-finding` issue volume and any metric derived from it (findings per PR, `/pipeline-health` finding rates, amplification ratio) drop relative to pre-#3060 history. Compare against the `notes_*` counts in the review summary, not issue counts alone, when judging review depth across the change.
2288
2785
 
2289
- Every NOTE disposition must be recorded (never silently dropped): list each NOTE (id, file:line, one-line reason) with its disposition in the review summary comment, including dropped ones, so a reviewer can audit the decision. NOTES are never passed to `Skill(issue)`. Record counts in the review summary (`notes_fixed`, `notes_listed`, `notes_dropped`, `findings_filed`, `inpr_fix`, `contract_demoted`) and the §6B.4 counts (`findings_dropped_stale`, `findings_preexisting`, `notes_demoted_no_scenario`). A contract-demoted NOTE is listed with its disposition (`tracked on #N` or `accepted-risk`) and counted only in `contract_demoted`. If no finding is an ISSUE after this step, skip 6C and continue to Phase 7. Findings classified `INPR_FIX` (§6B.6) are neither notes nor filed this round.
2786
+ Every NOTE disposition must be recorded (never silently dropped): list each NOTE (id, file:line, one-line reason) with its disposition in the review summary comment, including dropped ones, so a reviewer can audit the decision. NOTES are never passed to `Skill(issue)`. Record counts in the review summary (`notes_fixed`, `notes_listed`, `notes_dropped`, `findings_filed`, `inpr_fix`, `contract_demoted`, `notes_reflag`) and the §6B.4 counts (`findings_dropped_stale`, `findings_preexisting`, `notes_demoted_no_scenario`). A contract-demoted NOTE is listed with its disposition (`tracked on #N` or `accepted-risk`) and counted only in `contract_demoted`. If no finding is an ISSUE after this step, skip 6C and continue to Phase 7. Findings classified `INPR_FIX` (§6B.6) are neither notes nor filed this round.
2290
2787
 
2291
- **Disposition marker (MANDATORY whenever Phase 6A extracted at least one finding):** post one PR comment whose first line is the machine-readable record below, followed by the NOTE list. Phase 8 refuses to auto-merge a PR that has `FINDING:` markers but no `FORGE:NOTE_DISPOSITION` comment — the disposition step is enforced, not advisory.
2788
+ **Disposition marker (MANDATORY whenever Phase 6A extracted at least one finding):** post one PR comment whose first line is the machine-readable record below, followed by the NOTE list. Phase 8 refuses to auto-merge a PR that has `FINDING:` markers but no `FORGE:NOTE_DISPOSITION` comment — the disposition step is enforced, not advisory. `notes_reflag` counts findings re-flagged from an earlier head (see Cross-head re-flag); `reviewed_sha` is the head this record covers. Consumers match the marker prefix only, so extra keys are safe.
2292
2789
 
2293
2790
  ```bash
2294
2791
  CLASSIFIER_MODE=script # the §6B.5 guard blocks the review when the classifier is unresolvable, so this is always script
2792
+ REVIEW_SHA="{REVIEW_SHA}"; NOTES_REFLAG="${NOTES_REFLAG:-0}" # fresh shell: reviewed_sha lets a later review of ANOTHER head read this record as a prior list (never the same head's own)
2295
2793
  # NOTE_LIST_FILE: mktemp file named for the PR number, holding one line per NOTE (id, file:line, disposition, reason).
2296
- gh pr comment {PR_NUMBER} -R {GH_REPO} --body "<!-- FORGE:NOTE_DISPOSITION: notes_fixed=${NOTES_FIXED:-0} notes_listed=${NOTES_LISTED:-0} notes_dropped=${NOTES_DROPPED:-0} findings_filed=${FINDINGS_FILED:-0} inpr_fix=${INPR_FIX_COUNT:-0} contract_demoted=${CONTRACT_DEMOTED:-0} findings_dropped_stale=${FINDINGS_DROPPED_STALE:-0} findings_preexisting=${FINDINGS_PREEXISTING:-0} notes_demoted_no_scenario=${NOTES_DEMOTED_NO_SCENARIO:-0} lineage=${FINDING_LINEAGE} classifier=${CLASSIFIER_MODE} -->
2794
+ gh pr comment {PR_NUMBER} -R {GH_REPO} --body "<!-- FORGE:NOTE_DISPOSITION: notes_fixed=${NOTES_FIXED:-0} notes_listed=${NOTES_LISTED:-0} notes_dropped=${NOTES_DROPPED:-0} findings_filed=${FINDINGS_FILED:-0} inpr_fix=${INPR_FIX_COUNT:-0} contract_demoted=${CONTRACT_DEMOTED:-0} findings_dropped_stale=${FINDINGS_DROPPED_STALE:-0} findings_preexisting=${FINDINGS_PREEXISTING:-0} notes_demoted_no_scenario=${NOTES_DEMOTED_NO_SCENARIO:-0} lineage=${FINDING_LINEAGE} classifier=${CLASSIFIER_MODE} notes_reflag=${NOTES_REFLAG:-0} reviewed_sha=${REVIEW_SHA} -->
2297
2795
  $(cat "$NOTE_LIST_FILE" 2>/dev/null)" # allowlist:check-command-side-effects
2298
2796
  ```
2299
2797
 
@@ -2351,7 +2849,19 @@ MILESTONE_FLAG=""
2351
2849
  # milestone via Closes/Fixes/Resolves #N -> branch-slug match against
2352
2850
  # milestone/* branches) supersedes the old base/head-branch-only slug match
2353
2851
  # that used to be hand-rolled here.
2354
- MILESTONE_TITLE=$(bash scripts/derive-finding-milestone.sh "${PR_NUMBER}" -R "${REPO}")
2852
+ # HELPER_SCRIPT resolver (DERIVE_MILESTONE_SCRIPT; trusted-install tiers only, never the working directory or the target repo, #3400/#3483): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
2853
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
2854
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
2855
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
2856
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
2857
+ done
2858
+ DERIVE_MILESTONE_SCRIPT=""
2859
+ while IFS= read -r _c; do
2860
+ case "$_c" in /*) [ -z "$DERIVE_MILESTONE_SCRIPT" ] && [ -f "$_c/scripts/derive-finding-milestone.sh" ] && DERIVE_MILESTONE_SCRIPT="$_c/scripts/derive-finding-milestone.sh" ;; esac
2861
+ done <<< "$_tc"
2862
+ # Unresolved helper: empty milestone (non-blocking), never a cwd-relative fallback.
2863
+ MILESTONE_TITLE=""
2864
+ [ -n "$DERIVE_MILESTONE_SCRIPT" ] && MILESTONE_TITLE=$(bash "$DERIVE_MILESTONE_SCRIPT" "${PR_NUMBER}" -R "${REPO}")
2355
2865
  # Quoted so multi-word milestone titles (e.g. "Watch & Fleet Observability") survive
2356
2866
  # as a single value wherever MILESTONE_FLAG is later interpolated — matches the
2357
2867
  # quoting convention already used for --title/--body-file/--label below.
@@ -2370,8 +2880,23 @@ PR_NUMBER="{PR_NUMBER}"; MERGE_ISSUE="{MERGE_ISSUE}"
2370
2880
  FINDING_TITLE_DEDUP="fix: brief description of finding (review finding — PR #${PR_NUMBER})"
2371
2881
  # Never dedup against the issue this PR fixes (MERGE_ISSUE): it is about to be closed, so a
2372
2882
  # finding "matched" to it is tracked nowhere. Field test #3169: SEC-2 matched #3169 itself and was dropped.
2373
- DEDUP_RESULT=$(scripts/issue-dedup.sh "$FINDING_TITLE_DEDUP" "$GH_FLAG" ${MERGE_ISSUE:+--exclude "$MERGE_ISSUE"} 2>&1)
2374
- DEDUP_EXIT=$?
2883
+ # HELPER_SCRIPT resolver (DEDUP_SCRIPT; trusted-install tiers only, never the working directory or the target repo, #3400/#3483): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
2884
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
2885
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
2886
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
2887
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
2888
+ done
2889
+ DEDUP_SCRIPT=""
2890
+ while IFS= read -r _c; do
2891
+ case "$_c" in /*) [ -z "$DEDUP_SCRIPT" ] && [ -f "$_c/scripts/issue-dedup.sh" ] && DEDUP_SCRIPT="$_c/scripts/issue-dedup.sh" ;; esac
2892
+ done <<< "$_tc"
2893
+ # Unresolved helper: "not deduped" (exit 0, empty result) so the line-range check still runs; NEVER exit 1 (which means "duplicate, skip").
2894
+ if [ -n "$DEDUP_SCRIPT" ]; then
2895
+ DEDUP_RESULT=$(bash "$DEDUP_SCRIPT" "$FINDING_TITLE_DEDUP" "$GH_FLAG" ${MERGE_ISSUE:+--exclude "$MERGE_ISSUE"} 2>&1)
2896
+ DEDUP_EXIT=$?
2897
+ else
2898
+ DEDUP_RESULT=""; DEDUP_EXIT=0
2899
+ fi
2375
2900
  if [ "$DEDUP_EXIT" -eq 1 ]; then
2376
2901
  echo "DEDUP: Skipping — $DEDUP_RESULT"
2377
2902
  # Skip this finding — do NOT create a duplicate issue
@@ -2607,8 +3132,23 @@ FINDING_SEVERITY="LOW"
2607
3132
  # (CONFIRMED/LIKELY/POSSIBLE). scripts/severity-to-priority.sh is the single
2608
3133
  # source of truth for this mapping; commands/review-pr-staging.md calls the
2609
3134
  # identical script so the two specs cannot independently drift. <!-- forge#2447 -->
2610
- FINDING_PRIORITY=$(bash scripts/severity-to-priority.sh "$FINDING_SEVERITY")
2611
- FINDING_PRIORITY_EXIT=$?
3135
+ # HELPER_SCRIPT resolver (PRIORITY_SCRIPT; trusted-install tiers only, never the working directory or the target repo, #3400/#3483): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
3136
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
3137
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
3138
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
3139
+ [ -n "$_cfg" ] && _tc="$_tc"$'\n'"$(find -L "$_cfg/plugins/cache/forgedock/forgedock" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '$NF ~ /^[0-9]+\.[0-9]+\.[0-9]+$/{split($NF,a,".");printf "%d %d %d %s\n",a[1],a[2],a[3],$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr | cut -d' ' -f4- || true)"
3140
+ done
3141
+ PRIORITY_SCRIPT=""
3142
+ while IFS= read -r _c; do
3143
+ case "$_c" in /*) [ -z "$PRIORITY_SCRIPT" ] && [ -f "$_c/scripts/severity-to-priority.sh" ] && PRIORITY_SCRIPT="$_c/scripts/severity-to-priority.sh" ;; esac
3144
+ done <<< "$_tc"
3145
+ # Unresolved helper: non-zero status so finding creation is skipped (never defaulted to a priority).
3146
+ if [ -n "$PRIORITY_SCRIPT" ]; then
3147
+ FINDING_PRIORITY=$(bash "$PRIORITY_SCRIPT" "$FINDING_SEVERITY")
3148
+ FINDING_PRIORITY_EXIT=$?
3149
+ else
3150
+ FINDING_PRIORITY=""; FINDING_PRIORITY_EXIT=127
3151
+ fi
2612
3152
 
2613
3153
  # Exit code MUST be checked before use — severity-to-priority.sh exits 1 (empty stdout)
2614
3154
  # on a missing/unrecognized severity, mirroring the DEDUP_EXIT idiom above. Proceeding
@@ -3167,7 +3707,7 @@ else
3167
3707
  # forge#2570: the hold is now scoped by the PR's target branch. This PR still transitions
3168
3708
  # to workflow:awaiting-merge (clearing needs-human) in BOTH cases below — the transition is
3169
3709
  # unchanged; only who acts next differs:
3170
- # - `main` / deploy gate (or an unresolved base — fail closed): held for a HUMAN merge
3710
+ # - the deploy gate (`main`, the GitHub `default_branch`, or the default branch's committed `branches.default`; never read from the PR worktree) (or an unresolved base — fail closed): held for a HUMAN merge
3171
3711
  # decision, because `staging → main` is the genuine human gate no agent performs.
3172
3712
  # - any other base (staging, milestone/* — reversible integration branches): NOT held for
3173
3713
  # a human. It is parked for remediate.md Phase M7's base-scoped auto-land bar (bot APPROVED
@@ -3183,15 +3723,38 @@ if [ "$PREVIOUSLY_ESCALATED" = "true" ]; then
3183
3723
  # forge#2625: `jq -r '.baseRefName'` stringifies a JSON null to the literal text "null" (not
3184
3724
  # an empty string), so the `-n` check alone does not catch it — add an explicit `!= "null"`
3185
3725
  # check so a literal-string "null" is treated the same as an empty/unresolved base (strict).
3186
- if [ -n "$GUARD_BASE" ] && [ "$GUARD_BASE" != "null" ] && [ "$GUARD_BASE" != "main" ]; then
3187
- # forge#2570: non-`main` base — defer to remediate.md M7's auto-land, not a human.
3726
+ # forge#3753: trusted resolver only (GitHub default_branch + committed default-branch config); never forge.yaml from $PWD (the PR worktree is author-controlled). Install roots only; unresolved script => strict.
3727
+ # FORGE_ROOT bootstrap (canonical; keep byte-identical across specs, guarded by scripts/forge-root.test.sh)
3728
+ FORGE_ROOT=""
3729
+ # Windows drive-letter FORGEDOCK_HOME (C:/x or C:\x) is normalized to /c/x (cygpath when present); relative values stay rejected.
3730
+ _h="${FORGEDOCK_HOME:-}"; case "$_h" in [A-Za-z]:[/\\]*) _w="$_h"; _h="$(cygpath -u "$_w" 2>/dev/null || true)"; [ -n "$_h" ] || _h="/$(printf %s "$_w" | cut -c1 | tr 'A-Z' 'a-z')$(printf %s "${_w#??}" | tr '\\' '/')" ;; esac
3731
+ # Only the official marketplace is trusted (name pinned; override only via the trusted FORGEDOCK_MARKETPLACE env, never repo files).
3732
+ _mk="${FORGEDOCK_MARKETPLACE:-forgedock}"; case "$_mk" in ""|.|..|*[!A-Za-z0-9._-]*) _mk="forgedock" ;; esac
3733
+ if [ -n "${FORGEDOCK_HOME:-}" ]; then case "$_h" in /*) FORGE_ROOT="$_h" ;; esac; else
3734
+ # Portable to bash 3.2 (macOS), BSD/GNU coreutils and zsh: no mapfile, no sort -V, no bare globs (zsh aborts on no match). Every assignment ends in || true so the block survives set -e / pipefail.
3735
+ _l="$HOME/.claude/commands/work-on.md"; _l="$(readlink -f "$_l" 2>/dev/null || readlink "$_l" 2>/dev/null || true)"; [ -n "$_l" ] && _l="$(dirname "$(dirname "$_l")")"
3736
+ # Codex: install-codex.sh records the clone path in $CODEX_HOME/forge-home (one absolute path); skills are generated files, not symlinks.
3737
+ _cx="${CODEX_HOME:-$HOME/.codex}"; case "$_cx" in /*) _x="$(head -n 1 "$_cx/forge-home" 2>/dev/null || true)" ;; *) _x="" ;; esac
3738
+ # newest cached version first: numeric major.minor.patch of the version dir name only (non-semver names such as commit SHAs are skipped); a release outranks its pre-release (1.10.0 > 1.9.0 > 1.9.0-rc1)
3739
+ _v="$(find -L "$HOME/.claude/plugins/cache" -mindepth 3 -maxdepth 3 -type d 2>/dev/null | awk -F/ -v mk="$_mk" '$(NF-2)==mk && $(NF-1)=="forgedock" && $NF ~ /^[0-9]+\.[0-9]+\.[0-9]+(-.*)?$/{v=$NF;p=index(v,"-");r=1;if(p){v=substr(v,1,p-1);r=0};split(v,a,".");printf "%d %d %d %d %s\n",a[1],a[2],a[3],r,$(0)}' | sort -k1,1nr -k2,2nr -k3,3nr -k4,4nr | cut -d' ' -f5- || true)"
3740
+ _m="$HOME/.claude/plugins/marketplaces/$_mk"
3741
+ # '${CLAUDE_PLUGIN_ROOT}' is substituted by Claude Code when it loads a plugin spec (the exact spelling only, never as an env var), so a running plugin resolves to its own root first; unsubstituted (other runtimes) it stays a literal that the /* check rejects.
3742
+ _k="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_HOME:-}" "$_l" "$_x" "$_v" "$_m")"
3743
+ while IFS= read -r _c; do
3744
+ case "$_c" in /*) [ -z "$FORGE_ROOT" ] && [ -f "$_c/scripts/verify-phase-trail.sh" ] && [ -f "$_c/scripts/lint-dispatch-prompt.sh" ] && [ -f "$_c/scripts/is-docs-only.sh" ] && [ -f "$_c/bin/engine/resolve.mjs" ] && [ -f "$_c/bin/engine/orchestrate-canary.mjs" ] && [ -f "$_c/bin/engine/admission.mjs" ] && FORGE_ROOT="$_c" ;; esac
3745
+ done <<< "$_k"
3746
+ fi
3747
+ GATE_SCRIPT=""; [ -n "$FORGE_ROOT" ] && [ -f "$FORGE_ROOT/scripts/resolve-deploy-gate.sh" ] && GATE_SCRIPT="$FORGE_ROOT/scripts/resolve-deploy-gate.sh"
3748
+ if [ -n "$GATE_SCRIPT" ]; then GUARD_GATE=$(bash "$GATE_SCRIPT" "$GUARD_BASE" {GH_REPO} 2>/dev/null || echo true); else GUARD_GATE=true; fi; [ "$GUARD_GATE" = "false" ] || GUARD_GATE=true
3749
+ if [ "$GUARD_GATE" = "false" ]; then
3750
+ # forge#2570: non-default-branch base — defer to remediate.md M7's auto-land, not a human.
3188
3751
  # forge#2626: standalone `/review-pr` (no remediate.md M6→M7 chain) would otherwise
3189
3752
  # strand this PR at `workflow:awaiting-merge` with no actionable guidance — add a
3190
3753
  # manual-merge fallback so the comment is correct in both the chained and standalone cases.
3191
3754
  # GOVERNOR-exempt: intentional coordination side-effect (best-effort lease/board/finding post), DRY_RUN-safe — reviewed & accepted for the check-command-side-effects gate. Flagged only by the staging->main full-diff; passes on every feature PR. forge#2627
3192
- gh issue comment {MERGE_ISSUE} {MERGE_GH_FLAG} --body "🟠 PR #{PR_NUMBER} was previously escalated (\`needs-human\`) and has now been re-reviewed to \`${VERDICT:-APPROVED}\` with a clean mergeability check on non-\`main\` base \`${GUARD_BASE}\`. It is parked at \`workflow:awaiting-merge\` for the remediation auto-land bar (\`remediate.md\` Phase M7, forge#2570) — which auto-merges it on the same fast-lane condition the normal \`/work-on\` path uses for this target branch — NOT for a human merge decision. If no remediation \`M7\` follows (e.g. this review was invoked standalone), merge manually once reviewed: \`gh pr merge {PR_NUMBER} {MERGE_GH_FLAG} --merge\`."
3755
+ gh issue comment {MERGE_ISSUE} {MERGE_GH_FLAG} --body "🟠 PR #{PR_NUMBER} was previously escalated (\`needs-human\`) and has now been re-reviewed to \`${VERDICT:-APPROVED}\` with a clean mergeability check on non-default-branch base \`${GUARD_BASE}\`. It is parked at \`workflow:awaiting-merge\` for the remediation auto-land bar (\`remediate.md\` Phase M7, forge#2570) — which auto-merges it on the same fast-lane condition the normal \`/work-on\` path uses for this target branch — NOT for a human merge decision. If no remediation \`M7\` follows (e.g. this review was invoked standalone), merge manually once reviewed: \`gh pr merge {PR_NUMBER} {MERGE_GH_FLAG} --merge\`."
3193
3756
  else
3194
- # forge#2570: `main` / deploy gate (or unresolved base — fail closed to the human gate).
3757
+ # forge#2570: default branch / deploy gate (or unresolved base — fail closed to the human gate).
3195
3758
  gh issue comment {MERGE_ISSUE} {MERGE_GH_FLAG} --body "🟠 PR #{PR_NUMBER} was previously escalated (\`needs-human\`) and has now been re-reviewed to \`${VERDICT:-APPROVED}\` with a clean mergeability check. This PR targets the deploy gate (\`${GUARD_BASE:-unresolved base}\`), so it is held at \`workflow:awaiting-merge\` for a human merge decision — \`staging → main\` is the genuine human gate. Merge manually once reviewed: \`gh pr merge {PR_NUMBER} {MERGE_GH_FLAG} --merge\`."
3196
3759
  fi
3197
3760
  # Shell state does not persist between Bash calls and `resolve_script` is not defined in this spec:
@@ -3454,7 +4017,7 @@ A degraded run that skipped Task-based agent dispatch must be visible from this
3454
4017
 
3455
4018
  ```bash
3456
4019
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
3457
- PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"
4020
+ PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"
3458
4021
  # Same trust rule as Phase 3C/4: only trusted comments whose body starts with the marker count.
3459
4022
  # TRUSTED_SCRIPT resolver (canonical; byte-identical across specs, guarded by scripts/forge-root.test.sh): plugin root, FORGE_ROOT, FORGEDOCK_HOME, FORGE_HOME, install symlink, marketplaces, newest pinned plugin cache under CLAUDE_CONFIG_DIR then ~/.claude. Never the working directory (author-controlled, #3400). The cache scan matters because the plugin-root placeholder is not always substituted in forked runs.
3460
4023
  _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
@@ -3466,9 +4029,54 @@ TRUSTED_SCRIPT=""
3466
4029
  while IFS= read -r _c; do
3467
4030
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
3468
4031
  done <<< "$_tc"
4032
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
4033
+ REVIEW_DELTA_SCRIPT=""
4034
+ while IFS= read -r _c; do
4035
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
4036
+ done <<< "$_tc"
4037
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
4038
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
4039
+ # BEGIN review-pr-delta-mode
4040
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
4041
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
4042
+ # Needs REPO PR_NUMBER REVIEW_SHA BASE_BRANCH TRUSTED_SCRIPT REVIEW_DELTA_SCRIPT in scope. Every failure (unresolved script, bad output, bad SHA, empty delta) prints FULL, never a skipped review.
4043
+ review_delta_mode() {
4044
+ local out l1 l2 kind arg from to rounds=0 top
4045
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
4046
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
4047
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
4048
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
4049
+ out=$(bash "$REVIEW_DELTA_SCRIPT" --pr "$PR_NUMBER" --head "$REVIEW_SHA" --base "$BASE_BRANCH" --trusted-script "$TRUSTED_SCRIPT" --repo "$REPO" --repo-path "$top" 2>/dev/null) || out=""
4050
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
4051
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
4052
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
4053
+ kind="${l1%% *}"; arg="${l1#* }"
4054
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
4055
+ if [ "$kind" = "DELTA" ]; then
4056
+ from="${arg%%..*}"; to="${arg##*..}"
4057
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
4058
+ fi
4059
+ echo "FULL - $rounds"
4060
+ }
4061
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
4062
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
4063
+ carry_verified_from() {
4064
+ local kind from rounds n
4065
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
4066
+ read -r kind from rounds <<< "$(review_delta_mode)"
4067
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
4068
+ n=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "\\A(?=[\\s\\S]*\\nReviewed-SHA: ${REVIEW_SHA}\\r?(\\n|$))(?=[\\s\\S]*\\nCarried-From-SHA: ${from}\\r?(\\n|$))<!-- FORGE:REVIEW-CARRIED -->" 2>/dev/null || echo "")
4069
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
4070
+ [ "$n" -ge 1 ] && printf '%s' "$from"
4071
+ return 0
4072
+ }
4073
+ # END review-pr-delta-mode
4074
+ # BASE_SYNC_ONLY: zero reviewers is the expected panel only when the carry record is trusted and the resolver re-verifies it.
4075
+ CARRIED_FROM=""
4076
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then CARRIED_FROM=$(carry_verified_from); fi
3469
4077
  ACTUAL_AGENT_DOMAINS=""
3470
4078
  if [ -n "$TRUSTED_SCRIPT" ]; then
3471
- ACTUAL_AGENT_DOMAINS=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null \
4079
+ ACTUAL_AGENT_DOMAINS=$(printf '%s' "$COMMENTS_JSON" \
3472
4080
  | bash "$TRUSTED_SCRIPT" bodies "^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->" 2>/dev/null \
3473
4081
  | jq -rs '[.[] | scan("^<!-- FORGE:REVIEW-AGENT:([a-z-]+) -->") | .[0]] | unique | join(", ")' 2>/dev/null || echo "") # unresolvable => empty => count 0 (degraded, fail closed)
3474
4082
  fi
@@ -3478,9 +4086,12 @@ fi
3478
4086
  # second "0" line, corrupting the value to "0\n0" in exactly the zero-agent case this
3479
4087
  # check exists to detect. <!-- Added: forge#1849 -->
3480
4088
  ACTUAL_AGENT_COUNT=$(echo "$ACTUAL_AGENT_DOMAINS" | tr ',' '\n' | grep -c '.' 2>/dev/null)
4089
+ # A verified carried head dispatched no reviewers by design: report it as carried instead of a degraded zero-agent panel.
4090
+ if [ -n "$CARRIED_FROM" ]; then ACTUAL_AGENT_DOMAINS="carried from ${CARRIED_FROM}"; ACTUAL_AGENT_COUNT=0; fi
4091
+ echo "CARRIED_FROM: ${CARRIED_FROM}"
3481
4092
  ```
3482
4093
 
3483
- Substitute `ACTUAL_AGENT_COUNT`/`ACTUAL_AGENT_DOMAINS` into the summary's `**Agents**: [N] ([names])` field below — do NOT substitute a manually-counted or remembered figure. Compare it to `SELECTED_AGENT_COUNT` from Phase 3C. If it is smaller, the panel is degraded: the Phase 3C hard-stop path must already have labelled the PR `review-degraded`, added `needs-human`, and exited without a verdict. Never summarize, approve, or merge a partial panel. `ACTUAL_AGENT_COUNT=0` is the same hard stop, not a solo/inline review mode.
4094
+ Substitute `ACTUAL_AGENT_COUNT`/`ACTUAL_AGENT_DOMAINS` into the summary's `**Agents**: [N] ([names])` field below — do NOT substitute a manually-counted or remembered figure. Compare it to `SELECTED_AGENT_COUNT` from Phase 3C. If it is smaller, the panel is degraded: the Phase 3C hard-stop path must already have labelled the PR `review-degraded`, added `needs-human`, and exited without a verdict. Never summarize, approve, or merge a partial panel. `ACTUAL_AGENT_COUNT=0` is the same hard stop, not a solo/inline review mode, with one exception: when the block printed a non-empty `CARRIED_FROM` (a `BASE_SYNC_ONLY` head whose trusted `FORGE:REVIEW-CARRIED` record the resolver re-verified), zero reviewers is the correct panel; write `**Agents**: carried from <CARRIED_FROM>` in the summary and skip this comparison. An empty `CARRIED_FROM` on a `BASE_SYNC_ONLY` head is the hard stop.
3484
4095
 
3485
4096
  ```bash
3486
4097
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.