@chrono-meta/fh-gate 2.0.0 → 2.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 (56) hide show
  1. package/.claude/capabilities/adapters/gstack-content-safety.cap +110 -0
  2. package/.claude/capabilities/adapters/mate-agent-boundary.cap +110 -0
  3. package/.claude/capabilities/adapters/qasp-new-code-anchor.cap +116 -0
  4. package/.claude/capabilities/degrade-direction-scan.cap +24 -0
  5. package/.claude/capabilities/public-surface-scan.cap +24 -0
  6. package/.claude/rules/fh_4axis_gate.md +77 -1
  7. package/.claude-plugin/marketplace.json +3 -3
  8. package/AGENTS.md +25 -3
  9. package/CHEATSHEET.md +17 -1
  10. package/CLAUDE.md +241 -5
  11. package/README.ja.md +55 -20
  12. package/README.ko.md +86 -20
  13. package/README.md +57 -20
  14. package/README.zh.md +49 -19
  15. package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +122 -5
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +83 -1
  19. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +255 -0
  20. package/knowledge/shared/harness-core/harness_verification_core_extended.md +1 -1
  21. package/knowledge/shared/harness-core/ship_readiness_gate.md +234 -11
  22. package/knowledge/shared/learnings/subagent_invocations_log.yaml +128 -0
  23. package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
  24. package/package.json +12 -3
  25. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  26. package/plugins/fh-commons/agents/quench-challenger.md +6 -1
  27. package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
  28. package/plugins/fh-meta/CHANGELOG.md +60 -0
  29. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
  30. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
  31. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
  32. package/scripts/adapters/gstack_content_safety.sh +186 -0
  33. package/scripts/adapters/mate_agent_boundary.sh +120 -0
  34. package/scripts/adapters/peer_resolve.sh +99 -0
  35. package/scripts/adapters/qasp_new_code_anchor.sh +159 -0
  36. package/scripts/branch_claim.sh +14 -0
  37. package/scripts/capability_effect_probe.sh +340 -20
  38. package/scripts/capability_registry_check.sh +271 -20
  39. package/scripts/chamber_run.sh +24 -3
  40. package/scripts/chamber_witness.sh +25 -0
  41. package/scripts/cluster_capability_scan.sh +629 -0
  42. package/scripts/fh-gate.sh +16 -1
  43. package/scripts/fh_session_load.sh +70 -0
  44. package/scripts/package_coverage_check.sh +10 -0
  45. package/scripts/portability_lint.sh +257 -0
  46. package/scripts/prepublish_scope_note.sh +139 -0
  47. package/scripts/relay_channel.sh +70 -7
  48. package/scripts/selfcheck.sh +167 -5
  49. package/scripts/test_adapter_lanes.sh +296 -0
  50. package/scripts/test_capability_entrypoint_shipping.sh +8 -1
  51. package/scripts/test_fh_gate_regressions.sh +21 -9
  52. package/scripts/test_marker_axes_run_lanes.sh +87 -3
  53. package/scripts/test_marker_crossfamily_lanes.sh +31 -1
  54. package/scripts/test_relay_channel_lanes.sh +76 -0
  55. package/templates/.git-hooks/pre-commit +239 -12
  56. package/templates/subagent-tally-hook.json +2 -2
@@ -97,7 +97,12 @@ AXES_SET="verdict_binding"
97
97
  # 선언이 VIOLATION 으로 거부된다 — 2026-08-11 실측: registry_check 를 통과한 capfile 2개가
98
98
  # relay 에서 8건 VIOLATION. 같은 스펙에 대해 두 계기가 서로 다른 스키마를 든
99
99
  # divergent-normalizer 이고, 관대함이 갈리면 한쪽만 통과하는 입력이 생긴다.
100
- META_KEYS="id entry requires_cwd verdict_channel verdict_enum verdict_stdout_key upstream_argv echoes_upstream calibration_positive_args calibration_positive_expect calibration_negative_args calibration_negative_expect"
100
+ # 🟥 **2026-08-16 재발.** 문단이 함정을 2026-08-11 사례로 적어놨는데, `summary`/`tags` 가
101
+ # `capability_registry_check.sh` 에만 추가되고 여기 안 들어와 **같은 파일이 같은 함정을 두 번**
102
+ # 밟았다 — 출하된 `.claude/capabilities/*.cap` 2개가 `✅ REGISTRABLE` 인데 relay 에서
103
+ # `⛔ COMPOSITION_VIOLATION` 이었다(등록되지만 **부를 수 없는** 선언). 아래 교차 레인이
104
+ # 그 재발을 잡는 앵커다 — 산문 경고는 이미 있었고 막지 못했다.
105
+ META_KEYS="id entry requires_cwd summary tags verdict_channel verdict_enum verdict_stdout_key upstream_argv echoes_upstream calibration_positive_args calibration_positive_expect calibration_negative_args calibration_negative_expect"
101
106
 
102
107
  _die() { printf '❌ %s\n' "$*" >&2; exit "$RC_HARNESS"; }
103
108
  _violation() { printf '⛔ COMPOSITION_VIOLATION — %s\n' "$*" >&2; VIOLATED=1; }
@@ -378,7 +383,7 @@ _sha() { # stdin → sha256 hex (macOS/linux 양쪽)
378
383
  # 반환: 전역 NODE_VERDICT / NODE_EXIT / NODE_OUT_FILE
379
384
  # 파이프로 verdict 를 읽지 않는다(D6) — 출력은 파일로 받고 exit status 를 직접 취한다.
380
385
  _invoke_node() {
381
- local f="$1" task="$2" upstream="$3" outdir="$4" n="$5"
386
+ local f="$1" task="$2" upstream="$3" outdir="$4" n="$5" node_args="${6:-}"
382
387
  local entry cwd chan enum key argv_extra rc line code name
383
388
  entry=$(_cap_get "$f" entry)
384
389
  cwd=$(_cap_get "$f" requires_cwd)
@@ -387,6 +392,19 @@ _invoke_node() {
387
392
  key=$(_cap_get "$f" verdict_stdout_key); [ -z "$key" ] && key="FH_GATE_VERDICT"
388
393
 
389
394
  [ -n "$entry" ] || { NODE_VERDICT="HARNESS_ERROR"; NODE_EXIT=$RC_HARNESS; return 0; }
395
+ # ★`requires_cwd: SELF` = «이 선언을 품은 레포». 해석은 `capability_registry_check.sh` ·
396
+ # `capability_effect_probe.sh` 와 **같은 규칙**(git 레포 루트)이어야 한다 — 규칙이 갈리면
397
+ # 두 계기가 서로 다른 트리를 재고, 그건 이 파일이 §divergent-normalizer 로 이름 붙인 결함이다.
398
+ # 🟥 이 값이 없으면 **tracked 선언이 구조적으로 uncallable** 하다: `SELF` 는 공개표면에
399
+ # 운영자 홈 절대경로를 안 싣기 위해 도입된 형태라, 하네스 자기선언은 전부 이 형태를 쓴다.
400
+ # 실측(2026-08-16): SELF 두 노드 → `NOT_CALLABLE … HARNESS_ERROR` · 절대경로 두 노드 → 진짜 판정.
401
+ if [ "$cwd" = "SELF" ]; then
402
+ cwd="$(git -C "$(dirname "$f")" rev-parse --show-toplevel 2>/dev/null)"
403
+ if [ -z "$cwd" ]; then
404
+ printf 'ℹ️ NOT_CALLABLE: requires_cwd: SELF 인데 선언이 git 레포 안에 없다 → dispatch 폴백\n' >&2
405
+ NODE_VERDICT="HARNESS_ERROR"; NODE_EXIT=$RC_HARNESS; return 0
406
+ fi
407
+ fi
390
408
  if [ -n "$cwd" ] && [ ! -d "$cwd" ]; then
391
409
  printf 'ℹ️ NOT_CALLABLE: requires_cwd 부재(%s) → dispatch 폴백 대상\n' "$cwd" >&2
392
410
  NODE_VERDICT="HARNESS_ERROR"; NODE_EXIT=$RC_HARNESS; return 0
@@ -406,8 +424,10 @@ _invoke_node() {
406
424
  argv_extra="--upstream-verdict $upstream"
407
425
  fi
408
426
 
427
+ # 순서: entry → **노드 인자** → 결박 플래그. 노드 인자를 결박 플래그 뒤에 붙이면
428
+ # 위치 인자를 읽는 진입점이 `--upstream-verdict` 를 자기 인자로 삼는다.
409
429
  ( [ -n "$cwd" ] && cd "$cwd"; RELAY_TASK="$task" RELAY_UPSTREAM_VERDICT="$upstream" \
410
- $entry $argv_extra ) > "$NODE_OUT_FILE" 2>&1
430
+ $entry $node_args $argv_extra ) > "$NODE_OUT_FILE" 2>&1
411
431
  rc=$?
412
432
  NODE_EXIT=$rc
413
433
 
@@ -489,11 +509,54 @@ _invoke_node() {
489
509
 
490
510
  # ── run ───────────────────────────────────────────────────────────────────────
491
511
  do_run() {
492
- local task="" recdir="" ack=0 n=0 upstream="" rc final=$RC_PASS
493
- CAPS=()
512
+ local task="" recdir="" ack=0 n=0 upstream="" rc final=$RC_PASS _ci
513
+ CAPS=(); CAPARGS=(); CAPARGS_SET=()
494
514
  while [ $# -gt 0 ]; do
495
515
  case "$1" in
496
- --cap) shift; CAPS+=("$1") ;;
516
+ --cap) shift; CAPS+=("$1"); CAPARGS+=("") ;;
517
+ # ── 노드별 호출 인자 채널 ────────────────────────────────────────────────
518
+ # 🟥 **왜 이게 필요한가(실측 2026-08-16)**: 호출 시점 인자를 요구하는 능력은
519
+ # relay 를 통과할 방법이 아예 없었다. pmh-dev 의 `merge-noop-check` 는
520
+ # «무엇을 무엇에 반입하나» 가 인자인데, 채널이 없어 **항상 ARGS→HARNESS_ERROR
521
+ # →BLOCKED** 로 떨어졌다. 방향은 fail-closed 로 옳았지만 **신호가 0** 이었고,
522
+ # 그건 정체성 ①의 «재사용이 실재하고 호출 가능한가» 를 구조적으로 못 만족시킨다.
523
+ # ⚠️ **결박은 «가장 최근 --cap»** 이다. 위치 인덱스로 짝지으면 순서가 어긋났을 때
524
+ # 조용히 다른 노드에 붙는다 — 그건 계기가 대상을 바꾸는 형태다.
525
+ # ⚠️ `--cap` 없이 먼저 오면 **fail-closed**: 어디 붙일지 모르는 인자를 버리지 않는다.
526
+ --cap-args)
527
+ shift
528
+ [ "${#CAPS[@]}" -gt 0 ] || _die "--cap-args 는 --cap 뒤에 와야 한다(어느 노드의 인자인지 결박 불가)"
529
+ _ci=$(( ${#CAPS[@]} - 1 ))
530
+ # 🟥 **중복 지정은 fail-closed**(cross-family 지목 + 재현, 2026-08-16). 초판은 같은
531
+ # `--cap`에 `--cap-args` 를 두 번 주면 **앞 값이 조용히 사라지고** 정상 실행됐다
532
+ # (`--cap A --cap-args FIRST --cap-args SECOND` → 노드가 `ARGS=[SECOND]`).
533
+ # 무음 폐기는 이 레포가 반복해서 이름 붙인 결함이다 — 의미가 append 인지 replace 인지
534
+ # **정하지 않은 채 하나를 버리느니** 막는다.
535
+ [ -z "${CAPARGS_SET[$_ci]:-}" ] || _die "같은 --cap 에 --cap-args 가 두 번 왔다 — 무음 폐기 대신 막는다(의미가 append 인지 replace 인지 정의되지 않았다)"
536
+ # 🟥 **결박 플래그와의 충돌은 fail-closed**(cross-family 지목 + 재현). 진입점은
537
+ # `$entry $node_args $argv_extra` 순으로 단어분해 실행되므로, `upstream_argv: yes` 를
538
+ # 선언한 노드에 `--cap-args --` 를 주면 그 노드의 옵션 파서가 `--` 에서 멈추고
539
+ # 뒤따르는 `--upstream-verdict <v>` 를 **위치 인자로 삼는다** = 인과 결박이 조용히 죽는다.
540
+ # 순서를 뒤집으면 위치 인자를 읽는 진입점이 깨지므로, 안전한 순서가 없다.
541
+ # ⇒ 두 계약을 **동시에 요구하지 않게** 한다. 결박은 `RELAY_UPSTREAM_VERDICT` env 로
542
+ # 항상 전달되므로 이 노드도 결박 자체를 잃지는 않는다.
543
+ if [ "$(_cap_get "${CAPS[$_ci]}" upstream_argv 2>/dev/null)" = "yes" ]; then
544
+ _die "이 노드는 upstream_argv: yes 다 — argv 결박과 --cap-args 를 같이 쓸 수 없다(둘의 인자 순서가 정의되지 않았다). env RELAY_UPSTREAM_VERDICT 로 결박을 받아라"
545
+ fi
546
+ # 셸 메타문자·제어문자 거부. ⚠️ **이건 «명령 실행 우회» 방어가 아니다** — 진입점은
547
+ # `eval` 이 아니라 단어분해로 돌고 전역 `set -f` 라 `;` `|` `$()` 글롭 틸드 중괄호는
548
+ # 연산자/확장으로 재해석되지 않는다. 이 가드가 막는 것은 **인자 경계의 조용한 변형**이다.
549
+ # 초판 주석은 "프로브와 같은 규칙" 이라고 적었는데 **과잉주장이었다**: 개행이 통과해
550
+ # 한 인자가 두 인자로 쪼개졌다(`$'GO\nSECOND'` → 노드가 `ARGC=2`). 개행/탭/제어문자를
551
+ # 추가로 막고, 문구를 사실에 맞춘다.
552
+ case "$1" in
553
+ *[\;\|\&\`\$\(\)\<\>]*|*'..'/*) _die "--cap-args 에 셸 메타문자/상위경로: $1" ;;
554
+ esac
555
+ case "$1" in
556
+ *[$'\n\t\r\v\f']*) _die "--cap-args 에 개행/탭/제어문자 — 인자 경계가 조용히 바뀐다" ;;
557
+ esac
558
+ CAPARGS[$_ci]="$1"; CAPARGS_SET[$_ci]=1
559
+ ;;
497
560
  --task) shift; task="$1" ;;
498
561
  --record) shift; recdir="$1" ;;
499
562
  --ack) ack=1 ;;
@@ -539,7 +602,7 @@ do_run() {
539
602
 
540
603
  for f in "${CAPS[@]}"; do
541
604
  n=$((n+1))
542
- _invoke_node "$f" "$task" "$upstream" "$recdir" "$n"
605
+ _invoke_node "$f" "$task" "$upstream" "$recdir" "$n" "${CAPARGS[$((n-1))]:-}"
543
606
  printf 'FH_NODE%d_ID: %s\n' "$n" "$(_cap_get "$f" id)"
544
607
  printf 'FH_NODE%d_VERDICT: %s\n' "$n" "$NODE_VERDICT"
545
608
  printf 'FH_NODE%d_EXIT: %s\n' "$n" "$NODE_EXIT"
@@ -276,6 +276,86 @@ else
276
276
  fail=1
277
277
  fi
278
278
 
279
+ # gate-anchor harness — the SAME defect the block above documents, still open six days later.
280
+ # MEASURED 2026-08-16 (weekly_audit_2026-08-16.md 🟥-2): scripts/gate_anchor_check.sh has shipped in
281
+ # package.json files[] since 2026-08-10 (b18a2bf, PR #323) with ZERO callers — not selfcheck, not
282
+ # prepublishOnly, not .git-hooks/, not .github/, not npm scripts. Its only other mention is a comment
283
+ # line in a .cap adapter, which is not a call. So the one instrument this repo owns for asking
284
+ # "is the gate green for the RIGHT REASON?" was itself never green, never red, never run.
285
+ #
286
+ # 🟥 The detector did not see it: lane_runner_check.sh reports 46/46 wired · self-test 13/13, because
287
+ # this file is neither a lane suite nor a --self-test subject — it is OUTSIDE the detector's field of
288
+ # view, exactly like directional_diff_gate on 2026-08-15, and it survived that repair.
289
+ #
290
+ # ⚠️ Do NOT use "documented in a .md" as the wiring test: publish_freshness_check.sh has no reference
291
+ # in any TRACKED doc (knowledge/, docs/) and IS wired (prepublishOnly), and it blocked a publish on
292
+ # 2026-08-16. (An earlier draft of this line said "zero .md references" flat; that is false — three
293
+ # gitignored tracks/*.md files mention it. The thesis survives, the number did not, and Axis 3 caught
294
+ # it.) Call sites are the evidence; documentation is not. The first scan for this finding also missed
295
+ # templates/.git-hooks/* because those files carry no extension — scope corrected twice.
296
+ #
297
+ # Exit contract (from the script header): 0 = every known-pair held · 1 = a pair COLLAPSED (real
298
+ # defect) · 10 = harness error OR hooks-not-installed.
299
+ #
300
+ # 🟥 10 IS TWO DIFFERENT PROPOSITIONS AND THIS BLOCK MUST SPLIT THEM. gate_anchor_check.sh reaches
301
+ # `exit 10` from FOUR places, and only ONE of them is a legitimate consumer state:
302
+ # :43 cwd/script repo mismatch → harness defect
303
+ # :99 mktemp failed → harness defect
304
+ # :133 _fixture_fail (git init/seed/remote/origin-main) → harness defect
305
+ # :339 NOT_INSTALLED > 0 → legitimate: a package-mode consumer has no hooks
306
+ # Folding all four into a quiet SKIP is fail-open: a fixture collapse in THIS repo (a git default
307
+ # -branch policy change, a restricted TMPDIR, forced commit.gpgsign) would render byte-identically to
308
+ # a consumer who simply has no hooks. That is the "unmeasured → clean" move this file legislates
309
+ # against — and worse, the --self-test loop 200 lines below already routes rc=10 to `fail=1` saying
310
+ # "it could not measure, so its verdicts prove nothing". Same file, same code, opposite verdict.
311
+ # (Adversarial review 2026-08-16 found this. ⚠️ An earlier draft of this comment cited `:524` for
312
+ # it; the standpoint review measured the real sites — the loop dispatches at :528 and routes
313
+ # rc=10 to fail=1 at :562, and the quoted sentence lives at :429/:475. `:524` is none of them, so
314
+ # a reviewer following it lands on prose. A stale line number in the comment that argues against
315
+ # stale instruments is this commit's own defect class, recorded rather than quietly corrected.)
316
+ #
317
+ # The discriminator is mechanical, not a guess: the three harness-defect paths use plain `echo
318
+ # "❌ harness-error: …"`, while the NOT-INSTALLED path goes through `say()` which `--quiet` suppresses.
319
+ # Verified 2026-08-16: running the script from an unrelated git repo with --quiet returns rc=10 AND
320
+ # prints `harness-error` to the captured stream. So harness-error-in-output → FAIL; rc=10 without it
321
+ # → genuine NOT-INSTALLED → SKIP, labelled UNMEASURED and loud.
322
+ #
323
+ # ⚠️ NAMED RESIDUAL, not closed here: in CI this lane is structurally UNMEASURED. No workflow sets
324
+ # core.hooksPath (grep: 0 hits in .github/workflows/), so validate.yml's selfcheck step always lands
325
+ # on the NOT-INSTALLED arm — and `validate` is this repo's only required status check. A permanently
326
+ # SKIPPED lane in the one required check is worse than a permanently red one, because SKIP does not
327
+ # catch the eye. Fixing it is one line in validate.yml (set core.hooksPath before the selfcheck step)
328
+ # and is deliberately NOT bundled here — see the handoff. Until then the only real measurement arm is
329
+ # the author's machine, which is the exact shape portability_lint.sh was written to condemn.
330
+ if [ ! -f scripts/gate_anchor_check.sh ]; then
331
+ _absent_subject_verdict "gate-anchor harness" "scripts/gate_anchor_check.sh" || fail=1
332
+ else
333
+ # Capture the status BEFORE any display filter — $? after a pipe is the filter's, and a failed
334
+ # check would read as 0 (the repo's own pipe-verdict guard fired on this exact shape twice while
335
+ # this block was being written).
336
+ _ga_out="$(bash scripts/gate_anchor_check.sh --quiet 2>&1)"; _ga_rc=$?
337
+ case "$_ga_rc" in
338
+ 0) echo "PASS gate-anchor harness (every known-pair held — gate is green for the right reason)" ;;
339
+ 1) echo "FAIL gate-anchor harness: a known-pair COLLAPSED — a gate is green for the wrong reason"
340
+ [ -n "$_ga_out" ] && printf '%s\n' "$_ga_out" | sed 's/^/ /'
341
+ fail=1 ;;
342
+ 10) if printf '%s' "$_ga_out" | grep -q 'harness-error'; then
343
+ echo "FAIL gate-anchor harness: the anchor could not MEASURE (harness-error, rc=10) — a"
344
+ echo " collapsed fixture is a defect in the instrument, not a consumer's missing hook."
345
+ printf '%s\n' "$_ga_out" | sed 's/^/ /'
346
+ fail=1
347
+ else
348
+ echo "SKIP gate-anchor harness: UNMEASURED (hooks not installed, rc=10)"
349
+ echo " Absence is not a pass — this run did NOT establish that the gates are sound."
350
+ [ -n "$_ga_out" ] && printf '%s\n' "$_ga_out" | sed 's/^/ /'
351
+ fi ;;
352
+ *) echo "FAIL gate-anchor harness: undeclared exit code $_ga_rc — the contract is 0/1/10, so an"
353
+ echo " unknown status is a harness defect, not a pass."
354
+ [ -n "$_ga_out" ] && printf '%s\n' "$_ga_out" | sed 's/^/ /'
355
+ fail=1 ;;
356
+ esac
357
+ fi
358
+
279
359
  # count_check's README format override is a mandatory-pass gate whose pattern is caller-supplied.
280
360
  # Same subject-present/anchor-gone shape as the block above: if the guard ships without its known
281
361
  # pair, a template that defeats the gate (a rendered newline turns the pattern into an OR search)
@@ -320,14 +400,49 @@ fi
320
400
  # how a fresh CI checkout gets approximated, so the check was absent from exactly the tree used
321
401
  # to reason about CI. scripts/test_package_coverage_lanes.sh pins the predicate across all four
322
402
  # tree shapes plus a known pair.
403
+ # ── FH 내장 어댑터 레인 ────────────────────────────────────────────────────
404
+ # 어댑터(`.claude/capabilities/adapters/*.cap` + `scripts/adapters/*.sh`)는 남의 하네스 능력을
405
+ # FH 안에서 호출하는 **기본 경로**다(운영자 결정 2026-08-16 — 남의 레포에 선언을 심지 않는다).
406
+ # 🟥 배선이 없으면 이 레인은 «만들고 배선 안 함» 이 된다 — 같은 날 `cluster_capability_scan.sh`
407
+ # 가 비재귀 글롭이라 어댑터를 통째로 못 보던 것을 이 세션이 실측으로 잡았고, 그 회귀를 막는
408
+ # 레인(L16/L16b)이 그 파일 self-test 안에 있다. 어댑터 자체의 레인은 여기서 돈다.
409
+ if [ ! -f scripts/adapters/peer_resolve.sh ]; then
410
+ _absent_subject_verdict "test_adapter_lanes.sh" "scripts/adapters/peer_resolve.sh" || fail=1
411
+ elif [ -f scripts/test_adapter_lanes.sh ]; then
412
+ if ! bash scripts/test_adapter_lanes.sh; then
413
+ fail=1
414
+ fi
415
+ else
416
+ _absent_subject_verdict "test_adapter_lanes.sh" "scripts/test_adapter_lanes.sh" || fail=1
417
+ fi
418
+
323
419
  if [ ! -f scripts/package_coverage_check.sh ]; then
324
420
  _absent_subject_verdict "test_package_coverage_lanes.sh" "scripts/package_coverage_check.sh" || fail=1
325
421
  elif [ -f scripts/test_package_coverage_lanes.sh ]; then
326
- if ! bash scripts/test_package_coverage_lanes.sh; then
422
+ if ! bash scripts/test_package_coverage_lanes.sh; then
327
423
  fail=1
328
424
  fi
329
- if ! bash scripts/package_coverage_check.sh; then
330
- fail=1
425
+ # The CHECKER's semantics are deliberately fail-closed and must not be softened: given a git
426
+ # checkout with no package.json it reports UNMEASURED, never "clean", and its own lane L4 pins
427
+ # exactly that. Applicability is the CALLER's judgment, and it is made mechanically here.
428
+ #
429
+ # A repo that ships no npm surface at all is not "unmeasured npm coverage" — it has no npm
430
+ # coverage to measure. Measured 2026-08-16 in a sibling harness that carries this suite verbatim
431
+ # and is not an npm package: this was a PERMANENT red, on every run forever. A gate that can
432
+ # never go green is not strict, it is a gate people learn to ignore — and this suite is the
433
+ # mandatory-pass surface, so that habit is expensive.
434
+ #
435
+ # Note the asymmetry, because it is the whole point: this repo HAS an npm surface, so if its
436
+ # package.json ever went missing the checker would fire and SHOULD — that is a real defect here.
437
+ # The guard below distinguishes "the surface is absent by nature" from "the surface is missing",
438
+ # which is the distinction `not found ≠ 0` exists to protect.
439
+ if [ -f package.json ] || [ -d bin ]; then
440
+ if ! bash scripts/package_coverage_check.sh; then
441
+ fail=1
442
+ fi
443
+ else
444
+ echo "SKIP package-coverage — NOT APPLICABLE: this repo ships no npm surface"
445
+ echo " (checked mechanically: package.json absent, bin/ absent — not a claim, a test)"
331
446
  fi
332
447
  else
333
448
  echo "FAIL test_package_coverage_lanes.sh: package_coverage_check.sh present but its anchor is missing"
@@ -489,7 +604,16 @@ done
489
604
  # and capability_registry_check does not: its terminal line is a genuine VERDICT
490
605
  # ("publish_freshness 캘리브레이션 통과/실패"), not a Korean test-case title, so a future rename of
491
606
  # any single lane cannot flip a real PASS into a false "dispatcher missing?" here.
492
- for _subj in compaction_probe judgment_circuit_lint novelty_claim_check chamber_witness digest_landing_check publish_freshness_check; do
607
+ # portability_lint joined 2026-08-16 (weekly_audit 🟧-3). Same qualification as publish_freshness_check:
608
+ # its terminal line is a genuine verdict ("portability-lint 캘리브레이션 통과/실패"), emitted once in
609
+ # the summary position, not a Korean test-case title — so renaming any single lane cannot flip a real
610
+ # PASS into a false "dispatcher missing?" here. It earns the slot for a second reason: its own
611
+ # self-test caught THREE defects in it before it ever shipped (a `|` field separator colliding with
612
+ # regex alternation, a line-window too narrow to see the next-line guard, and a rule that flagged its
613
+ # own prescription), and a hand-check then removed two false-positive classes that had inflated its
614
+ # first repo-wide count by 40% (50 → 30). An instrument that has never been able to fail is not
615
+ # calibrated; this one has failed, been fixed, and still discriminates (10 pass / 0 fail).
616
+ for _subj in compaction_probe judgment_circuit_lint novelty_claim_check chamber_witness digest_landing_check publish_freshness_check portability_lint; do
493
617
  if [ ! -f "scripts/$_subj.sh" ]; then
494
618
  _absent_subject_verdict "$_subj --self-test" "scripts/$_subj.sh" || fail=1
495
619
  else
@@ -528,6 +652,24 @@ for _subj in compaction_probe judgment_circuit_lint novelty_claim_check chamber_
528
652
  echo " so its verdicts prove nothing about $_subj. Not a lane failure, not a pass either."
529
653
  fail=1; continue ;;
530
654
  esac
655
+ # 🟥 portability_lint 만 **전체 줄 + 비영 카운트**를 요구한다 (적대검증 2026-08-16).
656
+ # 아래 substring 게이트는 «`캘리브레이션` 이라는 글자가 출력 어딘가에 있나» 만 본다. 그래서
657
+ # 그 스크립트의 `RULES` 를 통째로 비우고 `_ck` 호출을 전부 지워도
658
+ # `portability-lint 캘리브레이션 통과: 0 pass / 0 fail` 이 나오고 rc=0 → **PASS** 다.
659
+ # 이건 이 파일이 directional_diff_gate 에 대해 이미 명시적으로 방어한 결함
660
+ # (*"a suite with nothing left in it certifies itself"*)이고, 새 subject 만 약한 게이트에
661
+ # 들어갔다. 저자의 되돌림 프로브(«디스패처 제거 → FAIL»)는 **디스패처 존재**만 입증하고
662
+ # **레인 생존**은 입증하지 않는다 — 더 강한 뮤턴트(레인 삭제)는 안 돌았고, 돌았으면 초록이었다.
663
+ if [ "$_subj" = portability_lint ]; then
664
+ case "$_st_out" in
665
+ *"portability-lint 캘리브레이션 통과: "[1-9]*" pass / 0 fail"*) : ;;
666
+ *) echo "FAIL $_subj: --self-test 가 «비영 pass + 0 fail» 종단 verdict 를 내지 않았다"
667
+ echo " (레인이 전멸해도 substring 게이트는 통과한다 — 그래서 여기만 전체 줄을 본다)"
668
+ printf '%s\n' "$_st_out" | tail -3 | sed 's/^/ /'
669
+ fail=1; _st_rc=0 ;;
670
+ esac
671
+ continue
672
+ fi
531
673
  case "$_st_out" in
532
674
  *캘리브레이션*) : ;;
533
675
  *) echo "FAIL $_subj: --self-test produced no calibration verdict (dispatcher missing?)"
@@ -557,6 +699,24 @@ else
557
699
  fail=1
558
700
  fi
559
701
 
702
+ # cluster_capability_scan — wired 2026-08-16 (정체성 ①-(a) cluster-wizard).
703
+ # 종단 판정줄이 `… 캘리브레이션 통과/실패: N PASS / M FAIL` 이라 위 `_subj` 루프의
704
+ # 캘리브레이션 게이트를 **진짜 판정줄로** 만족한다 — 테스트 케이스 제목이 아니다.
705
+ # 그래서 이 subject 는 그 루프에 넣어도 안전하지만, 루프는 `scripts/<name>.sh` 존재를
706
+ # 전제로 SKIP/FAIL 을 가르므로 여기 직접 디스패치로 둔다(다른 두 capability 검사기와 같은 형태).
707
+ # 🟥 exit code 만 보지 않는다 — 레인이 전멸해도 0 이 나온다.
708
+ if [ ! -f scripts/cluster_capability_scan.sh ]; then
709
+ echo "FAIL cluster_capability_scan.sh: missing — it ships via package.json files[], so absence is deletion, not package mode"
710
+ fail=1
711
+ elif _out=$(bash scripts/cluster_capability_scan.sh --self-test < /dev/null 2>&1) \
712
+ && printf '%s\n' "$_out" | grep -qE '캘리브레이션 통과: [1-9][0-9]* PASS / 0 FAIL'; then
713
+ echo "PASS cluster_capability_scan.sh --self-test ($(printf '%s\n' "$_out" | grep -oE '[0-9]+ PASS / [0-9]+ FAIL' | tail -1))"
714
+ else
715
+ echo "FAIL cluster_capability_scan.sh: --self-test failed or produced no terminal verdict line"
716
+ _show_failure "$_out"
717
+ fail=1
718
+ fi
719
+
560
720
  # capability_effect_probe — wired 2026-08-16. `lane_runner_check.sh` had been flagging it as an
561
721
  # embedded --self-test subject with ZERO dispatchers, and that warning was load-bearing: the same
562
722
  # day, two SECURITY regression anchors were added to this suite (L8 args injection · L9 the git
@@ -752,7 +912,9 @@ else
752
912
  fail=1
753
913
  fi
754
914
 
755
- # marker axes-run lanes — 훅의 4축 자기대조 형식 검사(§CLAUDE.md 3층 자기 대조)의 앵커.
915
+ # marker axes-run lanes — 훅의 축 자기대조 형식 검사(§CLAUDE.md 3층 자기 대조)의 앵커.
916
+ # ⚠️ 「4축」이라고 적혀 있었는데 2026-08-17 부로 **6축 분기가 생겼다**(마커 날짜 >= 그 날이면
917
+ # 기호 키 ⓐ~ⓕ 요구, 미만이면 옛 ASCII 넷). 산문 층이라 레인은 안 깨지고 조용히 stale 이었다.
756
918
  # subject 부재를 FAIL 로 두는 이유는 branch-claim 블록과 같다: 이 subject 는 pre-commit 훅
757
919
  # 자체이고 files[] 에 있으므로 «정당한 부재» 가 없다. 부재 = 삭제다.
758
920
  if [ ! -f templates/.git-hooks/pre-commit ]; then