@chrono-meta/fh-gate 1.4.99 → 2.0.1

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 (41) 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-plugin/marketplace.json +3 -3
  7. package/AGENTS.md +9 -3
  8. package/CHEATSHEET.md +17 -1
  9. package/CLAUDE.md +2 -2
  10. package/README.ja.md +55 -20
  11. package/README.ko.md +86 -20
  12. package/README.md +57 -20
  13. package/README.zh.md +49 -19
  14. package/knowledge/shared/harness-core/fh_three_layer_canon.md +96 -5
  15. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +36 -0
  16. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +1 -1
  17. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +214 -0
  18. package/knowledge/shared/harness-core/harness_verification_core_extended.md +1 -1
  19. package/knowledge/shared/harness-core/ship_readiness_gate.md +29 -10
  20. package/knowledge/shared/learnings/subagent_invocations_log.yaml +73 -0
  21. package/package.json +13 -3
  22. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  23. package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
  24. package/plugins/fh-meta/CHANGELOG.md +45 -0
  25. package/scripts/adapters/gstack_content_safety.sh +186 -0
  26. package/scripts/adapters/mate_agent_boundary.sh +120 -0
  27. package/scripts/adapters/peer_resolve.sh +99 -0
  28. package/scripts/adapters/qasp_new_code_anchor.sh +159 -0
  29. package/scripts/branch_claim.sh +14 -0
  30. package/scripts/capability_effect_probe.sh +701 -0
  31. package/scripts/capability_registry_check.sh +313 -16
  32. package/scripts/cluster_capability_scan.sh +629 -0
  33. package/scripts/fh_session_load.sh +70 -0
  34. package/scripts/portability_lint.sh +257 -0
  35. package/scripts/publish_freshness_check.sh +204 -0
  36. package/scripts/relay_channel.sh +70 -7
  37. package/scripts/selfcheck.sh +166 -1
  38. package/scripts/test_adapter_lanes.sh +296 -0
  39. package/scripts/test_capability_entrypoint_shipping.sh +83 -6
  40. package/scripts/test_relay_channel_lanes.sh +76 -0
  41. package/templates/.git-hooks/pre-commit +14 -2
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ # qasp_new_code_anchor.sh — FH 소유 **어댑터**. peer `qasp` 의
3
+ # `scripts/new_code_anchor_scan.sh` 를 클러스터 호출 가능한 typed capability 로 감싼다.
4
+ #
5
+ # ─────────────────────────────────────────────────────────────────────────────
6
+ # 무엇을 재나
7
+ # ─────────────────────────────────────────────────────────────────────────────
8
+ # **새로 추가된 실행 코드에 그것을 보는 앵커(테스트)가 있는가.** peer 헤더의 기원 실측:
9
+ # 앵커 0개인 신규 스크립트가 들어왔는데 CI 는 `success / 123 passed` 였다 — 그 123개 중
10
+ # 그 파일을 보는 게 하나도 없었을 뿐이다. 그 상태로 fail-open 3건이 같이 통과했다.
11
+ # 이 스캔이 닫는 것은 딱 하나: **「안 돌았다」가 「깨끗하다」로 읽히는 것.**
12
+ # 진입점은 **peer 소유 코드**이고 이 어댑터가 만든 것이 아니다.
13
+ #
14
+ # ─────────────────────────────────────────────────────────────────────────────
15
+ # VERDICT ENUM (선언과 정확히 일치해야 한다)
16
+ # ─────────────────────────────────────────────────────────────────────────────
17
+ # 0 PASS 앵커 없는 신규 실행 코드 없음
18
+ # 1 VIOLATION 앵커 없는 신규 실행 코드가 있다
19
+ # 2 ARGS 인자 오류 — 판정이 아니다
20
+ # 3 HARNESS_ERROR ref 해석 불가 · git 실패 · 계기 부재 — 판정이 아니다
21
+ # 4 OUT_OF_SCOPE **다른 언어의 신규 실행 코드가 있어 검사되지 않았다** — 판정이 아니다
22
+ # 20 PEER_ABSENT **peer 하네스가 이 머신에 없다**
23
+ #
24
+ # 🟥 값 넷이 「안 쟀다」의 서로 다른 얼굴이다. peer 헤더가 같은 말을 한다 —
25
+ # *"스캔이 못 돌았는데 1 을 내면 «위반» 으로, 0 을 내면 «깨끗» 으로 읽힌다. 둘 다 틀렸다."*
26
+ # 어댑터는 그 위에 한 층을 더 얹는다:
27
+ # PEER_ABSENT = 그 하네스가 이 머신에 없다 (클러스터에 대한 진술)
28
+ # HARNESS_ERROR = 하네스는 있는데 못 쟀다 (계기에 대한 진술)
29
+ # OUT_OF_SCOPE = 계기는 돌았는데 대상이 사각지대다 (범위에 대한 진술)
30
+ # `verdict_binding` 에 PASS 를 뺀 전부가 들어간다.
31
+ #
32
+ # ⚠️ 하위 계기의 exit 을 그대로 전파하지 않는다 — 아래 case 가 명시 재매핑이다.
33
+ # peer 헤더 자신이 그걸 규율로 적어 뒀다: *"같은 숫자가 이 레포 안에서 다른 사건을
34
+ # 뜻한다 — 값을 옮겨 쓰지 마라."* (`judge_rer.sh` 의 4=BLOCKED · `pull_cli.py` 의
35
+ # 4=NO_CORPUS 와 여기의 4=OUT_OF_SCOPE 는 전부 다른 사건이다.)
36
+ #
37
+ # ─────────────────────────────────────────────────────────────────────────────
38
+ # 캘리브레이션 쌍 — **합성 레포**에서 돈다. 왜 peer 히스토리를 안 쓰나
39
+ # ─────────────────────────────────────────────────────────────────────────────
40
+ # ① peer 의 이 진입점은 **오늘 수리 중**이다(qasp PR #174, 브랜치
41
+ # `fix/anchor-scan-out-of-scope-repairs`). 고정 SHA 를 쌍으로 박으면 그 브랜치가
42
+ # squash 머지되는 순간 커밋이 orphan 이 되어 쌍이 해석 불가로 죽는다.
43
+ # ② 상대 ref(`HEAD~N`)는 커밋이 쌓이면 가리키는 구간이 조용히 움직인다 — 같은 선언이
44
+ # 다른 것을 재게 된다.
45
+ # ③ peer 는 `residency: company` 다. peer 히스토리를 arm 으로 쓰면 캘리브레이션 출력에
46
+ # 조직 경로명이 실린다. 합성 레포는 그 표면을 아예 만들지 않는다.
47
+ # 대가: 쌍이 재는 것은 «peer 의 히스토리» 가 아니라 **«peer 의 계기가 답 아는 두 케이스를
48
+ # 가르는가»** 다. 등록 바가 M4 에서 묻는 것이 정확히 후자이므로 맞는 표적이다.
49
+ # 양성: 신규 `src/plain_config.py`(앵커 0) → VIOLATION
50
+ # 음성: 같은 파일 + `tests/test_plain_config.py`(앵커) → PASS
51
+ # ★`src/plain_config.py` 라는 이름은 peer 자신의 B1 실측에서 온 **대조군**이다
52
+ # (`latest_config.py` 는 `test_` 부분문자열 때문에 무검사 통과했고, `plain_config.py`
53
+ # 는 정상 차단됐다). 즉 이 양성 arm 은 peer 가 이미 손으로 가른 케이스와 같은 것이다.
54
+ #
55
+ # ─────────────────────────────────────────────────────────────────────────────
56
+ # CLEANUP-TRAP 규율 (물려받은 실패)
57
+ # ─────────────────────────────────────────────────────────────────────────────
58
+ # OWNED_TMP(내가 만든 것, 지운다)와 peer 트리는 **다른 변수**다. OWNED_TMP 는 최초 1회만
59
+ # 대입하고 트랩이 임시경로인지 한 번 더 검문한다. peer 트리는 rm 에 절대 안 넘어간다.
60
+ #
61
+ # 사용법
62
+ # qasp_new_code_anchor.sh --range <base-ref> [<head-ref>] # peer 레포에서 실행
63
+ # qasp_new_code_anchor.sh --known-positive # expect VIOLATION
64
+ # qasp_new_code_anchor.sh --known-negative # expect PASS
65
+ set -o pipefail
66
+
67
+ PEER_NAME="qasp"
68
+ RC_PASS=0; RC_VIOLATION=1; RC_ARGS=2; RC_HARNESS=3; RC_OUT_OF_SCOPE=4; RC_PEER_ABSENT=20
69
+
70
+ _harness_error() { printf 'qasp-adapter: HARNESS_ERROR — %s\n' "$1" >&2; exit "$RC_HARNESS"; }
71
+ _args_error() { printf 'qasp-adapter: ARGS — %s\n' "$1" >&2; exit "$RC_ARGS"; }
72
+ _peer_absent() { printf 'qasp-adapter: PEER_ABSENT — %s\n' "$1" >&2; exit "$RC_PEER_ABSENT"; }
73
+
74
+ SELF_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
75
+ _LIB="$SELF_DIR/peer_resolve.sh"
76
+ [ -r "$_LIB" ] || _harness_error "peer 해석 라이브러리 부재: $_LIB"
77
+ # shellcheck source=/dev/null
78
+ . "$_LIB"
79
+
80
+ # ── peer 해석 — tracked 파일에 운영자 홈 절대경로를 싣지 않는다 ──────────────
81
+ PEER_ROOT="$(fh_peer_resolve "$PEER_NAME")"; _rrc=$?
82
+ case "$_rrc" in
83
+ 0) ;;
84
+ 1) _peer_absent "peer «$PEER_NAME» 가 이 머신에 없다 — 안 쟀다(통과가 아니다)" ;;
85
+ 2) _harness_error "peer «$PEER_NAME» 해석이 모호하다 — 어느 트리를 쟀는지 말할 수 없으므로 판정하지 않는다" ;;
86
+ *) _harness_error "peer 루트 목록 자체가 없다 — 전제 파손" ;;
87
+ esac
88
+
89
+ ENTRY="$PEER_ROOT/scripts/new_code_anchor_scan.sh"
90
+ [ -r "$ENTRY" ] || _harness_error "peer 진입점 부재: $ENTRY (하네스는 있으나 계기가 없다)"
91
+ command -v git >/dev/null 2>&1 || _harness_error "git 부재 — 이 계기는 git 없이는 못 돈다"
92
+
93
+ OWNED_TMP="" # 캘리브레이션 arm 에서만 만든다. 1회 대입, 재대입 금지.
94
+ _cleanup() {
95
+ [ -n "$OWNED_TMP" ] || return 0
96
+ case "$OWNED_TMP" in
97
+ /tmp/*|/var/folders/*|/private/var/folders/*)
98
+ [ -d "$OWNED_TMP" ] && rm -rf "$OWNED_TMP" ;;
99
+ *) printf 'qasp-adapter: cleanup 거부 — OWNED_TMP 가 임시경로가 아니다: %s\n' "$OWNED_TMP" >&2 ;;
100
+ esac
101
+ }
102
+ trap _cleanup EXIT
103
+
104
+ # 합성 레포를 세운다. $1 = "anchored" 면 앵커 파일을 같이 넣는다.
105
+ # stdout 은 오염시키지 않는다(경로는 전역 변수로 넘긴다).
106
+ FIXTURE_REPO=""; FIXTURE_BASE=""
107
+ _build_fixture_repo() {
108
+ local anchored="${1:-}"
109
+ OWNED_TMP="$(mktemp -d)" || _harness_error "mktemp 실패"
110
+ local R="$OWNED_TMP/fixture"
111
+ mkdir -p "$R/src" "$R/tests" || _harness_error "픽스처 레포를 만들 수 없다"
112
+ # `-c` 로 신원을 준다 — 전역 gitconfig 를 읽지도 쓰지도 않기 위해서다.
113
+ local G=(git -c user.email=fixture@localhost -c user.name=fixture -c commit.gpgsign=false)
114
+ ( cd "$R" && "${G[@]}" init -q . ) >/dev/null 2>&1 || _harness_error "픽스처 레포 init 실패"
115
+ printf 'fixture base\n' > "$R/README.md"
116
+ ( cd "$R" && "${G[@]}" add -A && "${G[@]}" commit -qm "base" ) >/dev/null 2>&1 \
117
+ || _harness_error "픽스처 base 커밋 실패"
118
+ FIXTURE_BASE="$( cd "$R" && git rev-parse HEAD 2>/dev/null )"
119
+ [ -n "$FIXTURE_BASE" ] || _harness_error "픽스처 base ref 해석 실패"
120
+ # 신규 실행 코드 1개. peer 의 대상 case(`src/*.py`)에 걸린다.
121
+ printf 'def compute(a, b):\n return a + b\n' > "$R/src/plain_config.py"
122
+ if [ "$anchored" = "anchored" ]; then
123
+ # peer 의 앵커 판정은 «HEAD 시점의 tests/ 가 이 파일의 stem 을 언급하는가» 다.
124
+ printf 'from src.plain_config import compute\n\n\ndef test_compute():\n assert compute(1, 2) == 3\n' \
125
+ > "$R/tests/test_plain_config.py"
126
+ fi
127
+ ( cd "$R" && "${G[@]}" add -A && "${G[@]}" commit -qm "add new code" ) >/dev/null 2>&1 \
128
+ || _harness_error "픽스처 head 커밋 실패"
129
+ FIXTURE_REPO="$R"
130
+ }
131
+
132
+ RUN_CWD=""; BASE=""; HEAD_REF=""
133
+ case "${1:-}" in
134
+ --known-positive) _build_fixture_repo ""; RUN_CWD="$FIXTURE_REPO"; BASE="$FIXTURE_BASE"; HEAD_REF="HEAD" ;;
135
+ --known-negative) _build_fixture_repo "anchored"; RUN_CWD="$FIXTURE_REPO"; BASE="$FIXTURE_BASE"; HEAD_REF="HEAD" ;;
136
+ --range)
137
+ BASE="${2:-}"; HEAD_REF="${3:-HEAD}"
138
+ [ -n "$BASE" ] || _args_error "--range 에 base-ref 가 필요하다"
139
+ RUN_CWD="$PEER_ROOT"
140
+ ;;
141
+ '') _args_error "모드 미지정 — 돌지 않은 실행을 판정으로 렌더하지 않는다" ;;
142
+ *) _args_error "알 수 없는 모드 '$1'" ;;
143
+ esac
144
+
145
+ # peer 트리를 **변이하지 않는다** — `--range` 에서는 cwd 만 peer 로 두고 읽기만 한다.
146
+ # 판정은 파이프를 통과시키지 않는다(PIPE-VERDICT).
147
+ out=$(cd "$RUN_CWD" && bash "$ENTRY" "$BASE" "$HEAD_REF" 2>&1); rc=$?
148
+ printf '%s\n' "$out"
149
+
150
+ # ── 명시 재매핑 (같은 숫자, 다른 사건 — 전파가 아니다) ───────────────────────
151
+ case "$rc" in
152
+ 0) printf 'qasp-adapter: PASS — 앵커 없는 신규 실행 코드 없음\n'; exit "$RC_PASS" ;;
153
+ 1) printf 'qasp-adapter: VIOLATION — 앵커 없는 신규 실행 코드가 있다\n'; exit "$RC_VIOLATION" ;;
154
+ 2) printf 'qasp-adapter: ARGS — peer 계기가 인자를 거부했다(판정이 아니다)\n' >&2; exit "$RC_ARGS" ;;
155
+ 3) printf 'qasp-adapter: HARNESS_ERROR — ref 해석 불가/git 실패(판정이 아니다)\n' >&2; exit "$RC_HARNESS" ;;
156
+ 4) printf 'qasp-adapter: OUT_OF_SCOPE — 검사되지 않은 신규 실행 코드가 있다(깨끗함이 아니다)\n' >&2
157
+ exit "$RC_OUT_OF_SCOPE" ;;
158
+ *) _harness_error "peer 진입점이 자기 계약 {0,1,2,3,4} 밖의 $rc 를 반환했다" ;;
159
+ esac
@@ -21,6 +21,19 @@
21
21
  # 세션 정체성은 배관이 필요 없다. 실측(2026-08-09): git 훅은 Claude Code 가 주입한
22
22
  # `CLAUDE_CODE_SESSION_ID` · `CLAUDE_PID` 를 그대로 상속한다.
23
23
  #
24
+ # `show` 를 읽는 사람(사람이든 세션이든)에게: claim 수 ≠ 실제 동시편집 스레드 수 ★ 관측 보강, 2026-08-16
25
+ # `show` 는 `.git/fh-claims/*` 를 그대로 나열하고 pid 생존만 본다. 그래서 **claim 이 "live"라는
26
+ # 것은 «그 pid 가 아직 살아있다»는 뜻이지 «그 세션이 지금 이 브랜치를 편집 중」이란 뜻이 아니다.**
27
+ # 실측(2026-08-16, 이 감사-remediation 세션 자신): `show` 가 이 브랜치에 live claim 5개를 냈다.
28
+ # 교차확인(peer 에게 직접 SendMessage) 결과 5개 중 실질 편집 스레드는 **1~2개** 뿐이었다 —
29
+ # 나머지는 ⓐ idle bg 세션(작업 없음) ⓑ pid 는 살아있지만 **다른 레포로 이동한 세션의 stale claim**
30
+ # (release 를 안 하고 옮겨감) ⓒ 이미 handoff 되어 ListAgents 에도 안 잡히는 완료 세션의 잔존 파일.
31
+ # 즉 이 스크립트의 판정(§판정 키 = 세션)은 여전히 옳다 — «내 claim vs HEAD» 비교는 claim 수와
32
+ # 무관하게 성립한다. 틀렸던 건 **그 옆에서 `show` 출력을 읽고 "N개 세션이 동시에 이 브랜치를
33
+ # 건드리고 있다"고 위험도를 추정하는 것**이다. 그 추정을 하려면 `show` 가 아니라 ⓐ`reap` 으로
34
+ # 죽은 claim 을 먼저 걷어내고 ⓑ 남은 claim 의 세션에 **직접 물어라**(ListAgents+SendMessage) —
35
+ # claim 파일 자체엔 "지금 이 트리를 실제로 만지고 있는가"를 답할 필드가 없다.
36
+ #
24
37
  # WHAT THIS DOES / DOESN'T
25
38
  # 막는다 : «내가 마지막으로 claim 한 브랜치» ≠ HEAD 인 채로 커밋
26
39
  # 안 막는다 : `git switch` 자체 (git 에 switch 훅이 없다 — 막을 자리가 없다)
@@ -223,6 +236,7 @@ cmd_show() {
223
236
  "$(_field "$f" branch)" "$(_field "$f" label)" "$alive"
224
237
  done
225
238
  else echo " (기록 없음)"; fi
239
+ echo " (live = pid 생존일 뿐, 지금 이 트리를 편집 중이란 뜻 아님 — 위험도 추정은 reap 후 직접 문의)"
226
240
  }
227
241
 
228
242
  # 0 = 통과, 1 = 차단