@chrono-meta/fh-gate 2.0.1 → 2.2.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.
- package/.claude/rules/fh_4axis_gate.md +77 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +26 -3
- package/CLAUDE.md +240 -4
- package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +38 -12
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +82 -0
- package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +85 -10
- package/knowledge/shared/harness-core/ship_readiness_gate.md +241 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +102 -0
- package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
- package/package.json +7 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/agents/quench-challenger.md +6 -1
- package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_negative.md +19 -0
- package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_positive.md +20 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +88 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
- package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
- package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
- package/scripts/capability_registry_check.sh +75 -12
- package/scripts/chamber_run.sh +39 -5
- package/scripts/chamber_witness.sh +25 -0
- package/scripts/fh-gate.sh +16 -1
- package/scripts/ko_tech_writer_calibrate.py +127 -0
- package/scripts/package_coverage_check.sh +10 -0
- package/scripts/prepublish_scope_note.sh +139 -0
- package/scripts/publish_freshness_check.sh +21 -2
- package/scripts/selfcheck.sh +32 -6
- package/scripts/test_fh_gate_regressions.sh +21 -9
- package/scripts/test_ko_tech_writer_lanes.sh +61 -0
- package/scripts/test_marker_axes_run_lanes.sh +87 -3
- package/scripts/test_marker_crossfamily_lanes.sh +31 -1
- package/templates/.git-hooks/pre-commit +225 -10
- package/templates/subagent-tally-hook.json +2 -2
|
@@ -569,7 +569,61 @@ validate_crossfamily_leg() { # $1 = marker path
|
|
|
569
569
|
|
|
570
570
|
case "$val" in
|
|
571
571
|
declined)
|
|
572
|
-
|
|
572
|
+
# `declined` means ONE thing: the OPERATOR declined sidecars (UAP), a chosen floor. It was
|
|
573
|
+
# the only value in this enum with no grounds requirement, and that made it the enum's soft
|
|
574
|
+
# spot: an author who judged decorrelation unnecessary reached for the nearest permissive
|
|
575
|
+
# token and it passed clean. Measured 2026-08-16, in this repo, by the session that had
|
|
576
|
+
# CORRECTED a different marker's crossfamily value earlier the same day and had the correct
|
|
577
|
+
# semantics printed to it in this hook's own error text — i.e. neither ignorance of the enum
|
|
578
|
+
# nor of the file. Form was satisfied, so the question stopped.
|
|
579
|
+
#
|
|
580
|
+
# A closed enum stops free prose from collapsing distinct states; it does NOT stop a wrong
|
|
581
|
+
# token from being a valid one. This lane is the difference. The grounds bar is deliberately
|
|
582
|
+
# the same shape as the degrade branch below (substance + a token naming WHO/WHERE), because
|
|
583
|
+
# the claim being made is factual and attributable: an operator decision has a record.
|
|
584
|
+
# 🟥 THE TEST IS A RESOLVABLE RECORD, NOT VOCABULARY. The first version of this lane grepped
|
|
585
|
+
# for words (operator|uap|consent|…). A cross-family review (codex/gpt-5.6-terra) attacked it
|
|
586
|
+
# and all three findings reproduced on the spot:
|
|
587
|
+
# · SELF-VALIDATING `declined — declined because I judged it unnecessary` PASSED, because
|
|
588
|
+
# `declin` was in the keyword list and the author had echoed the value.
|
|
589
|
+
# · VACUOUS PASS `declined — operator documentation says authors may choose` PASSED
|
|
590
|
+
# while describing no declination at all.
|
|
591
|
+
# · OVER-BLOCK `declined — the person who runs this harness asked us not to use
|
|
592
|
+
# external reviewers here` was BLOCKED — a real operator declination,
|
|
593
|
+
# in natural prose, carrying none of the listed tokens.
|
|
594
|
+
# Adding patterns is the Grep-Collision Treadmill (P10): each new word relocates the evasion.
|
|
595
|
+
# So the check changed KIND. Per CLAUDE.md §Mechanization Boundary a channel check asserts a
|
|
596
|
+
# property OF THE RECORD, never a conclusion — and the honest record property here is:
|
|
597
|
+
# **the claim names a file that exists.** Vocabulary cannot be stuffed into it, a fabricated
|
|
598
|
+
# path fails, and a legitimate declination just cites where it lives.
|
|
599
|
+
# HONEST RESIDUAL, stated rather than implied: this proves a cited record EXISTS, not that it
|
|
600
|
+
# SAYS what is claimed. That is the marker's own declared scope (form + non-vacuity +
|
|
601
|
+
# auditability, NOT provenance) — identical ceiling, not a new gap.
|
|
602
|
+
_cf_cited=""
|
|
603
|
+
for _tok in $reason; do
|
|
604
|
+
_tok=${_tok%%,}; _tok=${_tok%%\)}; _tok=${_tok##\(}
|
|
605
|
+
case "$_tok" in
|
|
606
|
+
*/*|*.md|*.yaml|*.yml|*.json)
|
|
607
|
+
if [ -e "$REPO_ROOT/$_tok" ] || [ -e "$_tok" ] || [ -e "$HOME/$_tok" ]; then
|
|
608
|
+
_cf_cited="$_tok"; break
|
|
609
|
+
fi ;;
|
|
610
|
+
esac
|
|
611
|
+
done
|
|
612
|
+
if [ "${#reason}" -lt 20 ] || [ -z "$_cf_cited" ]; then
|
|
613
|
+
echo " ❌ FAIL — 'declined' must cite a record that EXISTS on disk."
|
|
614
|
+
echo " declined = the OPERATOR declined sidecars, a chosen floor. It does NOT mean"
|
|
615
|
+
echo " 'I judged decorrelation unnecessary here' — that is a CHOICE the author made"
|
|
616
|
+
echo " with a panel reachable, and its honest value is DEGRADED_PANEL_UNUSED."
|
|
617
|
+
echo " An operator decision has a record. Name its path on this same line:"
|
|
618
|
+
echo " crossfamily: declined — operator declined sidecars, tracks/_meta/user_adaptation_profile.md"
|
|
619
|
+
echo " crossfamily: declined — chosen floor per CLAUDE.local.md §sidecar"
|
|
620
|
+
echo " No such record → you are not describing an operator declination:"
|
|
621
|
+
echo " crossfamily: DEGRADED_PANEL_UNUSED — codex+agy reachable, not recruited (<why>)"
|
|
622
|
+
[ "${#reason}" -ge 20 ] && echo " (grounds were substantive but named no resolvable path)"
|
|
623
|
+
return 1
|
|
624
|
+
fi
|
|
625
|
+
echo " ✅ cross-family leg: declined (operator-chosen floor, first-class — not a degrade)"
|
|
626
|
+
echo " $(printf '%s' "$reason" | cut -c1-80)" ;;
|
|
573
627
|
panel*)
|
|
574
628
|
if ! printf '%s' "$val" | grep -qE '^panel\([^)]+\)$'; then
|
|
575
629
|
echo " ❌ FAIL — panel claim without a family list: '$val'"
|
|
@@ -677,33 +731,143 @@ validate_crossfamily_leg() { # $1 = marker path
|
|
|
677
731
|
# 브랜치가 전부 막히고, 그 과차단이 `--no-verify` 를 습관화시켜 같은 훅 안의 Destructive-Op
|
|
678
732
|
# 게이트까지 무장해제한다(오늘 실측된 트레이드오프).
|
|
679
733
|
AXES_RUN_GRACE_DATE="2026-08-10"
|
|
734
|
+
# ── 6축 확장 (2026-08-17) ────────────────────────────────────────────────────────
|
|
735
|
+
# 산문 정본은 6축(`fh_three_layer_canon.md` §1-a-2)인데 이 훅은 네 글자만 봤다. 확장하면서
|
|
736
|
+
# **키 표기를 산문과 1:1 로 정렬**한다 — 그 이유가 「예쁘게」가 아니라 아래 실측이다.
|
|
737
|
+
#
|
|
738
|
+
# 🟥 옛 배열과 새 배열은 **같은 글자가 다른 축을 가리킨다**:
|
|
739
|
+
# 문자 §1-a (옛 4축, 마커가 쓰던 것) §1-a-2 (현 6축, 산문이 쓰는 것)
|
|
740
|
+
# ⓐ 다른 계열 다른 계열 (동일)
|
|
741
|
+
# ⓑ 첫 실사용 **입장** ← 옛 ⓑ 는 현 ⓔ 로 밀렸다
|
|
742
|
+
# ⓒ 기록 그라운딩 격리 그라운딩 (대응)
|
|
743
|
+
# ⓓ 되돌림 실측 **3자 대면** ← 옛 ⓓ 는 현 ⓕ 로 밀렸다
|
|
744
|
+
# 그래서 `b=`/`d=` 를 그대로 쓰면서 의미만 바꾸면 **훅이 조용히 다른 축으로 읽으며
|
|
745
|
+
# 오류를 내지 않는다.** 무음 재해석은 이 레포가 `not found ≠ 0` 라 부르는 것의 형제다.
|
|
746
|
+
#
|
|
747
|
+
# 실측(2026-08-17): 디스크 마커 190개 중 유예일 이후 51개. 훅은 «오늘·이 브랜치» 마커
|
|
748
|
+
# **한 개만** 검증하므로(아래 호출부) 확장으로 **커밋이 막히는 기존 마커는 0건**이다.
|
|
749
|
+
# 실제 비용은 그 51건이 «옛 배열인지 새 배열인지 읽을 수 없다» 는 것 — 파손이 아니라
|
|
750
|
+
# **미측정**이다. 그래서 처방이 「고친다」가 아니라 **「어느 배열인지 말하게 만든다」**:
|
|
751
|
+
# 마커 파일명 날짜 < 2026-08-17 → 옛 4축 배열 (ASCII 키)
|
|
752
|
+
# 마커 파일명 날짜 >= 2026-08-17 → 신 6축 배열 (기호 키)
|
|
753
|
+
#
|
|
754
|
+
# 🟥 **판별자는 날짜지 표기법이 아니다 — 초판이 그 반대로 적었고 코퍼스가 반증했다.**
|
|
755
|
+
# 초판 주석: «표기법 자체가 배열을 선언하므로 감사자가 grep 한 방으로 판별한다».
|
|
756
|
+
# ⓓ3자대면 축이 디스크 마커를 손으로 세어 반례를 냈다 — axes-run 보유 53건 중
|
|
757
|
+
# 기호 키 4 · 혼용 1 이고, 기호 4건 중 **2건이 2026-08-10 자이면서 옛 4축 의미**다:
|
|
758
|
+
# …fix_digest-collection-spec-reality_2026-08-10.marker
|
|
759
|
+
# → `ⓐ 미실행 · ⓑ 첫실사용 … · ⓓ 미실행` (ⓑ·ⓓ 가 현 배열에선 ⓔ·ⓕ)
|
|
760
|
+
# …fix_entrypoint-drift-and-close-protocol_2026-08-10.marker
|
|
761
|
+
# → `ⓐ 적대검증 · ⓑ 첫실사용 … · ⓓ 되돌림`
|
|
762
|
+
# 훅은 이 셋을 절대 안 읽으므로 커밋을 막지 않는다. 틀리는 건 **감사자**다.
|
|
763
|
+
# ⇒ 표기 정렬은 «앞으로 쓸 때 안 헷갈리게» 하는 값은 있으나 **소급 판별자로 쓰면 안 된다**.
|
|
764
|
+
#
|
|
765
|
+
# ⚠️ **그리고 이 날짜 비교는 프로덕션에서 도달 불가 분기다 — 숨기지 않고 적는다.**
|
|
766
|
+
# 아래 호출부가 마커 경로를 `${TODAY}` 로 **구성**하므로 `mdate` 는 항상 오늘이다. 즉
|
|
767
|
+
# 오늘 이후 `six=0` 분기를 타는 실제 커밋은 없고, 살아있는 소비자는 픽스처뿐이다.
|
|
768
|
+
# **기존 마커를 지킨 것은 이 상수가 아니라 그 경로 구성이다.** 상수는 픽스처가 경계를
|
|
769
|
+
# 고정하는 값 + 훗날 호출부가 임의 마커를 검증하게 될 때의 대비로 남긴다.
|
|
770
|
+
# (선례: 8일 전 `crossfamily:` 를 free prose → closed enum 으로 바꿀 때는 컷오프 없이
|
|
771
|
+
# 그냥 강제했고 문제가 없었다 — 이유가 정확히 같다.)
|
|
772
|
+
# 부수 노출: 시계가 과거로 틀어진 노드는 파일명도 과거 날짜라 `six=0` 으로 새 나간다
|
|
773
|
+
# (fail-open, 저위험). 닫으려면 상수를 지우고 무조건 6축을 요구하면 된다 — 어차피
|
|
774
|
+
# 도달 불가 분기이므로 **지우는 것이 곧 닫는 것**이다. 지금은 안 지웠다(픽스처 경계값).
|
|
775
|
+
#
|
|
776
|
+
# 이식성: bash 3.2 + BSD grep, LC_ALL={C, POSIX, en_US.UTF-8, ko_KR.UTF-8} 4개 arm 에서
|
|
777
|
+
# 6/6 HIT · known-negative(ⓖ) 0건으로 실측했다. ⚠️ Linux/GNU grep 은 **미측정** — CI(ubuntu)
|
|
778
|
+
# 가 첫 측정이고, 깨지면 아래 픽스처 레인이 빨개진다(무음 아님).
|
|
779
|
+
#
|
|
780
|
+
# ⓑ입장은 값을 여기 적지 않는다 — 이미 `standpoint:` 자기 필드가 정본이라 이중 기록이 된다.
|
|
781
|
+
# 대신 `ⓑ=→standpoint` 포인터를 요구하고, 그 경우 `standpoint:` 줄의 **존재**를 확인한다.
|
|
782
|
+
# (`standpoint:` 값 자체의 enum 검증은 별건이고 정본이 명시적으로 유보해 뒀다.)
|
|
783
|
+
SIX_AXES_GRACE_DATE="2026-08-17"
|
|
680
784
|
validate_marker_axes_run() { # $1 = marker path
|
|
681
|
-
local m="$1" mdate line ax miss=""
|
|
785
|
+
local m="$1" mdate line ax miss="" nlines axkeys six=0
|
|
682
786
|
mdate=$(basename "$m" | sed -nE 's/.*_([0-9]{4}-[0-9]{2}-[0-9]{2})\.marker$/\1/p')
|
|
683
787
|
# 날짜를 못 읽으면 요구하지 않는다 — 파일명 규약 밖의 마커를 이 검사가 판정할 근거가 없다.
|
|
684
788
|
[ -n "$mdate" ] || return 0
|
|
685
789
|
[ "$mdate" \< "$AXES_RUN_GRACE_DATE" ] && return 0
|
|
790
|
+
[ "$mdate" \< "$SIX_AXES_GRACE_DATE" ] || six=1
|
|
791
|
+
|
|
792
|
+
# 다중 `axes-run:` 줄 차단. `crossfamily:` 는 예전부터 이걸 막는데 `axes-run` 은 안 막아서,
|
|
793
|
+
# 둘째 줄이 **없다고 판정되는 게 아니라 안 보인다**(아래 grep -m1). 실측: 디스크에 2줄짜리
|
|
794
|
+
# 마커 1건 존재(2026-08-15). 같은 얼굴이므로 같은 자리에서 닫는다.
|
|
795
|
+
nlines=$(grep -c -E '^[[:space:]]*axes-run:' "$m" 2>/dev/null)
|
|
796
|
+
if [ "${nlines:-0}" -gt 1 ]; then
|
|
797
|
+
echo " ❌ MARKER FORMAT — 'axes-run:' 줄이 ${nlines}개다 (하나여야 한다)"
|
|
798
|
+
echo " 이 검사는 첫 줄만 읽는다. 둘째 줄은 «없다고 판정»되는 게 아니라 **안 보인다** —"
|
|
799
|
+
echo " 즉 거기 적은 축은 검사를 통째로 우회한다. 한 줄로 합쳐라."
|
|
800
|
+
return 1
|
|
801
|
+
fi
|
|
686
802
|
|
|
687
803
|
line=$(grep -m1 -E '^[[:space:]]*axes-run:' "$m" 2>/dev/null)
|
|
688
804
|
if [ -z "$line" ]; then
|
|
689
|
-
echo " ❌ MARKER FORMAT — missing 'axes-run:' line (
|
|
805
|
+
echo " ❌ MARKER FORMAT — missing 'axes-run:' line (축 자기 대조)"
|
|
690
806
|
echo " CLAUDE.md §3층 자기 대조: **돌린 축과 안 돌린 축을 각각 이름으로** 적는다."
|
|
691
807
|
echo " 안 돈 축은 침묵이 아니라 'none' 이다 — 미측정을 0으로 렌더하지 않는다."
|
|
692
|
-
|
|
693
|
-
|
|
808
|
+
if [ "$six" -eq 1 ]; then
|
|
809
|
+
echo " axes-run: ⓐ=codex(4건) ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=CI배선 ⓕ=되돌림(국소성)"
|
|
810
|
+
echo " (ⓐ다른계열 · ⓑ입장 · ⓒ격리그라운딩 · ⓓ3자대면 · ⓔ첫실사용 · ⓕ되돌림실측)"
|
|
811
|
+
else
|
|
812
|
+
echo " axes-run: a=codex(4건) b=CI배선 c=none d=되돌림(국소성 확인)"
|
|
813
|
+
echo " (a=다른계열 · b=첫실사용 · c=기록그라운딩 · d=되돌림)"
|
|
814
|
+
fi
|
|
694
815
|
return 1
|
|
695
816
|
fi
|
|
696
|
-
|
|
817
|
+
if [ "$six" -eq 1 ]; then axkeys="ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ"; else axkeys="a b c d"; fi
|
|
818
|
+
for ax in $axkeys; do
|
|
697
819
|
echo "$line" | grep -qE "(^|[[:space:]])${ax}=[^[:space:]]" || miss="$miss $ax"
|
|
698
820
|
done
|
|
699
821
|
if [ -n "$miss" ]; then
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
822
|
+
if [ "$six" -eq 1 ]; then
|
|
823
|
+
echo " ❌ MARKER — 'axes-run:' 에 빠진 축:$miss"
|
|
824
|
+
echo " 여섯 축을 **전부** 적는다. 안 돌린 축은 'none' 으로 명시한다 — 빠뜨리는 것과"
|
|
825
|
+
echo " 안 돌렸다고 적는 것은 다른 명제이고, 빠뜨리면 읽는 쪽이 전자를 후자로 읽는다."
|
|
826
|
+
echo " axes-run: ⓐ=codex(4건) ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=CI배선 ⓕ=되돌림(국소성)"
|
|
827
|
+
echo " (ⓐ다른계열 · ⓑ입장 · ⓒ격리그라운딩 · ⓓ3자대면 · ⓔ첫실사용 · ⓕ되돌림실측)"
|
|
828
|
+
echo " 🟥 옛 ASCII 배열(a=…d=)과 **글자 의미가 다르다** — 옛 b=첫실사용은 지금 ⓔ,"
|
|
829
|
+
echo " 옛 d=되돌림은 지금 ⓕ 다. 옛 표기를 그대로 옮기면 축이 뒤바뀐다."
|
|
830
|
+
else
|
|
831
|
+
echo " ❌ MARKER — 'axes-run:' 에 빠진 축:$miss"
|
|
832
|
+
echo " 네 축을 **전부** 적는다. 안 돌린 축은 'none' 으로 명시한다 — 빠뜨리는 것과"
|
|
833
|
+
echo " 안 돌렸다고 적는 것은 다른 명제이고, 빠뜨리면 읽는 쪽이 전자를 후자로 읽는다."
|
|
834
|
+
echo " axes-run: a=codex(4건) b=CI배선 c=none d=되돌림(국소성 확인)"
|
|
835
|
+
fi
|
|
704
836
|
return 1
|
|
705
837
|
fi
|
|
706
838
|
|
|
839
|
+
if [ "$six" -eq 1 ]; then
|
|
840
|
+
# ── 혼용 가드는 **짓지 않는다**. 지었다가 실측으로 지웠다 ────────────────────
|
|
841
|
+
# 초판은 `grep -qE "(^|[[:space:]])[a-f]=[^[:space:]]"` 로 옛 ASCII 키 잔존을 차단했다.
|
|
842
|
+
# 자기 프로브에서 두 가지가 드러났다:
|
|
843
|
+
# ⓐ **오탐이 실재한다** — 값이 자유 산문이라 `ⓔ=실사용 ⓕ=되돌림 f=0.9 상승` 이나
|
|
844
|
+
# `ⓔ=arm a=제어 b=처리 비교` 가 «혼용» 으로 차단됐다. 과차단은 `--no-verify` 를
|
|
845
|
+
# 훈련시키고, 그건 같은 훅 안의 Destructive-Op 게이트까지 무장해제한다.
|
|
846
|
+
# ⓑ **그리고 애초에 불필요하다** — 「어느 배열인가」는 **여섯 기호가 다 있는가**로
|
|
847
|
+
# 이미 판별된다. 옛 ASCII 키가 기호 키를 *대신하는* 경우는 그 기호가 빠졌다는
|
|
848
|
+
# 뜻이므로 바로 위 missing-key 검사가 이미 차단한다. 여섯이 다 있는 줄에 남은
|
|
849
|
+
# ASCII 조각은 값의 일부일 뿐 배열 선언을 흐리지 않는다.
|
|
850
|
+
# ⇒ 오탐만 만들고 아무것도 더 막지 않는 필터였다. **삭제가 수리보다 낫다**
|
|
851
|
+
# ([[feedback_deletion_beats_repair_dead_filter]]). 픽스처는 남긴다 — 그 케이스는
|
|
852
|
+
# 여전히 차단돼야 하고, 이제 *다른 사유로* 차단된다는 사실 자체가 고정할 값어치가 있다.
|
|
853
|
+
|
|
854
|
+
# ⓑ입장은 값을 여기 적지 않는다 — `standpoint:` 자기 필드가 정본이라 이중 기록이 된다.
|
|
855
|
+
# 포인터를 쓴 경우 그 필드의 **존재**만 확인한다(값 enum 검증은 별건, 정본이 유보).
|
|
856
|
+
# 🟥 화살표를 필수로 보면 **fail-open** 이다 — 자기 프로브 실측: `ⓑ=standpoint` (화살표
|
|
857
|
+
# 없음)는 검사를 통째로 비껴가서 죽은 포인터가 그대로 통과했다. 그래서 «ⓑ 값이
|
|
858
|
+
# standpoint 를 참조하는가» 로 넓힌다. **명시 잔여**: `ⓑ=입장리뷰 standpoint 참조`
|
|
859
|
+
# 처럼 참조어가 **다른 토큰**에 있으면 여전히 안 잡힌다(값에 공백이 허용되므로
|
|
860
|
+
# 토큰 경계를 알 수 없다). 넓히려면 값 문법을 조여야 하고 그건 별건이다.
|
|
861
|
+
if echo "$line" | grep -qE "(^|[[:space:]])ⓑ=[^[:space:]]*standpoint" \
|
|
862
|
+
&& ! grep -qE '^[[:space:]]*standpoint:[[:space:]]*[^[:space:]]' "$m" 2>/dev/null; then
|
|
863
|
+
echo " ❌ MARKER — 'ⓑ=→standpoint' 라고 적었는데 'standpoint:' 줄이 없다"
|
|
864
|
+
echo " 포인터가 가리키는 곳이 비어 있으면 그건 기록이 아니라 죽은 포인터다."
|
|
865
|
+
echo " standpoint: tier1 (내용만 — 남의 하네스 정본을 안 읽었다)"
|
|
866
|
+
echo " standpoint: not-applicable"
|
|
867
|
+
return 1
|
|
868
|
+
fi
|
|
869
|
+
fi
|
|
870
|
+
|
|
707
871
|
line=$(grep -m1 -E '^[[:space:]]*controls:' "$m" 2>/dev/null \
|
|
708
872
|
| sed -E 's/^[[:space:]]*controls:[[:space:]]*//')
|
|
709
873
|
if [ -z "$line" ]; then
|
|
@@ -1176,6 +1340,57 @@ if [ -f "$JC_REG" ] && [ -f "$JC_LINT" ]; then
|
|
|
1176
1340
|
fi
|
|
1177
1341
|
fi
|
|
1178
1342
|
|
|
1343
|
+
# ── Portability lint (advisory — the SHELL-layer sibling of the two lints above) ──────────────
|
|
1344
|
+
# 🟥 WHY THIS IS HERE AND NOT DEFERRED. `scripts/portability_lint.sh` shipped in the immediately
|
|
1345
|
+
# preceding commit with its self-test wired and NO real-run caller, and `fh_4axis_gate.md:232` says
|
|
1346
|
+
# exactly what that is:
|
|
1347
|
+
# "If you build a checker and defer its caller, you shipped prose. The gate below trims what
|
|
1348
|
+
# you *attached*; it never licenses shipping something unreachable."
|
|
1349
|
+
# The standpoint review (2026-08-16, isolated agent reading the canon COLD before the diff) caught
|
|
1350
|
+
# it — and caught that the same commit CITED that section as its bundling justification while
|
|
1351
|
+
# violating it in the other half. Two other adversarial legs and a cross-family leg had all missed
|
|
1352
|
+
# this, because all three were handed the author's framing; the standpoint leg was handed the canon.
|
|
1353
|
+
# That is the axis earning its place, and this block is the repair.
|
|
1354
|
+
#
|
|
1355
|
+
# Placement is also not free choice: weekly_audit_2026-08-16 🟧-3 prescribed "pre-commit 에 패턴
|
|
1356
|
+
# denylist 한 장 … 새 러너가 아니라 degrade_direction_scan 옆 한 레인" — the runner already exists,
|
|
1357
|
+
# so this lane is the half that was missing from the prescription, not a second mechanism.
|
|
1358
|
+
#
|
|
1359
|
+
# STAGED BLOBS, not working-tree paths — same reason the two lints above use _stage_blob: measuring
|
|
1360
|
+
# the disk after `git add` reports clean while a different blob commits (optimistic fail-open).
|
|
1361
|
+
# ADVISORY by construction: FP-tolerant pre-screen, never blocks. Over-blocking here would train
|
|
1362
|
+
# `--no-verify`, which disarms the irreversible Destructive-Op gate living in the sibling hook.
|
|
1363
|
+
PL_LINT="$REPO_ROOT/scripts/portability_lint.sh"
|
|
1364
|
+
if [ -f "$PL_LINT" ]; then
|
|
1365
|
+
_pl_targets=()
|
|
1366
|
+
while IFS= read -r _pl_line; do
|
|
1367
|
+
[ -n "$_pl_line" ] || continue
|
|
1368
|
+
# 확장자 없는 셸 파일도 대상이다(.git-hooks/pre-commit·pre-push, bin/fh-*) — 이 레포에서
|
|
1369
|
+
# BSD/GNU 결함이 가장 비싼 파일들이 바로 그 형태이고, 초판 스캔이 그래서 놓쳤다.
|
|
1370
|
+
case "$_pl_line" in
|
|
1371
|
+
*.sh) : ;;
|
|
1372
|
+
templates/.git-hooks/*|bin/*) : ;;
|
|
1373
|
+
*) continue ;;
|
|
1374
|
+
esac
|
|
1375
|
+
_b="$(_stage_blob "$_pl_line")" || continue
|
|
1376
|
+
# 비-셸(bin/*.js 등)은 shebang 으로 걸러낸다. 확장자로 거르면 확장자 없는 훅이 다시 빠진다.
|
|
1377
|
+
case "$_pl_line" in
|
|
1378
|
+
*.sh) : ;;
|
|
1379
|
+
*) head -1 "$_b" 2>/dev/null | grep -qE '^#!.*\b(ba)?sh' || { rm -f "$_b"; continue; } ;;
|
|
1380
|
+
esac
|
|
1381
|
+
_pl_targets+=("$_b"); _STAGE_TMPS+=("$_b"); _STAGE_MAP+=("$_b=$_pl_line")
|
|
1382
|
+
done < <(printf '%s\n' "$STAGED")
|
|
1383
|
+
if [ "${#_pl_targets[@]}" -gt 0 ]; then
|
|
1384
|
+
echo "[Gate] shell file staged — portability lint (advisory)..."
|
|
1385
|
+
_pl_out="$(bash "$PL_LINT" "${_pl_targets[@]}" 2>&1)"; _pl_rc=$?
|
|
1386
|
+
printf '%s\n' "$(_unmap "$_pl_out")" | sed 's/^/ /'
|
|
1387
|
+
# rc=10 은 이 스캐너 자신의 HARNESS-ERROR 다. PASS 도 FAIL 도 아니며, 삼키면 죽은 계기가
|
|
1388
|
+
# 깨끗한 실행으로 렌더된다 — 이 커밋 전체가 고치고 있는 그 클래스다.
|
|
1389
|
+
[ "$_pl_rc" = "10" ] && echo " 🟥 portability lint HARNESS-ERROR (rc=10) — instrument did not measure"
|
|
1390
|
+
echo " (advisory — does not block. 알고 쓰는 줄엔 \`# portability-noqa: <사유>\`.)"
|
|
1391
|
+
fi
|
|
1392
|
+
fi
|
|
1393
|
+
|
|
1179
1394
|
# ── Novelty-claim ledger (advisory) ───────────────────────────────────────────
|
|
1180
1395
|
# "Ask the world before asserting novelty" is an INTENT trigger — un-hookable, like the
|
|
1181
1396
|
# repo-go-public surface. So this does not force the asking; it makes NOT having asked visible
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_readme": "FH dispatch-tally hook. Merge the `hooks.SubagentStop` block below into your .claude/settings.json (that file is gitignored, which is why this template exists). It feeds session_close_check.sh ④-e, which blocks a close push when sub-agents were dispatched and the day's invocation-log entries are zero. The hook TALLIES ONLY — it never writes a log entry, because a fabricated outcome/evidence field would poison the 60/40 promotion gate worse than a missing one.",
|
|
2
|
+
"_readme": "FH dispatch-tally hook. Merge the `hooks.SubagentStop` block below into your .claude/settings.json (that file is gitignored, which is why this template exists). It feeds session_close_check.sh ④-e, which blocks a close push when sub-agents were dispatched and the day's invocation-log entries are zero. The hook TALLIES ONLY — it never writes a log entry, because a fabricated outcome/evidence field would poison the 60/40 promotion gate worse than a missing one. Root resolution is 3-tier (CLAUDE_PROJECT_DIR -> git rev-parse --show-toplevel -> hardcoded path), matching the SAME convention fh_node_check.sh already uses in its own SessionStart hook -- a 2026-08-16 standpoint review on a sibling harness (pmh-dev) found this template's earlier 2-tier version get copy-pasted without picking up that convention, silently diverging from session_close_check.sh's own git-toplevel-first resolution in a nested/worktree/subagent context. If you adapt this template for another repo, keep the 3-tier form.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SubagentStop": [
|
|
5
5
|
{
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "bash -c 'HUB=\"${CLAUDE_PROJECT_DIR:-$HOME/projects/forge-harness}\"; T=\"$HUB/tracks/_meta/.subagent_dispatch_tally\"; mkdir -p \"$(dirname \"$T\")\" 2>/dev/null; printf \"%s\\n\" \"$(date +%Y-%m-%d)\" >> \"$T\" 2>/dev/null; exit 0'",
|
|
10
|
+
"command": "bash -c 'HUB=\"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || echo \"$HOME/projects/forge-harness\")}\"; T=\"$HUB/tracks/_meta/.subagent_dispatch_tally\"; mkdir -p \"$(dirname \"$T\")\" 2>/dev/null; printf \"%s\\n\" \"$(date +%Y-%m-%d)\" >> \"$T\" 2>/dev/null; exit 0'",
|
|
11
11
|
"timeout": 5
|
|
12
12
|
}
|
|
13
13
|
]
|