@chrono-meta/fh-gate 1.4.89 → 1.4.90
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/judgment_circuits.txt +14 -0
- package/.claude/rules/fh_4axis_gate.md +7 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +25 -0
- package/CLAUDE.md +179 -12
- package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +105 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +165 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +100 -0
- package/knowledge/shared/harness-core/onboarding_acceleration_autopilot.md +3 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +181 -13
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +528 -0
- package/package.json +16 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +56 -8
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +33 -0
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +104 -15
- package/scripts/chamber_run.sh +64 -2
- package/scripts/chamber_witness.sh +439 -0
- package/scripts/compaction_probe.sh +456 -0
- package/scripts/digest_landing_check.sh +385 -0
- package/scripts/directional_diff_gate.sh +459 -0
- package/scripts/judgment_circuit_lint.sh +239 -0
- package/scripts/novelty_claim_check.sh +193 -0
- package/scripts/relay_channel.sh +645 -0
- package/scripts/reviewer_capability_corpus.tsv +124 -0
- package/scripts/selfcheck.sh +66 -0
- package/scripts/test_marker_crossfamily_lanes.sh +132 -0
- package/scripts/test_marker_floor_lanes.sh +9 -8
- package/scripts/test_relay_channel_lanes.sh +583 -0
- package/scripts/test_reviewer_capability_conformance.sh +173 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +104 -11
- package/scripts/utterance_landing_check.sh +209 -0
- package/templates/.git-hooks/pre-commit +253 -13
- package/templates/settings.Compaction.snippet.json +56 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# judgment_circuit_lint.sh — 정체성 문서가 **판단 회로**인지 **페르소나 선언**인지 기계로 가른다.
|
|
3
|
+
#
|
|
4
|
+
# ── 명제 (105런 실측, 2026-08-07~08) ──
|
|
5
|
+
# *"정체성 선언 ≠ 판단 회로."* 한 파일 안에 방향이 반대인 둘이 섞여 있고, 측정은 이렇게 갈렸다:
|
|
6
|
+
#
|
|
7
|
+
# ❌ 역할 부여 "너는 ~이다" Haiku 순손실(A 2.00 → B0 1.67) · **제거 시 +0.67 회복**(양 채점자 일치)
|
|
8
|
+
# 외부 앵커: arXiv 2311.10054 — 162 페르소나 · 2,410 MMLU · 9모델, 개선 없음
|
|
9
|
+
# ❌ 강도 주문 "꼼꼼히" 지적 2.8배 · **팬텀 3.1배** · 검출 +0.33 (n=3)
|
|
10
|
+
# 기전: 만족 조건이 모델 자기평가에 맡겨지고 degrade 방향이 무제약 → 분량으로 샌다
|
|
11
|
+
# ✅ 분기·봉인·충돌·방향·형태 전부 검증 가능. 이것이 회로다
|
|
12
|
+
#
|
|
13
|
+
# **우변은 전부 검증 가능하고 좌변은 전부 검증 불가능하다.** 그게 새는 것과 조이는 것의 경계다.
|
|
14
|
+
#
|
|
15
|
+
# ── 형제 관계 (셋째 판본을 만들지 마라) ──
|
|
16
|
+
# 강도 주문은 FH 독트린 *"prose grants discretion; degrade direction unconstrained"* 의 **프롬프트-층
|
|
17
|
+
# 판본**이다. 코드-층 판본은 `scripts/degrade_direction_scan.sh` 이고, 그건 `.py`/`.sh` 만 보고 `.md` 를
|
|
18
|
+
# 명시적으로 건너뛴다. 같은 명제 · 다른 코퍼스 · 겹침 0. 합치지도, 옆에 셋째를 만들지도 마라.
|
|
19
|
+
# (`harness-doctor --lint` 은 또 다른 축 — 저자의 **마케팅 voice**(self-marketing·cushion·version)를
|
|
20
|
+
# 본다. 어휘 교집합 0.)
|
|
21
|
+
#
|
|
22
|
+
# ── ⚠️ 이 계기의 가장 큰 오채점: 언급(mention) vs 사용(use) ──
|
|
23
|
+
# 금지 형식을 **설명하는** 문서(이 스크립트의 헤더 · 심지 설계 정본 · 금지어 표)는 그 어휘를 잔뜩
|
|
24
|
+
# 포함한다. 순진하게 grep 하면 **금지형식을 가르치는 문서가 최악 점수**를 받는다 — 계기가 자기를
|
|
25
|
+
# 채점하는 함정이고, 2026-08-08 세션이 실제로 밟았다(코퍼스를 자기 regex 에 맞춰 재단).
|
|
26
|
+
# 완화: 금지 마커(❌·🟥·금지·forbidden·never·안 된다·쓰지 마라)가 같은 줄에 있으면 **언급**으로 본다.
|
|
27
|
+
# ⚠️ **완화지 해결이 아니다** — 마커 없이 금지형식을 서술하는 줄은 여전히 오탐이고, 반대로 마커를
|
|
28
|
+
# 달아 놓고 실제로 쓰는 줄은 놓친다. 그래서 이 스크립트는 **advisory 지 게이트가 아니다.**
|
|
29
|
+
#
|
|
30
|
+
# ── 판정 ──
|
|
31
|
+
# CIRCUIT 금지 0 ∧ 허용형식 ≥3/5 — 판단 회로로 선다
|
|
32
|
+
# PARTIAL 그 사이
|
|
33
|
+
# PERSONA 금지 ≥1 ∧ 허용형식 ≤2/5 — 페르소나 선언이지 회로가 아니다
|
|
34
|
+
#
|
|
35
|
+
# ── 사용 ──
|
|
36
|
+
# bash scripts/judgment_circuit_lint.sh <file.md> [file.md ...]
|
|
37
|
+
# bash scripts/judgment_circuit_lint.sh --self-test
|
|
38
|
+
# ── 종료 코드 ──
|
|
39
|
+
# 0 CIRCUIT 2 PARTIAL/PERSONA (ADVISORY — 단독 차단 금지) 10 입력 불량
|
|
40
|
+
#
|
|
41
|
+
# 히트는 "버그다"가 아니라 **"이게 회로임을 증명하라"** 로 읽어라.
|
|
42
|
+
|
|
43
|
+
set -uo pipefail
|
|
44
|
+
|
|
45
|
+
# 금지 ①: 역할 부여 — "너는 ~이다" / "You are a ..."
|
|
46
|
+
RE_ROLE='(너는[[:space:]].*(이다|다\.|야\.|이야)|당신은[[:space:]].*(입니다|이다)|[Yy]ou are (a|an|the)[[:space:]])'
|
|
47
|
+
# 금지 ②: 강도 주문 — 만족 조건이 모델 자기평가에 맡겨지는 부사/명령
|
|
48
|
+
RE_INTENSITY='(꼼꼼히|철저히|신중하게|충분히|깊이를[[:space:]]*더|판단을[[:space:]]*잘|최대한|가능한[[:space:]]*한|반복해서[[:space:]]*보완|thoroughly|carefully|diligently|as much as possible|do your best|be sure to be)'
|
|
49
|
+
# 언급(mention) 마커 — 같은 줄에 있으면 사용이 아니라 설명으로 본다
|
|
50
|
+
RE_META='(❌|🟥|🚫|금지|forbidden|never|하지[[:space:]]*마라|쓰지[[:space:]]*마라|안[[:space:]]*된다|測|기각|반증|안티패턴|anti-pattern)'
|
|
51
|
+
|
|
52
|
+
# 허용 5형식 — 존재를 센다 (회로라면 이것들이 있어야 한다)
|
|
53
|
+
declare -a ALLOW_NAME=(분기규정 자기봉인 충돌해소 판단방향 형태강제)
|
|
54
|
+
declare -a ALLOW_RE=(
|
|
55
|
+
'(못[[:space:]]*찾으면|없으면|실패하면|아니면).*(하지[[:space:]]*마|중단|멈춰|금지|안[[:space:]]*된다)|if[[:space:]].*(not|missing|absent|unavailable).*(do not|stop|abort|block|never)|fail-closed|unless[[:space:]].*(do not|never)'
|
|
56
|
+
'(대상이[[:space:]]*아니다|범위[[:space:]]*밖|다루지[[:space:]]*않는다|out of scope|not the target of)'
|
|
57
|
+
'(상충하면|충돌하면|부딪히면).*(이긴다|우선|우세|앞선다)|(wins|takes precedence|overrides)'
|
|
58
|
+
'(불확실하면|모르면|판단이[[:space:]]*안[[:space:]]*서면|애매하면).*(기운다|쪽으로|보고|default)|when (uncertain|unclear|unsure).*(default|lean|err)|default(s)? to|degrade(s)? (toward|to)'
|
|
59
|
+
# ⚠️ 🟥 명명된 미커버: ALLOW_RE[3] 은 판단방향 형식의 **존재**를 셀 뿐 **방향을 안 본다.**
|
|
60
|
+
# `when unclear, default to PASS` 도 여기서 크레딧을 받는다 — 즉 이 계기는 degrade-toward-PASS
|
|
61
|
+
# 문서를 "판단 회로 있음"으로 셀 수 있다. cross-family 지적 (b), 2026-08-08. 옳은 지적이다.
|
|
62
|
+
#
|
|
63
|
+
# 그런데도 **DENY 로직을 제거했다.** 한 세션에서 세 판본을 시도했고 매번 오류가 방향만 바꿨다:
|
|
64
|
+
# · 무앵커 부분문자열 → `OK` 가 look·token·broken 에 걸려 정당한 fail-closed 문장의 크레딧을
|
|
65
|
+
# 뺏었다 ("default to blocking and look at the source" 가 4/5→3/5)
|
|
66
|
+
# · 단어경계로 조이자 → 굴절형이 새어 "default to passing the check" 가 2/5→3/5 로 **승격**,
|
|
67
|
+
# degrade-toward-PASS 문서를 회로로 인증했다 (net regression)
|
|
68
|
+
# · 명사형 `default to approval` 은 어느 판본으로도 안 잡혔다
|
|
69
|
+
# 세 번 다 같은 벽이다 — 재려는 성질이 **문장의 방향**인데 계기는 **키워드**다. 키워드는 방향을
|
|
70
|
+
# 판정할 수 없고, 조일수록 반대편 오류가 커진다. 정확도가 안 오르는데 복잡도만 오르는 구간에서는
|
|
71
|
+
# 조이는 게 아니라 줄이는 게 맞다([[feedback_convergence_measured_by_change_not_rounds]]).
|
|
72
|
+
#
|
|
73
|
+
# 그래서 방향 판정은 이 계기의 일이 아니다. 코드 층 판본은 `scripts/degrade_direction_scan.sh`
|
|
74
|
+
# 가 이미 갖고 있고(`.py`/`.sh` 만 본다), **산문 층의 방향은 지금 아무 계기도 커버하지 않는다.**
|
|
75
|
+
# 그게 잔여다 — 셋째 판본을 여기 붙이지 말고, 커버하려면 방향을 볼 수 있는 계기로 따로 지어라.
|
|
76
|
+
# 이 계기가 advisory 라서(게이트 아님) 이 미커버가 fail-open 으로 환전되지는 않는다.
|
|
77
|
+
'```|^\|.*\|.*\||출력[[:space:]]*(형식|칸|형태)|output format'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
scan_file() {
|
|
81
|
+
local f="$1"
|
|
82
|
+
local role=0 inten=0 mention=0
|
|
83
|
+
local -a HITS=()
|
|
84
|
+
|
|
85
|
+
while IFS= read -r ln; do
|
|
86
|
+
local num="${ln%%:*}" txt="${ln#*:}"
|
|
87
|
+
if printf '%s' "$txt" | grep -qE "$RE_META"; then mention=$((mention+1)); continue; fi
|
|
88
|
+
# 인용부호 안의 금지어는 **지칭**이지 발화가 아니다. 판단 회로의 실제 지시는 문서 자신의
|
|
89
|
+
# 목소리로 인용 없이 나온다. 실측 2026-08-08: 실파일 첫 히트 3/3 이 전부 인용 안이었다
|
|
90
|
+
# (트리거 키워드 표 행 · `Not "..."` 부정문 · "나쁜 예시" 인용). 명명된 잔여: **인용부호로
|
|
91
|
+
# 감싸 쓴 진짜 지시는 놓친다** — 그 대가로 오탐을 막는다. advisory 라 방향이 이쪽이 맞다.
|
|
92
|
+
local bare; bare="$(printf '%s' "$txt" | sed -E 's/"[^"]*"/ /g; s/“[^”]*”/ /g; s/「[^」]*」/ /g; s/`[^`]*`/ /g')"
|
|
93
|
+
if ! printf '%s' "$bare" | grep -qE "$RE_ROLE|$RE_INTENSITY"; then
|
|
94
|
+
mention=$((mention+1)); continue
|
|
95
|
+
fi
|
|
96
|
+
# 분류도 $bare 로 한다. $txt 로 하면 "인용은 지칭"이라고 통과시켜 놓고 그 인용을 히트로
|
|
97
|
+
# 기록한다 — cross-family 지적 (d-2), 2026-08-08. 같은 손이 규칙과 테스트를 써서 못 봤다.
|
|
98
|
+
if printf '%s' "$bare" | grep -qE "$RE_ROLE"; then
|
|
99
|
+
role=$((role+1)); HITS+=(" 🟥 역할부여 $f:$num $(printf '%s' "$txt" | cut -c1-72)")
|
|
100
|
+
elif printf '%s' "$bare" | grep -qE "$RE_INTENSITY"; then
|
|
101
|
+
inten=$((inten+1)); HITS+=(" 🟥 강도주문 $f:$num $(printf '%s' "$txt" | cut -c1-72)")
|
|
102
|
+
fi
|
|
103
|
+
done < <(grep -nE "$RE_ROLE|$RE_INTENSITY" "$f" 2>/dev/null)
|
|
104
|
+
|
|
105
|
+
local cov=0; local -a MISSING=()
|
|
106
|
+
local i
|
|
107
|
+
for i in "${!ALLOW_RE[@]}"; do
|
|
108
|
+
_hit=$(grep -hoE "${ALLOW_RE[$i]}" "$f" 2>/dev/null | head -20)
|
|
109
|
+
if [ -n "$_hit" ]; then cov=$((cov+1))
|
|
110
|
+
else MISSING+=("${ALLOW_NAME[$i]}"); fi
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
local forb=$((role+inten)) verdict
|
|
114
|
+
if [ "$forb" -eq 0 ] && [ "$cov" -ge 3 ]; then verdict=CIRCUIT
|
|
115
|
+
elif [ "$forb" -ge 1 ] && [ "$cov" -le 2 ]; then verdict=PERSONA
|
|
116
|
+
else verdict=PARTIAL; fi
|
|
117
|
+
|
|
118
|
+
echo "── $f"
|
|
119
|
+
printf ' 판정: %-8s 금지 %d건 (역할 %d · 강도 %d) 허용형식 %d/5\n' \
|
|
120
|
+
"$verdict" "$forb" "$role" "$inten" "$cov"
|
|
121
|
+
[ "${#MISSING[@]}" -gt 0 ] && echo " 없는 형식: ${MISSING[*]}"
|
|
122
|
+
[ "$mention" -gt 0 ] && echo " (언급으로 제외한 줄: ${mention}건 — 금지 마커 동반)"
|
|
123
|
+
local h; for h in "${HITS[@]:-}"; do [ -n "$h" ] && echo "$h"; done
|
|
124
|
+
[ "$verdict" = CIRCUIT ] && return 0 || return 2
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
self_test() {
|
|
128
|
+
local T f=0 n=0 rc
|
|
129
|
+
T=$(mktemp -d); trap 'rm -rf "$T"' RETURN
|
|
130
|
+
t() { n=$((n+1)); if [ "$2" = "$3" ]; then echo "✅ $1 → $3"; else echo "❌ $1 → $3 (기대 $2)"; f=1; fi; }
|
|
131
|
+
v() { scan_file "$1" 2>/dev/null | awk '/판정:/{print $2}'; }
|
|
132
|
+
|
|
133
|
+
# known-positive ① 실측으로 기각된 형식만 있는 문서 = PERSONA
|
|
134
|
+
cat > "$T/persona.md" <<'EOF'
|
|
135
|
+
너는 최고의 QA 엔지니어다.
|
|
136
|
+
모든 항목을 꼼꼼히 검토하고 깊이를 더해라.
|
|
137
|
+
충분히 반복해서 보완해라.
|
|
138
|
+
EOF
|
|
139
|
+
t "역할부여+강도주문만 → PERSONA" PERSONA "$(v "$T/persona.md")"
|
|
140
|
+
|
|
141
|
+
# known-negative ① 실측으로 채택된 5형식 = CIRCUIT
|
|
142
|
+
cat > "$T/circuit.md" <<'EOF'
|
|
143
|
+
근거를 못 찾으면 그 항목을 보고하지 마라.
|
|
144
|
+
성능 최적화는 이 작업의 대상이 아니다.
|
|
145
|
+
원문 인용과 비중 추정이 상충하면 원문 인용이 이긴다.
|
|
146
|
+
불확실하면 보고 쪽으로 기운다.
|
|
147
|
+
출력 형식:
|
|
148
|
+
```
|
|
149
|
+
[등급] 근거 | 위치
|
|
150
|
+
```
|
|
151
|
+
EOF
|
|
152
|
+
t "허용 5형식만 → CIRCUIT" CIRCUIT "$(v "$T/circuit.md")"
|
|
153
|
+
|
|
154
|
+
scan_file "$T/circuit.md" >/dev/null 2>&1; rc=$?
|
|
155
|
+
t "CIRCUIT 는 exit 0" 0 "$rc"
|
|
156
|
+
scan_file "$T/persona.md" >/dev/null 2>&1; rc=$?
|
|
157
|
+
t "PERSONA 는 exit 2 (advisory)" 2 "$rc"
|
|
158
|
+
|
|
159
|
+
# ★ 언급 vs 사용 — 금지형식을 **가르치는** 문서가 최악 점수를 받으면 안 된다
|
|
160
|
+
cat > "$T/teaching.md" <<'EOF'
|
|
161
|
+
❌ 금지: "너는 최고의 QA 다" 같은 역할 부여는 쓰지 마라.
|
|
162
|
+
❌ 금지: "꼼꼼히", "깊이를 더해라" 같은 강도 주문은 안 된다.
|
|
163
|
+
근거를 못 찾으면 보고하지 마라.
|
|
164
|
+
성능은 이 작업의 대상이 아니다.
|
|
165
|
+
A 와 B 가 상충하면 A 가 이긴다.
|
|
166
|
+
EOF
|
|
167
|
+
t "금지형식을 가르치는 문서 → PERSONA 아님" CIRCUIT "$(v "$T/teaching.md")"
|
|
168
|
+
|
|
169
|
+
# ★ 그 완화가 진짜 히트까지 숨기면 안 된다 (완화의 known-negative)
|
|
170
|
+
cat > "$T/sneaky.md" <<'EOF'
|
|
171
|
+
❌ 금지: 역할 부여는 쓰지 마라.
|
|
172
|
+
너는 세계 최고의 보안 전문가다.
|
|
173
|
+
모든 것을 꼼꼼히 본다.
|
|
174
|
+
EOF
|
|
175
|
+
local sn; sn="$(scan_file "$T/sneaky.md" 2>/dev/null | grep -c '🟥')"
|
|
176
|
+
[ "$sn" -ge 2 ] && rc=YES || rc=NO
|
|
177
|
+
t "마커 있는 줄만 제외 — 실사용 줄은 여전히 잡힌다" YES "$rc"
|
|
178
|
+
|
|
179
|
+
# 빈 파일: 금지 0 이지만 허용형식도 0 → CIRCUIT 이면 안 된다 (공허한 통과 방지)
|
|
180
|
+
: > "$T/empty.md"
|
|
181
|
+
t "빈 파일 → CIRCUIT 아님 (공허한 통과 금지)" PARTIAL "$(v "$T/empty.md")"
|
|
182
|
+
|
|
183
|
+
# ── 회귀 레인: 실파일 첫 실행에서 나온 오탐 3건 (2026-08-08, 전부 손으로 확인) ──
|
|
184
|
+
# 초판은 실파일 히트 3/3 이 오탐이었고 CLAUDE.md 를 PERSONA 로 오판정했다.
|
|
185
|
+
# 셋 다 **인용부호 안**이라는 공통 구조였다. 원문 그대로 픽스처로 박는다.
|
|
186
|
+
cat > "$T/fp_real.md" <<'EOF'
|
|
187
|
+
| "research this deeply", "survey the literature", "comprehensive analysis" | /deep-research |
|
|
188
|
+
**Therefore: do not commit FH assets from a worktree.** Not "carry the evidence in carefully"
|
|
189
|
+
기계가 혼자 쓰면 "정확하고 신중하게 검증한다" 가 된다 ← 방향이 없다. 검증 불가
|
|
190
|
+
EOF
|
|
191
|
+
local fp; fp="$(scan_file "$T/fp_real.md" 2>/dev/null | grep -c '🟥')"
|
|
192
|
+
t "실파일 오탐 3건 재현 → 금지 0건" 0 "$fp"
|
|
193
|
+
|
|
194
|
+
# (d-2) 인용을 지칭으로 통과시켰으면 분류도 인용 밖에서 해야 한다
|
|
195
|
+
printf 'Not "너는 심사자이다"; 꼼꼼히 검토하라\n' > "$T/mixed.md"
|
|
196
|
+
local mk; mk="$(scan_file "$T/mixed.md" 2>/dev/null | grep -oE '역할부여|강도주문' | head -1)"
|
|
197
|
+
t "인용 안 역할부여는 세지 않고 실사용 강도주문만" "강도주문" "$mk"
|
|
198
|
+
|
|
199
|
+
# ── 🟥 미커버를 **못박는** 레인 (2026-08-08, DENY 제거와 함께 방향이 뒤집혔다) ──
|
|
200
|
+
# 이 계기는 판단방향 형식의 **존재**만 세고 **방향은 안 본다**. 그건 결함이 아니라 결정이다 —
|
|
201
|
+
# 키워드로 방향을 판정하려던 세 판본이 전부 반대편 오류로 갈아탔고(관대문 누락 ↔ 보수문 강등),
|
|
202
|
+
# 정확도가 안 오르는데 복잡도만 오르는 구간이라 조이는 대신 줄였다. 근거는 헤더 ALLOW_RE[3] 주석.
|
|
203
|
+
#
|
|
204
|
+
# ⚠️ 이 레인들을 **삭제하지 마라.** 삭제하면 미커버가 침묵이 되고, 다음 사람이 "방향도 보겠지"
|
|
205
|
+
# 라고 가정하게 된다. 기대값을 뒤집어 남겨 두면 미커버가 **기계로 기록**되고, 누군가 넷째 DENY
|
|
206
|
+
# 판본을 붙이는 순간 여기가 빨개져서 *의도적* 미커버를 건드렸다고 알려준다.
|
|
207
|
+
# ⚠️ **커버리지 숫자에 건다. verdict 에 걸면 못 본다** — 다른 형식이 3개 있으면 판단방향 크레딧
|
|
208
|
+
# 유무와 무관하게 verdict 는 CIRCUIT 로 같다. 지난 라운드 레인이 verdict 에 걸려서 구멍을 놓쳤다.
|
|
209
|
+
_cov() { # $1=판단방향 자리에 넣을 문장 → "n/5"
|
|
210
|
+
local d; d="$(mktemp -d)"
|
|
211
|
+
printf 'When unclear, default to %s.\n근거를 못 찾으면 보고하지 마라.\n성능은 이 작업의 대상이 아니다.\nA 와 B 가 상충하면 A 가 이긴다.\n' "$1" > "$d/x.md"
|
|
212
|
+
scan_file "$d/x.md" 2>/dev/null | awk '/판정:/{print $NF}'
|
|
213
|
+
rm -rf "$d"
|
|
214
|
+
}
|
|
215
|
+
# 관대 4종과 보수 2종이 **같은 4/5** 를 받는다 = 이 계기는 방향을 구분하지 않는다.
|
|
216
|
+
# 그 동일성 자체가 여기서 검증되는 성질이다.
|
|
217
|
+
t "🟥 미커버: 관대 'PASS' 도 크레딧 받음" "4/5" "$(_cov 'PASS')"
|
|
218
|
+
t "🟥 미커버: 관대 'passing' 굴절형도 크레딧" "4/5" "$(_cov 'passing the check')"
|
|
219
|
+
t "🟥 미커버: 관대 'approval' 명사형도 크레딧" "4/5" "$(_cov 'approval')"
|
|
220
|
+
t "🟥 미커버: 관대 'allowing' 도 크레딧" "4/5" "$(_cov 'allowing the operation')"
|
|
221
|
+
t "보수 'blocking' 도 같은 크레딧 (방향 무구분)" "4/5" "$(_cov 'blocking and look at the source')"
|
|
222
|
+
t "보수 'token/broken' 도 같은 크레딧" "4/5" "$(_cov 'blocking on a broken token')"
|
|
223
|
+
|
|
224
|
+
echo
|
|
225
|
+
[ "$f" -eq 0 ] && echo "✅ 캘리브레이션 통과 ($n 쌍)" || echo "❌ 캘리브레이션 실패 ($n 쌍)"
|
|
226
|
+
return "$f"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
[ "${1:-}" = "--self-test" ] && { self_test; exit $?; }
|
|
230
|
+
[ "$#" -eq 0 ] && { echo "usage: $0 <file.md> [...]"; echo " $0 --self-test"; exit 10; }
|
|
231
|
+
|
|
232
|
+
WORST=0
|
|
233
|
+
for target in "$@"; do
|
|
234
|
+
[ -f "$target" ] || { echo "🟥 파일 없음: $target"; exit 10; }
|
|
235
|
+
scan_file "$target" || WORST=2
|
|
236
|
+
echo
|
|
237
|
+
done
|
|
238
|
+
echo "ⓘ ADVISORY — 단독으로 차단하지 마라. 히트는 '이게 회로임을 증명하라'는 뜻이다."
|
|
239
|
+
exit "$WORST"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# novelty_claim_check.sh — **신규성·부재 주장**이 외부 앵커 없이 서 있는지 기계로 잡는다.
|
|
3
|
+
#
|
|
4
|
+
# ── 왜 (실측된 결함) ──
|
|
5
|
+
# 2026-08-08 세션: 내부 grep 만 돌리고 `net-new` 를 단정했다가 철회했다. 외부 조회는 운영자가
|
|
6
|
+
# *"지금 물어봐"* 라고 해서 비로소 돌았다. `persona-innovator` Mode F 는 존재하지만
|
|
7
|
+
# **context-entry 제안**이지 짓기 직전 자동 발화가 아니다.
|
|
8
|
+
#
|
|
9
|
+
# "짓기 직전"은 **의도 트리거**라 훅으로 못 잡는다(go-public 표면과 같은 부류). 그래서 이 계기는
|
|
10
|
+
# 발화를 강제하지 않는다 — 대신 **안 물어본 흔적을 커밋 시점에 보이게** 만든다.
|
|
11
|
+
# `[[feedback_shift_left_ledger_not_rule]]`: 일반 규율은 살리언스 의존이라 이미 실패했고,
|
|
12
|
+
# 구체 목록은 놓칠 여지가 없다. 규율을 한 번 더 적는 대신 원장을 앞단에 놓는다.
|
|
13
|
+
#
|
|
14
|
+
# ── 왜 하필 이 주장 클래스인가 (방향 비대칭) ──
|
|
15
|
+
# `[[feedback_source_grounding_direction_asymmetry]]`: 결함 주장(FAIL)에는 소스 확인을 요구하면서
|
|
16
|
+
# 강점·신규성 주장(PASS)은 무검증으로 통과시켜 왔다 = **낙관 방향만 뚫린다.**
|
|
17
|
+
# 그리고 부재 주장은 구조적으로 더 나쁘다 — **인용할 대상이 없어서** 팬텀 검사가 원리적으로 안 걸린다
|
|
18
|
+
# (phantom-quench 는 *인용된 것이 주장을 지지하는가*를 보지, *인용이 아예 없는 부재 주장*은 못 본다).
|
|
19
|
+
# 이 스크립트는 그 빈칸 하나만 본다. 겹침 없음.
|
|
20
|
+
#
|
|
21
|
+
# ── 이 계기가 재는 것과 못 재는 것 ──
|
|
22
|
+
# 잰다: 신규성/부재 주장 옆에 외부 앵커가 있는가 (±N 줄)
|
|
23
|
+
# 못 잰다: 그 앵커가 **그 주장을 실제로 지지하는가** — 그건 phantom-quench 의 일이다.
|
|
24
|
+
# 앵커 존재가 진위를 못 본다(`[[feedback_metric_measures_presence_not_relation]]`).
|
|
25
|
+
# 즉 통과는 "물어는 봤다"까지지 "맞다"가 아니다.
|
|
26
|
+
#
|
|
27
|
+
# ── 사용 ──
|
|
28
|
+
# bash scripts/novelty_claim_check.sh <file.md> [...]
|
|
29
|
+
# bash scripts/novelty_claim_check.sh --self-test
|
|
30
|
+
# ── 종료 코드 ──
|
|
31
|
+
# 0 전건 앵커 있음 2 무앵커 주장 있음 (ADVISORY — 단독 차단 금지) 10 입력 불량
|
|
32
|
+
|
|
33
|
+
set -uo pipefail
|
|
34
|
+
|
|
35
|
+
# ⚠️ 레인에서 `cmd | grep -q` 를 쓰지 마라. `grep -q` 는 매치 즉시 파이프를 닫고, `pipefail` 이
|
|
36
|
+
# 그 SIGPIPE(141)를 파이프라인 실패로 렌더한다 — **출력이 맞는데 레인이 빨개지고, 경합이라 flaky 하다**
|
|
37
|
+
# (2026-08-08 실측: 같은 레인이 실행마다 갈렸다). 출력을 변수로 캡처한 뒤 `case` 로 검사한다.
|
|
38
|
+
|
|
39
|
+
WINDOW=6 # 앵커 탐색 반경(줄). 좁히면 오탐↑, 넓히면 미탐↑ — 6은 FH 문서의 문단 길이 기준
|
|
40
|
+
|
|
41
|
+
# 강한 신규성·부재 주장만. 일반 부정("없다")은 한국어 문서에 편재해서 넣으면 Grep-Collision
|
|
42
|
+
# Treadmill 이 된다(`[[feedback_typed_verdict_channel]]`). 단정 어휘만 좁게 잡는다.
|
|
43
|
+
# ⚠️ `net-new` 는 FH 내부 어휘에서 **두 뜻으로 과부하**돼 있다 (실측 2026-08-08, 손 확인 6/6 오탐):
|
|
44
|
+
# ① 세상에 없다 = 신규성 주장 → 외부 앵커 필요
|
|
45
|
+
# ② 이 레인이 새로 찾은 지적("cross-family 가 net-new finding 3건") → 앵커 불요, 로컬 카운트다
|
|
46
|
+
# 그래서 `net-new` 단독은 주장으로 안 센다 — **세계-스코프 한정어와 같은 줄일 때만** 센다.
|
|
47
|
+
# 나머지 문구(선례 없·상용 0개·no prior art…)는 그 자체로 세계-스코프라 단독으로 센다.
|
|
48
|
+
RE_WORLD='(시장|학계|외부|선례|전례|상용|업계|prior art|state of the art|SOTA|공개된|published)'
|
|
49
|
+
RE_CLAIM='(선례[[:space:]]*(가[[:space:]]*)?없|전례[[:space:]]*(가[[:space:]]*)?없|유일하|전무하|존재하지[[:space:]]*않는다|시장에[[:space:]]*(는[[:space:]]*)?없|학계엔?[[:space:]]*(는[[:space:]]*)?없|상용[[:space:]]*0개|no prior art|first of its kind|unprecedented|nothing comparable|does not exist anywhere)'
|
|
50
|
+
|
|
51
|
+
# 외부 앵커 — "세계에 물어본" 흔적
|
|
52
|
+
RE_ANCHOR='(arXiv|arxiv|DOI|doi:|https?://|WebSearch|WebFetch|서베이|외부[[:space:]]*(조회|앵커|문헌)|T1|T2|Sources?:|출처|원문[[:space:]]*확인)'
|
|
53
|
+
|
|
54
|
+
# 언급(mention) 마커 — 규칙 자체를 서술하는 줄
|
|
55
|
+
RE_META='(❌|🟥|🚫|금지|forbidden|never|안티패턴|anti-pattern|예:|example:|RE_CLAIM|RE_ANCHOR)'
|
|
56
|
+
|
|
57
|
+
scan_file() {
|
|
58
|
+
local f="$1" total=0 bad=0 meta=0
|
|
59
|
+
local -a HITS=()
|
|
60
|
+
# `wc -l` 은 **개행을 센다** — 마지막 줄에 개행이 없으면 한 줄 적게 나오고, 앵커 창 상한이
|
|
61
|
+
# 한 줄 짧아져 **주장 자기 줄을 창 밖으로 밀어낸다** → 거짓 무앵커(high 재리뷰 #10).
|
|
62
|
+
# `git show :file` 이 blob 바이트를 그대로 쓰므로 pre-commit 경로에서 실제로 도달 가능하다.
|
|
63
|
+
local nlines; nlines=$(awk 'END{print NR}' "$f" 2>/dev/null)
|
|
64
|
+
[ -z "$nlines" ] && nlines=0
|
|
65
|
+
|
|
66
|
+
while IFS= read -r ln; do
|
|
67
|
+
local num="${ln%%:*}" txt="${ln#*:}"
|
|
68
|
+
# ⚠️ 초판은 meta 줄을 **아무 데도 안 세고** continue 했다. 그래서 주장 3건이 전부 meta 어휘를
|
|
69
|
+
# 동반하면 출력이 "주장 없음" 이 된다 — 부분 미스캔을 **깨끗한 코퍼스로** 렌더하는 것이고,
|
|
70
|
+
# 이 계기가 막으라고 존재하는 `not found ≠ 0` 그 자체다(high 리뷰 #9).
|
|
71
|
+
if printf '%s' "$txt" | grep -qE "$RE_META"; then meta=$((meta+1)); continue; fi
|
|
72
|
+
# 인용부호 안의 주장은 지칭이지 발화가 아니다 (judgment_circuit_lint 와 동일 규칙)
|
|
73
|
+
local bare; bare="$(printf '%s' "$txt" | sed -E 's/"[^"]*"/ /g; s/“[^”]*”/ /g; s/「[^」]*」/ /g; s/`[^`]*`/ /g')"
|
|
74
|
+
if printf '%s' "$bare" | grep -qE "$RE_CLAIM"; then :
|
|
75
|
+
elif printf '%s' "$bare" | grep -qE 'net-new' && printf '%s' "$bare" | grep -qE "$RE_WORLD"; then :
|
|
76
|
+
else continue; fi
|
|
77
|
+
total=$((total+1))
|
|
78
|
+
local lo=$(( num - WINDOW )); [ "$lo" -lt 1 ] && lo=1
|
|
79
|
+
local hi=$(( num + WINDOW )); [ "$hi" -gt "$nlines" ] && hi="$nlines"
|
|
80
|
+
if sed -n "${lo},${hi}p" "$f" 2>/dev/null | grep -qE "$RE_ANCHOR"; then
|
|
81
|
+
HITS+=(" ✅ 앵커있음 $f:$num $(printf '%s' "$txt" | cut -c1-60)")
|
|
82
|
+
else
|
|
83
|
+
bad=$((bad+1))
|
|
84
|
+
HITS+=(" 🟥 무앵커 $f:$num $(printf '%s' "$txt" | cut -c1-60)")
|
|
85
|
+
fi
|
|
86
|
+
done < <(grep -nE "$RE_CLAIM|net-new" "$f" 2>/dev/null)
|
|
87
|
+
|
|
88
|
+
echo "── $f"
|
|
89
|
+
if [ "$total" -eq 0 ]; then
|
|
90
|
+
if [ "$meta" -gt 0 ]; then
|
|
91
|
+
echo " 신규성·부재 주장 0건 — 단 ${meta}줄을 언급(meta)으로 제외했다. **전수 스캔이 아니다.**"
|
|
92
|
+
echo " 제외된 줄에 실제 주장이 있을 수 있다 — 미스캔이지 깨끗함이 아니다."
|
|
93
|
+
else
|
|
94
|
+
echo " 신규성·부재 주장 없음"
|
|
95
|
+
fi
|
|
96
|
+
return 0
|
|
97
|
+
fi
|
|
98
|
+
printf ' 주장 %d건 · 무앵커 %d건 · 언급제외 %d줄 (앵커 반경 ±%d줄)\n' "$total" "$bad" "$meta" "$WINDOW"
|
|
99
|
+
local h; for h in "${HITS[@]:-}"; do [ -n "$h" ] && echo "$h"; done
|
|
100
|
+
[ "$bad" -eq 0 ] && return 0 || return 2
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
self_test() {
|
|
104
|
+
local T f=0 n=0 rc
|
|
105
|
+
T=$(mktemp -d); trap 'rm -rf "$T"' RETURN
|
|
106
|
+
t() { n=$((n+1)); if [ "$2" = "$3" ]; then echo "✅ $1 → $3"; else echo "❌ $1 → $3 (기대 $2)"; f=1; fi; }
|
|
107
|
+
bad() { scan_file "$1" 2>/dev/null | awk '/무앵커 [0-9]+건/{for(i=1;i<=NF;i++) if($i=="·"){print $(i+2)}}' | tr -d '건'; }
|
|
108
|
+
|
|
109
|
+
# known-positive: 앵커 없는 부재 주장
|
|
110
|
+
printf '이 패턴은 선례가 없다.\n그래서 우리가 처음 만든다.\n' > "$T/ungrounded.md"
|
|
111
|
+
scan_file "$T/ungrounded.md" >/dev/null 2>&1; rc=$?
|
|
112
|
+
t "무앵커 부재 주장 → exit 2" 2 "$rc"
|
|
113
|
+
|
|
114
|
+
# known-negative: 같은 주장 + 외부 앵커
|
|
115
|
+
printf '이 패턴은 선례가 없다 (arXiv 2607.08032 서베이가 그렇게 진술한다).\n' > "$T/grounded.md"
|
|
116
|
+
scan_file "$T/grounded.md" >/dev/null 2>&1; rc=$?
|
|
117
|
+
t "앵커 동반 주장 → exit 0" 0 "$rc"
|
|
118
|
+
|
|
119
|
+
# 앵커가 반경 안/밖일 때 갈리는가 (계기가 반경을 실제로 쓰는지)
|
|
120
|
+
{ printf '이 패턴은 선례가 없다.\n'; for i in $(seq 1 12); do echo "채움 $i"; done; echo "출처: https://example.org"; } > "$T/far.md"
|
|
121
|
+
scan_file "$T/far.md" >/dev/null 2>&1; rc=$?
|
|
122
|
+
t "앵커가 반경 밖 → 여전히 무앵커" 2 "$rc"
|
|
123
|
+
{ printf '이 패턴은 선례가 없다.\n'; echo "출처: https://example.org"; } > "$T/near.md"
|
|
124
|
+
scan_file "$T/near.md" >/dev/null 2>&1; rc=$?
|
|
125
|
+
t "앵커가 반경 안 → 통과" 0 "$rc"
|
|
126
|
+
|
|
127
|
+
# 주장 자체가 없는 문서는 통과 (과발화 방지)
|
|
128
|
+
printf '오늘은 훅을 배선했다. 캘리브레이션 8/8.\n' > "$T/plain.md"
|
|
129
|
+
scan_file "$T/plain.md" >/dev/null 2>&1; rc=$?
|
|
130
|
+
t "주장 없는 문서 → 무발화 통과" 0 "$rc"
|
|
131
|
+
|
|
132
|
+
# 일반 부정은 잡으면 안 된다 (Grep-Collision 방지)
|
|
133
|
+
printf '이 파일에는 마커가 없다. 훅은 차단하지 않는다.\n' > "$T/plainneg.md"
|
|
134
|
+
_o="$(scan_file "$T/plainneg.md" 2>/dev/null)"; case "$_o" in *"주장 없음"*) rc=YES ;; *) rc=NO ;; esac
|
|
135
|
+
t "일반 부정('마커가 없다')은 주장 아님" YES "$rc"
|
|
136
|
+
|
|
137
|
+
# 인용 안의 주장은 지칭 (judgment_circuit_lint 와 같은 규칙)
|
|
138
|
+
printf '초판은 "이 패턴은 선례가 없다" 고 적었다가 철회했다.\n' > "$T/quoted.md"
|
|
139
|
+
_o="$(scan_file "$T/quoted.md" 2>/dev/null)"; case "$_o" in *"주장 없음"*) rc=YES ;; *) rc=NO ;; esac
|
|
140
|
+
t "인용 안의 주장은 지칭으로 제외" YES "$rc"
|
|
141
|
+
|
|
142
|
+
# ── 회귀 레인: `net-new` 과부하 (2026-08-08 실측, 손 확인 6/6 오탐) ──
|
|
143
|
+
# 원문 그대로. 로컬 발견 라벨은 주장이 아니다.
|
|
144
|
+
cat > "$T/netnew_local.md" <<'EOF'
|
|
145
|
+
The one net-new finding was the most severe one.
|
|
146
|
+
terra 6/6 known-positives + 5 net-new (approval-negation regex, heredoc tail-drop)
|
|
147
|
+
Second consecutive session where the cross-family leg produced net-new findings.
|
|
148
|
+
EOF
|
|
149
|
+
_o="$(scan_file "$T/netnew_local.md" 2>/dev/null)"; case "$_o" in *"주장 없음"*) rc=YES ;; *) rc=NO ;; esac
|
|
150
|
+
t "net-new 로컬 발견 라벨 3건 → 주장 아님" YES "$rc"
|
|
151
|
+
|
|
152
|
+
# 반대편: 세계-스코프 net-new 는 여전히 잡혀야 한다 (완화가 진짜를 숨기면 안 된다)
|
|
153
|
+
printf 'net-new 다 — 시장에 이런 상용 제품이 하나도 없다.\n' > "$T/netnew_world.md"
|
|
154
|
+
scan_file "$T/netnew_world.md" >/dev/null 2>&1; rc=$?
|
|
155
|
+
t "세계-스코프 net-new 는 여전히 주장" 2 "$rc"
|
|
156
|
+
|
|
157
|
+
# 회귀 레인: #9 meta 제외분이 "주장 없음" 으로 렌더되면 안 된다 (high 리뷰 실측)
|
|
158
|
+
cat > "$T/metahidden.md" <<'EOF'
|
|
159
|
+
이 아키텍처는 선례가 없다 — 그러니 절대 relabel 하지 마라, never.
|
|
160
|
+
시장에 이런 상용 제품은 존재하지 않는다. 금지 사항은 아래와 같다.
|
|
161
|
+
EOF
|
|
162
|
+
_o="$(scan_file "$T/metahidden.md" 2>/dev/null)"; case "$_o" in *"전수 스캔이 아니다"*) rc=YES ;; *) rc=NO ;; esac
|
|
163
|
+
t "#9 meta 제외분이 있으면 '깨끗함' 으로 렌더 안 한다" YES "$rc"
|
|
164
|
+
case "$_o" in *"신규성·부재 주장 없음"*) rc=YES ;; *) rc=NO ;; esac
|
|
165
|
+
t "#9 '주장 없음' 단독 출력은 안 나온다" NO "$rc"
|
|
166
|
+
|
|
167
|
+
# #10 마지막 줄에 개행이 없어도 앵커 창이 자기 줄을 포함해야 한다 (high 재리뷰)
|
|
168
|
+
printf '더미 줄\n이 패턴은 선례가 없다 (arXiv 2607.08032 서베이).' > "$T/nonl.md"
|
|
169
|
+
scan_file "$T/nonl.md" >/dev/null 2>&1; rc=$?
|
|
170
|
+
t "#10 개행 없는 마지막 줄도 앵커가 잡힌다" 0 "$rc"
|
|
171
|
+
printf '더미 줄\n이 패턴은 선례가 없다 (arXiv 2607.08032 서베이).\n' > "$T/wnl.md"
|
|
172
|
+
scan_file "$T/wnl.md" >/dev/null 2>&1; rc=$?
|
|
173
|
+
t "#10 개행 있는 판본과 동일 판정" 0 "$rc"
|
|
174
|
+
|
|
175
|
+
echo
|
|
176
|
+
[ "$f" -eq 0 ] && echo "✅ 캘리브레이션 통과 ($n 쌍)" || echo "❌ 캘리브레이션 실패 ($n 쌍)"
|
|
177
|
+
return "$f"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
[ "${1:-}" = "--self-test" ] && { self_test; exit $?; }
|
|
181
|
+
[ "$#" -eq 0 ] && { echo "usage: $0 <file.md> [...]"; echo " $0 --self-test"; exit 10; }
|
|
182
|
+
|
|
183
|
+
WORST=0
|
|
184
|
+
for target in "$@"; do
|
|
185
|
+
[ -f "$target" ] || { echo "🟥 파일 없음: $target"; exit 10; }
|
|
186
|
+
scan_file "$target" || WORST=2
|
|
187
|
+
done
|
|
188
|
+
[ "$WORST" -eq 2 ] && {
|
|
189
|
+
echo
|
|
190
|
+
echo "ⓘ ADVISORY — 차단하지 않는다. 무앵커 주장은 '틀렸다'가 아니라 **'아직 안 물어봤다'** 다."
|
|
191
|
+
echo " 통과해도 앵커가 그 주장을 지지하는지는 별도(phantom-quench) — 존재가 진위를 못 본다."
|
|
192
|
+
}
|
|
193
|
+
exit "$WORST"
|