# 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.
#
# WARNING (#2501): a pin skips the ENTIRE installed file, not just the section
# it exists to protect. Every unrelated upstream fix to that file is blocked
# too, indefinitely, and nothing reports the growing drift (champion-epic.md
# drifted far behind upstream this way; see #2494). Before adding a pin, consider
# upstreaming the change instead; when you do pin, record what it protects
# and when it can be dropped. The drift inventory and retirement conditions
# for existing pins live in docs/loom-pin-drift.md (repo-owned, not resynced).
#
# 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).

# 2am#4345 pin audit (operator ruling 2026-10-15, Q5 option 1) RETIRED these
# pins; each file now carries the installed v0.19.1160 upstream content:
#   - commands/loom/champion-epic.md (#606): upstream Step 2.75 (#6601), the
#     unrevised-epic cap (#5865), OPERATOR_RULED (#7921) and ALREADY_ROUTED
#     hold skip (#7734) cover it; the 4 drift suites pass only on upstream.
#   - commands/loom/champion.md (#662): upstream drops held epics itself (#7734).
#   - scripts/verdict-staleness-guard.sh + test (#1356): fixed upstream as
#     rjwalters/loom#6799.
#   - commands/loom/champion-pr-merge.md + docs/label-state-machine.md (#1749,
#     #2549): the frozen copy re-added `loom:operator` to migrated holds,
#     stripped `loom:blocked` in Capped-PR Recovery and merged without the head
#     pin (#11871/#11888/#11945); #1749's propagation is obsolete (the daemon
#     never dispatches an issue with an open linked PR, #4123/#8865) and #2549's
#     exit-code wiring is upstream. The repo-owned
#     test-champion-pr-hold-issue-propagation.sh is deleted with it.
#   - scripts/check-promotion-landed.sh + test (#2193): upstream refuses to
#     write over any hold (#11512, #11795).
# hermit-patterns, builder-pr (both copies), sweep-lease-fence,
# verify-proposal-refs and pr-worktree stay pinned below but were RE-PORTED
# onto current upstream in the same change (only their local delta remains).
# curator/judge/doctor (#668) were unpinned by #1966 PR 2; dep-recheck-fingerprint stays.

# #668 (RETIRED by #1966 PR 2): commands/loom/{curator,judge,doctor}.md used to
# be pinned whole-file to protect an inline "Frozen-Timeline Fallback" (the
# 2026-08-10 reclaim livelock on #509). That protection now lives in the
# pinned scripts/claim-staleness.sh below (#1966 PR 1, covered by
# scripts/tests/test-claim-staleness.sh T23-T32), which the upstream role
# files already drive, so the three role files track upstream again.

# #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 (RETIRED 2026-09-26 by #2543): scripts/lib/forge-helpers.sh and
# scripts/merge-pr.sh used to be pinned here to preserve a local-only
# App-installation permission-scope 403 retry (originally #1308, closing
# #1293). That pin's own stated removal condition — "if the upstream Loom
# project ever adopts an equivalent retry, these pins can be dropped" — is now
# satisfied, verified against rjwalters/loom @ 7259b24cb:
#   * `is_app_permission_error()` is byte-identical upstream (same
#     "not accessible by integration" match).
#   * Upstream's `forge_gh_perm_safe` is `forge_cmd_perm_safe gh "$@"`, and
#     `forge_cmd_perm_safe` runs the identical 3-rung ladder the local fix
#     shipped (ambient -> freshly minted installation token -> personal
#     token / personal-ambient), plus an extra owner-config rung (2am#446).
#   * `forge_merge_pr()`'s GitHub PUT still routes through
#     `forge_gh_perm_safe` upstream (both the with- and without-`sha` arms).
#   * The pin's second half is moot: upstream #8410/#8427 retired the native
#     `loom-daemon forge auto-merge` dispatch and `forge_auto_merge()`
#     entirely, so the 403-on-the-native-path case the local fallback
#     handled no longer exists — `merge-pr.sh --auto` now settles checks and
#     merges in-process through the same ladder-routed `forge_merge_pr`.
# Dropping the pin is what lets #7754/#8845's repo-aware merge-method
# detection (`forge_detect_merge_method`, `REPO_MERGE_METHOD`,
# `--merge-method`) reach this repo, which only allows merge commits —
# hardcoded squash 405'd every merge here. Do NOT re-add these paths without
# re-checking the ladder above.

# #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.
#
# #2437 (2026-10-09): re-evaluated against upstream rjwalters/loom#8510
# (merged 418d1badca76eb8449962f89b1c8830105554dbc — repo-aware
# `owner/repo#N` named dependencies) and the installed runtime the upstream
# thin-stub wrapper would exec (loom-daemon 0.19.950, source 72047016). The
# pins are RETAINED: resyncing would regress protected local behavior, shown
# by running this repo's test-dep-recheck-fingerprint.sh against the upstream
# wrapper + installed daemon (18 failures) and by direct probes:
#   - #1671: a prose-prefixed item (`- [ ] Landing of #1607 (Closes #1600)`)
#     is dropped entirely -> DEPS='' / false VERDICT=clear (upstream accepts
#     only a fixed phrase/`PR `/`Issue ` token before the ref).
#   - #1569: multiple Dependencies sections are UNIONED, not last-wins, so a
#     stale original section's refs leak into DEPS/the verdict.
#   - #1546/#1544: the dep-recheck hash formula differs (narrowed label
#     component, `n/a` merge bucket, canonicalized block reason) with no
#     FORMULA_VERSION output, and every KEY=VALUE value is now shell-quoted
#     (`DEPS=''`, `DEPS=$'...'`) — byte changes to every consumer-visible line.
# Migration chosen instead: the upstream routing/output CONTRACT was ported
# into this pinned shell script — an unchecked `owner/repo#N` is read in that
# repo (never the invoking one, fail closed on lookup failure), a bare `#N`
# in the invoking repo (`--repo` or cwd default), cross-repo DEPS lines render
# `owner/repo#N:<state>`, and same-repo lines stay the bare `N:<state>` so no
# same-repo named-dependency hash moves (pinned by T18a); FORMULA_VERSION
# stays v1 because the dep-recheck/operator-premise formulas are untouched.
# Repo-aware regressions live in test-dep-recheck-fingerprint.sh (T18a-T18j).
# Drop these pins only when upstream's runtime passes that whole local suite
# (or an intentional, versioned migration of the local consumers lands).
scripts/dep-recheck-fingerprint.sh
scripts/tests/test-dep-recheck-fingerprint.sh

# #1966 (PR 1 of the operator's two-PR "Option B, gated" plan, ruled
# 2026-09-18): scripts/claim-staleness.sh (and its test) carry the local-only
# frozen-timeline protection that the #668 pin above has so far kept as inline
# prose in curator.md/judge.md/doctor.md -- relocated into the shared script
# upstream's role files already drive, mirroring the #1546 pin's shape (an
# isolated script pin instead of whole role-file pins). When the `check`
# subcommand reaches a `stale`/`stale-bounded-fallback` verdict it now (a)
# re-reads the issue/PR resource itself (labels + updated_at, a NON-timeline
# read) and fails safe to `unknown` if that read fails or is rate-limited, and
# (b) runs a reclaim-loop guard: a trusted reclaim comment newer than
# CLAIMED_AT (the #668 `<!-- loom:reclaimed claim=... -->` marker or
# upstream's marker-less "Reclaiming ... claim" prose) plus a ground-truth
# updated_at past CLAIMED_AT means /timeline is frozen, so the verdict is
# downgraded to `fresh` (FROZEN_TIMELINE=true) -- one reclaim per frozen
# CLAIMED_AT instead of the 30+ that hit #509 on 2026-08-10. The test's
# T23-T32 cases reproduce #509 and the fail-safe paths. Upstream's
# defaults/scripts/claim-staleness.sh has no equivalent (verified 2026-10-02
# against rjwalters/loom @ 65442ba48: no `updatedAt`/`frozen`/`RECLAIM`
# anywhere in it), so an unpinned resync would silently revert it -- the same
# reversion pattern as #606/#662/#1292/#1316/#1356/#1546/#1674/#1898. The #668
# whole-file pins above stay until PR 2 of #1966 unpins them, which the
# operator gated on THIS pin landing and being verified (done). If the upstream Loom
# project ever adopts an equivalent frozen-timeline guard, these pins can be
# dropped and the files resynced normally.
scripts/claim-staleness.sh
scripts/tests/test-claim-staleness.sh

# #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

# #2128 (commit d0b770ad): verify-proposal-refs.sh and
# tests/test-verify-proposal-refs.sh carry a local-only check of BARE
# `name.ext:L` / `name.ext:L-M` citations (no directory). A bare basename is
# considered only when its extension is in a known-extension set (so prose
# and host:port strings are not read as files); it is then resolved to a
# unique tracked path and its line range checked, reported as MISSING when no
# tracked file has that name and AMBIGUOUS when several do. Fixtures 6-9b and
# the bare-sibling case in the test suite assert it. Upstream checks only
# directory-qualified paths, so an unpinned resync would silently drop the
# check (17 local cases fail against upstream's script).
#
# History: this pin was added for #1898 (the SIGPIPE race / subshell cache
# bug, #1863 / PR #1881, reverted by resync fcbf52f8). That fix IS upstream
# now (rjwalters/loom#7736 / #7771, `full_tree_contains`), so #1898 no longer
# needs the pin; the 2am#4345 audit (2026-10-15) re-ported both files onto
# v0.19.1160 upstream with only the #2128 delta on top.
# Drop this pin only when upstream resolves bare `name.ext:L` citations
# (unique-basename resolution with MISSING/AMBIGUOUS reporting).
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 (#1898 since
# upstreamed). 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

# #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 (since
# upstreamed)/#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
