# Pins intentional per-repo customizations to installed Loom surfaces so
# `resync-installed.sh` (a routine `chore(loom): resync installed surfaces`
# commit) never overwrites them with upstream Loom's template. One relative
# path per line (relative to .loom/, e.g. "hooks/foo.sh", "roles/bar.md",
# "commands/loom/mine.md"); blank lines and `#` comments are ignored. See
# resync-installed.sh's header comment ("Local-override convention") for the
# full path-shape reference.
#
# NOTE: `scripts/tests/ci-wired.txt` (and its sibling `ci-excluded.txt`) does
# NOT belong in this list, and never has — it is upstream Loom's own CI
# manifest for upstream Loom's own test suite, has zero effect on this repo's
# CI (no workflow here calls `run-ci-suites.sh`), and pinning it would freeze
# it against upstream's own future additions. If you're here because a
# locally-added `.loom/scripts/tests/test-*.sh` suite needs CI wiring, see
# `.loom/docs/local-test-suite-wiring.md` instead (issue #1532).

# #606: champion-epic.md carries a local-only "Re-approval Guard and Rejection
# Idempotency" section (originally #532/#533) that stops duplicate Phase 1
# issue creation on an already-approved epic (epic #375 had its Phase 1
# created three times — #379-381, #376-378, #529-531 — before this guard
# existed) and caps unrevised-rejection loops (#520, rejected 3x with
# identical feedback) with an N=2 escalation to loom:operator-only. This
# section was never upstreamed into rjwalters/loom's own champion-epic.md
# template, so an unpinned resync silently reverted it once already (commit
# 71d46d0, 2026-08-05) — this pin is what prevents a recurrence. If the
# upstream Loom project ever adopts an equivalent mechanism, this pin can be
# dropped and the file resynced normally.
commands/loom/champion-epic.md

# #662: champion.md's Priority 4 (epic-discovery) query carries a local-only
# 5-clause `select()` exclusion filter (loom:evaluating/loom:operator-only/
# loom:blocked/loom:issue/loom:building), matching the filter already used by
# Priorities 2/3, added by #609 (closing #608) so that already-escalated
# epics (e.g. epic #520, repeatedly rejected and capped to
# loom:operator-only) stop being re-surfaced by Champion on every pass. This
# filter was never upstreamed into rjwalters/loom's own champion.md template,
# so an unpinned resync silently reverted it once already (commit 1ae78c6,
# 2026-08-09) — this pin is what prevents a recurrence. If the upstream Loom
# project ever adopts an equivalent filter, this pin can be dropped and the
# file resynced normally.
commands/loom/champion.md

# #668: curator.md/judge.md/doctor.md's "Stale `loom:curating`/`loom:reviewing`/
# `loom:treating` Claim Check" sections carry a local-only "Frozen-Timeline
# Fallback" -- a `RECLAIM_MARKER`/`RECLAIM_COUNT` cross-check plus a
# `gh issue|pr view --json labels,updatedAt` ground-truth read -- added after a
# live-verified false-positive livelock on issue #509 (2026-08-10): a real
# label toggle was confirmed via `gh issue view --json labels`, but
# `/events`/`/timeline` kept returning the same stale `labeled` event for
# several minutes, so `CLAIMED_AT` never advanced and the same "Reclaiming
# stale claim..." comment fired 30+ times. This mitigation was never
# upstreamed into rjwalters/loom's own templates, so an unpinned resync would
# silently drop it and reintroduce the livelock. If the upstream Loom project
# ever adopts an equivalent mitigation, these pins can be dropped and the
# files resynced normally.
commands/loom/curator.md
commands/loom/judge.md
commands/loom/doctor.md

# #1292: hermit-patterns.md's `stateless_ceremony` AST detector carries two
# local-only exclusions added by #1288 (closing #920) — exclusion 4 skips
# custom exception subclasses (base name ending in Error/Exception/Warning,
# which must be classes to be raised/caught) and exclusion 5 skips declarative
# data containers (classes whose body is only class-level `AnnAssign`
# annotations, e.g. dataclasses). Without them the detector was a 100%
# false-positive generator on this tree (107 hits, all false; 8 after the
# fix), which is the recurring-waste failure #920 was filed to end. These
# exclusions were never upstreamed into rjwalters/loom's own
# hermit-patterns.md template (which still has only exclusions 1-3), so an
# unpinned resync would silently revert them and restore the false-positive
# rate with no signal — the same reversion that already happened to #606
# (commit 71d46d0) and #662 (commit 1ae78c6). If the upstream Loom project
# ever adopts an equivalent exclusion, this pin can be dropped and the file
# resynced normally.
commands/loom/hermit-patterns.md

# #1316: scripts/lib/forge-helpers.sh's forge_merge_pr()/forge_auto_merge()
# and scripts/merge-pr.sh's native `loom-daemon forge auto-merge` fallback
# carry a local-only App-installation permission-scope 403 retry (originally
# #1308, closing #1293): forge_merge_pr()/forge_auto_merge() route their `gh
# api` calls through forge_gh_perm_safe (fresh installation-token retry on a
# 403 "not accessible by integration"), and merge-pr.sh's native path detects
# that same 403 via is_app_permission_error() and falls through to the
# (fixed) shell forge_auto_merge path instead of just retrying the native
# binary call again. This fix was never upstreamed into rjwalters/loom's own
# templates, so an unpinned resync silently reverted it once already (commit
# 1edbd2b, "chore: resync installed Loom surfaces") even though #1308 had
# already merged to main — the same reversion pattern as #606/#662/#1292.
# These pins are what prevent a recurrence. If the upstream Loom project ever
# adopts an equivalent retry, these pins can be dropped and the files
# resynced normally.
scripts/lib/forge-helpers.sh
scripts/merge-pr.sh

# #1356: verdict-staleness-guard.sh (and its test) carry a local-only fix
# dropping the nonexistent `merged` field from its `gh pr view --json` request
# and deriving merged-ness from `state == "MERGED"` instead. `gh pr view` has
# no `merged` field and `gh` validates the field list up front, so upstream's
# `--json headRefOid,labels,state,merged` fails the WHOLE call with
# `Unknown JSON field: "merged"` on every currently shipping `gh` (reproduced
# on 2.96.0 and 2.98.0) — which silently darkened step 0 of every Judge pass
# (the Stale-Verdict Sweep) and Champion's verdict-freshness check for every
# verdict-bearing PR. The companion test stubs `gh`'s field-list validation so
# the regression cannot return. This fix was never upstreamed into
# rjwalters/loom's own templates, so an unpinned resync would silently revert
# it — the same reversion that already happened to #606 (commit 71d46d0),
# #662 (commit 1ae78c6), and #1316 (commit 1edbd2b). If the upstream Loom
# project ever adopts an equivalent fix, these pins can be dropped and the
# files resynced normally.
scripts/verdict-staleness-guard.sh
scripts/tests/test-verdict-staleness-guard.sh

# #1546: scripts/dep-recheck-fingerprint.sh's FORMULA_VERSION guard (originally
# #1544/#1545, commit a6f9997) — a literal `FORMULA_VERSION="v1"` constant
# folded into every `dep-recheck`/`operator-premise` marker alongside
# CONCLUSION_HASH so a consumer (check-dep-recheck-idempotency.sh) can tell
# "the hash formula changed under us" apart from "the dependency state
# changed" and never misread the former as the latter — was reverted wholesale
# by an unpinned resync (commit 090cd9c, ~51 minutes after #1545 merged), and
# a second resync landed one minute later (commit fdc5018) compounding the
# damage: it added a brand new `named-dependency` subcommand on top of the
# already-reverted base without restoring the guard, so the file ended up with
# MORE functionality than #1545 shipped but WITHOUT the fix #1545 added. This
# guard was never upstreamed into rjwalters/loom's own dep-recheck-fingerprint.sh
# template (klayout-tools is the only consumer of curator.md's versioned
# `<!-- curator:dep-recheck:<version>:<hash> -->` marker format), so an
# unpinned resync will keep reverting it indefinitely — the same reversion
# pattern as #606/#662/#1292/#1316/#1356. These pins are what prevent a third
# occurrence. If the upstream Loom project ever adopts an equivalent
# formula-version guard, these pins can be dropped and the files resynced
# normally.
scripts/dep-recheck-fingerprint.sh
scripts/tests/test-dep-recheck-fingerprint.sh

# #1749: champion-pr-merge.md carries a local-only propagation from a held
# PR's `loom:operator` state onto the issue(s) it would close — when the
# merge-risk hold (criterion #2) or the critical-file hold (criterion #3)
# applies `loom:operator` to a PR, the linked issue(s) (via
# `closingIssuesReferences`) are marked `loom:blocked` with a machine-
# parseable `Blocked by #<PR>` comment, and the release paths remove it again.
# Without this, the daemon's work-finder kept re-dispatching a no-op sweep on
# the issue every cycle even though its sole open PR was already held for
# human merge (`loom-daemon`'s `PARK_LABELS` hard-skips `loom:blocked`, but
# had no signal that the issue was, transitively, also stuck on a human) —
# confirmed recurring three times on issue #1651 / PR #1659 before this fix.
# This was never upstreamed into rjwalters/loom's own champion-pr-merge.md /
# label-state-machine.md templates, so an unpinned resync would silently drop
# it — the same reversion pattern as #606/#662/#1292/#1316/#1356/#1546/#1674.
# If the upstream Loom project ever adopts an equivalent propagation, these
# pins can be dropped and the files resynced normally.
commands/loom/champion-pr-merge.md
docs/label-state-machine.md

# #1674: builder-pr.md carries a local-only "Quoting a `Closes #N`-shaped
# example inside a PR body closes an UNRELATED issue" section (added after
# the 2026-09-11 incident where PR #1671's inline-code-quoted illustrative
# example — `(Closes #1600)`, describing a *different* issue's checklist
# wording — was matched by GitHub's plain-text auto-close scanner anyway and
# falsely closed issue #1600, still-open implementation PR and all). The
# section documents the convention (break the keyword/#N adjacency in quoted
# example text) and points at the new `check-pr-body-closing-keywords.sh`
# guard. This was never upstreamed into rjwalters/loom's own builder-pr.md
# template, so an unpinned resync would silently drop it — the same
# reversion pattern as #606/#662/#1292/#1316/#1356/#1546. If the upstream
# Loom project ever adopts an equivalent section, this pin can be dropped and
# the file resynced normally.
commands/loom/builder-pr.md

# #1898: verify-proposal-refs.sh and tests/test-verify-proposal-refs.sh carry
# a fix for a SIGPIPE race under `set -o pipefail` and a subshell-in-pipe cache
# bug (closing #1863 via PR #1881), both issues that produced flaky false
# "MISSING FILE" reports on paths that actually exist, especially early-sorting
# paths like `docs/...` entries. The fix replaces a `full_tree()` helper
# function and `full_tree | grep -qFx` pipe with a direct `FULL_TREE_CACHE`
# assignment and here-string match (`grep -qFx "$path" <<< "$FULL_TREE_CACHE"`),
# and restores `Fixture 5b` (8-iteration flake-detection loop) to the test suite.
# A same-day resync commit (fcbf52f8, 2026-09-15T17:03:12Z) reverted both halves
# of that fix back to the buggy pre-#1881 state, so this pin prevents an
# unlimited stream of recurrences of the same reversion pattern as #606/#662/
# #1292/#1316/#1356/#1546/#1674. This fix was never upstreamed into
# rjwalters/loom's own templates, so an unpinned resync would keep reverting it.
# If the upstream Loom project ever adopts an equivalent SIGPIPE race fix and
# flake-detection test, this pin can be dropped and the files resynced normally.
scripts/verify-proposal-refs.sh
scripts/tests/test-verify-proposal-refs.sh

# #2020: pr-worktree.sh carries a local-only call to the repo-owned
# .loom/hooks/post-worktree.sh (a `run_post_worktree_hook` helper, invoked
# both from the "reuse an existing PR worktree" path and the "freshly
# created PR worktree" path) so a PR review worktree gets its own uv-synced
# venv + editable install, the same treatment worktree.sh already gave issue
# worktrees. Without it, `pytest` run inside a `.loom/worktrees/pr-<N>/`
# review worktree could silently import the MAIN CHECKOUT's src/ via a stale
# editable install recorded elsewhere -- a false negative on a PR that fixes
# a bug (old code still runs) or a false positive on a PR that introduces
# one (discovered evaluating PR #2013, reproduced in
# `.loom/worktrees/pr-2013`). This call was never upstreamed into
# rjwalters/loom's own pr-worktree.sh template (klayout-tools is a uv-based
# Python project; upstream's own template has no Python-specific setup
# step), so an unpinned resync would silently drop it -- the same reversion
# pattern as #606/#662/#1292/#1316/#1356/#1546/#1674/#1898. If the upstream
# Loom project ever adopts an equivalent post-worktree-hook call from
# pr-worktree.sh, this pin can be dropped and the file resynced normally.
scripts/pr-worktree.sh

# #2193: check-promotion-landed.sh (and its test) carry a local-only "Step 1c"
# guard that treats a current `loom:operator-only` label exactly like the
# `loom:building`/`loom:blocked` labels its Step 1b later-lifecycle guard
# already recognized -- DECISION=OK (or ALREADY_ESCALATED when this script's
# own `<!-- champion:promotion-landed-mismatch -->` marker comment shows a
# prior run raised the escalation), never a label write. Upstream's version
# checks `loom:operator-only` ONLY inside the tier-UNRECOVERABLE escalation
# branch, so an issue with a stale `Champion Review: APPROVED` comment, no
# `loom:issue`, and a recoverable tier took the DECISION=COMPLETED branch and
# re-applied `loom:issue` + tier on top of a live human escalation. Observed
# live on issue #1779 (2026-09-20): approved 2026-09-14, weeks of legitimate
# loom:blocked/loom:curated dependency cycling, correctly re-escalated to
# loom:operator-only,loom:operator-decision, then overwritten ~15 hours later
# by Pass 0c -- bypassing a pending human ruling, caught and reverted by hand
# before a Builder claimed it. This contradicts champion-issue-promo.md /
# champion-pr-merge.md's own "When NOT to Promote" rule (Champion must never
# write loom:issue onto a loom:operator-only issue). The fix was not upstreamed
# into rjwalters/loom's own check-promotion-landed.sh template, so an unpinned
# resync would silently revert it -- the same reversion pattern as
# #606/#662/#1292/#1316/#1356/#1546/#1674/#1898/#2020. If the upstream Loom
# project ever adopts an equivalent guard, these pins can be dropped and the
# files resynced normally.
scripts/check-promotion-landed.sh
scripts/tests/test-check-promotion-landed.sh

# #2237 (2026-09-21): sweep-lease-fence.sh's `check` subcommand (and its test
# suite, and the pre-push call site in builder-pr.md) carry a local-only
# `--sweep-id ID` parameter. Upstream's `cmd_check` has NO sweep-id parameter
# at all: its EXPIRED-ownership test is `[[ "$lease_host" == "$host" ]]` and
# its SUPERSEDED test is `[[ "$lease_host" != "$host" ]]` -- host only, never
# the freshest lease's own `sweep=` field. So two independent dispatches on
# the SAME host are indistinguishable to the fence, and an abandoned
# same-host lease record left behind by a dead predecessor sweep permanently
# aborts (exit 3, EXPIRED) every successor dispatch on that issue, forever --
# a dead sweep's never-renewed, never-yielded record can never stop being the
# freshest lease comment on its own. Observed live on issue #2226
# (2026-09-21): dispatch `sweep-issue-2226-1789988240`, whose own renewal
# loop was alive and healthy the whole time, was fenced out by the expired
# lease of `sweep-issue-2226-1789985384` on the same host; the Builder had to
# hand-publish a real lease with `sweep-lease-publish.sh` to get unstuck.
# This is the same failure #6783 already fixed for the DIFFERENT-host case
# upstream -- the same-host/different-sweep case was simply never reachable
# there, because `check` cannot know its own sweep id. `--sweep-id` closes
# that hole by making "this dispatch's own lease" a (host, sweep) pair, the
# identity `sweep-lease-publish.sh --sweep-id` (which DOES take one) already
# publishes. Omitting the flag keeps the exact pre-#2237 host-only behavior,
# so an un-updated caller is unaffected.
#
# The fix was not upstreamed into rjwalters/loom's own sweep-lease-fence.sh
# template, so an unpinned resync would silently revert it -- the same
# reversion pattern as #606/#662/#1292/#1316/#1356/#1546/#1674/#1898/#2020/
# #2193. `roles/builder-pr.md` is pinned here too because `.loom/roles/` is
# resynced from upstream's own `defaults/roles/` tree, NOT regenerated from
# this repo's `.claude/commands/loom/` copy -- an unpinned `roles/` file is
# reverted independently of its `commands/loom/` twin (the two have already
# drifted apart on this repo for exactly that reason). Its `commands/loom/`
# twin needs no new entry: `commands/loom/builder-pr.md` is ALREADY pinned by
# the #1674 entry above -- do not drop that pin without moving its path here,
# or this fix's call-site half goes back to upstream's sweep-id-less form.
# If the upstream Loom project ever adopts an equivalent `--sweep-id`
# parameter, these pins can be dropped and the files resynced normally.
scripts/sweep-lease-fence.sh
scripts/tests/test-sweep-lease-fence.sh
roles/builder-pr.md
