@chrono-meta/fh-gate 1.4.88 → 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 +215 -12
- package/knowledge/shared/harness-core/claude_md_gate_details.md +37 -0
- 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 +601 -0
- package/package.json +19 -3
- 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/prepush_guard_check.sh +15 -0
- package/scripts/psa_scan_lib.sh +36 -0
- package/scripts/relay_channel.sh +645 -0
- package/scripts/reviewer_capability_corpus.tsv +124 -0
- package/scripts/selfcheck.sh +204 -17
- package/scripts/session_close_check.sh +55 -5
- 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_selfcheck_state_lanes.sh +103 -0
- package/scripts/test_session_close_chain_lanes.sh +79 -4
- package/scripts/test_version_lockstep_lanes.sh +82 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +104 -11
- package/scripts/universal_guard_check.sh +19 -6
- package/scripts/utterance_landing_check.sh +209 -0
- package/scripts/version_lockstep_check.sh +80 -0
- package/templates/.git-hooks/pre-commit +261 -13
- package/templates/.git-hooks/pre-push +14 -2
- package/templates/settings.Compaction.snippet.json +56 -0
|
@@ -196,8 +196,12 @@ run_case "placeholder BEFORE real, one line → BLOCK" \
|
|
|
196
196
|
# Each of these previously passed at commit time while BLOCKING at publish time — the two copies
|
|
197
197
|
# of this logic had diverged in leniency. run_state_case swaps the pattern source rather than the
|
|
198
198
|
# staged content, so it needs its own runner.
|
|
199
|
-
run_state_case() { # <name> <override-content|__NONE__> <defaults:keep|drop> <expect-exit: block|pass>
|
|
200
|
-
|
|
199
|
+
run_state_case() { # <name> <override-content|__NONE__> <defaults:keep|drop> <expect-exit: block|pass> [expect-verdict-substring]
|
|
200
|
+
# The 5th arg exists because exit code alone cannot see this defect class: a scan whose
|
|
201
|
+
# operator-literal layer never ran exits 0 and used to print the same `✅ PASS` as a scan where
|
|
202
|
+
# every layer ran. Identical verdicts for different amounts of measurement is the fail-open —
|
|
203
|
+
# so the anchor has to read the verdict LINE, not just the status.
|
|
204
|
+
local name="$1" ovc="$2" defmode="$3" expect="$4" want="${5:-}" out rc got
|
|
201
205
|
printf 'operator literal zzsynthoperator\n' > "$SANDBOX/README.md"
|
|
202
206
|
git -C "$SANDBOX" add -- README.md >/dev/null 2>&1
|
|
203
207
|
local ov="$SANDBOX/.psa_state_override"
|
|
@@ -208,17 +212,26 @@ run_state_case() { # <name> <override-content|__NONE__> <defaults:keep|drop> <e
|
|
|
208
212
|
if [ "$defmode" = drop ]; then mv "$defbak" "$SANDBOX/.claude/rules/.public-surface-patterns.defaults" 2>/dev/null; fi
|
|
209
213
|
git -C "$SANDBOX" rm -q --cached -- README.md >/dev/null 2>&1; rm -f "$SANDBOX/README.md" "$ov"
|
|
210
214
|
if [ "$rc" -ne 0 ]; then got=block; else got=pass; fi
|
|
211
|
-
if [ "$got"
|
|
212
|
-
echo " ✅ $name (expected $expect)"
|
|
213
|
-
else
|
|
215
|
+
if [ "$got" != "$expect" ]; then
|
|
214
216
|
echo " ❌ $name — expected $expect, got $got (exit $rc)"
|
|
215
217
|
printf '%s\n' "$out" | sed 's/^/ | /' | head -10
|
|
216
218
|
FAILED=1
|
|
219
|
+
elif [ -n "$want" ] && ! printf '%s' "$out" | grep -qF "$want"; then
|
|
220
|
+
echo " ❌ $name — exit was $got but the verdict line did not say: $want"
|
|
221
|
+
printf '%s\n' "$out" | grep -E 'PASS|PARTIAL|Confidentiality' | sed 's/^/ | /' | head -5
|
|
222
|
+
FAILED=1
|
|
223
|
+
else
|
|
224
|
+
echo " ✅ $name (expected $expect${want:+, verdict names \"$want\"})"
|
|
217
225
|
fi
|
|
218
226
|
}
|
|
219
227
|
run_state_case "no patterns at all (instrument down) → BLOCK" "__NONE__" drop block
|
|
220
228
|
run_state_case "malformed regex in override → BLOCK" "HIGH zzsynth[" keep block
|
|
221
|
-
|
|
229
|
+
# 2026-08-06 — this state still PASSES (a commit is reversible; blocking every fresh clone's first
|
|
230
|
+
# commit is what trains the override into a reflex), but it may no longer CLAIM a clean scan. Pinned
|
|
231
|
+
# as a pair so the label cannot drift back to a bare PASS, and so an unconditional PARTIAL — which
|
|
232
|
+
# would be just as wrong — is caught by the control below it.
|
|
233
|
+
run_state_case "empty override + defaults present → PASS " "" keep pass "PARTIAL"
|
|
234
|
+
run_state_case "control: override PRESENT, no hit → PASS " "HIGH zzunrelatedliteral" keep pass "✅ PASS"
|
|
222
235
|
|
|
223
236
|
# ── Pair 7: pattern-ROW malformations that are not invalid regex. Each of these used to be skipped
|
|
224
237
|
# in silence, i.e. a detector the author believed in that never existed, and a scan that certified
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# utterance_landing_check.sh — 마감 전, 운영자 발화가 기록에 착지했는지 grep 검증.
|
|
3
|
+
#
|
|
4
|
+
# WHY. 마감 체인이 `CONSISTENT` 를 내면서도 운영자 발화가 통째로 빠지는 일이 두 세션 연속
|
|
5
|
+
# 일어났다(2026-08-07 5건 · 2026-08-08 2건). close check 는 **형식**만 본다 — 카드가 로그보다
|
|
6
|
+
# 새로운가, 필수 아티팩트가 있는가. **내용이 착지했는지는 안 본다.**
|
|
7
|
+
#
|
|
8
|
+
# 가장 잘 빠지는 것은 잊은 발화가 아니라 **행동으로 대응한 발화**다. 답장에서 판단까지 내리고
|
|
9
|
+
# 나면 처리된 느낌이 남아서, 그 판단이 어디에도 안 적힌 채 세션이 끝난다. 2026-08-08 실측:
|
|
10
|
+
# "PMH 가 개인위키에 4축을 돌려 늦어졌다" 에 대해 과적용이라는 판정을 답장에 썼고 기록은 0건.
|
|
11
|
+
#
|
|
12
|
+
# ── 이 스크립트가 존재하는 두 번째 이유: 손으로 짜면 계기가 죽는다 ──
|
|
13
|
+
# 같은 검증을 손으로 짤 때마다 계기가 죽었다(N=3). 마지막 사망 원인은 **zsh word-split**:
|
|
14
|
+
#
|
|
15
|
+
# S="a.md b.md"; grep -l "$pat" $S # bash 는 분리, zsh 는 파일명 하나로 전달 → 전건 0
|
|
16
|
+
#
|
|
17
|
+
# 컨트롤이 없으면 그 0 이 "전부 미착지" 로 읽힌다. 그래서 이 스크립트는 (1) 파일을 배열로
|
|
18
|
+
# 다루고 (2) **컨트롤을 먼저 돌려 살아있음을 증명한 뒤에만** 타깃 결과를 출력한다.
|
|
19
|
+
# 컨트롤이 죽으면 타깃 결과를 아예 인쇄하지 않는다 — 죽은 계기의 출력은 데이터가 아니다.
|
|
20
|
+
#
|
|
21
|
+
# ── 사용 ──
|
|
22
|
+
# bash scripts/utterance_landing_check.sh <probes.tsv> <file> [<file>...]
|
|
23
|
+
# bash scripts/utterance_landing_check.sh --self-test
|
|
24
|
+
#
|
|
25
|
+
# probes.tsv 형식 (탭 구분):
|
|
26
|
+
# CONTROL<TAB><정규식><TAB><라벨> 착지가 확실한 것. 하나라도 0이면 HARNESS-ERROR
|
|
27
|
+
# TARGET<TAB><정규식><TAB><라벨> 검증 대상 발화
|
|
28
|
+
#
|
|
29
|
+
# ── ⚠️ 명명된 잔여: 프로브는 문구 변경에 깨진다 (2026-08-08 실측) ──
|
|
30
|
+
# 정규식을 **정확한 문구**에 걸면, 그 문구를 나중에 고치는 순간 프로브가 죽고 **착지한 항목이
|
|
31
|
+
# 미착지로 뜬다**. 실측: 프로브 `2건만|두 개에만` 이 카드 제목을 `2건만` → `2건 병렬` 로
|
|
32
|
+
# 편집하자 오탐을 냈다(내용은 그대로 있었다).
|
|
33
|
+
#
|
|
34
|
+
# 이건 `[[feedback_typed_verdict_channel]]` 이 이름 붙인 Grep-Collision Treadmill 의 반대편이다:
|
|
35
|
+
# 충돌은 없는데 **너무 좁아서** 깨진다. 두 방향 모두 "문자열을 재고 대상을 안 본다" 의 사례다.
|
|
36
|
+
#
|
|
37
|
+
# 완화(닫히지 않음):
|
|
38
|
+
# · 정규식은 **여러 표현을 OR** 로 — 한 문구에 걸지 마라 (`A|B|C`)
|
|
39
|
+
# · **미착지가 뜨면 먼저 손으로 열어 확인**하라. 프로브 노후가 첫 번째 의심 대상이다.
|
|
40
|
+
# rc=1 은 "기록하라"가 아니라 "확인하라"로 읽어야 한다.
|
|
41
|
+
# · 이 실패 방향은 **안전**하다 — 착지한 것을 미착지로 부르지, 미착지를 착지로 부르지 않는다.
|
|
42
|
+
# 과차단이지 fail-open 이 아니다. (반대였다면 이 도구는 못 쓴다.)
|
|
43
|
+
#
|
|
44
|
+
# ── 종료 코드 ──
|
|
45
|
+
# 0 전건 착지
|
|
46
|
+
# 1 미착지 있음 (라벨과 함께 출력)
|
|
47
|
+
# 10 HARNESS-ERROR — 컨트롤 사망 또는 입력 불량. **PASS 도 FAIL 도 아니다**
|
|
48
|
+
|
|
49
|
+
set -uo pipefail
|
|
50
|
+
|
|
51
|
+
self_test() {
|
|
52
|
+
local T f=0 n=0
|
|
53
|
+
# mktemp 실패를 안 보면 T 가 빈 문자열이 되고 이어지는 쓰기가 `/a.md` 로 나간다 — 루트에
|
|
54
|
+
# 쓰려다 실패하고, 그 실패가 '캘리브레이션 실패' 로 렌더된다. 계기 부재를 계기 결함으로
|
|
55
|
+
# 오진하는 경로라 여기서 갈라 놓는다. (cross-family 감사에서 실제 샌드박스가 이걸 밟았다.)
|
|
56
|
+
T=$(mktemp -d 2>/dev/null) || T=""
|
|
57
|
+
if [ -z "$T" ] || [ ! -d "$T" ]; then
|
|
58
|
+
echo "🟥 HARNESS-ERROR: mktemp -d 실패 — 캘리브레이션을 돌릴 수 없다(계기 결함이 아니라 환경 부재)."
|
|
59
|
+
return 10
|
|
60
|
+
fi
|
|
61
|
+
trap 'rm -rf "$T"' RETURN
|
|
62
|
+
printf 'alpha 라는 단어\n' > "$T/a.md"
|
|
63
|
+
printf 'beta 라는 단어\n' > "$T/b.md"
|
|
64
|
+
t() { n=$((n+1)); if [ "$2" = "$3" ]; then echo "✅ $1 → $3"; else echo "❌ $1 → $3 (기대 $2)"; f=1; fi; }
|
|
65
|
+
|
|
66
|
+
printf 'CONTROL\talpha\tctl\nTARGET\tbeta\thit\n' > "$T/p1.tsv"
|
|
67
|
+
bash "$0" "$T/p1.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1 && r=PASS || r=FAIL
|
|
68
|
+
t "컨트롤 생존 + 타깃 착지" PASS "$r"
|
|
69
|
+
|
|
70
|
+
printf 'CONTROL\talpha\tctl\nTARGET\tgamma\tmiss\n' > "$T/p2.tsv"
|
|
71
|
+
bash "$0" "$T/p2.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
72
|
+
t "타깃 미착지 → rc=1" 1 "$rc"
|
|
73
|
+
|
|
74
|
+
# 핵심 레인: 컨트롤이 죽으면 타깃이 '전부 미착지'처럼 보이는데, 그때 1을 내면 안 된다.
|
|
75
|
+
printf 'CONTROL\tzzz없는단어\tctl\nTARGET\tbeta\thit\n' > "$T/p3.tsv"
|
|
76
|
+
bash "$0" "$T/p3.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
77
|
+
t "컨트롤 사망 → rc=10 (1 아님)" 10 "$rc"
|
|
78
|
+
|
|
79
|
+
# zsh word-split 재현 방어: 파일 인자가 여러 개일 때 실제로 전부 읽히는가.
|
|
80
|
+
# ⚠️ 이 픽스처는 원래 TARGET 없이 컨트롤 2건만 두고 rc=0 을 기대했다. 그 형태가 곧
|
|
81
|
+
# "아무것도 안 쟀는데 합격" 을 **레인이 승인하는** 상태였다(아래 p6 이 그래서 신설됐다).
|
|
82
|
+
# word-split 을 재려면 컨트롤이 두 파일에 흩어져 있으면 충분하고, TARGET 은 있어야 한다.
|
|
83
|
+
printf 'CONTROL\talpha\tctl\nCONTROL\tbeta\tctl2\nTARGET\t라는\thit\n' > "$T/p4.tsv"
|
|
84
|
+
bash "$0" "$T/p4.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
85
|
+
t "두 파일에 흩어진 컨트롤 2건 모두 생존" 0 "$rc"
|
|
86
|
+
|
|
87
|
+
printf 'TARGET\tbeta\thit\n' > "$T/p5.tsv"
|
|
88
|
+
bash "$0" "$T/p5.tsv" "$T/a.md" >/dev/null 2>&1; rc=$?
|
|
89
|
+
t "컨트롤 0개 → rc=10 (검증 불가)" 10 "$rc"
|
|
90
|
+
|
|
91
|
+
# 잰 발화가 0 건인데 초록을 내면 안 된다. rc=1(미착지)과도 **다른 값**이어야 한다 —
|
|
92
|
+
# 둘이 뭉치면 "기록하라" 와 "프로브를 안 썼다" 가 구분되지 않는다.
|
|
93
|
+
printf 'CONTROL\talpha\tctl\n' > "$T/p6.tsv"
|
|
94
|
+
bash "$0" "$T/p6.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
95
|
+
t "TARGET 0개 → rc=10 (0 도 1 도 아님)" 10 "$rc"
|
|
96
|
+
|
|
97
|
+
# 깨진 정규식은 무매치가 아니다. grep 은 에러도 비영으로 주므로 순진하게 받으면
|
|
98
|
+
# **고장난 프로브가 '미착지' 로 렌더된다** = 있지도 않은 누락을 기록하게 만든다.
|
|
99
|
+
printf 'CONTROL\talpha\tctl\nTARGET\t[\tbroken\n' > "$T/p7.tsv"
|
|
100
|
+
bash "$0" "$T/p7.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
101
|
+
t "깨진 정규식 → rc=10 (1 아님)" 10 "$rc"
|
|
102
|
+
|
|
103
|
+
# 대시로 시작하는 패턴이 grep 옵션으로 먹히면 계기가 통째로 오작동한다.
|
|
104
|
+
printf 'CONTROL\talpha\tctl\nTARGET\t-베타\tdashy\n' > "$T/p8.tsv"
|
|
105
|
+
bash "$0" "$T/p8.tsv" "$T/a.md" "$T/b.md" >/dev/null 2>&1; rc=$?
|
|
106
|
+
t "대시 시작 패턴이 옵션으로 안 먹힘 → rc=1(정상 미착지)" 1 "$rc"
|
|
107
|
+
|
|
108
|
+
echo; [ "$f" -eq 0 ] && echo "✅ 캘리브레이션 통과 ($n 쌍)" || echo "❌ 캘리브레이션 실패 ($n 쌍)"
|
|
109
|
+
return "$f"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[ "${1:-}" = "--self-test" ] && { self_test; exit $?; }
|
|
113
|
+
|
|
114
|
+
if [ "$#" -lt 2 ]; then
|
|
115
|
+
echo "usage: $0 <probes.tsv> <file> [<file>...]" >&2
|
|
116
|
+
echo " $0 --self-test" >&2
|
|
117
|
+
exit 10
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
PROBES="$1"; shift
|
|
121
|
+
FILES=("$@") # 배열 — word-split 에 의존하지 않는다
|
|
122
|
+
[ -f "$PROBES" ] || { echo "🟥 HARNESS-ERROR: probes 파일 없음: $PROBES"; exit 10; }
|
|
123
|
+
for f in "${FILES[@]}"; do
|
|
124
|
+
[ -f "$f" ] || { echo "🟥 HARNESS-ERROR: 대상 파일 없음: $f"; exit 10; }
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
# 단일 검색 경로. CONTROL 과 TARGET 이 **같은 함수**를 통과해야 한다 — 두 벌로 나누면 한쪽만
|
|
128
|
+
# 통과하는 입력이 다른 쪽에서 무음 드롭된다([[feedback_divergent_leniency_duplicate_normalizers]]).
|
|
129
|
+
# 반환: 0=매치 · 1=무매치 · 2=grep 에러(잘못된 정규식 등). grep 은 에러도 2 로 주는데, 그걸
|
|
130
|
+
# 무매치와 뭉치면 **깨진 프로브가 '미착지'로 렌더된다** — 계기 고장을 데이터로 파는 것이라
|
|
131
|
+
# 이 스크립트가 막으려는 바로 그 오독이다. `--` 는 `-foo` 같은 패턴이 옵션으로 먹히는 걸 막는다.
|
|
132
|
+
_probe_hits() {
|
|
133
|
+
local pat="$1"; shift
|
|
134
|
+
grep -lE -- "$pat" "$@" 2>/dev/null
|
|
135
|
+
return "${PIPESTATUS[0]:-$?}"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ── 1단계: 컨트롤. 살아있음을 증명하기 전에는 타깃을 인쇄하지 않는다 ──
|
|
139
|
+
CTL_TOTAL=0; CTL_DEAD=0; DEAD_LABELS=""; BAD_PAT=""
|
|
140
|
+
while IFS=$'\t' read -r kind pat label; do
|
|
141
|
+
[ "${kind:-}" = "CONTROL" ] || continue
|
|
142
|
+
CTL_TOTAL=$((CTL_TOTAL+1))
|
|
143
|
+
_probe_hits "$pat" "${FILES[@]}" >/dev/null; _rc=$?
|
|
144
|
+
if [ "$_rc" -ge 2 ]; then
|
|
145
|
+
BAD_PAT="${BAD_PAT:+$BAD_PAT, }CONTROL/${label:-$pat}"
|
|
146
|
+
elif [ "$_rc" -ne 0 ]; then
|
|
147
|
+
CTL_DEAD=$((CTL_DEAD+1)); DEAD_LABELS="${DEAD_LABELS:+$DEAD_LABELS, }${label:-$pat}"
|
|
148
|
+
fi
|
|
149
|
+
done < "$PROBES"
|
|
150
|
+
|
|
151
|
+
if [ "$CTL_TOTAL" -eq 0 ]; then
|
|
152
|
+
echo "🟥 HARNESS-ERROR: probes 에 CONTROL 이 없다."
|
|
153
|
+
echo " 컨트롤 없는 부재 측정은 근거가 아니다 — 0 이 '미착지'인지 '계기 사망'인지 구분 불가."
|
|
154
|
+
exit 10
|
|
155
|
+
fi
|
|
156
|
+
if [ "$CTL_DEAD" -gt 0 ]; then
|
|
157
|
+
echo "🟥 HARNESS-ERROR: 컨트롤 $CTL_DEAD/$CTL_TOTAL 사망 — [$DEAD_LABELS]"
|
|
158
|
+
echo " 착지가 확실한 문자열이 안 잡힌다 = 계기가 대상을 못 읽고 있다."
|
|
159
|
+
echo " 타깃 결과는 **인쇄하지 않는다** — 죽은 계기의 출력은 데이터가 아니라 소음이다."
|
|
160
|
+
echo " 흔한 원인: zsh word-split(\$FILES 인용 누락) · 경로 오류 · 인코딩 불일치."
|
|
161
|
+
exit 10
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# TARGET 이 0건이면 잰 것이 없다. 예전 판본은 여기서 `발화 0 건 전부 착지` 를 인쇄하고 **0 을
|
|
165
|
+
# 냈다** — 빈 프로브 세트가 초록 도장으로 환전됐다는 뜻이고, 그건 이 스크립트가 존재하는
|
|
166
|
+
# 이유와 정확히 반대 방향이다(미측정을 0 으로, 0 을 합격으로). cross-family 감사가 지목했고,
|
|
167
|
+
# self-test 의 p4 픽스처가 그 상태를 **기대값 0 으로 인코딩해** 굳히고 있었다 — 레인이 결함을
|
|
168
|
+
# 방어하는 대신 승인하고 있었던 셈이다([[feedback_not_found_is_not_zero_family]]).
|
|
169
|
+
if ! grep -qE '^TARGET'$'\t' "$PROBES"; then
|
|
170
|
+
echo "🟥 HARNESS-ERROR: probes 에 TARGET 이 없다 — 잰 발화가 0 건이다."
|
|
171
|
+
echo " '전부 착지' 가 아니라 '아무것도 안 쟀다' 다. 빈 프로브 세트는 합격이 아니다."
|
|
172
|
+
exit 10
|
|
173
|
+
fi
|
|
174
|
+
echo "계기 생존: 컨트롤 $CTL_TOTAL/$CTL_TOTAL"
|
|
175
|
+
echo
|
|
176
|
+
|
|
177
|
+
# ── 2단계: 타깃 ──
|
|
178
|
+
MISS=0; N=0
|
|
179
|
+
while IFS=$'\t' read -r kind pat label; do
|
|
180
|
+
[ "${kind:-}" = "TARGET" ] || continue
|
|
181
|
+
N=$((N+1))
|
|
182
|
+
hits=$(_probe_hits "$pat" "${FILES[@]}" | wc -l | tr -d ' '); _rc=${PIPESTATUS[0]}
|
|
183
|
+
if [ "$_rc" -ge 2 ]; then
|
|
184
|
+
printf " 🟥 %-46s 프로브 오류\n" "${label:-$pat}"
|
|
185
|
+
BAD_PAT="${BAD_PAT:+$BAD_PAT, }TARGET/${label:-$pat}"
|
|
186
|
+
elif [ "$hits" -gt 0 ]; then
|
|
187
|
+
printf " ✅ %-46s %s파일\n" "${label:-$pat}" "$hits"
|
|
188
|
+
else
|
|
189
|
+
printf " 🟥 %-46s 미착지\n" "${label:-$pat}"
|
|
190
|
+
MISS=$((MISS+1))
|
|
191
|
+
fi
|
|
192
|
+
done < "$PROBES"
|
|
193
|
+
|
|
194
|
+
echo
|
|
195
|
+
# 깨진 프로브는 '미착지' 가 아니다. 둘을 같은 rc 로 내면 "기록하라" 와 "프로브를 고쳐라" 가
|
|
196
|
+
# 구분되지 않고, 후자를 전자로 읽으면 있지도 않은 누락을 기록하게 된다.
|
|
197
|
+
if [ -n "$BAD_PAT" ]; then
|
|
198
|
+
echo "🟥 HARNESS-ERROR: 프로브 정규식 오류 — [$BAD_PAT]"
|
|
199
|
+
echo " grep 이 에러(rc≥2)를 냈다. 무매치가 아니라 계기 고장이다 — 정규식을 고쳐라."
|
|
200
|
+
exit 10
|
|
201
|
+
fi
|
|
202
|
+
if [ "$MISS" -eq 0 ]; then
|
|
203
|
+
echo "✅ 발화 $N 건 전부 착지"
|
|
204
|
+
exit 0
|
|
205
|
+
fi
|
|
206
|
+
echo "🟥 $N 건 중 $MISS 건 미착지 — 마감 전에 기록하라."
|
|
207
|
+
echo " ⚠️ 가장 잘 빠지는 것은 잊은 발화가 아니라 **행동으로 대응한 발화**다."
|
|
208
|
+
echo " 답장에서 판단까지 내리고 나면 처리된 느낌이 남고, 그 판단이 어디에도 안 적힌다."
|
|
209
|
+
exit 1
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# version_lockstep_check.sh — every shipped manifest must carry package.json's version.
|
|
3
|
+
#
|
|
4
|
+
# Why this exists (measured 2026-08-06, while republishing 1.4.88 → 1.4.89):
|
|
5
|
+
# CLAUDE.md §④-b says to bump "package.json + every .claude-plugin/plugin.json + marketplace.json".
|
|
6
|
+
# That instruction reads as four files, but `.claude-plugin/marketplace.json` carries **one version
|
|
7
|
+
# per plugin entry** — five strings across four files. A bump that edited the first entry left the
|
|
8
|
+
# second at the old version, and `test_tag_version_lanes.sh` passed 8/8 anyway: that lane compares
|
|
9
|
+
# the git TAG to package.json and never opens marketplace.json. The stale entry was caught by an
|
|
10
|
+
# ad-hoc `sort | uniq -c`, i.e. by luck, not by an instrument.
|
|
11
|
+
#
|
|
12
|
+
# Codex caches plugins on the plugin.json version, so a stale entry ships a plugin that the other
|
|
13
|
+
# runtime believes it already has — the drift is silent on exactly the surface that cannot report it.
|
|
14
|
+
#
|
|
15
|
+
# Exit 0 = all aligned · exit 1 = drift (names every offending file:line) · exit 2 = harness error
|
|
16
|
+
# (a manifest is missing or unparseable — NOT a pass; an instrument that could not look has not
|
|
17
|
+
# looked, and this gate guards an irreversible surface).
|
|
18
|
+
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
ROOT="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
21
|
+
|
|
22
|
+
command -v python3 >/dev/null 2>&1 || { echo "LOCKSTEP: HARNESS-ERROR — python3 unavailable"; exit 2; }
|
|
23
|
+
|
|
24
|
+
python3 - "$ROOT" <<'PY'
|
|
25
|
+
import json, sys, glob, os
|
|
26
|
+
root = sys.argv[1]
|
|
27
|
+
pkg_path = os.path.join(root, 'package.json')
|
|
28
|
+
try:
|
|
29
|
+
want = json.load(open(pkg_path))['version']
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"LOCKSTEP: HARNESS-ERROR — cannot read {pkg_path}: {e}")
|
|
32
|
+
sys.exit(2)
|
|
33
|
+
|
|
34
|
+
# (path, list-of-(label, version)) — every manifest that ships a version string.
|
|
35
|
+
targets = []
|
|
36
|
+
for p in sorted(glob.glob(os.path.join(root, 'plugins', '*', '.claude-plugin', 'plugin.json'))):
|
|
37
|
+
targets.append(p)
|
|
38
|
+
mk = os.path.join(root, '.claude-plugin', 'marketplace.json')
|
|
39
|
+
if os.path.exists(mk):
|
|
40
|
+
targets.append(mk)
|
|
41
|
+
|
|
42
|
+
if not targets:
|
|
43
|
+
# Absence is not alignment. If the manifests vanished, say so rather than reporting a clean run.
|
|
44
|
+
print("LOCKSTEP: HARNESS-ERROR — no plugin/marketplace manifest found; alignment is UNKNOWN, not aligned")
|
|
45
|
+
sys.exit(2)
|
|
46
|
+
|
|
47
|
+
drift, checked = [], 0
|
|
48
|
+
for p in targets:
|
|
49
|
+
try:
|
|
50
|
+
d = json.load(open(p))
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"LOCKSTEP: HARNESS-ERROR — cannot parse {p}: {e}")
|
|
53
|
+
sys.exit(2)
|
|
54
|
+
rel = os.path.relpath(p, root)
|
|
55
|
+
# A manifest carries a version either at the top level (plugin.json) or once per plugin entry
|
|
56
|
+
# (marketplace.json). Walk both shapes so a new entry cannot be silently uncovered.
|
|
57
|
+
found = []
|
|
58
|
+
if isinstance(d.get('version'), str):
|
|
59
|
+
found.append(('(top-level)', d['version']))
|
|
60
|
+
for key in ('plugins', 'entries'):
|
|
61
|
+
for i, ent in enumerate(d.get(key) or []):
|
|
62
|
+
if isinstance(ent, dict) and isinstance(ent.get('version'), str):
|
|
63
|
+
found.append((f"{key}[{i}] {ent.get('name', '?')}", ent['version']))
|
|
64
|
+
if not found:
|
|
65
|
+
print(f"LOCKSTEP: HARNESS-ERROR — {rel} carries no version string; expected at least one")
|
|
66
|
+
sys.exit(2)
|
|
67
|
+
for label, got in found:
|
|
68
|
+
checked += 1
|
|
69
|
+
if got != want:
|
|
70
|
+
drift.append(f" {rel} :: {label} = {got} (package.json = {want})")
|
|
71
|
+
|
|
72
|
+
if drift:
|
|
73
|
+
print(f"LOCKSTEP: DRIFT — {len(drift)} of {checked} shipped version string(s) do not match package.json")
|
|
74
|
+
print("\n".join(drift))
|
|
75
|
+
print("Bump every string above in the same commit — Codex caches plugins on this version,")
|
|
76
|
+
print("so a stale entry ships as 'already installed' on the runtime that cannot report it.")
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
|
|
79
|
+
print(f"LOCKSTEP: PASS — {checked} shipped version string(s) all at {want}")
|
|
80
|
+
PY
|