forgedock 1.13.1 → 1.14.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 (34) hide show
  1. package/bin/engine/phases.mjs +160 -18
  2. package/bin/engine-cli.mjs +18 -5
  3. package/bin/engine.mjs +109 -6
  4. package/bin/forgedock.mjs +4 -1
  5. package/commands/orchestrate/phase-3-dependency.md +5 -2
  6. package/commands/orchestrate/phase-4-execution.md +144 -24
  7. package/commands/pipeline-health/phase-2-metrics.md +5 -3
  8. package/commands/review-pr-staging.md +289 -39
  9. package/commands/review-pr.md +604 -42
  10. package/commands/validate.md +11 -3
  11. package/commands/work-on/build/architect.md +11 -8
  12. package/commands/work-on/build.md +27 -6
  13. package/commands/work-on/close.md +5 -4
  14. package/commands/work-on/decompose.md +7 -2
  15. package/commands/work-on/investigate.md +11 -0
  16. package/commands/work-on/remediate.md +135 -74
  17. package/commands/work-on/review.md +3 -3
  18. package/docs/CONFIG.md +14 -1
  19. package/docs/RELEASE-NOTES.md +8 -0
  20. package/package.json +1 -1
  21. package/scripts/architect-resume-match.test.sh +53 -0
  22. package/scripts/check-spec-bash.sh +73 -3
  23. package/scripts/check-spec-bash.test.sh +50 -0
  24. package/scripts/checkbox-sections.test.sh +19 -0
  25. package/scripts/classify-finding.sh +96 -1
  26. package/scripts/classify-finding.test.sh +47 -0
  27. package/scripts/forge-root.test.sh +48 -1
  28. package/scripts/orchestrate-soft-edge.test.sh +58 -0
  29. package/scripts/remediate-basesync.test.sh +62 -3
  30. package/scripts/review-delta.sh +3 -2
  31. package/scripts/review-delta.test.sh +1 -1
  32. package/scripts/review-pr-delta-wiring.test.sh +266 -0
  33. package/scripts/review-pr-staging-scope.test.sh +60 -0
  34. package/scripts/trusted-comments.test.sh +11 -0
@@ -1536,6 +1536,210 @@ DOMAIN_FILES_API=$(echo "$FILES" | grep -iE "router|route|endpoint|openapi|sdk|a
1536
1536
 
1537
1537
  ### 3C: Load Agent Templates & Launch
1538
1538
 
1539
+ #### Review mode: FULL, DELTA or BASE_SYNC_ONLY (run first, before loading any template)
1540
+
1541
+ 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.
1542
+
1543
+ | Mode | Meaning | What changes |
1544
+ |------|---------|--------------|
1545
+ | `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. |
1546
+ | `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. |
1547
+ | `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. |
1548
+
1549
+ 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.
1550
+
1551
+ ```bash
1552
+ # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
1553
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; SELECTED_AGENTS="{SELECTED_AGENTS}"
1554
+ THOROUGH="{THOROUGH}"; IS_MILESTONE_TO_STAGING="{IS_MILESTONE_TO_STAGING}"
1555
+ # 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.
1556
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1557
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1558
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1559
+ [ -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)"
1560
+ done
1561
+ TRUSTED_SCRIPT=""
1562
+ while IFS= read -r _c; do
1563
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1564
+ done <<< "$_tc"
1565
+ # 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.
1566
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1567
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1568
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1569
+ [ -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)"
1570
+ done
1571
+ REVIEW_DELTA_SCRIPT=""
1572
+ while IFS= read -r _c; do
1573
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
1574
+ done <<< "$_tc"
1575
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1576
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1577
+ # BEGIN review-pr-delta-mode
1578
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1579
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
1580
+ # 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.
1581
+ review_delta_mode() {
1582
+ local out l1 l2 kind arg from to rounds=0 top
1583
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
1584
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
1585
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
1586
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
1587
+ 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=""
1588
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
1589
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
1590
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
1591
+ kind="${l1%% *}"; arg="${l1#* }"
1592
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
1593
+ if [ "$kind" = "DELTA" ]; then
1594
+ from="${arg%%..*}"; to="${arg##*..}"
1595
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
1596
+ fi
1597
+ echo "FULL - $rounds"
1598
+ }
1599
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
1600
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
1601
+ carry_verified_from() {
1602
+ local kind from rounds n
1603
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
1604
+ read -r kind from rounds <<< "$(review_delta_mode)"
1605
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
1606
+ 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 "")
1607
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
1608
+ [ "$n" -ge 1 ] && printf '%s' "$from"
1609
+ return 0
1610
+ }
1611
+ # END review-pr-delta-mode
1612
+ # BEGIN review-pr-delta-roster
1613
+ # Effective roster for a DELTA head. While fewer than two full panels have run (FULL_ROUNDS < 2) the 3B roster stands;
1614
+ # from the third review on it narrows to Security plus the domains whose trigger paths the delta touches.
1615
+ # Args: FULL_ROUNDS, the canonical 3B roster (space separated), the delta file list (one path per line).
1616
+ delta_roster() {
1617
+ local rounds="${1}" full="${2}" files="${3}" out="security"
1618
+ case "$rounds" in ''|*[!0-9]*) printf '%s' "$full"; return 0 ;; esac
1619
+ if [ "$rounds" -lt 2 ]; then printf '%s' "$full"; return 0; fi
1620
+ printf '%s\n' "$files" | grep -qiE "auth|session|login|logout|jwt|oauth|token|user|permission|middleware" && out="$out auth"
1621
+ printf '%s\n' "$files" | grep -qiE "billing|credit|payment|charge|refund|subscription|pricing|tier" && out="$out billing"
1622
+ printf '%s\n' "$files" | grep -qiE "lock|queue|atomic|transaction|worker|job|task|celery|redis|claim|lease|engine-state" && out="$out concurrency"
1623
+ printf '%s\n' "$files" | grep -qiE "migration|\.sql$|model|schema|db|database|alembic" && out="$out database"
1624
+ printf '%s\n' "$files" | grep -qiE "docker|nginx|traefik|\.github|Makefile|deploy|infra" && out="$out infra"
1625
+ printf '%s\n' "$files" | grep -qiE "scrape|crawler|stealth|browser.*pool|headless|anti.bot|captcha" && out="$out scraper"
1626
+ printf '%s\n' "$files" | grep -qiE "^web/|\.tsx?$|\.jsx?$|component|page|layout|style|css" && out="$out frontend"
1627
+ printf '%s\n' "$files" | grep -qiE "router|route|endpoint|openapi|sdk|api" && out="$out api"
1628
+ printf '%s\n' "$files" | grep -qE "^(commands|bin)/" && out="$out spec-cli"
1629
+ printf '%s' "$out"
1630
+ }
1631
+ # END review-pr-delta-roster
1632
+ REVIEW_MODE="FULL"; DELTA_FROM=""; CARRIED_FROM=""; FULL_ROUNDS=0
1633
+ if [ "$THOROUGH" != "true" ] && [ "$IS_MILESTONE_TO_STAGING" != "true" ]; then
1634
+ read -r RD_KIND RD_ARG RD_ROUNDS <<< "$(review_delta_mode)"
1635
+ FULL_ROUNDS="${RD_ROUNDS:-0}"
1636
+ case "$RD_KIND" in
1637
+ DELTA) REVIEW_MODE="DELTA"; DELTA_FROM="$RD_ARG" ;;
1638
+ BASE_SYNC_ONLY) REVIEW_MODE="BASE_SYNC_ONLY"; CARRIED_FROM="$RD_ARG" ;;
1639
+ esac
1640
+ fi
1641
+ if [ "$REVIEW_MODE" = "DELTA" ]; then
1642
+ RD_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
1643
+ DELTA_FILES=$(git -C "$RD_TOP" -c core.quotepath=off diff --name-only --no-renames "${DELTA_FROM}" "${REVIEW_SHA}" 2>/dev/null) || DELTA_FILES=""
1644
+ if [ -z "$DELTA_FILES" ]; then
1645
+ REVIEW_MODE="FULL"; DELTA_FROM="" # unreadable or empty delta: review the full diff
1646
+ else
1647
+ SELECTED_AGENTS=$(delta_roster "$FULL_ROUNDS" "$SELECTED_AGENTS" "$DELTA_FILES")
1648
+ fi
1649
+ fi
1650
+ [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ] && SELECTED_AGENTS=""
1651
+ echo "REVIEW_MODE: ${REVIEW_MODE}"
1652
+ echo "DELTA_FROM: ${DELTA_FROM}"
1653
+ echo "CARRIED_FROM: ${CARRIED_FROM}"
1654
+ echo "FULL_ROUNDS: ${FULL_ROUNDS}"
1655
+ echo "SELECTED_AGENTS: ${SELECTED_AGENTS}"
1656
+ ```
1657
+
1658
+ 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.
1659
+
1660
+ **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:
1661
+
1662
+ ```bash
1663
+ # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
1664
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"
1665
+ # 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.
1666
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1667
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1668
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1669
+ [ -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)"
1670
+ done
1671
+ TRUSTED_SCRIPT=""
1672
+ while IFS= read -r _c; do
1673
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1674
+ done <<< "$_tc"
1675
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
1676
+ REVIEW_DELTA_SCRIPT=""
1677
+ while IFS= read -r _c; do
1678
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
1679
+ done <<< "$_tc"
1680
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1681
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1682
+ # BEGIN review-pr-delta-mode
1683
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1684
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
1685
+ # 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.
1686
+ review_delta_mode() {
1687
+ local out l1 l2 kind arg from to rounds=0 top
1688
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
1689
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
1690
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
1691
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
1692
+ 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=""
1693
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
1694
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
1695
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
1696
+ kind="${l1%% *}"; arg="${l1#* }"
1697
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
1698
+ if [ "$kind" = "DELTA" ]; then
1699
+ from="${arg%%..*}"; to="${arg##*..}"
1700
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
1701
+ fi
1702
+ echo "FULL - $rounds"
1703
+ }
1704
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
1705
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
1706
+ carry_verified_from() {
1707
+ local kind from rounds n
1708
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
1709
+ read -r kind from rounds <<< "$(review_delta_mode)"
1710
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
1711
+ 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 "")
1712
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
1713
+ [ "$n" -ge 1 ] && printf '%s' "$from"
1714
+ return 0
1715
+ }
1716
+ # END review-pr-delta-mode
1717
+ read -r RD_KIND RD_FROM RD_ROUNDS <<< "$(review_delta_mode)"
1718
+ if [ "$RD_KIND" != "BASE_SYNC_ONLY" ]; then
1719
+ echo "CARRY_RECORD: NOT_APPLICABLE (resolver says ${RD_KIND}); run the FULL path"; exit 0
1720
+ fi
1721
+ if [ -n "$(carry_verified_from)" ]; then
1722
+ echo "CARRY_RECORD: PRESENT (verified from ${RD_FROM})"; exit 0 # re-entry: never post a second record
1723
+ fi
1724
+ gh pr comment "$PR_NUMBER" -R "$REPO" --body "<!-- FORGE:REVIEW-CARRIED -->
1725
+ Reviewed-SHA: ${REVIEW_SHA}
1726
+ Carried-From-SHA: ${RD_FROM}
1727
+
1728
+ ## Review carried forward
1729
+
1730
+ 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."
1731
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1732
+ if [ -n "$(carry_verified_from)" ]; then
1733
+ echo "CARRY_RECORD: POSTED (verified from ${RD_FROM})"
1734
+ else
1735
+ echo "CARRY_RECORD: UNVERIFIED (the record is not trusted or could not be read back); run the FULL path"
1736
+ fi
1737
+ ```
1738
+
1739
+ `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.
1740
+
1741
+ **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 -->
1742
+
1539
1743
  **>>> MANDATORY TEMPLATE-RESOLUTION GUARD — run BEFORE any Read of persona templates:**
1540
1744
 
1541
1745
  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 +1800,7 @@ Read: $MONOLITHIC_CATALOG
1596
1800
  ```
1597
1801
  Then extract the shared protocols section and each selected persona's section from within that single file — same content, different packaging.
1598
1802
 
1599
- Persona file names: `security.md` (always), `auth.md`, `billing.md`, `concurrency.md`, `scraper.md`, `frontend.md`, `api.md`, `database.md`, `infra.md`.
1803
+ 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/**`).
1600
1804
  See `review-pr-agents.md` for the full routing table mapping domains → persona files.
1601
1805
 
1602
1806
  (`$FORGE_HOME` defaults to `~/.claude`)
@@ -1628,7 +1832,17 @@ Before launching agents, query the code index to build a domain-scoped file list
1628
1832
 
1629
1833
  ```bash
1630
1834
  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"
1835
+ # 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.
1836
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
1837
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
1838
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
1839
+ [ -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)"
1840
+ done
1841
+ CODE_INDEX_SCRIPT=""
1842
+ while IFS= read -r _c; do
1843
+ case "$_c" in /*) [ -z "$CODE_INDEX_SCRIPT" ] && [ -f "$_c/scripts/code-index.sh" ] && CODE_INDEX_SCRIPT="$_c/scripts/code-index.sh" ;; esac
1844
+ done <<< "$_tc"
1845
+ # REPO_PATH is the PR checkout (author-controlled): it is only the index target, never the script source. Unresolved: skip the index.
1632
1846
 
1633
1847
  # Ensure index is current (cache-hit on unchanged HEAD — instant if already built)
1634
1848
  if [[ -x "$CODE_INDEX_SCRIPT" ]]; then
@@ -1665,10 +1879,22 @@ Each domain agent receives only the diff slice relevant to its domain, not the f
1665
1879
 
1666
1880
  ```bash
1667
1881
  # 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}"
1882
+ PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"; DELTA_FROM="{DELTA_FROM}"
1669
1883
  # Full diff fetched once — agents do NOT re-fetch it
1670
1884
  FULL_DIFF=$(gh pr diff "$PR_NUMBER" -R "$REPO")
1671
1885
  FULL_FILES=$(gh pr diff "$PR_NUMBER" -R "$REPO" --name-only)
1886
+ # DELTA (review-delta mode): the slice source becomes the range diff since the last fully reviewed head, and FULL_FILES stays as
1887
+ # context. Every failure (bad SHA, commit missing from the checkout, unreadable or empty range) keeps the full PR diff above.
1888
+ # The variable keeps its FULL_DIFF name so every awk slice below is unchanged.
1889
+ if [ "$REVIEW_MODE" = "DELTA" ]; then
1890
+ is_hex() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
1891
+ RD_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
1892
+ if is_hex "$DELTA_FROM" && is_hex "$REVIEW_SHA" && [ -n "$RD_TOP" ] \
1893
+ && 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
1894
+ 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=""
1895
+ [ -n "$DELTA_DIFF" ] && FULL_DIFF="$DELTA_DIFF"
1896
+ fi
1897
+ fi
1672
1898
 
1673
1899
  # --- Security agent: always receives the full diff (cross-cutting domain) ---
1674
1900
  DIFF_SLICE_SECURITY="$FULL_DIFF"
@@ -1736,6 +1962,14 @@ DIFF_SLICE_SCRAPER=$(echo "$FULL_DIFF" | awk '
1736
1962
  in_block { print }
1737
1963
  ')
1738
1964
  [ -z "$DIFF_SLICE_SCRAPER" ] && DIFF_SLICE_SCRAPER="$FULL_DIFF"
1965
+
1966
+ # --- Spec/CLI agent (DELTA rosters only): commands/** and bin/** files ---
1967
+ DIFF_SLICE_SPEC_CLI=$(echo "$FULL_DIFF" | awk '
1968
+ /^diff --git/ { in_block=0 }
1969
+ /^diff --git a\/(commands|bin)\// { in_block=1 }
1970
+ in_block { print }
1971
+ ')
1972
+ [ -z "$DIFF_SLICE_SPEC_CLI" ] && DIFF_SLICE_SPEC_CLI="$FULL_DIFF"
1739
1973
  ```
1740
1974
 
1741
1975
  **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 +1995,7 @@ DIFF_SLICE_FRONTEND=$(truncate_slice "$DIFF_SLICE_FRONTEND")
1761
1995
  DIFF_SLICE_API=$(truncate_slice "$DIFF_SLICE_API")
1762
1996
  DIFF_SLICE_INFRA=$(truncate_slice "$DIFF_SLICE_INFRA")
1763
1997
  DIFF_SLICE_SCRAPER=$(truncate_slice "$DIFF_SLICE_SCRAPER")
1998
+ DIFF_SLICE_SPEC_CLI=$(truncate_slice "$DIFF_SLICE_SPEC_CLI")
1764
1999
  ```
1765
2000
 
1766
2001
  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 +2012,7 @@ The `protocols.md` file contains the Evidence-Based Review Protocol, Structured
1777
2012
 
1778
2013
  **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
2014
 
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.
2015
+ **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
2016
 
1782
2017
  ```bash
1783
2018
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
@@ -1900,7 +2135,7 @@ Reviewer completion is read from GitHub, never from notifications (see the "Revi
1900
2135
 
1901
2136
  ```bash
1902
2137
  # 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}"
2138
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; SELECTED_AGENTS="{SELECTED_AGENTS}"; REVIEW_MODE="{REVIEW_MODE}"
1904
2139
  DISPATCH_TOOL="{DISPATCH_TOOL}" # Task | Agent | task (OpenCode)
1905
2140
  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
2141
  if [ "$REVIEW_SHA_OK" != "true" ]; then
@@ -1951,9 +2186,58 @@ TRUSTED_SCRIPT=""
1951
2186
  while IFS= read -r _c; do
1952
2187
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
1953
2188
  done <<< "$_tc"
2189
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2190
+ REVIEW_DELTA_SCRIPT=""
2191
+ while IFS= read -r _c; do
2192
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2193
+ done <<< "$_tc"
2194
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
1954
2195
  COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
1955
2196
  MISSING_AGENT_COMMENTS=""
1956
2197
  ACTUAL_AGENT_COUNT=0
2198
+ # BEGIN review-pr-delta-mode
2199
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2200
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2201
+ # 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.
2202
+ review_delta_mode() {
2203
+ local out l1 l2 kind arg from to rounds=0 top
2204
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2205
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2206
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2207
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2208
+ 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=""
2209
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2210
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2211
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2212
+ kind="${l1%% *}"; arg="${l1#* }"
2213
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2214
+ if [ "$kind" = "DELTA" ]; then
2215
+ from="${arg%%..*}"; to="${arg##*..}"
2216
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2217
+ fi
2218
+ echo "FULL - $rounds"
2219
+ }
2220
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2221
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2222
+ carry_verified_from() {
2223
+ local kind from rounds n
2224
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2225
+ read -r kind from rounds <<< "$(review_delta_mode)"
2226
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2227
+ 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 "")
2228
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2229
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2230
+ return 0
2231
+ }
2232
+ # END review-pr-delta-mode
2233
+ # A zero-reviewer panel is acceptable only for a BASE_SYNC_ONLY head whose carry record is trusted AND re-verified by the
2234
+ # resolver right now. An empty roster with no verified record is a shortfall, never a clean panel (fail closed).
2235
+ CARRIED_FROM=""
2236
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then CARRIED_FROM=$(carry_verified_from); fi
2237
+ EMPTY_UNVERIFIED=false
2238
+ if [ "$SELECTED_AGENT_COUNT" -eq 0 ] && [ -z "$CARRIED_FROM" ]; then
2239
+ EMPTY_UNVERIFIED=true; MISSING_AGENT_COMMENTS=" (empty roster without a verified carried record)"
2240
+ fi
1957
2241
  # BEGIN review-pr-agent-domain
1958
2242
  agent_domain() {
1959
2243
  case "$(printf '%s' "${1}" | tr '[:upper:]' '[:lower:]')" in
@@ -1981,8 +2265,9 @@ for AGENT in $SELECTED_AGENTS; do
1981
2265
  done
1982
2266
 
1983
2267
  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
2268
+ rm -f "$DEADLINE_FILE"
2269
+ 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
2270
+ elif [ "$JOINED" = "true" ] || [ "$EMPTY_UNVERIFIED" = "true" ] || [ "$(date +%s)" -ge "$DEADLINE" ]; then
1986
2271
  rm -f "$DEADLINE_FILE"
1987
2272
  echo "REVIEW DELIVERY FAILURE: missing trusted current-SHA findings comment(s) for:${MISSING_AGENT_COMMENTS}"
1988
2273
  echo "Use each agent's returned verdict, findings, and durable body path to recover the review."
@@ -1995,17 +2280,19 @@ fi
1995
2280
 
1996
2281
  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
2282
 
2283
+ 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.
2284
+
1998
2285
  **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
2286
 
2000
2287
  ---
2001
2288
 
2002
2289
  ## Phase 5: Synthesis (Multi-Agent Arbitration)
2003
2290
 
2004
- **Skip if**: Only 1 agent OR total findings ≤ 3.
2291
+ **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
2292
 
2006
2293
  ```bash
2007
2294
  # 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}"
2295
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"
2009
2296
  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
2297
  [ "$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
2298
  # Extract structured finding IDs from FINDING HTML comments.
@@ -2024,8 +2311,56 @@ TRUSTED_SCRIPT=""
2024
2311
  while IFS= read -r _c; do
2025
2312
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2026
2313
  done <<< "$_tc"
2314
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2315
+ REVIEW_DELTA_SCRIPT=""
2316
+ while IFS= read -r _c; do
2317
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2318
+ done <<< "$_tc"
2319
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
2027
2320
  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|$)"
2321
+ # BEGIN review-pr-delta-mode
2322
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2323
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2324
+ # 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.
2325
+ review_delta_mode() {
2326
+ local out l1 l2 kind arg from to rounds=0 top
2327
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2328
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2329
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2330
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2331
+ 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=""
2332
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2333
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2334
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2335
+ kind="${l1%% *}"; arg="${l1#* }"
2336
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2337
+ if [ "$kind" = "DELTA" ]; then
2338
+ from="${arg%%..*}"; to="${arg##*..}"
2339
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2340
+ fi
2341
+ echo "FULL - $rounds"
2342
+ }
2343
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2344
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2345
+ carry_verified_from() {
2346
+ local kind from rounds n
2347
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2348
+ read -r kind from rounds <<< "$(review_delta_mode)"
2349
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2350
+ 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 "")
2351
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2352
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2353
+ return 0
2354
+ }
2355
+ # END review-pr-delta-mode
2356
+ # BASE_SYNC_ONLY: no reviewer posted for this head, so the findings are the carried head's. Only a verified carry record
2357
+ # switches the SHA (the SHA comes from the validated record, never from untrusted text); an unverified claim keeps the
2358
+ # current head, finds no reviewer bodies and blocks below (fail closed).
2359
+ FINDINGS_SHA="$REVIEW_SHA"
2360
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then
2361
+ CARRIED_FROM=$(carry_verified_from); [ -n "$CARRIED_FROM" ] && FINDINGS_SHA="$CARRIED_FROM"
2362
+ fi
2363
+ AGENT_RE="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${FINDINGS_SHA}(\\r?\\n|$)"
2029
2364
  if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then
2030
2365
  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
2366
  fi
@@ -2033,7 +2368,7 @@ AGENT_BODIES=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$AG
2033
2368
  || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted reviewer comments unreadable (fail closed)"; exit 0; }
2034
2369
  ALL_FINDINGS=$(printf '%s\n' "$AGENT_BODIES" | jq -r 'scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null)
2035
2370
  # 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 "")
2371
+ 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
2372
  case "$AGENT_COUNT" in ''|*[!0-9]*) AGENT_COUNT=0 ;; esac # fail closed
2038
2373
  FINDING_COUNT=$(echo "$ALL_FINDINGS" | grep -c '.' || echo 0)
2039
2374
  ```
@@ -2057,10 +2392,10 @@ If synthesis needed, launch a `general-purpose` Task (model: `"{SUBAGENT_MODEL}"
2057
2392
 
2058
2393
  ```bash
2059
2394
  # 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}"
2395
+ REPO="{GH_REPO}"; PR_NUMBER="{PR_NUMBER}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"; DELTA_FROM="{DELTA_FROM}"
2061
2396
  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
2397
  [ "$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):
2398
+ # 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
2399
  # findings from earlier heads are already fixed or re-reviewed, and an untrusted commenter must never be able to inject
2065
2400
  # findings or post a forged synthesis block that makes the review read clean (the head SHA is public).
2066
2401
  # 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 +2408,82 @@ TRUSTED_SCRIPT=""
2073
2408
  while IFS= read -r _c; do
2074
2409
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2075
2410
  done <<< "$_tc"
2411
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
2412
+ REVIEW_DELTA_SCRIPT=""
2413
+ while IFS= read -r _c; do
2414
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
2415
+ done <<< "$_tc"
2416
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
2076
2417
  COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
2077
2418
  if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then
2078
2419
  echo "REVIEW_RESULT: status: BLOCKED, blocker: scripts/trusted-comments.sh unresolvable or PR comments unreadable; refusing to read clean (fail closed)"; exit 0
2079
2420
  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; }
2421
+ # BEGIN review-pr-delta-mode
2422
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
2423
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
2424
+ # 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.
2425
+ review_delta_mode() {
2426
+ local out l1 l2 kind arg from to rounds=0 top
2427
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
2428
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
2429
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
2430
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
2431
+ 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=""
2432
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
2433
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
2434
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
2435
+ kind="${l1%% *}"; arg="${l1#* }"
2436
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
2437
+ if [ "$kind" = "DELTA" ]; then
2438
+ from="${arg%%..*}"; to="${arg##*..}"
2439
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
2440
+ fi
2441
+ echo "FULL - $rounds"
2442
+ }
2443
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
2444
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
2445
+ carry_verified_from() {
2446
+ local kind from rounds n
2447
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
2448
+ read -r kind from rounds <<< "$(review_delta_mode)"
2449
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
2450
+ 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 "")
2451
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
2452
+ [ "$n" -ge 1 ] && printf '%s' "$from"
2453
+ return 0
2454
+ }
2455
+ # END review-pr-delta-mode
2456
+ # Trusted synthesized block for one head when present, else that head's trusted agent bodies (the same predicate as before,
2457
+ # now per head). Returns non-zero when the trust script cannot count or read.
2458
+ bodies_at() {
2459
+ local sha="${1}" synth agent has
2460
+ synth="^<!-- REVIEW-FINDINGS-SYNTHESIZED-START -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${sha}(\\r?\\n|$)"
2461
+ agent="^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->[\\s\\S]*(^|\\n)Reviewed-SHA: ${sha}(\\r?\\n|$)"
2462
+ has=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" count "$synth" 2>/dev/null || echo "")
2463
+ case "$has" in ''|*[!0-9]*) return 1 ;; esac
2464
+ if [ "$has" -gt 0 ]; then
2465
+ printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$synth" 2>/dev/null || return 1
2466
+ else
2467
+ printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies "$agent" 2>/dev/null || return 1
2468
+ fi
2469
+ }
2470
+ # FULL: the current head's bodies, exactly as before. BASE_SYNC_ONLY: the head has no reviewers, so the findings are the
2471
+ # carried head's, and only a VERIFIED carry record (trusted, same head, resolver agrees) selects that SHA. DELTA: reviewers
2472
+ # saw only the delta, so the earlier fully reviewed head's findings are read too (they must still reach the Phase 8 gates);
2473
+ # the §6B.4 head-SHA and provenance gates then drop any whose cited line is gone from the current head.
2474
+ PRIMARY_SHA="$REVIEW_SHA"; EARLIER_SHA=""
2475
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then
2476
+ CARRIED_FROM=$(carry_verified_from); [ -n "$CARRIED_FROM" ] && PRIMARY_SHA="$CARRIED_FROM"
2477
+ fi
2478
+ if [ "$REVIEW_MODE" = "DELTA" ] && is_sha "$DELTA_FROM"; then EARLIER_SHA="$DELTA_FROM"; fi
2479
+ SOURCE_BODIES=$(bodies_at "$PRIMARY_SHA") \
2480
+ || { echo "REVIEW_RESULT: status: BLOCKED, blocker: trusted reviewer comments unreadable (fail closed)"; exit 0; }
2481
+ # The panel guard guarantees at least one trusted reviewer body for this head; none means a misread, never "clean".
2482
+ [ -n "$SOURCE_BODIES" ] || { echo "REVIEW_RESULT: status: BLOCKED, blocker: no trusted reviewer comments for head ${PRIMARY_SHA} (fail closed)"; exit 0; }
2483
+ if [ -n "$EARLIER_SHA" ]; then
2484
+ EARLIER_BODIES=$(bodies_at "$EARLIER_SHA") && [ -n "$EARLIER_BODIES" ] \
2485
+ || { echo "REVIEW_RESULT: status: BLOCKED, blocker: earlier reviewed head ${EARLIER_SHA} findings unreadable (fail closed)"; exit 0; }
2486
+ SOURCE_BODIES=$(printf '%s\n%s' "$SOURCE_BODIES" "$EARLIER_BODIES")
2095
2487
  fi
2096
2488
  FINDINGS=$(printf '%s\n' "$SOURCE_BODIES" | jq -r 'scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null)
2097
2489
  ```
@@ -2161,7 +2553,8 @@ Filing a standalone `review-finding` issue for every LOW/POSSIBLE reviewer note
2161
2553
  - **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
2554
  - **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
2555
  - **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.
2556
+ - **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, OR >= 3 shared title tokens) 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`; an unreadable source only shrinks the TSV, it never blocks.
2557
+ - **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
2558
 
2166
2559
  ```bash
2167
2560
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.
@@ -2212,7 +2605,7 @@ fi
2212
2605
  # issue still open) or `accepted-risk:` become NOTEs. `not-affected` items never demote. Fail toward
2213
2606
  # filing: any missing input (no linked issue, no trusted contract, unparseable section, unreadable
2214
2607
  # 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.
2608
+ # verification (closed, unreadable, a PR, MERGE_ISSUE itself, no DEFERRED_FROM marker + path, or not authored by a trusted author) is dropped.
2216
2609
  CONTRACT_SCOPE_FILE=""; CONTRACT_OPEN_FILE=""; PR_FILES_FILE=""; CONTRACT_DEMOTED=0
2217
2610
  if [ -n "${MERGE_ISSUE:-}" ]; then
2218
2611
  # 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 +2635,9 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2242
2635
  && [ -s "$CONTRACT_SCOPE_FILE" ] \
2243
2636
  && gh pr diff {PR_NUMBER} -R {GH_REPO} --name-only > "$PR_FILES_FILE" 2>/dev/null; then
2244
2637
  # 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.
2638
+ # carries `FORGE:DEFERRED_FROM: #<MERGE_ISSUE>` plus the row's path, and is authored by a trusted author
2639
+ # (OWNER/MEMBER/COLLABORATOR or Bot; same predicate as trusted-comments.sh; missing fields fail closed).
2640
+ # Unverified rows are dropped.
2246
2641
  _verified=$(mktemp "$_scratch/{PR_NUMBER}_contract-verified.XXXXXX")
2247
2642
  while IFS="$(printf '\t')" read -r _disp _ipath _n; do
2248
2643
  if [ "$_disp" = "deferred" ]; then
@@ -2250,7 +2645,7 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2250
2645
  [ "$_n" = "${MERGE_ISSUE#\#}" ] && continue
2251
2646
  gh api "repos/{GH_REPO}/issues/$_n" 2>/dev/null \
2252
2647
  | 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 \
2648
+ '.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
2649
  || continue
2255
2650
  grep -Fxq -- "$_n" "$CONTRACT_OPEN_FILE" || echo "$_n" >> "$CONTRACT_OPEN_FILE"
2256
2651
  fi
@@ -2263,18 +2658,109 @@ if [ -n "${MERGE_ISSUE:-}" ]; then
2263
2658
  rm -f "$CONTRACT_BODY_FILE"
2264
2659
  fi
2265
2660
  fi
2661
+ PRIOR_FILE=""; DIFF_MAP_FILE=""; NOTES_REFLAG=0
2662
+ _pscr="${FORGE_SCRATCHPAD:-$PWD/.forge-scratch}"; mkdir -p "$_pscr"
2663
+ REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"
2664
+ if [ -z "${TRUSTED_SCRIPT:-}" ]; then
2665
+ # 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.
2666
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
2667
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
2668
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
2669
+ [ -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)"
2670
+ done
2671
+ TRUSTED_SCRIPT=""
2672
+ while IFS= read -r _c; do
2673
+ case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
2674
+ done <<< "$_tc"
2675
+ fi
2676
+ # Cross-head re-flag input (#3641). Fail soft: every unreadable source just leaves the TSV smaller, never blocks the review.
2677
+ # 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
2678
+ # (disposition: filed = linked issue OPEN, filed-closed = linked issue CLOSED, note = no linked issue). Every comment is read through
2679
+ # scripts/trusted-comments.sh, so an untrusted commenter cannot pre-post a finding to suppress a real one. With PF_SKIP set, reviewer
2680
+ # bodies for that head are skipped and only NOTE_DISPOSITION records carrying a different `reviewed_sha=` count (this head's own record,
2681
+ # from an earlier run of this same review, must never swallow its findings).
2682
+ pf_split_loc() { # PF_LOC="file[:N[-M]]" -> file<TAB>N<TAB>M (line columns empty when absent)
2683
+ local _loc="$PF_LOC" _file _r _a="" _b=""; _file="$_loc"
2684
+ case "$_loc" in *:[0-9]*) _r="${_loc##*:}"; case "$_r" in *[!0-9-]*|"") ;; *) _file="${_loc%:*}"; _a="${_r%%-*}"; _b="${_r#*-}"; [ "$_b" != "$_r" ] || _b="$_a" ;; esac ;; esac
2685
+ printf '%s\t%s\t%s\n' "$_file" "$_a" "$_b"
2686
+ }
2687
+ prior_findings_tsv() {
2688
+ local pr cj rows issues _id _c _s _loc _t _p _hit _f _a _b
2689
+ [ -n "${TRUSTED_SCRIPT:-}" ] || return 0
2690
+ rows=$(mktemp "${_pscr}/prior-rows.XXXXXX"); issues=$(mktemp "${_pscr}/prior-issues.XXXXXX")
2691
+ for pr in $PF_PRS; do
2692
+ case "$pr" in ''|*[!0-9]*) continue ;; esac
2693
+ cj=$(gh api --paginate "repos/${REPO}/issues/${pr}/comments" 2>/dev/null) || continue
2694
+ [ -n "$cj" ] || continue
2695
+ 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 \
2696
+ | jq -r --arg skip "${PF_SKIP:-}" 'select($skip == "" or (test("(^|\\n)Reviewed-SHA: " + $skip + "\\r?(\\n|$)") | not)) | scan("<!-- FINDING:([^>]+) -->") | .[0]' 2>/dev/null \
2697
+ | while IFS='|' read -r _id _c _s _loc _t; do
2698
+ [ -n "$_t" ] || continue
2699
+ _p=$(PF_LOC="$_loc" pf_split_loc); printf '%s\t%s\n' "$_p" "$(printf '%s' "$_t" | tr '\t' ' ')"
2700
+ done >> "$rows"
2701
+ printf '%s' "$cj" | bash "$TRUSTED_SCRIPT" bodies '^<!-- FORGE:NOTE_DISPOSITION' 2>/dev/null \
2702
+ | 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 \
2703
+ | sed -n 's/^- [^ ]* \([^ ]*\) \[[A-Za-z?]*\] \(.*\): [^:]*$/\1\t\2/p' \
2704
+ | while IFS="$(printf '\t')" read -r _loc _t; do
2705
+ [ "$_loc" != "?" ] || continue
2706
+ _p=$(PF_LOC="$_loc" pf_split_loc); printf '%s\t%s\n' "$_p" "$_t"
2707
+ done >> "$rows"
2708
+ gh issue list -R "$REPO" --label review-finding --state all --search "${pr} in:body" --limit 100 --json number,state,title,body 2>/dev/null \
2709
+ | jq -c '.[]' >> "$issues" 2>/dev/null || true
2710
+ done
2711
+ # Linkage: a filed issue whose body names the file and whose title shares >= 3 tokens with the finding (open preferred).
2712
+ sort -u "$rows" | while IFS="$(printf '\t')" read -r _f _a _b _t; do
2713
+ [ -n "$_f" ] || continue
2714
+ _hit=$(jq -rs --arg f "$_f" --arg t "$_t" 'def toks: ascii_downcase | [scan("[a-z0-9]{3,}")] | unique;
2715
+ ($t | toks) as $a | [ .[] | select((.body // "") | contains($f))
2716
+ | select((((.title // "") | toks) as $b | [$a[] | select(. as $x | $b | index($x))] | length) >= 3)
2717
+ | {n: .number, s: (.state | ascii_downcase)} ]
2718
+ | (map(select(.s == "open")) + map(select(.s != "open"))) | .[0] // empty | "\(.s)\t\(.n)"' "$issues" 2>/dev/null) || _hit=""
2719
+ case "$_hit" in
2720
+ open*) printf '%s\t%s\t%s\t%s\tfiled\t%s\n' "$_f" "$_a" "$_b" "$_t" "${_hit#* }" ;;
2721
+ closed*) printf '%s\t%s\t%s\t%s\tfiled-closed\t%s\n' "$_f" "$_a" "$_b" "$_t" "${_hit#* }" ;;
2722
+ *) printf '%s\t%s\t%s\t%s\tnote\t\n' "$_f" "$_a" "$_b" "$_t" ;;
2723
+ esac
2724
+ done
2725
+ rm -f "$rows" "$issues"
2726
+ }
2727
+ PRIOR_ROWS=$(PF_SKIP="$REVIEW_SHA" PF_PRS="{PR_NUMBER}" prior_findings_tsv)
2728
+ if [ -n "$PRIOR_ROWS" ]; then
2729
+ PRIOR_FILE=$(mktemp "$_pscr/{PR_NUMBER}_prior-findings.XXXXXX"); printf '%s\n' "$PRIOR_ROWS" > "$PRIOR_FILE"
2730
+ # 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.
2731
+ PRIOR_SHA=$(gh api --paginate "repos/{GH_REPO}/issues/{PR_NUMBER}/comments" 2>/dev/null \
2732
+ | 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 \
2733
+ | jq -r 'scan("(^|\\n)Reviewed-SHA: ([0-9a-f]{40,64})") | .[1]' 2>/dev/null | grep -vx "$REVIEW_SHA" | tail -n 1)
2734
+ _top=$(git rev-parse --show-toplevel 2>/dev/null) || _top=""
2735
+ if [ -n "$PRIOR_SHA" ] && [ -n "$_top" ]; then
2736
+ DIFF_MAP_FILE=$(mktemp "$_pscr/{PR_NUMBER}_prior-diffmap.XXXXXX")
2737
+ { git -C "$_top" diff -U0 --no-color --no-renames "$PRIOR_SHA" "$REVIEW_SHA" 2>/dev/null || true; } | while IFS= read -r _l; do
2738
+ case "$_l" in
2739
+ "+++ b/"*) _f="${_l#+++ b/}" ;;
2740
+ "@@ "*) _h="${_l#@@ -}"; _o="${_h%% *}"; _n="${_h#* +}"; _n="${_n%% *}"
2741
+ case "$_o" in *,*) _oc="${_o#*,}" ;; *) _oc=1 ;; esac; case "$_n" in *,*) _nc="${_n#*,}" ;; *) _nc=1 ;; esac
2742
+ printf '%s\t%s\t%s\t%s\t%s\n' "$_f" "${_o%%,*}" "$_oc" "${_n%%,*}" "$_nc" ;;
2743
+ esac
2744
+ done > "$DIFF_MAP_FILE" || : > "$DIFF_MAP_FILE"
2745
+ fi
2746
+ fi
2266
2747
  # Per deduped finding (FINDING_TEXT_FILE = mktemp file holding title + body + affected paths;
2267
2748
  # FINDING_FILE = the finding's file path, file:line accepted):
2268
2749
  # DISPOSITION=$(bash "$CLASSIFY_SCRIPT" --severity "$FINDING_SEVERITY" --confidence "$FINDING_CONFIDENCE" \
2269
2750
  # --agent "$FINDING_AGENT" --lineage "$FINDING_LINEAGE" --text-file "$FINDING_TEXT_FILE" \
2270
2751
  # ${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"})
2752
+ # ${CONTRACT_SCOPE_FILE:+--contract-scope "$CONTRACT_SCOPE_FILE" --contract-open "$CONTRACT_OPEN_FILE" --pr-files "$PR_FILES_FILE" --merge-issue "$MERGE_ISSUE"} \
2753
+ # ${PRIOR_FILE:+--prior-findings "$PRIOR_FILE" --title "$FINDING_TITLE" ${DIFF_MAP_FILE:+--diff-map "$DIFF_MAP_FILE"}} \
2754
+ # ${FINDING_LINE_START:+--line-start "$FINDING_LINE_START" --line-end "${FINDING_LINE_END:-$FINDING_LINE_START}"})
2272
2755
  # case "$DISPOSITION" in ISSUE*) ;; NOTE*) ;; INPR_FIX*) ;; esac # first word is the class, the rest is the reason
2273
2756
  # Contract-matched NOTE (`NOTE contract-deferred #N`): comment the finding (file:line + title) on the open
2274
2757
  # deferred issue instead of filing a new one, and count it:
2275
2758
  # 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
2759
  # CONTRACT_DEMOTED=$((CONTRACT_DEMOTED + 1))
2277
2760
  # `NOTE contract-accepted-risk` is counted in CONTRACT_DEMOTED too (no comment; the contract already records the reason).
2761
+ # 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
2762
+ # (`tracked on #N` or `already noted on an earlier head`), file nothing, and count it ONLY in notes_reflag (not notes_listed):
2763
+ # NOTES_REFLAG=$((NOTES_REFLAG + 1))
2278
2764
  ```
2279
2765
 
2280
2766
  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 +2772,15 @@ Each NOTE gets exactly one disposition, in this preference order:
2286
2772
 
2287
2773
  **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
2774
 
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.
2775
+ 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
2776
 
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.
2777
+ **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
2778
 
2293
2779
  ```bash
2294
2780
  CLASSIFIER_MODE=script # the §6B.5 guard blocks the review when the classifier is unresolvable, so this is always script
2781
+ 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
2782
  # 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} -->
2783
+ 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
2784
  $(cat "$NOTE_LIST_FILE" 2>/dev/null)" # allowlist:check-command-side-effects
2298
2785
  ```
2299
2786
 
@@ -2351,7 +2838,19 @@ MILESTONE_FLAG=""
2351
2838
  # milestone via Closes/Fixes/Resolves #N -> branch-slug match against
2352
2839
  # milestone/* branches) supersedes the old base/head-branch-only slug match
2353
2840
  # that used to be hand-rolled here.
2354
- MILESTONE_TITLE=$(bash scripts/derive-finding-milestone.sh "${PR_NUMBER}" -R "${REPO}")
2841
+ # 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.
2842
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
2843
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
2844
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
2845
+ [ -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)"
2846
+ done
2847
+ DERIVE_MILESTONE_SCRIPT=""
2848
+ while IFS= read -r _c; do
2849
+ case "$_c" in /*) [ -z "$DERIVE_MILESTONE_SCRIPT" ] && [ -f "$_c/scripts/derive-finding-milestone.sh" ] && DERIVE_MILESTONE_SCRIPT="$_c/scripts/derive-finding-milestone.sh" ;; esac
2850
+ done <<< "$_tc"
2851
+ # Unresolved helper: empty milestone (non-blocking), never a cwd-relative fallback.
2852
+ MILESTONE_TITLE=""
2853
+ [ -n "$DERIVE_MILESTONE_SCRIPT" ] && MILESTONE_TITLE=$(bash "$DERIVE_MILESTONE_SCRIPT" "${PR_NUMBER}" -R "${REPO}")
2355
2854
  # Quoted so multi-word milestone titles (e.g. "Watch & Fleet Observability") survive
2356
2855
  # as a single value wherever MILESTONE_FLAG is later interpolated — matches the
2357
2856
  # quoting convention already used for --title/--body-file/--label below.
@@ -2607,8 +3106,23 @@ FINDING_SEVERITY="LOW"
2607
3106
  # (CONFIRMED/LIKELY/POSSIBLE). scripts/severity-to-priority.sh is the single
2608
3107
  # source of truth for this mapping; commands/review-pr-staging.md calls the
2609
3108
  # 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=$?
3109
+ # 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.
3110
+ _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
3111
+ _tc="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGEDOCK_HOME:-}" "${FORGE_HOME:-}" "$_l" "$HOME/.claude/plugins/marketplaces/forgedock")"
3112
+ for _cfg in "${CLAUDE_CONFIG_DIR:-}" "$HOME/.claude"; do
3113
+ [ -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)"
3114
+ done
3115
+ PRIORITY_SCRIPT=""
3116
+ while IFS= read -r _c; do
3117
+ case "$_c" in /*) [ -z "$PRIORITY_SCRIPT" ] && [ -f "$_c/scripts/severity-to-priority.sh" ] && PRIORITY_SCRIPT="$_c/scripts/severity-to-priority.sh" ;; esac
3118
+ done <<< "$_tc"
3119
+ # Unresolved helper: non-zero status so finding creation is skipped (never defaulted to a priority).
3120
+ if [ -n "$PRIORITY_SCRIPT" ]; then
3121
+ FINDING_PRIORITY=$(bash "$PRIORITY_SCRIPT" "$FINDING_SEVERITY")
3122
+ FINDING_PRIORITY_EXIT=$?
3123
+ else
3124
+ FINDING_PRIORITY=""; FINDING_PRIORITY_EXIT=127
3125
+ fi
2612
3126
 
2613
3127
  # Exit code MUST be checked before use — severity-to-priority.sh exits 1 (empty stdout)
2614
3128
  # on a missing/unrecognized severity, mirroring the DEDUP_EXIT idiom above. Proceeding
@@ -3454,7 +3968,7 @@ A degraded run that skipped Task-based agent dispatch must be visible from this
3454
3968
 
3455
3969
  ```bash
3456
3970
  # 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}"
3971
+ PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REVIEW_MODE="{REVIEW_MODE}"
3458
3972
  # Same trust rule as Phase 3C/4: only trusted comments whose body starts with the marker count.
3459
3973
  # 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
3974
  _l="$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null || true)"; _l="${_l%/commands/work-on.md}"
@@ -3466,9 +3980,54 @@ TRUSTED_SCRIPT=""
3466
3980
  while IFS= read -r _c; do
3467
3981
  case "$_c" in /*) [ -z "$TRUSTED_SCRIPT" ] && [ -f "$_c/scripts/trusted-comments.sh" ] && TRUSTED_SCRIPT="$_c/scripts/trusted-comments.sh" ;; esac
3468
3982
  done <<< "$_tc"
3983
+ # Same trusted tiers as the TRUSTED_SCRIPT resolver above (_tc is that block's list); never the working directory or the PR checkout.
3984
+ REVIEW_DELTA_SCRIPT=""
3985
+ while IFS= read -r _c; do
3986
+ case "$_c" in /*) [ -z "$REVIEW_DELTA_SCRIPT" ] && [ -f "$_c/scripts/review-delta.sh" ] && REVIEW_DELTA_SCRIPT="$_c/scripts/review-delta.sh" ;; esac
3987
+ done <<< "$_tc"
3988
+ BASE_BRANCH=$(gh pr view "$PR_NUMBER" -R "$REPO" --json baseRefName --jq '.baseRefName' 2>/dev/null || echo "")
3989
+ COMMENTS_JSON=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null || echo "")
3990
+ # BEGIN review-pr-delta-mode
3991
+ is_sha() { case "${1}" in *[!0-9a-f]*|"") return 1 ;; esac; [ "${#1}" -eq 40 ] || [ "${#1}" -eq 64 ]; }
3992
+ # Prints "<MODE> <ARG> <FULL_ROUNDS>" from scripts/review-delta.sh: "FULL - n", "DELTA <reviewed-sha> n" or "BASE_SYNC_ONLY <reviewed-sha> n".
3993
+ # 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.
3994
+ review_delta_mode() {
3995
+ local out l1 l2 kind arg from to rounds=0 top
3996
+ is_sha "$REVIEW_SHA" || { echo "FULL - 0"; return 0; }
3997
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$REVIEW_DELTA_SCRIPT" ] || [ -z "$BASE_BRANCH" ]; then echo "FULL - 0"; return 0; fi
3998
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=""
3999
+ [ -n "$top" ] || { echo "FULL - 0"; return 0; }
4000
+ 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=""
4001
+ l1=$(printf '%s\n' "$out" | sed -n 1p); l2=$(printf '%s\n' "$out" | sed -n 2p)
4002
+ case "$l2" in FULL_ROUNDS=*) rounds="${l2#FULL_ROUNDS=}" ;; esac
4003
+ case "$rounds" in ''|*[!0-9]*) echo "FULL - 0"; return 0 ;; esac
4004
+ kind="${l1%% *}"; arg="${l1#* }"
4005
+ if [ "$kind" = "BASE_SYNC_ONLY" ] && is_sha "$arg" && [ "$arg" != "$REVIEW_SHA" ]; then echo "BASE_SYNC_ONLY $arg $rounds"; return 0; fi
4006
+ if [ "$kind" = "DELTA" ]; then
4007
+ from="${arg%%..*}"; to="${arg##*..}"
4008
+ if is_sha "$from" && [ "$to" = "$REVIEW_SHA" ] && [ "$from" != "$to" ]; then echo "DELTA $from $rounds"; return 0; fi
4009
+ fi
4010
+ echo "FULL - $rounds"
4011
+ }
4012
+ # Prints the Carried-From-SHA only when a TRUSTED FORGE:REVIEW-CARRIED record for this head names it AND the resolver
4013
+ # still answers BASE_SYNC_ONLY from that same reviewed SHA right now. Needs COMMENTS_JSON as well. Marker text alone never counts.
4014
+ carry_verified_from() {
4015
+ local kind from rounds n
4016
+ if [ -z "$TRUSTED_SCRIPT" ] || [ -z "$COMMENTS_JSON" ]; then return 0; fi
4017
+ read -r kind from rounds <<< "$(review_delta_mode)"
4018
+ [ "$kind" = "BASE_SYNC_ONLY" ] || return 0
4019
+ 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 "")
4020
+ case "$n" in ''|*[!0-9]*) return 0 ;; esac
4021
+ [ "$n" -ge 1 ] && printf '%s' "$from"
4022
+ return 0
4023
+ }
4024
+ # END review-pr-delta-mode
4025
+ # BASE_SYNC_ONLY: zero reviewers is the expected panel only when the carry record is trusted and the resolver re-verifies it.
4026
+ CARRIED_FROM=""
4027
+ if [ "$REVIEW_MODE" = "BASE_SYNC_ONLY" ]; then CARRIED_FROM=$(carry_verified_from); fi
3469
4028
  ACTUAL_AGENT_DOMAINS=""
3470
4029
  if [ -n "$TRUSTED_SCRIPT" ]; then
3471
- ACTUAL_AGENT_DOMAINS=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" 2>/dev/null \
4030
+ ACTUAL_AGENT_DOMAINS=$(printf '%s' "$COMMENTS_JSON" \
3472
4031
  | bash "$TRUSTED_SCRIPT" bodies "^<!-- FORGE:REVIEW-AGENT:[a-z-]+ -->" 2>/dev/null \
3473
4032
  | jq -rs '[.[] | scan("^<!-- FORGE:REVIEW-AGENT:([a-z-]+) -->") | .[0]] | unique | join(", ")' 2>/dev/null || echo "") # unresolvable => empty => count 0 (degraded, fail closed)
3474
4033
  fi
@@ -3478,9 +4037,12 @@ fi
3478
4037
  # second "0" line, corrupting the value to "0\n0" in exactly the zero-agent case this
3479
4038
  # check exists to detect. <!-- Added: forge#1849 -->
3480
4039
  ACTUAL_AGENT_COUNT=$(echo "$ACTUAL_AGENT_DOMAINS" | tr ',' '\n' | grep -c '.' 2>/dev/null)
4040
+ # A verified carried head dispatched no reviewers by design: report it as carried instead of a degraded zero-agent panel.
4041
+ if [ -n "$CARRIED_FROM" ]; then ACTUAL_AGENT_DOMAINS="carried from ${CARRIED_FROM}"; ACTUAL_AGENT_COUNT=0; fi
4042
+ echo "CARRIED_FROM: ${CARRIED_FROM}"
3481
4043
  ```
3482
4044
 
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.
4045
+ 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
4046
 
3485
4047
  ```bash
3486
4048
  # Each Bash call is a fresh shell: re-declare the state this block needs with the orchestrator's values.