@chrono-meta/fh-gate 2.15.1 → 3.1.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 (134) hide show
  1. package/.claude/regression/probes_live.yaml +137 -0
  2. package/.claude/rules/.residency-patterns.defaults +7 -0
  3. package/.claude/rules/fh_4axis_gate.md +88 -1
  4. package/.claude-plugin/marketplace.json +8 -2
  5. package/AGENTS.md +27 -0
  6. package/CATALOG.md +17 -0
  7. package/CLAUDE.md +13 -3
  8. package/README.ja.md +51 -7
  9. package/README.ko.md +48 -7
  10. package/README.md +43 -6
  11. package/README.zh.md +45 -8
  12. package/docs/STANDARDS_ALIGNMENT.md +61 -0
  13. package/docs/USER_GUIDE.md +3 -0
  14. package/docs/USE_CASES.md +50 -0
  15. package/docs/model_tier_expectations.md +60 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -10
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +18 -0
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
  19. package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
  20. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +35 -1
  21. package/knowledge/shared/harness-core/ship_readiness_gate.md +19 -2
  22. package/knowledge/shared/learnings/subagent_invocations_log.yaml +489 -3
  23. package/package.json +48 -2
  24. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  25. package/plugins/fh-commons/skills/preprep/README.md +4 -1
  26. package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
  27. package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
  28. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
  29. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
  30. package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
  31. package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
  32. package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
  33. package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
  34. package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
  35. package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
  36. package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
  37. package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
  38. package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
  39. package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
  40. package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
  41. package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
  42. package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
  43. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  44. package/plugins/fh-meta/CHANGELOG.md +67 -0
  45. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
  46. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
  47. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
  48. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
  49. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
  50. package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
  51. package/plugins/fh-qp/README.md +71 -0
  52. package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
  53. package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
  54. package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
  55. package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
  56. package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
  57. package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
  58. package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
  59. package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
  60. package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
  61. package/plugins/fh-qp/qp_profile.example.yaml +29 -0
  62. package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
  63. package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
  64. package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
  65. package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
  66. package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
  67. package/scripts/backtick_guard.sh +194 -0
  68. package/scripts/chamber_run.sh +14 -5
  69. package/scripts/com.forge-harness.live-eval.plist +84 -0
  70. package/scripts/compaction_probe.sh +9 -35
  71. package/scripts/context_continuity_score.sh +49 -7
  72. package/scripts/directional_diff_gate.sh +14 -2
  73. package/scripts/fh-gate.sh +3 -3
  74. package/scripts/files_manifest_shipping_check.sh +19 -0
  75. package/scripts/frontier_digest_autopilot.sh +4 -1
  76. package/scripts/gate_pathspec_check.sh +1 -1
  77. package/scripts/map_postprocess.py +90 -0
  78. package/scripts/outbound_query_guard.sh +131 -0
  79. package/scripts/outbound_query_hook.sh +373 -0
  80. package/scripts/package_coverage_check.sh +79 -18
  81. package/scripts/pipe_verdict_guard.sh +41 -1
  82. package/scripts/probe_live_eval.sh +240 -0
  83. package/scripts/probe_live_eval_lib.py +579 -0
  84. package/scripts/proposal_hook.sh +192 -0
  85. package/scripts/public_surface_scan_files.sh +11 -2
  86. package/scripts/push_zone_check.sh +78 -0
  87. package/scripts/residency_closure_scan.py +252 -0
  88. package/scripts/revert_probe.sh +250 -0
  89. package/scripts/selfcheck.sh +106 -3
  90. package/scripts/session_close_check.sh +100 -0
  91. package/scripts/sim_isolated_run.sh +195 -9
  92. package/scripts/test_action_yml_lanes.sh +97 -0
  93. package/scripts/test_backtick_guard_lanes.sh +115 -0
  94. package/scripts/test_degrade_scan_shell_probes.sh +7 -7
  95. package/scripts/test_fh_qp_lanes.sh +105 -0
  96. package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
  97. package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
  98. package/scripts/test_heavy_classifier_lanes.sh +1 -1
  99. package/scripts/test_lane_runner_lanes.sh +59 -33
  100. package/scripts/test_map_postprocess_lanes.sh +143 -0
  101. package/scripts/test_mapped_tracks_lanes.sh +1 -1
  102. package/scripts/test_marker_affected_lanes.sh +93 -0
  103. package/scripts/test_marker_crossfamily_lanes.sh +90 -6
  104. package/scripts/test_marker_oracle_lanes.sh +136 -0
  105. package/scripts/test_marker_soul_check_lanes.sh +24 -0
  106. package/scripts/test_node_check_lanes.sh +34 -34
  107. package/scripts/test_outbound_query_hook_lanes.sh +433 -0
  108. package/scripts/test_outbound_query_lanes.sh +87 -0
  109. package/scripts/test_package_coverage_lanes.sh +53 -27
  110. package/scripts/test_pipe_verdict_guard_lanes.sh +31 -5
  111. package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
  112. package/scripts/test_preprep_diagram_lanes.sh +87 -0
  113. package/scripts/test_preprep_drift_anchor.sh +16 -7
  114. package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
  115. package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
  116. package/scripts/test_probe_live_eval_lanes.sh +437 -0
  117. package/scripts/test_proposal_hook_lanes.sh +57 -0
  118. package/scripts/test_push_zone_lanes.sh +304 -0
  119. package/scripts/test_residency_closure_lanes.sh +70 -0
  120. package/scripts/test_revert_probe_lanes.sh +146 -0
  121. package/scripts/test_session_close_lanes.sh +3 -5
  122. package/scripts/test_sim_isolated_run_lanes.sh +136 -0
  123. package/scripts/test_utterance_intake_lanes.sh +414 -0
  124. package/scripts/test_worktree_reclaim_lanes.sh +70 -0
  125. package/scripts/transcript_utterances.py +222 -0
  126. package/scripts/utterance_intake.sh +424 -0
  127. package/scripts/utterance_landing_check.sh +2 -2
  128. package/scripts/validate_yaml.sh +27 -0
  129. package/scripts/worktree_reclaim.sh +95 -0
  130. package/templates/.git-hooks/pre-commit +380 -5
  131. package/templates/.git-hooks/pre-push +91 -0
  132. package/templates/RED_TEAM_REPORT.md +49 -0
  133. package/templates/settings.PreToolUse.snippet.json +101 -1
  134. package/plugins/fh-commons/README.md +0 -38
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env bash
2
+ # revert_probe.sh — general-purpose ⓕ revert-and-observe probe (rung 강화 #2, six_axis_review_2026-09-04).
3
+ #
4
+ # WHY THIS EXISTS. `되돌림`(revert-and-observe) has been done by hand 15+ times in this repo's own
5
+ # history (see 6축 실측: ⓕ none 비율 57.4%, «정확히 4레인 적색» 급 실물은 매번 손으로 짠 1회성
6
+ # 스크립트였다). Hand-rolled revert probes are the exact shape §Mechanize-at-repetition names —
7
+ # N≥3 recurrence on the SAME operation (swap a file to an older version, rerun a suite, read which
8
+ # lines changed color) is a mechanization trigger, not a one-off. This is that mechanization.
9
+ #
10
+ # 🟥 FRONTIER WARNING — read before citing this tool's PASS as "the suite has detection power".
11
+ # A single revert of a single file is ONE mutant. Mutation-testing research (arXiv 2607.22880,
12
+ # and the companion Meta engineering write-up cited alongside it in
13
+ # frontier_verification_map_2026-09-04.md §ⓕ) found that coverage/mutation SCORE loses its
14
+ # correlation with real fault-detection effectiveness once suite size is controlled for — a
15
+ # single kill is evidence the ANCHOR under test is load-bearing for THIS ONE reverted file,
16
+ # never a general claim that the suite "has good mutation coverage" or "catches regressions".
17
+ # Run this against every file you actually care about; do not average or extrapolate from one.
18
+ #
19
+ # WHAT IT DOES
20
+ # 1. Runs the lane suite AS-IS against the file's CURRENT (working-tree) content — the "수리 후"
21
+ # run.
22
+ # 2. Swaps ONLY the target file to its content at --baseline (default HEAD), backing up the
23
+ # current content first.
24
+ # 3. Reruns the same lane suite against that swapped-in baseline content — the "되돌린" run.
25
+ # 4. Restores the target file to its exact pre-probe content — ALWAYS, even if either suite run
26
+ # crashes, hangs past its timeout, or this script itself errors. Restore is attempted from an
27
+ # EXIT trap (safety net) in addition to the normal-path restore, so a `kill`-free abnormal
28
+ # exit still restores. Physical restore, never `git checkout <ref> -- <path>` — that stages
29
+ # the revert into the index ([[feedback_git_checkout_path_stages_the_revert]]); this tool
30
+ # writes bytes to the file only, with `git show <ref>:<path>` (never `git checkout`), and
31
+ # never touches the index.
32
+ # 5. Diffs the two runs' ✅/❌ label lines (this repo's universal `ok()`/`no()` convention —
33
+ # every test_*.sh / *_lanes.sh in scripts/ prints ` ✅ <label>` / ` ❌ <label>`) and reports
34
+ # EXACTLY which labels flipped from ✅ (current) to ❌ (baseline) — i.e. which lane actually
35
+ # went red when the fix was undone.
36
+ #
37
+ # WHAT IT ASSUMES (named, not hidden): the lane suite's ✅/❌ label text is STABLE across the two
38
+ # runs for a given lane (the suite script itself does not change between the two invocations —
39
+ # only the target file's content does). A suite whose pass/fail label text is dynamically built
40
+ # from data that changes with the target file (e.g. embeds a byte count in the label) will show
41
+ # as "label only in one run" rather than a flip — reported honestly as UNMATCHED, not silently
42
+ # dropped, and not counted toward the flip total.
43
+ #
44
+ # USAGE
45
+ # bash scripts/revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>] [--timeout <sec>]
46
+ # bash scripts/revert_probe.sh scripts/foo.sh scripts/test_foo_lanes.sh
47
+ # bash scripts/revert_probe.sh scripts/foo.sh scripts/test_foo_lanes.sh --baseline HEAD~1
48
+ #
49
+ # EXIT CODES (fail-closed, per CLAUDE.md §Irreversibility Surface-Class Degrade Invariant — this
50
+ # is a REVERSIBLE, read-then-restore surface, so the floor here is "never mis-score", not
51
+ # "never run"):
52
+ # 0 = exactly ≥1 lane flipped ✅→❌ when reverted (anchor is alive — it caught the mutant)
53
+ # 1 = 0 lanes flipped (anchor is DECORATIVE for this file — nothing depended on the fix)
54
+ # 2 = usage error (bad args, target/suite/ref not found)
55
+ # 10 = the probe itself is unreliable for this run — either suite run produced ZERO parseable
56
+ # ✅/❌ lines (harness error, not "0 lanes exist"), OR the restore step failed (the target
57
+ # file may still hold BASELINE content — treated as the more severe failure and reported
58
+ # loudly, never silently folded into a verdict)
59
+ #
60
+ # Usage in a lane test: see scripts/test_revert_probe_lanes.sh for the known-pair calibration
61
+ # (decorative anchor → 1, real anchor → 0, restore-guaranteed-on-suite-crash).
62
+
63
+ set -uo pipefail
64
+
65
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
66
+
67
+ TARGET=""; SUITE=""; BASELINE="HEAD"; TIMEOUT="120"
68
+ _POS=()
69
+ while [ $# -gt 0 ]; do
70
+ case "$1" in
71
+ --baseline) BASELINE="${2:-HEAD}"; shift 2 ;;
72
+ --timeout) TIMEOUT="${2:-120}"; shift 2 ;;
73
+ -*) echo "FAIL: unknown flag: $1" >&2; exit 2 ;;
74
+ *) _POS+=("$1"); shift ;;
75
+ esac
76
+ done
77
+ TARGET="${_POS[0]:-}"; SUITE="${_POS[1]:-}"
78
+
79
+ [ -n "$TARGET" ] || { echo "FAIL: usage: revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>]" >&2; exit 2; }
80
+ [ -n "$SUITE" ] || { echo "FAIL: usage: revert_probe.sh <target-file> <lane-suite-script> [--baseline <ref>]" >&2; exit 2; }
81
+ [ -f "$TARGET" ] || { echo "FAIL: target file not found: $TARGET" >&2; exit 2; }
82
+ [ -f "$SUITE" ] || { echo "FAIL: lane suite script not found: $SUITE" >&2; exit 2; }
83
+
84
+ # 🟥 physical path (`pwd -P`), not logical — macOS `/tmp` and `$TMPDIR` are symlinks into
85
+ # `/private/...`, and `git rev-parse --show-toplevel` always answers with the PHYSICAL path.
86
+ # A logical `pwd` here would make every fixture under a temp dir fail the prefix-strip below
87
+ # (same class of defect `sim_isolated_run.sh` already names for its own path isolation).
88
+ TARGET_DIR="$(cd "$(dirname "$TARGET")" && pwd -P)"
89
+ TARGET_ABS="$TARGET_DIR/$(basename "$TARGET")"
90
+ GIT_ROOT="$(cd "$TARGET_DIR" && git rev-parse --show-toplevel 2>/dev/null)"
91
+ [ -n "$GIT_ROOT" ] || { echo "FAIL: target file is not inside a git repository: $TARGET" >&2; exit 2; }
92
+ case "$TARGET_ABS" in
93
+ "$GIT_ROOT"/*) REL_PATH="${TARGET_ABS#"$GIT_ROOT"/}" ;;
94
+ *) echo "FAIL: could not compute a repo-relative path for $TARGET" >&2; exit 2 ;;
95
+ esac
96
+
97
+ if ! git -C "$GIT_ROOT" cat-file -e "${BASELINE}:${REL_PATH}" 2>/dev/null; then
98
+ echo "FAIL: $REL_PATH not found at baseline '$BASELINE' (bad ref, or the file did not exist there)" >&2
99
+ exit 2
100
+ fi
101
+
102
+ echo "── revert_probe ────────────────────────────────────────────────────"
103
+ echo "target: $REL_PATH"
104
+ echo "suite: $SUITE"
105
+ echo "baseline: $BASELINE"
106
+ echo ""
107
+ echo "🟥 FRONTIER WARNING (arXiv 2607.22880): this run reverts EXACTLY ONE file — ONE mutant."
108
+ echo " A ✅ verdict here means the anchor caught THIS mutant, not that the suite has general"
109
+ echo " mutation-detection power. Coverage/mutation score decorrelates from real effectiveness"
110
+ echo " once suite size is controlled for — do not average or extrapolate from a single run."
111
+ echo "되돌린 파일(뮤턴트) 수: 1 — $REL_PATH"
112
+ echo ""
113
+
114
+ WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/revert_probe.XXXXXX")" || { echo "FAIL: mktemp -d failed" >&2; exit 10; }
115
+ BACKUP="$WORKDIR/backup"
116
+ RUN_A="$WORKDIR/run_current.txt"
117
+ RUN_B="$WORKDIR/run_baseline.txt"
118
+
119
+ cp -p "$TARGET_ABS" "$BACKUP" || { echo "FAIL: could not back up $TARGET_ABS — refusing to touch it" >&2; rm -rf "$WORKDIR"; exit 10; }
120
+
121
+ RESTORED=0
122
+ _restore() {
123
+ [ "$RESTORED" = 1 ] && return 0
124
+ if cp -p "$BACKUP" "$TARGET_ABS" 2>/dev/null; then
125
+ RESTORED=1
126
+ else
127
+ echo "🟥🟥🟥 RESTORE FAILED — $TARGET_ABS may still hold BASELINE ($BASELINE) content." >&2
128
+ echo " Backup of the pre-probe content is kept at: $BACKUP" >&2
129
+ echo " Restore it by hand: cp \"$BACKUP\" \"$TARGET_ABS\"" >&2
130
+ fi
131
+ }
132
+ # Safety net — fires on ANY exit path (normal, error, unbound-var under set -u), so a crash
133
+ # mid-probe still restores. The normal path below also calls _restore explicitly and checks its
134
+ # result directly, because a trap cannot hand its own success/failure back to the exit-code logic.
135
+ trap '_restore' EXIT
136
+
137
+ _run_suite() { # $1=output-file
138
+ if command -v timeout >/dev/null 2>&1; then
139
+ timeout "$TIMEOUT" bash "$SUITE" > "$1" 2>&1
140
+ else
141
+ bash "$SUITE" > "$1" 2>&1
142
+ fi
143
+ return 0 # the suite's own exit code (pass/fail count) is not this function's concern
144
+ }
145
+
146
+ echo "── run 1/2: 현재(수리 후) 판 ──"
147
+ _run_suite "$RUN_A"
148
+ echo " captured $(wc -l < "$RUN_A" | tr -d ' ') lines"
149
+
150
+ if ! git -C "$GIT_ROOT" show "${BASELINE}:${REL_PATH}" > "$TARGET_ABS.new" 2>"$WORKDIR/show.err"; then
151
+ echo "FAIL: git show ${BASELINE}:${REL_PATH} failed:" >&2
152
+ cat "$WORKDIR/show.err" >&2
153
+ rm -f "$TARGET_ABS.new"
154
+ # trap restores (no-op here, file was never swapped) and exits
155
+ exit 10
156
+ fi
157
+ mv "$TARGET_ABS.new" "$TARGET_ABS"
158
+
159
+ echo "── run 2/2: 기준($BASELINE) 판 (파일만 되돌림, 인덱스는 안 건드림) ──"
160
+ _run_suite "$RUN_B"
161
+ echo " captured $(wc -l < "$RUN_B" | tr -d ' ') lines"
162
+
163
+ _restore
164
+ trap - EXIT # explicit restore already ran; the safety net has nothing left to do
165
+ if [ "$RESTORED" != 1 ]; then
166
+ echo "" >&2
167
+ echo "RESULT: RESTORE-FAILED — do not trust the file on disk, see backup path above" >&2
168
+ rm -rf "$WORKDIR"
169
+ exit 10
170
+ fi
171
+
172
+ # ── ✅/❌ 라벨 추출 — 심볼\tSPACE-트림한 설명 ────────────────────────────────────────────
173
+ _extract_labels() { # $1=source-file → writes "P|F<TAB>desc"
174
+ grep -E '(✅|❌)' "$1" 2>/dev/null | while IFS= read -r line; do
175
+ case "$line" in
176
+ *✅*) sym=P; rest="${line#*✅}" ;;
177
+ *) sym=F; rest="${line#*❌}" ;;
178
+ esac
179
+ rest="$(printf '%s' "$rest" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
180
+ [ -n "$rest" ] && printf '%s\t%s\n' "$sym" "$rest"
181
+ done
182
+ }
183
+
184
+ LABELS_A="$WORKDIR/labels_a.tsv"; LABELS_B="$WORKDIR/labels_b.tsv"
185
+ _extract_labels "$RUN_A" > "$LABELS_A"
186
+ _extract_labels "$RUN_B" > "$LABELS_B"
187
+ NA=$(wc -l < "$LABELS_A" | tr -d ' ')
188
+ NB=$(wc -l < "$LABELS_B" | tr -d ' ')
189
+
190
+ if [ "$NA" -eq 0 ] || [ "$NB" -eq 0 ]; then
191
+ echo ""
192
+ echo "🟥 HARNESS ERROR — one of the two runs produced ZERO ✅/❌ lines (current=$NA, baseline=$NB)."
193
+ echo " That is not \"0 lanes exist\" — it means this probe cannot see the suite's verdicts at"
194
+ echo " all for that run (crash, missing labels, wrong suite path). Read the raw output:"
195
+ echo " current: $RUN_A"
196
+ echo " baseline: $RUN_B"
197
+ rm -rf "$WORKDIR"
198
+ exit 10
199
+ fi
200
+
201
+ # DESC\tSYM, sorted by DESC — LC_ALL=C throughout so non-ASCII label text (this repo's labels are
202
+ # routinely Korean) never collapses under a locale-dependent string-equality/sort
203
+ # ([[feedback_locale_string_equality_breaks_nonascii]]).
204
+ DESC_A="$WORKDIR/desc_a.tsv"; DESC_B="$WORKDIR/desc_b.tsv"
205
+ awk -F'\t' '{print $2"\t"$1}' "$LABELS_A" | LC_ALL=C sort -t"$(printf '\t')" -k1,1 -k2,2 > "$DESC_A"
206
+ awk -F'\t' '{print $2"\t"$1}' "$LABELS_B" | LC_ALL=C sort -t"$(printf '\t')" -k1,1 -k2,2 > "$DESC_B"
207
+
208
+ JOINED="$WORKDIR/joined.tsv"
209
+ LC_ALL=C join -t "$(printf '\t')" -j 1 -o 1.1,1.2,2.2 "$DESC_A" "$DESC_B" > "$JOINED" 2>/dev/null || : > "$JOINED"
210
+
211
+ FLIPPED_RED="$WORKDIR/flipped_red.txt" # ✅(현재) → ❌(기준) — 앵커가 실제로 잡은 것
212
+ FLIPPED_GREEN="$WORKDIR/flipped_green.txt" # ❌(현재) → ✅(기준) — 이상 신호, 참고용
213
+ awk -F'\t' '$2=="P" && $3=="F" {print $1}' "$JOINED" > "$FLIPPED_RED"
214
+ awk -F'\t' '$2=="F" && $3=="P" {print $1}' "$JOINED" > "$FLIPPED_GREEN"
215
+ K=$(wc -l < "$FLIPPED_RED" | tr -d ' ')
216
+ J=$(wc -l < "$FLIPPED_GREEN" | tr -d ' ')
217
+
218
+ # 라벨 텍스트가 두 실행에서 안 겹치는 경우 — 조용히 버리지 않고 이름으로 남긴다(가정 위반 알림).
219
+ ONLY_A="$WORKDIR/only_a.txt"; ONLY_B="$WORKDIR/only_b.txt"
220
+ LC_ALL=C comm -23 <(cut -f1 "$DESC_A" | LC_ALL=C sort -u) <(cut -f1 "$DESC_B" | LC_ALL=C sort -u) > "$ONLY_A"
221
+ LC_ALL=C comm -13 <(cut -f1 "$DESC_A" | LC_ALL=C sort -u) <(cut -f1 "$DESC_B" | LC_ALL=C sort -u) > "$ONLY_B"
222
+ NUM_ONLY_A=$(wc -l < "$ONLY_A" | tr -d ' '); NUM_ONLY_B=$(wc -l < "$ONLY_B" | tr -d ' ')
223
+
224
+ echo ""
225
+ echo "── 결과 ──────────────────────────────────────────────────────────"
226
+ echo "현재(수리 후) 라벨: ${NA}줄 · 기준($BASELINE) 라벨: ${NB}줄"
227
+ echo ""
228
+ echo "되돌린 레인 (✅→❌, 앵커가 실제로 잡은 것): ${K}개"
229
+ if [ "$K" -gt 0 ]; then sed 's/^/ ❌ /' "$FLIPPED_RED"; fi
230
+ echo ""
231
+ echo "(참고) 반대방향 (❌→✅, 이상 신호): ${J}개"
232
+ if [ "$J" -gt 0 ]; then sed 's/^/ ⚠️ /' "$FLIPPED_GREEN"; fi
233
+ if [ "$NUM_ONLY_A" -gt 0 ] || [ "$NUM_ONLY_B" -gt 0 ]; then
234
+ echo ""
235
+ echo "🟥 라벨 텍스트가 두 실행에서 완전히 겹치지 않는다 — 이 도구의 가정(라벨 텍스트가 안정적)"
236
+ echo " 이 이 스위트에서는 안 맞을 수 있다. 아래는 매칭 대상에서 빠진 라벨(플립 집계에 미포함):"
237
+ [ "$NUM_ONLY_A" -gt 0 ] && sed 's/^/ 현재에만: /' "$ONLY_A"
238
+ [ "$NUM_ONLY_B" -gt 0 ] && sed 's/^/ 기준에만: /' "$ONLY_B"
239
+ fi
240
+
241
+ echo ""
242
+ if [ "$K" -ge 1 ]; then
243
+ echo "판정: 앵커 살아있음 — 되돌리면 정확히 ${K}개 레인이 빨개진다"
244
+ rm -rf "$WORKDIR"
245
+ exit 0
246
+ else
247
+ echo "판정: 앵커 장식 — 되돌려도 빨개지는 레인이 0개다"
248
+ rm -rf "$WORKDIR"
249
+ exit 1
250
+ fi
@@ -187,10 +187,22 @@ else
187
187
  fail=1
188
188
  fi
189
189
 
190
- # Bash surface: npm-shipped scripts + local bin wrappers + gate-chain infra
190
+ # Bash surface: npm-shipped scripts + local bin wrappers + gate-chain infra.
191
+ # `bin/fh-gate` · `bin/fh-run` · `bin/fh-goal` are named EXPLICITLY here (not a glob) and, unlike
192
+ # most of this list, are not covered by files_manifest_shipping_check.sh either (only their
193
+ # `.js` counterparts are declared in package.json files[]) — so a plain `[ -f "$f" ] || continue`
194
+ # made their disappearance invisible to BOTH checks at once. Reproduced 2026-09-03: deleting
195
+ # bin/fh-gate from a fixture tree left fail=0, no FAIL line, nothing. `scripts/*.sh` staying a
196
+ # silent skip on a genuinely-empty glob is correct (that arm still has no non-glob name); the
197
+ # named gate-chain-infra paths must not degrade the same way.
191
198
  for f in scripts/*.sh bin/fh-gate bin/fh-run bin/fh-goal \
192
199
  templates/regression_guard.sh templates/temper_check.sh templates/predelete_check.sh templates/.git-hooks/pre-commit; do
193
- [ -f "$f" ] || continue
200
+ if [ ! -f "$f" ]; then
201
+ case "$f" in
202
+ *'*'*) continue ;; # unmatched glob (nullglob off) — not a real path, legitimate skip
203
+ *) echo "FAIL bash -n coverage: gate-chain infra file missing: $f"; fail=1; continue ;;
204
+ esac
205
+ fi
194
206
  check "bash -n $f" bash -n "$f"
195
207
  done
196
208
 
@@ -594,6 +606,9 @@ for _pair in \
594
606
  "scripts/destructive_pre_gate.sh|scripts/test_destructive_pre_gate_lanes.sh" \
595
607
  "templates/.git-hooks/pre-push|scripts/test_prepush_destructive_lanes.sh" \
596
608
  "templates/.git-hooks/pre-push|scripts/test_prepush_destructive_liveness.sh" \
609
+ "templates/.git-hooks/pre-push|scripts/test_push_zone_lanes.sh" \
610
+ "scripts/push_zone_check.sh|scripts/test_push_zone_lanes.sh" \
611
+ "scripts/session_close_check.sh|scripts/test_push_zone_lanes.sh" \
597
612
  "scripts/session_close_check.sh|scripts/test_peer_worktree_detect_lanes.sh" \
598
613
  "scripts/env_purity_scan.sh|scripts/test_env_purity_lanes.sh" \
599
614
  "scripts/frontier_digest_daily.sh|scripts/test_frontier_digest_retry.sh" \
@@ -606,19 +621,36 @@ for _pair in \
606
621
  "templates/.git-hooks/pre-commit|scripts/test_marker_thirdparty_lanes.sh" \
607
622
  "templates/.git-hooks/pre-commit|scripts/test_marker_soul_check_lanes.sh" \
608
623
  "templates/.git-hooks/pre-commit|scripts/test_marker_soul_tenet_lanes.sh" \
624
+ "templates/.git-hooks/pre-commit|scripts/test_marker_affected_lanes.sh" \
625
+ "templates/.git-hooks/pre-commit|scripts/test_marker_oracle_lanes.sh" \
626
+ `# ── 판정을 둘로 (2026-09-06, SWE-Gate 답습): 합성 «PASS» 가 acceptance-evidence 를 가리지 않는가 ──` \
627
+ "templates/.git-hooks/pre-commit|scripts/test_gate_two_verdicts_lanes.sh" \
628
+ `# ── fh-qp (QP) — chamber run #18 EMIT 2026-09-05: qp_tools.sh known-pair + residency lanes ──` \
629
+ "plugins/fh-qp/scripts/qp_tools.sh|scripts/test_fh_qp_lanes.sh" \
630
+ "plugins/fh-commons/skills/preprep/diagram_from_json.py|scripts/test_preprep_diagram_lanes.sh" \
631
+ `# ── action.yml — the GitHub Action wrapper: its exit-code mapping is where a typed verdict could become a boolean ──` \
632
+ "action.yml|scripts/test_action_yml_lanes.sh" \
609
633
  "scripts/sim_isolated_run.sh|scripts/test_sim_path_isolation_lanes.sh" \
634
+ `# ── live-eval — the live twin of the static /prompt-regression probe check (2026-09-04) ──` \
635
+ "scripts/probe_live_eval.sh|scripts/test_probe_live_eval_lanes.sh" \
636
+ "scripts/probe_live_eval_lib.py|scripts/test_probe_live_eval_lanes.sh" \
610
637
  "templates/.git-hooks/pre-commit|scripts/test_marker_first_use_lanes.sh" \
611
638
  "scripts/fixture_guard_lib.sh|scripts/test_fixture_guard_lanes.sh" \
612
639
  "scripts/stray_path_scan.sh|scripts/test_stray_path_lanes.sh" \
613
640
  "scripts/session_close_check.sh|scripts/test_stray_path_lanes.sh" \
614
641
  "templates/.git-hooks/pre-commit|scripts/test_hook_leg_wiring_lanes.sh" \
615
642
  ".claude/soul_tenets.txt|scripts/test_marker_soul_tenet_lanes.sh" \
643
+ "docs/map/fh_assets.architecture.json|scripts/test_fh_map_paths_lanes.sh" \
644
+ `# ── 지도 후처리(2026-09-06): 발행 폭 하한 + SVG 재생성. 리터럴 드리프트를 fail-closed 로 잡는다 ──` \
645
+ "scripts/map_postprocess.py|scripts/test_map_postprocess_lanes.sh" \
616
646
  "templates/.git-hooks/pre-commit|scripts/test_precommit_staged_drift_lanes.sh" \
617
647
  "templates/.git-hooks/pre-commit|scripts/test_marker_address_lanes.sh" \
648
+ "templates/.git-hooks/pre-commit|scripts/test_precommit_pointer_index_lanes.sh" \
618
649
  "scripts/residency_closure_scan.py|scripts/test_residency_closure_lanes.sh" \
619
650
  "scripts/reviewer_capability_corpus.tsv|scripts/test_reviewer_capability_conformance.sh" \
620
651
  "scripts/field_canon_preload.sh|scripts/test_field_canon_lanes.sh" \
621
652
  "scripts/stale_clone_guard.sh|scripts/test_stale_clone_guard_lanes.sh" \
653
+ "scripts/proposal_hook.sh|scripts/test_proposal_hook_lanes.sh" \
622
654
  "plugins/fh-commons/skills/ko-tech-writer/SKILL.md|scripts/test_ko_tech_writer_lanes.sh" \
623
655
  "scripts/script_caller_ratchet.sh|scripts/test_script_caller_ratchet_lanes.sh" \
624
656
  "scripts/script_caller_ratchet.sh|scripts/test_runner_surface_index_lanes.sh" \
@@ -630,14 +662,23 @@ for _pair in \
630
662
  "plugins/fh-commons/skills/preprep/lane_progression.py|scripts/test_preprep_progression_lanes.sh" \
631
663
  "plugins/fh-commons/skills/preprep/lane_adjacent_dup.py|scripts/test_preprep_adjacent_dup_lanes.sh" \
632
664
  "plugins/fh-commons/skills/preprep/lane_promise.py|scripts/test_preprep_promise_lanes.sh" \
665
+ "plugins/fh-commons/skills/preprep/lane_slide_refs.py|scripts/test_preprep_slide_refs_lanes.sh" \
633
666
  "plugins/fh-commons/skills/preprep/SKILL.md|scripts/test_preprep_drift_anchor.sh" \
667
+ "scripts/test_preprep_drift_anchor.sh|scripts/test_preprep_drift_anchor_lanes.sh" \
634
668
  "scripts/field_canon_preload.sh|scripts/test_skill_canon_preload_lanes.sh" \
635
669
  `# ── round/ 회차 계기 4종(2026-09-01). 넷 다 한 스위트가 잡는다 — 주체별로 행을 둔다 ──` \
636
670
  "scripts/round/delta_guard.sh|scripts/test_round_instruments_lanes.sh" \
637
671
  "scripts/round/target_pin.sh|scripts/test_round_instruments_lanes.sh" \
638
672
  "scripts/round/instrument_manifest.sh|scripts/test_round_instruments_lanes.sh" \
639
673
  "scripts/round/eligcheck_qset.sh|scripts/test_round_instruments_lanes.sh" \
640
- "scripts/round/gatecheck_qset.sh|scripts/test_round_instruments_lanes.sh"
674
+ "scripts/round/gatecheck_qset.sh|scripts/test_round_instruments_lanes.sh" \
675
+ `# ── 발화 착지(2026-09-05). 네 주체가 한 스위트로 묶인다 — 주체별로 행을 둔다 ──` \
676
+ "scripts/utterance_intake.sh|scripts/test_utterance_intake_lanes.sh" \
677
+ "scripts/transcript_utterances.py|scripts/test_utterance_intake_lanes.sh" \
678
+ "scripts/compaction_probe.sh|scripts/test_utterance_intake_lanes.sh" \
679
+ "scripts/session_close_check.sh|scripts/test_utterance_intake_lanes.sh" \
680
+ `# ── 워크트리 회수(2026-09-05, N=2 재발 → 스크립트). 사람이 부르는 도구지만 레인은 자동 ──` \
681
+ "scripts/worktree_reclaim.sh|scripts/test_worktree_reclaim_lanes.sh"
641
682
  do
642
683
  _subj="${_pair%%|*}"; _anc="${_pair##*|}"; _lbl="${_anc##*/}"
643
684
  if [ ! -f "$_subj" ]; then
@@ -1056,6 +1097,29 @@ else
1056
1097
  esac
1057
1098
  fi
1058
1099
 
1100
+ # prepublish_scope_note — an embedded --self-test subject lane_runner_check.sh flagged as having
1101
+ # no dispatcher anywhere (2026-09-03): its own 7-lane known-pair (does validate.yml still call
1102
+ # selfcheck.sh — known-positive/negative, missing-workflow, commented-out call, real call beside a
1103
+ # stale commented one, the real `run: |` block-scalar shape, and echo-mention-is-not-a-call) lives
1104
+ # behind `--self-test`, and nothing runs it. It IS invoked at publish time (package.json
1105
+ # `prepublishOnly`) — but that is `check()`, the gate's default argument-less mode, running for
1106
+ # real; it never exercises the gate's OWN calibration. Not in the `for _subj in ...` loop above:
1107
+ # its terminal line is `── N pass / M fail`, never 캘리브레이션, same reason capability_registry_check
1108
+ # and capability_effect_probe were pulled out of that loop. Direct dispatch instead, same shape as
1109
+ # capability_effect_probe.sh above — whole-line terminal verdict with a non-zero PASS count, so an
1110
+ # emptied suite cannot certify itself. Ships via package.json files[], so absence is FAIL, not SKIP.
1111
+ if [ ! -f scripts/prepublish_scope_note.sh ]; then
1112
+ echo "FAIL prepublish_scope_note.sh: missing — it ships via package.json files[], so absence is deletion, not package mode"
1113
+ fail=1
1114
+ elif _out=$(bash scripts/prepublish_scope_note.sh --self-test < /dev/null 2>&1) \
1115
+ && printf '%s\n' "$_out" | grep -qE '^ ── [1-9][0-9]* pass / 0 fail$'; then
1116
+ echo "PASS prepublish_scope_note.sh --self-test ($(printf '%s\n' "$_out" | grep -oE '[0-9]+ pass / [0-9]+ fail' | tail -1))"
1117
+ else
1118
+ echo "FAIL prepublish_scope_note.sh: --self-test failed or produced no terminal verdict line"
1119
+ _show_failure "$_out"
1120
+ fail=1
1121
+ fi
1122
+
1059
1123
  # memory-link-check — the memory store is a GRAPH (memory_intent_recall.md: nodes=files,
1060
1124
  # edges=[[links]], recall walks one hop). Measured 2026-07-28: 50 of 872 edges pointed at a note
1061
1125
  # that existed under a different separator and 22 at nothing — a dead edge returns nothing and is
@@ -1143,6 +1207,20 @@ else
1143
1207
  fail=1
1144
1208
  fi
1145
1209
 
1210
+ # ⓕ 되돌림 범용 프로브 (six_axis_review_2026-09-04 강화 #2) — 15+ 손짜기 되돌림 스크립트를
1211
+ # 대체하는 계기다. 자기 자신을 known-pair 로 검증한다(장식 앵커→1, 실물 앵커→0, 복원 보장) —
1212
+ # 앵커가 아니라 그 앵커를 검증하는 계기이므로 반드시 실행돼야 한다.
1213
+ if [ ! -f scripts/revert_probe.sh ]; then
1214
+ _absent_subject_verdict "test_revert_probe_lanes.sh" "scripts/revert_probe.sh" || fail=1
1215
+ elif [ -f scripts/test_revert_probe_lanes.sh ]; then
1216
+ if ! bash scripts/test_revert_probe_lanes.sh; then
1217
+ fail=1
1218
+ fi
1219
+ else
1220
+ echo "FAIL test_revert_probe_lanes.sh: revert_probe.sh present but its anchor is missing"
1221
+ fail=1
1222
+ fi
1223
+
1146
1224
  # 무효 워터마크 — 무효 회차의 «숫자 줄»이 자기 무효를 나르는가.
1147
1225
  # 🟥 회차 3 은 자기 게이트가 VOID 를 찍고도 그 숫자만 기록으로 넘어갔다(VOID 낱말은 0회).
1148
1226
  # 판정이 표 «밖»에 있었고 사람은 표를 복사하기 때문이다. 그 채널을 닫은 배선의 앵커다.
@@ -1533,6 +1611,20 @@ else
1533
1611
  fail=1
1534
1612
  fi
1535
1613
 
1614
+ # outbound-query PreToolUse 훅 — 위 CLI 가드의 **배선**(WebSearch|WebFetch). 레인이 두 층 판정
1615
+ # (override→deny · defaults→advisory), 미측정 4가지→deny, N/A 무음, 그리고 🟥 «출력에 토큰 값이
1616
+ # 없다»(H10)를 각각 짝으로 잡는다. 마지막 것이 이 훅의 존재 이유를 지키는 레인이다.
1617
+ if [ ! -f scripts/outbound_query_hook.sh ]; then
1618
+ _absent_subject_verdict "test_outbound_query_hook_lanes.sh" "scripts/outbound_query_hook.sh" || fail=1
1619
+ elif [ -f scripts/test_outbound_query_hook_lanes.sh ]; then
1620
+ if ! bash scripts/test_outbound_query_hook_lanes.sh; then
1621
+ fail=1
1622
+ fi
1623
+ else
1624
+ echo "FAIL test_outbound_query_hook_lanes.sh: outbound_query_hook.sh present but its anchor is missing"
1625
+ fail=1
1626
+ fi
1627
+
1536
1628
  if [ ! -f scripts/pipe_verdict_guard.sh ]; then
1537
1629
  _absent_subject_verdict "test_pipe_verdict_guard_lanes.sh" "scripts/pipe_verdict_guard.sh" || fail=1
1538
1630
  elif [ -f scripts/test_pipe_verdict_guard_lanes.sh ]; then
@@ -1544,6 +1636,17 @@ else
1544
1636
  fail=1
1545
1637
  fi
1546
1638
 
1639
+ if [ ! -f scripts/backtick_guard.sh ]; then
1640
+ _absent_subject_verdict "test_backtick_guard_lanes.sh" "scripts/backtick_guard.sh" || fail=1
1641
+ elif [ -f scripts/test_backtick_guard_lanes.sh ]; then
1642
+ if ! bash scripts/test_backtick_guard_lanes.sh; then
1643
+ fail=1
1644
+ fi
1645
+ else
1646
+ echo "FAIL test_backtick_guard_lanes.sh: backtick_guard.sh present but its anchor is missing"
1647
+ fail=1
1648
+ fi
1649
+
1547
1650
  if [ ! -f scripts/halffix_propagation_scan.sh ]; then
1548
1651
  _absent_subject_verdict "test_halffix_lanes.sh" "scripts/halffix_propagation_scan.sh" || fail=1
1549
1652
  elif [ -f scripts/test_halffix_lanes.sh ]; then
@@ -269,6 +269,73 @@ fi
269
269
 
270
270
  fi
271
271
 
272
+ # ── ①-f UTTERANCE LANDING (advisory) ─────────────────────────────────────────
273
+ # WHY. 마감 체크는 **형식**만 본다 — 카드가 로그보다 새로운가, 필수 아티팩트가 있는가.
274
+ # **운영자 발화가 기록에 착지했는지는 아무도 안 봤다.** `utterance_landing_check.sh` 가 그걸
275
+ # 재려고 2026-08-08 에 지어졌는데 probes.tsv 를 **손으로** 짜야 해서 호출부가 0 개였다
276
+ # (2026-09-05 실측: 이 파일에서 그 스크립트를 부르는 줄이 하나도 없었다). 그 사이의 빠진 칸이
277
+ # `utterance_intake.sh` 다 — 전사본에서 발화를 뽑아 프로브를 자동 생성한다.
278
+ # 근거: 같은 날 운영자의 교리·설계급 발화 6 건이 착지한 경로가 전부 **거버너의 손**이었다.
279
+ #
280
+ # 🟥 라벨이 `①-e` 가 아닌 이유: 그건 아래 stray-path 블록이 **이미 쓰고 있다.** 한 라벨을 두
281
+ # 검사가 나눠 쓰면 「어느 ①-e 가 울렸나」를 아무도 못 가른다 — 이 배선을 준비한 진단은
282
+ # «①-e 는 비어 있다» 고 적었고, 그건 거짓이었다(레인 L13 이 «배선 전인데 초록» 으로 잡았다).
283
+ #
284
+ # 🟥 **advisory 다 — 막지 않는다.** 프로브는 키 낱말이라 기록이 같은 뜻을 다른 낱말로 적으면
285
+ # 과차단이 난다(안전한 방향이지만 소음이다). 노출 사다리(shadow) 로 몇 세션 관찰한 뒤
286
+ # close push 차단으로 승격할지는 **운영자 결정**이고, 그때까지 FAIL 을 세우지 않는다.
287
+ _UI_SUT="$FH/scripts/utterance_intake.sh"
288
+ if [ -f "$_UI_SUT" ]; then
289
+ # 세션 id 의 정본은 `branch_claim.sh` 다 — **새 판정기를 짓지 않는다**(재발명 금지).
290
+ # `show` 의 `세션ID` 줄이 그 값이고, 값이 `pid-` 로 시작하면 런타임이 id 를 안 준 것이라
291
+ # 전사본을 특정할 수 없다. 그때는 «다른 세션 것을 대신 읽는» 대신 UNMEASURED 로 남긴다 —
292
+ # 남의 전사본으로 낸 착지 판정은 틀린 답이지 근사값이 아니다.
293
+ _UI_SID=""
294
+ if [ -f "$FH/scripts/branch_claim.sh" ]; then
295
+ _UI_SID=$(LC_ALL=C bash "$FH/scripts/branch_claim.sh" show 2>/dev/null \
296
+ | LC_ALL=C sed -n 's/^세션ID[[:space:]][[:space:]]*//p' | head -1)
297
+ fi
298
+ case "$_UI_SID" in pid-*|'') _UI_SID="" ;; esac
299
+ # 전사본 slug = 프로젝트 절대경로에서 `/` `.` `_` 를 `-` 로 (실측 2026-09-05, ~/.claude/projects 대조)
300
+ _UI_SLUG=$(printf '%s' "$FH" | tr '/._' '---')
301
+ _UI_TRDIR="${FH_TRANSCRIPT_DIR:-$HOME/.claude/projects/$_UI_SLUG}"
302
+ _UI_TR="$_UI_TRDIR/$_UI_SID.jsonl"
303
+ if [ -z "$_UI_SID" ]; then
304
+ echo "ℹ️ ①-f UNMEASURED — 세션 id 미상(런타임이 CLAUDE_CODE_SESSION_ID 를 안 줬다)"
305
+ echo " 전사본을 특정할 수 없다. «발화가 다 적혔다» 가 아니라 «못 쟀다» 다."
306
+ elif [ ! -f "$_UI_TR" ]; then
307
+ echo "ℹ️ ①-f UNMEASURED — 전사본 부재: $_UI_TR"
308
+ echo " 부재를 0 으로 접지 마라 — 발화가 없었다는 뜻이 아니다."
309
+ else
310
+ # 기록 대상: 오늘자 산출 + 상시 카드/UAP. 배열로 모은다(zsh word-split 함정 회피).
311
+ _UI_RECS=()
312
+ for _p in "$FH/tracks/_meta/fh_completed_$TODAY.md" \
313
+ "$FH/tracks/_meta/user_adaptation_profile.md" \
314
+ "$FH/tracks/_meta/reference_next_session_starter.md"; do
315
+ [ -f "$_p" ] && _UI_RECS+=("$_p")
316
+ done
317
+ for _p in "$FH"/tracks/_meta/fh_signal_"$TODAY"_*.md "$FH"/tracks/*/*_"$TODAY".md; do
318
+ [ -f "$_p" ] && _UI_RECS+=("$_p")
319
+ done
320
+ if [ "${#_UI_RECS[@]}" -eq 0 ]; then
321
+ echo "⚠️ ①-f 오늘자 기록 파일이 하나도 없다 — 발화 착지를 잴 대상이 없다(UNMEASURED)"
322
+ echo " ④/⑤ 가 아직 안 돌았다는 뜻일 수 있다. 마감 순서를 확인해라."
323
+ else
324
+ _UI_OUT=$(bash "$_UI_SUT" "$_UI_TR" "${_UI_RECS[@]}" 2>&1); _UI_RC=$?
325
+ case "$_UI_RC" in
326
+ 0) echo "✅ ①-f 발화 착지 — 프로브 전건 착지 (${#_UI_RECS[@]} 파일 대조)"
327
+ echo " ⚠️ 발화별 최장 토큰 ⌈n/2⌉ 일치 기준(키 2 개 이하는 OR) — «제대로 적혔다» 의 증명이 아니다." ;;
328
+ 1) echo "⚠️ ①-f 미착지 발화가 있다 — advisory, 막지 않는다. 아래를 **확인**해라(기록 강제 아님):"
329
+ printf '%s\n' "$_UI_OUT" | sed 's/^/ /' ;;
330
+ *) echo "⚠️ ①-f 발화 착지 검사 UNMEASURED/계기이상 (rc=$_UI_RC) — 0 으로 읽지 마라"
331
+ printf '%s\n' "$_UI_OUT" | sed 's/^/ /' ;;
332
+ esac
333
+ fi
334
+ fi
335
+ else
336
+ echo "⚠️ ①-f utterance_intake.sh 없음 — skipped, not passed (UNMEASURED)"
337
+ fi
338
+
272
339
  # ② FH assets changed today → harvest-loop owed
273
340
  # NOTE: no `grep -q` here — under `set -o pipefail`, -q's early exit SIGPIPEs git log (141),
274
341
  # masking a real match as pipeline failure so the warning NEVER fired on true positives
@@ -704,5 +771,38 @@ else
704
771
  echo "⚠️ ①-e stray_path_scan.sh 없음 — skipped, not passed"
705
772
  fi
706
773
 
774
+ # ── ①-f PUSH-ZONE advisory (surface-not-block; mirrors the pre-push hook's own zone check) ────
775
+ # WHY: templates/.git-hooks/pre-push blocks a single push whose remote sits outside the owner-
776
+ # account list, but it only ever sees the ONE push in front of it. The 2026-07-26 miss this whole
777
+ # axis exists for was a multi-repo ROUND — CLAUDE.local.md §REST API push 계정규칙 was applied
778
+ # correctly to one org repo and forgotten on another in the SAME round. `scripts/push_zone_check.sh`
779
+ # is the hand-run ENUMERATE tool for that round; wiring it here means it runs at least once per
780
+ # close without the operator separately remembering to ask for it — same shape as the ①-b open-PR
781
+ # sweep above. Advisory only: it lists, it never blocks (CLAUDE.md §Surface-Class Degrade Invariant,
782
+ # reversible half — a session-close check is not the irreversible boundary; that is the pre-push
783
+ # hook's job). Applicability is mechanical: both the script and an owner-account list must exist,
784
+ # or this reports UNCALIBRATED rather than guessing.
785
+ _PZ_SCRIPT="$FH/scripts/push_zone_check.sh"
786
+ _PZ_OWNERS="${PUSH_ZONE_OWNERS:-$FH/.claude/rules/.push-zone-owners}"
787
+ if [ -x "$_PZ_SCRIPT" ] && [ -f "$_PZ_OWNERS" ]; then
788
+ if _pz_out=$(bash "$_PZ_SCRIPT" 2>&1); then
789
+ # Counts only — the enumerator prints owner-account names and per-repo owners, and this close
790
+ # output lands in transcripts and session cards. The pre-push block never prints the list; this
791
+ # advisory must not either (codex #9, 2026-09-05). The names are one command away for the operator.
792
+ _pz_n=$(printf '%s\n' "$_pz_out" | grep -cE '^(✅|🔶) ' 2>/dev/null || true)
793
+ _pz_rest=$(printf '%s\n' "$_pz_out" | grep -cE '^🔶 ' 2>/dev/null || true)
794
+ echo "⚠️ ①-f push-zone: ${_pz_n:-0} repo(s) enumerated · ${_pz_rest:-0} outside the owner accounts (REST channel) — names withheld here; run: bash scripts/push_zone_check.sh"
795
+ else
796
+ # Verified 2026-09-05: push_zone_check.sh does not exit non-zero merely because `gh` is
797
+ # missing (its own `active="(unknown)"` fallback absorbs that) — the only documented non-zero
798
+ # exit is UNCALIBRATED for a missing owner list, already excluded by the `-f "$_PZ_OWNERS"`
799
+ # guard above. A non-zero exit reaching here is therefore an unanticipated failure; report it
800
+ # as UNMEASURED rather than silently treating empty/partial output as "nothing to enumerate".
801
+ echo "⚠️ ①-f push-zone: UNMEASURED — push_zone_check.sh exited non-zero, could not enumerate"
802
+ fi
803
+ else
804
+ echo "⏭️ ①-f push-zone UNCALIBRATED — script or owner-account list absent"
805
+ fi
806
+
707
807
  echo "── close check: $([ "$FAIL" -eq 0 ] && echo CONSISTENT || echo VIOLATIONS) ──"
708
808
  exit "$FAIL"