@chrono-meta/fh-gate 1.4.87 → 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.
@@ -11,13 +11,13 @@
11
11
  "plugins": [
12
12
  {
13
13
  "name": "fh-meta",
14
- "version": "1.4.87",
14
+ "version": "1.4.88",
15
15
  "description": "Hub meta-operations toolkit — 35 skills + 7 agents. New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
16
16
  "source": "./plugins/fh-meta"
17
17
  },
18
18
  {
19
19
  "name": "fh-commons",
20
- "version": "1.4.87",
20
+ "version": "1.4.88",
21
21
  "description": "Project-agnostic utility skills — 4 skills (convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate) + 1 agent (quench-challenger). Domain-independent utilities transplantable into any project.",
22
22
  "source": "./plugins/fh-commons"
23
23
  }
@@ -944,3 +944,55 @@
944
944
  entry per the "counts and outcomes, not one stub per dispatch" rule.
945
945
  Instrument note: the invocation form matters — `| codex exec -` (stdin) as recorded in
946
946
  [[feedback_sidecar_liveness_not_passive_wait]]; the argv form hangs nondeterministically.
947
+
948
+ - date: '2026-08-04'
949
+ agent: codex/gpt-5.5 (cross-family sidecar, auto-decorrelation)
950
+ context: >-
951
+ FH self-dev — two load-bearing gate changes (PR #253 degrade-scan S5 rule, PR #254/#255 Axis-1
952
+ pathspec parity + anchor wiring). Pre-push gate demanded a cross-family leg on both.
953
+ dispatches: '3 (2 completed, 1 killed on timeout and re-run)'
954
+ outcome: accepted
955
+ evidence: >-
956
+ Round 1 (PR #253): REFUTED the author's own fix. The first repair widened the S5 rule with a
957
+ NAMED filter list; codex produced reproduced counter-examples in BOTH directions — still-missed
958
+ real disarms (`grep -Ec`, `grep --count`, `grep -Fcx`, `| cat`) and a NEW false positive the
959
+ repair introduced (`| tail -n +2` yields one line, not a disarm). It also caught that a fixture
960
+ labelled known-NEGATIVE was in fact a positive, because its upstream succeeded and therefore
961
+ never exercised the failure path it claimed to pin. All four re-run locally before acceptance.
962
+ Round 2 (PR #255 wiring): no blocker; one residual raised ("a distribution that SHOULD be
963
+ complete but drops a subject reports SKIP, not FAIL"), which was then TESTED and confirmed
964
+ uncovered by any existing anchor. Recorded as a WEAK signal — the auditor stated it could not
965
+ trace F1-F3 because those implementations were not in the diff it was given, and that residual
966
+ was closed locally by measurement, not by the auditor.
967
+ note: >-
968
+ Third consecutive session where the cross-family leg produced net-new findings the same-family
969
+ round walked past. New this session: the two rounds differed in SIGNAL STRENGTH (refutation vs
970
+ nothing-found), and both markers record which one it was — recording them at equal weight would
971
+ launder a weak pass into a strong one.
972
+ Instrument note: the FIRST attempt derailed into its own sidecar-availability exploration and
973
+ timed out (exit 124, ~7 min). Re-running with an explicit "Do NOT run shell commands, do NOT use
974
+ tools, answer ONLY from the text below" preamble fixed it. Stdin form `codex exec -m gpt-5.5
975
+ --skip-git-repo-check -` as recorded in [[feedback_sidecar_liveness_not_passive_wait]].
976
+ These were CLI sidecars, not Agent-tool spawns, so the SubagentStop hook tallied ZERO — the
977
+ ④-e check passed on a true zero while three real dispatches had happened. Named gap: the tally
978
+ is blind to CLI-invoked sidecars.
979
+
980
+ - date: '2026-08-05'
981
+ agent: (unattributed — SubagentStop tally only)
982
+ context: >-
983
+ Close chain of the 2026-08-04 session, which crossed midnight. The tally file recorded ONE
984
+ dispatch dated 2026-08-05; this session made no Agent-tool spawn that it is aware of.
985
+ dispatches: '1 (tallied, origin not attributable)'
986
+ outcome: sustained
987
+ evidence: >-
988
+ Recorded rather than guessed. The substantive sidecar work of this session is the 2026-08-04
989
+ entry above (3 codex CLI dispatches). MEASURED here, after the claim had already been written
990
+ into a commit message and the session card: a `codex exec` call does NOT increment the tally
991
+ (before=1, after=1 on `grep -c '^2026-08-05$'`), and the hook matcher is `SubagentStop`, i.e.
992
+ Agent-tool spawns only. So the CLI-blindness claim holds — but its evidence arrived AFTER
993
+ publication, which is the ordering CLAUDE.md §Instrument-Calibration forbids.
994
+ note: >-
995
+ What produced the 4 tallies dated 2026-08-04 and this 1 dated 2026-08-05 is UNKNOWN. Not
996
+ invented: a fabricated outcome would poison the 60/40 promotion gate worse than a missing one,
997
+ which is the stated reason the hook only tallies and never writes an entry. Open question for
998
+ the next session that touches the tally: which tool events actually fire SubagentStop here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "1.4.87",
3
+ "version": "1.4.88",
4
4
  "description": "FH runtime adapters — run FH governance, skills, and agents via Claude or Codex with machine-parseable gates.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "1.4.87",
3
+ "version": "1.4.88",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "1.4.87",
3
+ "version": "1.4.88",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -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
@@ -107,7 +107,12 @@ spec_matches() { # $1 = path
107
107
  for pair in \
108
108
  "plugins/fh-meta/skills/frontier-digest/SKILL_detail.md|tracks/_meta/x.md|PATHSPEC covers SKILL_detail.md" \
109
109
  "plugins/fh-meta/skills/frontier-digest/SKILL.md|README.md|PATHSPEC still covers SKILL.md" \
110
- "CLAUDE.md|CLAUDE.local.md|PATHSPEC covers CLAUDE.md but not the local override"
110
+ "CLAUDE.md|CLAUDE.local.md|PATHSPEC covers CLAUDE.md but not the local override" \
111
+ "scripts/selfcheck.sh|README.md|PATHSPEC covers scripts/*.sh (seam #1 — the HEAVY term already gated it, this array did not)" \
112
+ "scripts/sub/nested.sh|package.json|PATHSPEC covers scripts/ recursively (git/bash globs cross '/')" \
113
+ "templates/regression_guard.sh|package-lock.json|PATHSPEC covers templates/*.sh — i.e. the guard can guard ITSELF" \
114
+ "templates/.git-hooks/pre-commit|.gitignore|PATHSPEC covers the git-hook floor (the hook that hard-blocks commits)" \
115
+ "plugins/fh-meta/agents/challenger.md|tracks/_meta/y.md|PATHSPEC covers agent definitions (seam #3)"
111
116
  do
112
117
  IFS='|' read -r pos neg label <<< "$pair"
113
118
  ok=1
@@ -224,6 +224,38 @@ else
224
224
  fail=1
225
225
  fi
226
226
 
227
+ # gate-pathspec anchor — wired here 2026-08-04. It was reachable ONLY from templates/.git-hooks/
228
+ # pre-commit, i.e. only in a clone where the operator had run `git config core.hooksPath`. Every
229
+ # other clone, every CI run, and the npm package carried the anchor file and never executed it —
230
+ # the built-but-not-wired shape, one layer up: the anchor for the gate had no anchor of its own.
231
+ # That mattered the same day: PR #254 added five known-pairs to it, all of which would have been
232
+ # unexecuted outside the author's machine.
233
+ # Subject = the two implementations it reads (the hook's HEAVY term and the guard's GUARD_PATHSPEC).
234
+ # Absent subject → package/partial surface → legitimate SKIP; present subject with the anchor gone
235
+ # → FAIL, same shape as every block above.
236
+ # NAMED RESIDUAL (cross-family, gpt-5.5, 2026-08-04): if a distribution that SHOULD be complete
237
+ # accidentally drops one subject, this reports SKIP, not FAIL — silent non-coverage. Measured the
238
+ # same day: removing `templates/.git-hooks` from package.json `files[]` and running
239
+ # scripts/package_coverage_check.sh still PASSED, so no existing anchor catches that omission
240
+ # either. Deliberately NOT patched with a stricter branch: the only discriminator available
241
+ # ("templates/ exists but the hook does not") would be built on an UNMEASURED assumption about how
242
+ # a narrower package is shaped, and this repo's rule is not to build before the constraint is
243
+ # measured. What is cheap and honest is naming WHICH subject is missing, so a SKIP is diagnosable
244
+ # instead of opaque. Revisit when a real partial distribution is observed.
245
+ _gps_missing=""
246
+ [ -f templates/.git-hooks/pre-commit ] || _gps_missing="templates/.git-hooks/pre-commit"
247
+ [ -f templates/regression_guard.sh ] || _gps_missing="${_gps_missing:+$_gps_missing, }templates/regression_guard.sh"
248
+ if [ -n "$_gps_missing" ]; then
249
+ echo "SKIP gate_pathspec_check.sh (subject absent: $_gps_missing) — not-checked, NOT a pass"
250
+ elif [ -f scripts/gate_pathspec_check.sh ]; then
251
+ if ! bash scripts/gate_pathspec_check.sh; then
252
+ fail=1
253
+ fi
254
+ else
255
+ echo "FAIL gate_pathspec_check.sh: the gate implementations are present but their coverage anchor is missing"
256
+ fail=1
257
+ fi
258
+
227
259
  if [ ! -f scripts/ablation_calibrate.sh ]; then
228
260
  echo "SKIP test_ablation_calibrate_lanes.sh (subject scripts/ablation_calibrate.sh absent)"
229
261
  elif [ -f scripts/test_ablation_calibrate_lanes.sh ]; then
@@ -163,6 +163,15 @@ cat > "$TMP/s5_positive.sh" <<'EOF'
163
163
  set -uo pipefail
164
164
  N=$(find /nope . -maxdepth 1 2>/dev/null | grep -c . || echo 0)
165
165
  M=$(git log --oneline 2>/dev/null | wc -l || echo 0)
166
+ # WIDENED 2026-08-04. Every line below was INVISIBLE to the narrowed rule, and each was verified to
167
+ # actually produce "0\n0" before being pinned here (line count measured, not assumed):
168
+ P=$(cat /etc/hosts | grep -c . | tr -d ' ' || echo 0) # transparent filter after the counter
169
+ Q=$(grep -c "^nosuchline$" /etc/hosts 2>/dev/null | tr -d ' ' || echo 0) # the PR #251 shape
170
+ R=$(grep -Ec "^nosuchline$" /etc/hosts || echo 0) # combined flag cluster -Ec
171
+ S=$(grep --count "^nosuchline$" /etc/hosts || echo 0) # long option
172
+ T=$(grep -Fcx "nosuchline" /etc/hosts || echo 0) # -Fcx
173
+ U=$(false | grep -c . | cat || echo 0) # trailing stage that always emits
174
+ V=$(false | grep -c . | grep -v nosuch || echo 0) # trailing grep whose pattern misses the "0"
166
175
  EOF
167
176
  cat > "$TMP/s5_negative.sh" <<'EOF'
168
177
  #!/usr/bin/env bash
@@ -174,8 +183,8 @@ J=$(printf '%s' "$x" | jq -r '.a // 0' 2>/dev/null || echo 0)
174
183
  # A comment describing the defect must not be scored as the defect: cmd | grep -c . || echo 0
175
184
  EOF
176
185
  n=$(s_hits "$TMP/s5_positive.sh")
177
- [ "$n" -eq 2 ] && ok "S5 known-positive: counter-stage fallbacks (grep -c, wc) detected 2/2" \
178
- || bad "S5 known-positive: expected 2 S-hits, got $n — the pipefail disarm is invisible"
186
+ [ "$n" -eq 9 ] && ok "S5 known-positive: 9/9 — incl. -Ec/--count/-Fcx flag clusters, trailing \`| cat\`/\`| grep -v\`, and the no-upstream-pipe form" \
187
+ || bad "S5 known-positive: expected 9 S-hits, got $n — 2 = the pre-2026-08-04 rule (pipe-presence anchor); 4 = the first widening, which still missed every combined flag cluster (\`-Ec\`, \`--count\`, \`-Fcx\`) and any trailing stage outside a hardcoded name list"
179
188
 
180
189
  n=$(s_hits "$TMP/s5_negative.sh")
181
190
  [ "$n" -eq 0 ] && ok "S5 known-negative: \`||\` chains, empty-on-failure pipelines and comments stay silent" \
@@ -75,7 +75,14 @@ PY
75
75
  [ "$ok" -eq 0 ] ; chk $? "hook ends in exit 0 — a non-zero hook exit discards its stdout SILENTLY"
76
76
  # run it against a scratch HUB and confirm it appends today's date
77
77
  CLAUDE_PROJECT_DIR="$T/hub" bash -c "$cmd" >/dev/null 2>&1
78
- [ "$(grep -c "$(date +%Y-%m-%d)" "$T/hub/tracks/_meta/.subagent_dispatch_tally" 2>/dev/null || echo 0)" -ge 1 ]
78
+ # Split + sanitize, not `grep -c || echo 0`: on no-match `grep -c` PRINTS "0" and exits 1, so
79
+ # the fallback appends a SECOND line and `[ -ge ]` dies with "integer expression expected".
80
+ # Here that error happens to land on the FAIL branch — honest scope: this was never a live
81
+ # fail-open, it was a verdict reached by a bash error instead of a comparison, one refactor
82
+ # away from flipping. Found by the S5 sweep after the rule was widened (2026-08-04).
83
+ _tally_n=$(grep -c "$(date +%Y-%m-%d)" "$T/hub/tracks/_meta/.subagent_dispatch_tally" 2>/dev/null); _tally_n=${_tally_n:-0}
84
+ case "$_tally_n" in (*[!0-9]*|'') _tally_n=0 ;; esac
85
+ [ "$_tally_n" -ge 1 ]
79
86
  chk $? "hook actually appends a dated line when run (not merely present)"
80
87
  fi
81
88
  else
@@ -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 공백 →