@chrono-meta/fh-gate 1.4.86 → 1.4.88

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.
@@ -164,9 +164,41 @@ for f in "${FILES[@]}"; do
164
164
  # yields "9\n0" and the `-gt` guard goes silent. Verified as a known pair (both directions) in
165
165
  # scripts/test_degrade_scan_shell_probes.sh; narrowing without that anchor would just trade a
166
166
  # noisy probe for a blind one.
167
+ #
168
+ # WIDENED 2026-08-04 — the narrowing had gone one step too far, and the proof is that this
169
+ # probe was BLIND to the live instance that bit us the day before. PR #251 fixed
170
+ # `grep -c … | tr -d ' ' || echo 0` in session_close_check.sh ④-e; running this scanner over
171
+ # the pre-#251 file produced ZERO hits (re-measured: fixed rule = 2, old rule = 0).
172
+ #
173
+ # THE ANCHOR WAS ON THE WRONG PROPERTY. The old regex asked "is there an upstream pipe" and
174
+ # required the counter to be the LAST stage. Neither is the discriminator. The discriminator
175
+ # is: **does the failing side still EMIT?** `grep -c` prints "0" and exits 1 on no-match, so it
176
+ # disarms with no upstream pipe at all, and any trailing stage that passes that "0" through
177
+ # keeps the "0\n0" intact.
178
+ #
179
+ # A first fix widened to a NAMED filter list (tr|head|sed|…). Cross-family review (gpt-5.5,
180
+ # 2026-08-04) refuted it with reproduced counter-examples, and the refutation held on
181
+ # measurement in BOTH directions:
182
+ # · still missed, all verified to yield "0\n0": `grep -Ec …`, `grep --count …`, `grep -Fcx …`
183
+ # (combined/long flag forms the `-c` literal never matched) and `| cat`.
184
+ # · newly over-matched: `| tail -n +2` after the counter yields ONE line — not a disarm.
185
+ # Name-based approximation produced under-match and over-match simultaneously. So the trailing
186
+ # chain is no longer enumerated: the rule keys on the COUNTER (grep with a `c` in any flag
187
+ # cluster or `--count`; or `wc` behind a real pipe) plus the `|| echo 0` fallback, and accepts
188
+ # any chain between them.
189
+ #
190
+ # PRESERVED from the 2026-07-28 narrowing, still pinned as known-negatives: (a) `a || b ||
191
+ # echo 0` has no pipeline, and (b) a stage that emits NOTHING on failure (`| jq -r … ||
192
+ # echo 0`) — there the fallback supplies the only line, as intended.
193
+ # KNOWN RESIDUAL, stated not hidden: a trailing stage that can swallow the counter's output
194
+ # (`| tail -n +2`, `| sed -n '/[1-9]/p'`) is now flagged though it is not a disarm. That is a
195
+ # deliberate recall-over-precision trade on an ADVISORY probe, taken because the measured cost
196
+ # of the other direction was a real defect shipping. Revisit if non-fixture FPs appear.
197
+ # Known pair: scripts/test_degrade_scan_shell_probes.sh (9 positives / negatives silent).
198
+ # Reverting this line turns the positive lane red — checked by applying the revert.
167
199
  while IFS= read -r m; do
168
- emit "$f" "${m%%:*}" "S5:pipefail-fallback(sh)" "\`|| echo 0\` fallback on a pipeline ending in a counter (grep -c/wc) — that stage emits even when an upstream stage fails, so under \`set -o pipefail\` the value gains a SECOND line, the integer comparison errors out, and the guard passes silently; split the pipeline and sanitize to an integer"
169
- done < <(grep -nE '[^|]\|[[:space:]]*([a-z]+[[:space:]]+)*(grep[^|]*-c|wc)[^|]*\|\|[[:space:]]*echo[[:space:]]+[\"'"'"']?0' "$f" 2>/dev/null \
200
+ emit "$f" "${m%%:*}" "S5:pipefail-fallback(sh)" "\`|| echo 0\` fallback after a COUNTER (grep with -c / --count, or wc) — the counter PRINTS \"0\" and exits non-zero on no-match, so the fallback appends a SECOND line, the value becomes \"0\\n0\", the integer test dies with 'integer expression expected' (stderr only) and the guard goes silent; split it and sanitize: n=\$(...); n=\${n:-0}; case \$n in *[!0-9]*) n=0;; esac. VERIFY the trailing stage can actually emit — if it swallows the count (| tail -n +2), this is a known false positive"
201
+ done < <(grep -nE '(([^|]\|[[:space:]]*([a-z]+[[:space:]]+)*wc[^|]*)|(grep([[:space:]]+-[A-Za-z]*c[A-Za-z]*|[[:space:]]+--count)[^|]*))([^|]*\|[^|]*)*\|\|[[:space:]]*echo[[:space:]]+[\"'"'"']?0' "$f" 2>/dev/null \
170
202
  | grep -vE '^[0-9]+:[[:space:]]*#' \
171
203
  | grep -vE '#[[:space:]]*noqa[:[:space:]]*degrade')
172
204
  fi
@@ -117,6 +117,31 @@ GUARD_PATHSPEC=(
117
117
  'AGENTS.md'
118
118
  'docs/*.md'
119
119
  'templates/*.md'
120
+ # ── Added 2026-08-04 — the pathspec had drifted from the 4-axis ASSET LIST it exists to gate.
121
+ # Measured that day by mechanically walking every asset class in CLAUDE.md §FH Improvement
122
+ # 4-Axis against this array: four classes had NO covering pattern, and two of them are this
123
+ # repo's own mechanical floor — `templates/.git-hooks/*` (the hook that hard-blocks commits)
124
+ # and `templates/*.sh` (THIS FILE — the guard could not guard itself). The pre-commit HEAVY
125
+ # classifier already listed `^scripts/.*\.sh$`, so a scripts-only change entered the heavy
126
+ # path and then met Axis 1 returning `SKIP (not-checked, NOT a pass)`. Observed three times
127
+ # on 2026-08-04 alone, the third being the S5 fix (PR #253) — a change to a verdict-surface
128
+ # scanner that shipped with zero Axis-1 coverage.
129
+ #
130
+ # Calibrated before shipping, both directions (a widened copy, run against real history):
131
+ # · 12 consecutive historical commits touching scripts/*.sh → M=0 S=0 (no FP storm), and a
132
+ # control confirmed the files were actually SELECTED (4 files checked) rather than the
133
+ # zero coming from an inert pathspec — the unwired-instrument reading of a clean 0.
134
+ # · known-positive: truncating a shipped .sh by 60% → M-TIER BLOCK (`BLOCK` token dropped
135
+ # 100%, 60% line reduction). The md-shaped checks (F1 frontmatter, F2 sections, F3 fences)
136
+ # are inert on shell files; F4 keyword / F5 xref / F6 line-loss / F7 syntax are the ones
137
+ # that carry, and they are exactly the regressions that matter in a gate script.
138
+ # NOTE on globbing: git pathspec `*` crosses `/` (verified here — `templates/*.md` matches
139
+ # `templates/.claude/rules/session.md`), so these single-star forms are recursive.
140
+ 'scripts/*.sh'
141
+ 'templates/*.sh'
142
+ 'templates/.git-hooks/*'
143
+ 'plugins/*/agents/*.md'
144
+ '.claude/agents/*.md'
120
145
  )
121
146
 
122
147
  # 계기 무결: base ref 가 안 풀리면 diff 실패가 2>/dev/null 로 삼켜져 CHANGED 공백 →