@chrono-meta/fh-gate 2.6.0 → 2.8.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 +26 -3
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +28 -3
- package/CLAUDE.md +146 -168
- package/README.ja.md +144 -24
- package/README.ko.md +135 -22
- package/README.md +204 -27
- package/README.zh.md +126 -21
- package/docs/ETHOS.md +10 -3
- package/knowledge/shared/dialogue/ai_dialogue_playbook.md +131 -0
- package/knowledge/shared/harness-core/claude_md_gate_details.md +90 -0
- package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +75 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -3
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +92 -5
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +12 -2
- package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +32 -0
- package/knowledge/shared/harness-core/ship_readiness_gate.md +77 -8
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +196 -0
- package/knowledge/shared/rules/knowledge_layer_seam.md +1 -1
- package/knowledge/shared/rules/multi_session_close_protocol.md +7 -3
- package/package.json +15 -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/CHANGELOG.md +157 -0
- package/plugins/fh-meta/agents/persona-innovator.md +170 -0
- package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +37 -0
- package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +16 -1
- package/plugins/fh-meta/skills/steel-quench/SKILL.md +97 -0
- package/scripts/adapters/peer_resolve.sh +58 -6
- package/scripts/branch_claim.sh +3 -1
- package/scripts/cluster_capability_scan.sh +42 -13
- package/scripts/digest_landing_check.sh +142 -2
- package/scripts/fh_hub_identity.sh +83 -0
- package/scripts/fh_session_load.sh +53 -5
- package/scripts/fh_track_resolve.sh +114 -0
- package/scripts/field_canon_preload.sh +50 -5
- package/scripts/listing_watch.sh +187 -0
- package/scripts/package_coverage_check.sh +8 -0
- package/scripts/prior_art_prompt.sh +168 -0
- package/scripts/psa_scan_lib.sh +201 -15
- package/scripts/residency_admission_check.sh +204 -0
- package/scripts/selfcheck.sh +103 -0
- package/scripts/test_adapter_lanes.sh +67 -2
- package/scripts/test_heavy_classifier_lanes.sh +144 -0
- package/scripts/test_listing_watch_lanes.sh +162 -0
- package/scripts/test_marker_defense_lanes.sh +152 -0
- package/scripts/test_marker_soul_check_lanes.sh +211 -0
- package/scripts/test_prior_art_prompt_lanes.sh +128 -0
- package/scripts/test_psa_singlefile_lanes.sh +351 -1
- package/scripts/test_residency_admission_lanes.sh +60 -0
- package/scripts/test_track_resolve_lanes.sh +158 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +40 -0
- package/templates/.git-hooks/pre-commit +400 -4
- package/templates/.git-hooks/pre-push +17 -2
- package/templates/settings.PriorArt.snippet.json +44 -0
|
@@ -282,6 +282,46 @@ SessionStart REGISTRATION of the script that reports on them. So the wizard's un
|
|
|
282
282
|
message (GAP 1) is never contradicted by anything, and the check whose job is announcing an unwired \
|
|
283
283
|
machine can itself be unwired silently."
|
|
284
284
|
|
|
285
|
+
# ── SHIP-1/2: 출하되는 모든 스니펫이 머지 코드가 읽는 키를 갖고 있나 (2026-08-22 신설) ─────
|
|
286
|
+
# 🟥 왜 이 레인이 뒤늦게 생겼나 — 이 파일은 «머지 코드가 옳게 동작하나» 만 재고 있었고,
|
|
287
|
+
# «출하물이 그 코드가 읽는 모양인가» 는 아무도 안 봤다. 그 사이
|
|
288
|
+
# templates/settings.PriorArt.snippet.json 이 project_settings_json 래퍼 **없이** 출하됐고,
|
|
289
|
+
# 머지 코드는 그 키가 없으면 `SKIP (no project_settings_json)` 로 **조용히 건너뛴다**.
|
|
290
|
+
# 결과: install-wizard 를 돌려도 그 훅은 **한 번도 등록된 적이 없었다.**
|
|
291
|
+
# 실측 2026-08-22: 스니펫 5개 중 4개 래퍼 O · PriorArt 만 부재(전수, 컨트롤 = 나머지 넷).
|
|
292
|
+
# ⇒ 레인이 «구현» 만 보고 «출하물» 을 안 보면 이 클래스는 영원히 초록이다.
|
|
293
|
+
echo
|
|
294
|
+
echo "── SHIP: shipped-snippet schema conformance ──"
|
|
295
|
+
_ship_total=0; _ship_bad=0
|
|
296
|
+
for _snip in "$ROOT"/templates/settings.*.snippet.json; do
|
|
297
|
+
[ -f "$_snip" ] || continue
|
|
298
|
+
_ship_total=$((_ship_total+1))
|
|
299
|
+
if ! python3 -c "import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if isinstance(d.get('project_settings_json'),dict) else 1)" "$_snip" 2>/dev/null; then
|
|
300
|
+
_ship_bad=$((_ship_bad+1))
|
|
301
|
+
_fail "SHIP-1 $(basename "$_snip") has no dict 'project_settings_json' — install-wizard will SKIP it silently"
|
|
302
|
+
fi
|
|
303
|
+
done
|
|
304
|
+
if [ "$_ship_total" -eq 0 ]; then
|
|
305
|
+
# 부재를 통과로 렌더하지 않는다 — 0 개를 스캔하고 PASS 하면 계기가 죽은 것이다
|
|
306
|
+
_fail "SHIP-1 scanned ZERO snippets — instrument dead, not a pass (glob or ROOT wrong)"
|
|
307
|
+
elif [ "$_ship_bad" -eq 0 ]; then
|
|
308
|
+
_pass "SHIP-1 all $_ship_total shipped snippet(s) carry a dict 'project_settings_json'"
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
# SHIP-2 컨트롤 — 이 검사가 실제로 판별하나. 래퍼를 뗀 픽스처는 반드시 걸려야 한다.
|
|
312
|
+
_ctl=$(mktemp -t fh_ship_ctl.XXXXXX) || _ctl=""
|
|
313
|
+
if [ -n "$_ctl" ]; then
|
|
314
|
+
printf '%s\n' '{"hooks":{"PreToolUse":[]}}' > "$_ctl"
|
|
315
|
+
if python3 -c "import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if isinstance(d.get('project_settings_json'),dict) else 1)" "$_ctl" 2>/dev/null; then
|
|
316
|
+
_fail "SHIP-2 CONTROL — a wrapper-less fixture PASSED the check; the check does not discriminate"
|
|
317
|
+
else
|
|
318
|
+
_pass "SHIP-2 CONTROL — wrapper-less fixture is caught (check discriminates)"
|
|
319
|
+
fi
|
|
320
|
+
rm -f "$_ctl"
|
|
321
|
+
else
|
|
322
|
+
_fail "SHIP-2 CONTROL could not run (mktemp failed) — reported as a gap, not a pass"
|
|
323
|
+
fi
|
|
324
|
+
|
|
285
325
|
echo
|
|
286
326
|
echo "──────────────────────────────────────────────"
|
|
287
327
|
if [ "$FAILED" -ne 0 ]; then
|
|
@@ -356,6 +356,7 @@ else
|
|
|
356
356
|
fi
|
|
357
357
|
else
|
|
358
358
|
PSA_LEAK=0
|
|
359
|
+
PSA_DEAD=0
|
|
359
360
|
# NUL-delimited (regression caught by the refactor's own cross-family review): the pre-refactor
|
|
360
361
|
# loop read this list with `read -r -d ''`, and the rewrite dropped back to a line-oriented read.
|
|
361
362
|
# `core.quotePath=false` stops git quoting NON-ASCII names, but a path holding a backslash or a
|
|
@@ -367,7 +368,11 @@ else
|
|
|
367
368
|
added=$(git diff --cached -- "$f" 2>/dev/null | grep -E '^\+' | sed 's/^\+//' || true)
|
|
368
369
|
[ -z "$added" ] && continue
|
|
369
370
|
# Per-line pass, path-tagged so the LOW file allowlist applies.
|
|
370
|
-
|
|
371
|
+
# 🟥 rc 를 «비영» 으로 뭉개지 마라 (cross-family, 2026-08-21): 1=유출 · 3=계기 사망.
|
|
372
|
+
# 둘을 합치면 아래 PUBLIC_SURFACE_OK 가 **계기 사망까지 «승인된 유출»로 통과**시킨다.
|
|
373
|
+
_psa_rc=0
|
|
374
|
+
printf '%s\n' "$added" | sed "s|^|$f |" | psa_scan_tagged || _psa_rc=$?
|
|
375
|
+
case "$_psa_rc" in 0) ;; 3) PSA_DEAD=1 ;; *) PSA_LEAK=1 ;; esac
|
|
371
376
|
# Split-token backstop (pre-commit ONLY — this surface is a DIFF, so a literal can be wrapped
|
|
372
377
|
# mid-word across two added lines and neither line matches). TIGHT-join, no separator, so
|
|
373
378
|
# "fh-\nbe" becomes "fh-be". Accepted residual, verified not a missed fix: two whitespace-
|
|
@@ -375,15 +380,31 @@ else
|
|
|
375
380
|
# PUBLIC_SURFACE_OK escape. A sentinel separator would close the fusion but RE-OPEN the split
|
|
376
381
|
# catch, which is the more important case since real literals have no internal whitespace.
|
|
377
382
|
joined=$(printf '%s' "$added" | tr -d '\n\r')
|
|
378
|
-
|
|
383
|
+
_psa_rc2=0
|
|
384
|
+
printf '%s\t%s\n' "$f" "$joined" | psa_scan_tagged >/dev/null 2>&1 || _psa_rc2=$?
|
|
385
|
+
[ "$_psa_rc2" = "3" ] && PSA_DEAD=1
|
|
386
|
+
if [ "$_psa_rc2" != "0" ] && [ "$_psa_rc2" != "3" ]; then
|
|
379
387
|
# Only report if the per-line pass did not already flag this file, to avoid double-reporting.
|
|
380
388
|
if ! printf '%s\n' "$added" | sed "s|^|$f |" | psa_scan_tagged >/dev/null 2>&1; then :; else
|
|
389
|
+
# 🟥 **표시 전용 — 판정 경로가 아니다** (2026-08-21 배선 리뷰 R-1). 이 파이프는 비말단이라
|
|
390
|
+
# `$?` 가 `sed` 것이다. 그래도 되는 이유는 도달 조건이 **이미 «유출 확정»** 이고 바로
|
|
391
|
+
# 다음 줄이 `PSA_LEAK=1` 을 무조건 세우기 때문이다. 판정은 위 `_psa_rc` 3분기가 쥔다.
|
|
392
|
+
# ⚠️ 이 주석이 없으면 다음 사람이 판정 경로로 읽고 «고친다» — 그게 이 줄을 위험하게 만든다.
|
|
381
393
|
printf '%s\t%s\n' "$f" "$joined" | psa_scan_tagged | sed 's/leak —/leak (line-split) —/'
|
|
382
394
|
PSA_LEAK=1
|
|
383
395
|
fi
|
|
384
396
|
fi
|
|
385
397
|
done < <(git -c core.quotePath=false diff --cached --name-only --no-renames --diff-filter=ACMR -z 2>/dev/null || true)
|
|
386
|
-
|
|
398
|
+
# 🟥 계기 사망은 override 대상이 **아니다.** override 는 «알고 있는 언급»을 승인하는 것이지
|
|
399
|
+
# «안 잰 표면»을 승인하는 게 아니다. 초판은 1 과 3 을 합쳐서, PUBLIC_SURFACE_OK=1 이면
|
|
400
|
+
# 스캐너가 죽은 상태도 통과했다 — 비가역 표면에서 가장 나쁜 조합이다.
|
|
401
|
+
if [ "${PSA_DEAD:-0}" -eq 1 ]; then
|
|
402
|
+
echo " ⛔ INSTRUMENT DEAD — the public-surface scan did NOT run (rc=3). NOT SCANNED is not clean."
|
|
403
|
+
echo " PUBLIC_SURFACE_OK does NOT cover this. Fix the scanner, then commit:"
|
|
404
|
+
echo " · run under bash (zsh special vars can blank PATH inside the matcher)"
|
|
405
|
+
echo " · confirm psa_load ran and PSA_STREAM is non-empty"
|
|
406
|
+
FAILED=1
|
|
407
|
+
elif [ "$PSA_LEAK" -eq 1 ]; then
|
|
387
408
|
if [ "${PUBLIC_SURFACE_OK:-0}" = "1" ]; then
|
|
388
409
|
echo " ⚠️ public-surface hit(s) allowed by PUBLIC_SURFACE_OK=1 (conscious, reviewed intent)"
|
|
389
410
|
echo "$(date +%Y-%m-%dT%H:%M:%S) PUBLIC_SURFACE_OK override — branch $BRANCH — review suppressed hit(s) above" \
|
|
@@ -575,6 +596,86 @@ marker_recreate_hint() {
|
|
|
575
596
|
# VALUE is a closed enum; the three degrade values additionally require a non-vacuous reason
|
|
576
597
|
# on the SAME line (keeping the established one-line marker convention rather than adding a
|
|
577
598
|
# second field). Returns 0 = accepted, 1 = block.
|
|
599
|
+
validate_defense_leg() { # $1 = marker path — fires ONLY at the floor tiers (see call site)
|
|
600
|
+
# Wave 1-D defense line. Absorbed from a sibling harness 2026-08-20, but the machine is OURS:
|
|
601
|
+
# 🟥 the source document claimed "the commit hook reads that line" and "pinned by
|
|
602
|
+
# test_marker_floor_lanes.sh" — measured twice with a control (`crossfamily` 21 hits), BOTH were
|
|
603
|
+
# 0 hits there. Porting the prose without building this function would have imported the phantom.
|
|
604
|
+
#
|
|
605
|
+
# WHY IT EXISTS: measured n=6 in the origin field — a floor-tier pass executes the attack angles
|
|
606
|
+
# without defect but does NOT spontaneously ask these three of its own numbers. That is a
|
|
607
|
+
# checklist gap, not a capability gap, and a harness whose behaviour depends on which model drives
|
|
608
|
+
# it is defective by sonnet_floor_doctrine.md rather than merely limited.
|
|
609
|
+
# FH-side corroboration (2026-08-20, this session, n=1): a comparison was published from two
|
|
610
|
+
# DIFFERENT package versions and read as a defect ("15 lanes vs 13 lanes — two vanished"). The
|
|
611
|
+
# 비교공정성 question — *were the two arms measured under the same conditions?* — asks exactly
|
|
612
|
+
# that, and the claim was retracted only because a later step happened to surface the version gap.
|
|
613
|
+
#
|
|
614
|
+
# SCOPE, and why it is not wider: `below-floor` occurs 0 times across the existing marker corpus,
|
|
615
|
+
# so gating on it alone would be a decoration that never fires. `sonnet-floor` occurs 11 times.
|
|
616
|
+
# Both are gated here; the wide reading ("any marker carrying numbers") is deliberately NOT taken —
|
|
617
|
+
# it would price this axis at a near-universal rate, which is the over-trigger §7 rejects.
|
|
618
|
+
#
|
|
619
|
+
# 🟥 WHAT WAS NOT IMPORTED: the origin also amends its floor rule so that below-floor + this line
|
|
620
|
+
# + a crossfamily record PASSES WITHOUT the operator ack. That is a LOOSENING of an existing FH
|
|
621
|
+
# gate and it is not adopted. Here this leg is purely additive: below-floor still requires
|
|
622
|
+
# below-floor-ack, unchanged.
|
|
623
|
+
local m="$1" line r f e
|
|
624
|
+
if [ ! -f "$m" ] || ! grep -qE '^[[:space:]]*axis2-defense:[[:space:]]*[^[:space:]]' "$m"; then
|
|
625
|
+
echo " ❌ FAIL — floor-tier marker with no 'axis2-defense:' line."
|
|
626
|
+
echo " At the floor tier the attack angles run but the three defense questions do not get"
|
|
627
|
+
echo " asked spontaneously. Ask them OF YOUR OWN findings and numbers, then record:"
|
|
628
|
+
echo " axis2-defense: reproducibility=<exact command or file:line another session runs>"
|
|
629
|
+
echo " fairness=<reps/inputs/env named for BOTH arms>"
|
|
630
|
+
echo " estimation-layer=<per number: measured | estimate | quotation;"
|
|
631
|
+
echo " for measured, what showed the instrument works here>"
|
|
632
|
+
echo " Append to: $m"
|
|
633
|
+
return 1
|
|
634
|
+
fi
|
|
635
|
+
if [ "$(grep -cE '^[[:space:]]*axis2-defense:' "$m")" -gt 1 ]; then
|
|
636
|
+
echo " ❌ FAIL — marker carries MORE THAN ONE 'axis2-defense:' line."
|
|
637
|
+
grep -nE '^[[:space:]]*axis2-defense:' "$m" | sed 's/^/ /'
|
|
638
|
+
echo " The reader takes the FIRST, so an appended correction is silently shadowed."
|
|
639
|
+
echo " Leave exactly one line."
|
|
640
|
+
return 1
|
|
641
|
+
fi
|
|
642
|
+
# The whole value, including any continuation lines, up to the next top-level field.
|
|
643
|
+
line=$(sed -n '/^[[:space:]]*axis2-defense:/,/^[a-z0-9-]*:[[:space:]]/p' "$m" | sed -E '2,$ { /^[a-z0-9-]+:[[:space:]]/d; }' | tr '\n' ' ')
|
|
644
|
+
r=$(printf '%s' "$line" | sed -nE 's/.*reproducibility=([^ ].*)/\1/p' | sed -E 's/(fairness=|estimation-layer=).*//')
|
|
645
|
+
f=$(printf '%s' "$line" | sed -nE 's/.*fairness=([^ ].*)/\1/p' | sed -E 's/(reproducibility=|estimation-layer=).*//')
|
|
646
|
+
e=$(printf '%s' "$line" | sed -nE 's/.*estimation-layer=([^ ].*)/\1/p' | sed -E 's/(reproducibility=|fairness=).*//')
|
|
647
|
+
local missing=""
|
|
648
|
+
[ -n "$r" ] || missing="$missing reproducibility"
|
|
649
|
+
[ -n "$f" ] || missing="$missing fairness"
|
|
650
|
+
[ -n "$e" ] || missing="$missing estimation-layer"
|
|
651
|
+
if [ -n "$missing" ]; then
|
|
652
|
+
echo " ❌ FAIL — axis2-defense: missing sub-answer(s):$missing"
|
|
653
|
+
echo " All three are required. A partially-filled form is not a defense — the question you"
|
|
654
|
+
echo " skipped is the one you did not want to answer."
|
|
655
|
+
return 1
|
|
656
|
+
fi
|
|
657
|
+
# Non-vacuity. `ok` / `yes` / `n/a` / `done` are a FILLED FORM, not an answer — the same shape the
|
|
658
|
+
# crossfamily leg rejects. Anything under 12 chars cannot name a command, a reps count, or a layer.
|
|
659
|
+
local bad=""
|
|
660
|
+
for pair in "reproducibility:$r" "fairness:$f" "estimation-layer:$e"; do
|
|
661
|
+
local k="${pair%%:*}" v="${pair#*:}"
|
|
662
|
+
v=$(printf '%s' "$v" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
|
663
|
+
case "$(printf '%s' "$v" | tr 'A-Z' 'a-z')" in
|
|
664
|
+
ok|yes|y|n/a|na|none|done|true|-|tbd|todo) bad="$bad $k(vacuous:'$v')" ;;
|
|
665
|
+
*) [ "${#v}" -ge 12 ] || bad="$bad $k(too short:'$v')" ;;
|
|
666
|
+
esac
|
|
667
|
+
done
|
|
668
|
+
if [ -n "$bad" ]; then
|
|
669
|
+
echo " ❌ FAIL — axis2-defense: vacuous answer(s):$bad"
|
|
670
|
+
echo " 'ok'/'yes'/'n/a' is a filled form, not an answer. Name the command, the reps, the layer."
|
|
671
|
+
echo " 🟥 The hook checks PRESENCE and NON-VACUITY only. It cannot check whether the answers"
|
|
672
|
+
echo " are TRUE — that stays with the operator and the weekly audit, exactly like every other"
|
|
673
|
+
echo " marker field. Do not read a PASS here as a claim that these answers were verified."
|
|
674
|
+
return 1
|
|
675
|
+
fi
|
|
676
|
+
return 0
|
|
677
|
+
}
|
|
678
|
+
|
|
578
679
|
validate_crossfamily_leg() { # $1 = marker path
|
|
579
680
|
local m="$1" line val reason fams
|
|
580
681
|
if [ ! -f "$m" ] || ! grep -qE '^[[:space:]]*crossfamily:[[:space:]]*[^[:space:]]' "$m"; then
|
|
@@ -971,6 +1072,263 @@ validate_thirdparty_leg() { # $1 = marker path
|
|
|
971
1072
|
return 0
|
|
972
1073
|
}
|
|
973
1074
|
|
|
1075
|
+
SOUL_PRESENT_GRACE_DATE="2026-08-21"
|
|
1076
|
+
validate_soul_present_leg() { # $1 = marker path
|
|
1077
|
+
# ── ①영혼 존재 검사 (2026-08-21) ──────────────────────────────────────────────
|
|
1078
|
+
# CLAUDE.md §자기 대조 has mandated an ①영혼 line (성공 정의 / 절대 안 함) on every marker
|
|
1079
|
+
# since 2026-08-09. MEASURED on the real corpus this day, with the instrument calibrated over
|
|
1080
|
+
# EIGHT spellings and two controls (`axes-run` and `axis2-engine` = 98/98): of 98 post-grace
|
|
1081
|
+
# markers, 61 carry it and **37 (37.8%) carry no form of it at all**.
|
|
1082
|
+
#
|
|
1083
|
+
# 🟥 The first pass of that same measurement grepped only the exact `①영혼` string and returned
|
|
1084
|
+
# "92.9% missing". That would have been a false alarm ([[feedback_measured_scope_vs_claimed_scope]]
|
|
1085
|
+
# — hand-enumerating spellings). The union + hand-verification of one negative sample is what
|
|
1086
|
+
# makes the 37 real: `.axes_23_passed_feat_target-freeze-gate_2026-08-18.marker` was opened by
|
|
1087
|
+
# eye — a panel-reviewed, 28-lane, controls-alive marker with every OTHER field filled, and no
|
|
1088
|
+
# soul line in any spelling.
|
|
1089
|
+
#
|
|
1090
|
+
# 🟥 WHY THIS IS NOT THE THING THE ①영혼 COMMENT BELOW DECLINES. That comment refuses to check
|
|
1091
|
+
# «was it written BEFORE designing» — unverifiable from a file, correctly excluded, and this
|
|
1092
|
+
# lane does not check it. PRESENCE is a different question and is trivially decidable. The
|
|
1093
|
+
# impossible question carried the possible one out on its ticket.
|
|
1094
|
+
#
|
|
1095
|
+
# SCOPE — channel, not judgment. Asserts the field EXISTS and is not vacuous. Says nothing about
|
|
1096
|
+
# whether the success definition is good, was honoured, or was written in advance.
|
|
1097
|
+
local m="$1" n body words
|
|
1098
|
+
# Grace: only markers dated on/after the grace date are required to carry it. Retroactive
|
|
1099
|
+
# enforcement would block every in-flight branch, and that over-block trains `--no-verify`,
|
|
1100
|
+
# which would disarm the Destructive-Op gate living in this same hook.
|
|
1101
|
+
local mdate
|
|
1102
|
+
mdate=$(printf '%s' "$m" | sed -nE 's/.*_([0-9]{4}-[0-9]{2}-[0-9]{2})\.marker$/\1/p')
|
|
1103
|
+
# 🟥 An unparseable date used to `return 0` — fail-OPEN. The hook itself builds this filename
|
|
1104
|
+
# as `..._${TODAY}.marker`, so a name without a date is unexpected, not old. Unknown is not
|
|
1105
|
+
# exempt ([[feedback_not_found_is_not_zero_family]]); it falls through to the presence check.
|
|
1106
|
+
[ -z "$mdate" ] || { [ "$mdate" \< "$SOUL_PRESENT_GRACE_DATE" ] && return 0; }
|
|
1107
|
+
# 🟥 agy (claims axis): the comment above says the MEASUREMENT swept eight spellings; this
|
|
1108
|
+
# regex carries FOUR alternatives (①영혼 · a `soul:` key · 성공[ ]?정의 · 절대 안 함). Both are
|
|
1109
|
+
# true and they are different instruments — but writing only the first invites reading the
|
|
1110
|
+
# second's coverage off it ([[feedback_rule_misdescribes_its_own_machine]]). The four are the
|
|
1111
|
+
# ones the corpus actually uses as a FIELD; the other four measured spellings appeared only
|
|
1112
|
+
# inside prose, which this leg deliberately excludes.
|
|
1113
|
+
# 🟥 The soul detector must NOT read the `soul-check:` line itself. codex's alignment fix widened
|
|
1114
|
+
# the pattern and G8 flipped to PASS: `soul-check: reflected(…성공정의…)` matched as its own
|
|
1115
|
+
# evidence — self-referential FP ([[feedback_lane_runner_self_referential_fp]]). The claim can
|
|
1116
|
+
# never be its own ground. Same reason comment/prose lines are excluded: a marker that merely
|
|
1117
|
+
# SAYS 성공 정의 in narration has not declared one.
|
|
1118
|
+
# 🟥 A DECLARATION, not a MENTION. First real use (isolated clone, 2026-08-21) broke this on
|
|
1119
|
+
# attempt #1: this very delta's marker says «change: 마커의 ①영혼 슬롯에 소비처가 0이던 것을…»
|
|
1120
|
+
# in a prose FIELD VALUE, and the leg reported "①영혼 present: 10 words" for a marker that
|
|
1121
|
+
# declares nothing. Excluding `#` and `soul-check:` was not enough — any field whose value
|
|
1122
|
+
# discusses the axis satisfies a substring test, and markers ABOUT this axis always will.
|
|
1123
|
+
# 39 lanes, codex (diff axis) and agy (claims axis) all missed it; running it once did not
|
|
1124
|
+
# ([[feedback_adversarial_review_not_substitute_for_first_use]]).
|
|
1125
|
+
# So the token must sit at LINE START as the key (a leading markdown `#` is allowed — the real
|
|
1126
|
+
# corpus writes «## ①영혼 — 설계 전에 쓴 …» as a heading, and P7 flipped to BLOCK when the first
|
|
1127
|
+
# version of this regex forbade it. The known-pair caught that in one run).
|
|
1128
|
+
# Prose inside a field VALUE can no longer vote.
|
|
1129
|
+
_soul_src=$(grep -E '^[[:space:]]*#*[[:space:]]*(①영혼|soul|절대 안 함|성공 ?정의)[[:space:]]*[:—-]' "$m" 2>/dev/null \
|
|
1130
|
+
| grep -vE '^[[:space:]]*soul-check[[:space:]]*:')
|
|
1131
|
+
n=$(printf '%s\n' "$_soul_src" | grep -cE '①영혼|^[[:space:]]*soul:|성공 ?정의|절대 안 함'); n=${n:-0}
|
|
1132
|
+
if [ "$n" -eq 0 ]; then
|
|
1133
|
+
echo " ❌ FAIL — marker carries no ①영혼 line (성공 정의 / 절대 안 함)."
|
|
1134
|
+
echo " CLAUDE.md §자기 대조 requires it on every marker. Measured 2026-08-21: 37 of 98"
|
|
1135
|
+
echo " post-grace markers had none — including panel-reviewed ones with every other"
|
|
1136
|
+
echo " field filled. Nothing read the field, so nothing noticed."
|
|
1137
|
+
echo " Write, before the design: 성공 정의 = «…». 절대 안 함 = «…»."
|
|
1138
|
+
echo " 🟥 This checks PRESENCE, not provenance. «Was it written first» stays unverifiable"
|
|
1139
|
+
echo " and unchecked — see the ①영혼 comment below. Presence is not that question."
|
|
1140
|
+
return 1
|
|
1141
|
+
fi
|
|
1142
|
+
# Non-vacuity — counted in WORDS, not characters (Korean corpus; ${#} is locale-dependent).
|
|
1143
|
+
body=$(awk '/①영혼|^[[:space:]]*soul:|성공 ?정의|절대 안 함/{if (!f) f=1}
|
|
1144
|
+
f && /^[[:space:]]*$/{exit}
|
|
1145
|
+
f && seen && /^[[:space:]]*[A-Za-z][A-Za-z0-9_-]*:/{exit}
|
|
1146
|
+
f{print; seen=1}' <<< "$_soul_src")
|
|
1147
|
+
words=$(printf '%s' "$body" | wc -w | tr -d ' '); words=${words:-0}
|
|
1148
|
+
# 🟥 A DECLARED ABSENCE IS A VALUE, not a gap. CLAUDE.md §자기 대조 explicitly permits
|
|
1149
|
+
# «없으면 `없음`», and the first version of this leg BLOCKED a bare `soul: 없음` (2 words) while
|
|
1150
|
+
# PASSING a 33-word paragraph whose content was «I did not write one». That punishes the honest
|
|
1151
|
+
# form and rewards padding — [[feedback_unreachable_done_when_trains_evasion]] exactly. Found by
|
|
1152
|
+
# a peer session's first real use of this gate (forge-harness-59, 2026-08-21); self-detection 0,
|
|
1153
|
+
# and 41 lanes + two cross-family panels had all passed.
|
|
1154
|
+
# Same shape as the sibling axes' `UNKNOWN`: recorded, not silent, and never free of a record.
|
|
1155
|
+
# 🟥 `$body` — this leg's own local. The first version read `$soul_body`, which is the OTHER
|
|
1156
|
+
# leg's variable name; unset here, so the case never matched and the fix was inert while the
|
|
1157
|
+
# lane suite stayed green ([[feedback_unedited_parts_of_my_own_file]] · half-fix).
|
|
1158
|
+
_sbare=$(printf '%s' "$body" | sed -E 's/^[[:space:]]*#*[[:space:]]*(①영혼|soul|절대 안 함|성공 ?정의)[[:space:]]*[:—-]*//' | tr -d '[:space:].·]')
|
|
1159
|
+
case "$_sbare" in
|
|
1160
|
+
없음|없음—|none|None|NONE|n/a|N/A|-|해당없음)
|
|
1161
|
+
echo " ⚠️ ①영혼: 없음 — declared absent. Recorded, not silent."
|
|
1162
|
+
echo " 🟥 A marker that declares no success definition cannot carry"
|
|
1163
|
+
echo " soul-check: reflected(...) — there is nothing to reflect against."
|
|
1164
|
+
return 0 ;;
|
|
1165
|
+
esac
|
|
1166
|
+
if [ "$words" -lt 6 ]; then
|
|
1167
|
+
echo " ❌ FAIL — ①영혼 line is vacuous ($words word(s)). Name the success definition AND"
|
|
1168
|
+
echo " the 절대 안 함. A one-word placeholder trains the ritual this field exists to avoid."
|
|
1169
|
+
return 1
|
|
1170
|
+
fi
|
|
1171
|
+
echo " ✅ ①영혼 present: $words word(s)"
|
|
1172
|
+
return 0
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
validate_soul_check_leg() { # $1 = marker path
|
|
1176
|
+
# ── ⓒ 격리 그라운딩 — 사전 선언으로 확장 (2026-08-21) ─────────────────────────
|
|
1177
|
+
# 🟥 NOT a seventh axis. Classified against `fh_three_layer_canon.md §1-a-2`, whose
|
|
1178
|
+
# discriminator is explicit: «축은 «얼마나 적대적인가»로 갈리지 않는다. «무엇을 받았는가»로
|
|
1179
|
+
# 갈린다.» ⓒ 격리 그라운딩 receives «저자가 쓴 문장 + 지금의 트리» — which is, character for
|
|
1180
|
+
# character, what this leg receives (①영혼 is 저자가 쓴 문장; the delta is 지금의 트리).
|
|
1181
|
+
# The tense differs (사전 선언 vs 사후 주장) and tense is a posture, not an axis, exactly as
|
|
1182
|
+
# that section says adversarialness is («적대성은 자세지 축이 아니다»).
|
|
1183
|
+
# Precedent, in the canon itself: the author attributed 5 findings to ⓓ and two independent
|
|
1184
|
+
# cross-family classifiers reclassified 3 of them — «그 셋은 축이 필요했던 게 아니라 다른 축이
|
|
1185
|
+
# 놓친 것이다». Minting ⓖ would have been that mistake a second time.
|
|
1186
|
+
# ⇒ ⓒ's 「받는 것」 widens to «저자가 쓴 문장(사후 주장 및 **사전 선언**)», and `soul-check:`
|
|
1187
|
+
# is ⓒ's own field in the same shape `standpoint:` is ⓑ's. The six axes stay six.
|
|
1188
|
+
# WHY: CLAUDE.md §자기 대조 has required an `①영혼` line (성공 정의 / 절대 안 함) on every
|
|
1189
|
+
# marker since 2026-08-09, and it is written 4/4 on the real corpus. NOTHING has ever read it
|
|
1190
|
+
# back. Measured this day with a control: `crossfamily` = 21 hits in this hook, `①영혼`/`soul`
|
|
1191
|
+
# = 0 lines of consuming code anywhere in templates/ or scripts/; `fh_4axis_gate.md` does not
|
|
1192
|
+
# even name the field. That is the half-externalization shape — a slot with zero consumers,
|
|
1193
|
+
# which always reports "done" because presence is doing the judging.
|
|
1194
|
+
#
|
|
1195
|
+
# 🟥 The reason this hook gave for NOT checking the field answers a DIFFERENT question. Line
|
|
1196
|
+
# ~1058 declines it because «did you write it BEFORE designing» is unverifiable from a file —
|
|
1197
|
+
# true, and correctly excluded. But «was it then SATISFIED» is a separate question that was
|
|
1198
|
+
# never asked, and it rode out on the first one's ticket.
|
|
1199
|
+
#
|
|
1200
|
+
# SCOPE — channel, not judgment (CLAUDE.md §Mechanization Boundary). This lane asserts
|
|
1201
|
+
# properties of the RECORD: single · closed enum · non-vacuous · not wearing another axis's
|
|
1202
|
+
# tokens · **and consistent with the ①영혼 line in the same file**. It never asserts the
|
|
1203
|
+
# read-back reached the right conclusion. A fabricated `reflected(...)` with a fluent reason
|
|
1204
|
+
# PASSES — that hole is §4-b's (cross-family reads the marker), not this lane's, and it is
|
|
1205
|
+
# named here rather than papered over.
|
|
1206
|
+
local m="$1" line val reason n soul_n soul_body
|
|
1207
|
+
n=$(grep -cE '^[[:space:]]*soul-check:' "$m" 2>/dev/null); n=${n:-0}
|
|
1208
|
+
if [ "$n" -eq 0 ]; then
|
|
1209
|
+
# 🟥 A near-miss key reads as "absent" and takes the grace exit — silent, and the author
|
|
1210
|
+
# believes they recorded it. Absent is legal; MISSPELLED is not.
|
|
1211
|
+
if grep -qE '^[[:space:]]*(soul[ _-]?check[[:space:]]*:|soulcheck:|soul-checks:)' "$m" 2>/dev/null \
|
|
1212
|
+
&& ! grep -qE '^[[:space:]]*soul-check:' "$m" 2>/dev/null; then
|
|
1213
|
+
echo " ❌ FAIL — a line looks like a soul-check key but is not exactly 'soul-check:'."
|
|
1214
|
+
grep -nE '^[[:space:]]*(soul[ _-]?check[[:space:]]*:|soulcheck:|soul-checks:)' "$m" | sed 's/^/ /'
|
|
1215
|
+
return 1
|
|
1216
|
+
fi
|
|
1217
|
+
return 0 # genuinely absent — adoption is incremental, same as thirdparty:
|
|
1218
|
+
fi
|
|
1219
|
+
if [ "$n" -gt 1 ]; then
|
|
1220
|
+
echo " ❌ FAIL — marker carries MORE THAN ONE 'soul-check:' line."
|
|
1221
|
+
grep -nE '^[[:space:]]*soul-check:' "$m" | sed 's/^/ /'
|
|
1222
|
+
echo " Readers take the first, so an appended correction is silently shadowed."
|
|
1223
|
+
return 1
|
|
1224
|
+
fi
|
|
1225
|
+
line=$(grep -m1 -E '^[[:space:]]*soul-check:' "$m" | sed -E 's/^[[:space:]]*soul-check:[[:space:]]*//')
|
|
1226
|
+
val=$(printf '%s' "$line" | sed -E 's/\(.*$//' | sed -E 's/[[:space:]]*(—|--).*$//' | sed -E 's/[[:space:]]+$//')
|
|
1227
|
+
|
|
1228
|
+
# Cross-axis contamination — the other three axes ask different questions.
|
|
1229
|
+
case "$val" in
|
|
1230
|
+
tier1|tier1b|tier2|tier2b|tier3|panel|declined|checked|none-found|peer-review|\
|
|
1231
|
+
DEGRADED_SINGLE_FAMILY|DEGRADED_PANEL_UNUSED|DEGRADED_NO_TARGET_ACCESS|DEGRADED_NO_ACCESS)
|
|
1232
|
+
echo " ❌ FAIL — '$val' belongs to another axis, not soul-check:."
|
|
1233
|
+
echo " standpoint = whose repo was ground truth · crossfamily = which model family reviewed"
|
|
1234
|
+
echo " thirdparty = was this already done outside · soul-check = was THIS marker's own"
|
|
1235
|
+
echo " ①영혼 (성공 정의 / 절대 안 함) read back against the delta."
|
|
1236
|
+
return 1 ;;
|
|
1237
|
+
esac
|
|
1238
|
+
|
|
1239
|
+
# Is there an ①영혼 line in this same marker, and does it carry content?
|
|
1240
|
+
# 🟥 The soul detector must NOT read the `soul-check:` line itself. codex's alignment fix widened
|
|
1241
|
+
# the pattern and G8 flipped to PASS: `soul-check: reflected(…성공정의…)` matched as its own
|
|
1242
|
+
# evidence — self-referential FP ([[feedback_lane_runner_self_referential_fp]]). The claim can
|
|
1243
|
+
# never be its own ground. Same reason comment/prose lines are excluded: a marker that merely
|
|
1244
|
+
# SAYS 성공 정의 in narration has not declared one.
|
|
1245
|
+
# 🟥 A DECLARATION, not a MENTION. First real use (isolated clone, 2026-08-21) broke this on
|
|
1246
|
+
# attempt #1: this very delta's marker says «change: 마커의 ①영혼 슬롯에 소비처가 0이던 것을…»
|
|
1247
|
+
# in a prose FIELD VALUE, and the leg reported "①영혼 present: 10 words" for a marker that
|
|
1248
|
+
# declares nothing. Excluding `#` and `soul-check:` was not enough — any field whose value
|
|
1249
|
+
# discusses the axis satisfies a substring test, and markers ABOUT this axis always will.
|
|
1250
|
+
# 39 lanes, codex (diff axis) and agy (claims axis) all missed it; running it once did not
|
|
1251
|
+
# ([[feedback_adversarial_review_not_substitute_for_first_use]]).
|
|
1252
|
+
# So the token must sit at LINE START as the key (a leading markdown `#` is allowed — the real
|
|
1253
|
+
# corpus writes «## ①영혼 — 설계 전에 쓴 …» as a heading, and P7 flipped to BLOCK when the first
|
|
1254
|
+
# version of this regex forbade it. The known-pair caught that in one run).
|
|
1255
|
+
# Prose inside a field VALUE can no longer vote.
|
|
1256
|
+
_soul_src=$(grep -E '^[[:space:]]*#*[[:space:]]*(①영혼|soul|절대 안 함|성공 ?정의)[[:space:]]*[:—-]' "$m" 2>/dev/null \
|
|
1257
|
+
| grep -vE '^[[:space:]]*soul-check[[:space:]]*:')
|
|
1258
|
+
soul_n=$(printf '%s\n' "$_soul_src" | grep -cE '①영혼|^[[:space:]]*soul:|성공 ?정의|절대 안 함'); soul_n=${soul_n:-0}
|
|
1259
|
+
# Body = the ①영혼 block only: stop at a blank line OR at the next ASCII field key. An earlier
|
|
1260
|
+
# draft ran the range to EOF, so a marker reading «①영혼: 없음» followed by any other field was
|
|
1261
|
+
# scored as having content — the fixture G9 caught it.
|
|
1262
|
+
# 🟥 Counted in WORDS, not characters. `${#var}` on Korean text is bytes-or-chars depending on
|
|
1263
|
+
# locale, and this corpus is Korean — a char threshold silently changes meaning per machine
|
|
1264
|
+
# ⚠️ Honest caveat, measured: `wc -w` is NOT a human word count on Korean either («①영혼: 완주»
|
|
1265
|
+
# reports 3, not 2). It is stable per-machine and monotonic, which is all a floor needs — but
|
|
1266
|
+
# every threshold here was calibrated by RUNNING it, never by reasoning about word counts.
|
|
1267
|
+
# ([[feedback_instrument_vs_target_and_budget]], 단위 혼동). Word count is locale-independent.
|
|
1268
|
+
soul_body=$(awk '/①영혼|^[[:space:]]*soul:|성공 ?정의|절대 안 함/{if (!f) f=1}
|
|
1269
|
+
f && /^[[:space:]]*$/{exit}
|
|
1270
|
+
f && seen && /^[[:space:]]*[A-Za-z][A-Za-z0-9_-]*:/{exit}
|
|
1271
|
+
f{print; seen=1}' "$m" 2>/dev/null \
|
|
1272
|
+
| sed -E '1s/^[[:space:]]*(①영혼|soul)[^[:alnum:]«]*//')
|
|
1273
|
+
soul_words=$(printf '%s' "$soul_body" | wc -w | tr -d ' ')
|
|
1274
|
+
soul_words=${soul_words:-0}
|
|
1275
|
+
|
|
1276
|
+
case "$val" in
|
|
1277
|
+
UNKNOWN)
|
|
1278
|
+
echo " ⚠️ soul-check leg: UNKNOWN — 안 봤다. Recorded, not silent." ;;
|
|
1279
|
+
reflected|violated|DEGRADED_NOT_RUN|DEGRADED_NO_SOUL|not-applicable)
|
|
1280
|
+
case "$line" in *\(*) ;; *)
|
|
1281
|
+
echo " ❌ FAIL — '$val' without a (<grounds>) parenthetical."
|
|
1282
|
+
echo " A read-back that names neither WHAT read it nor WHAT came back is"
|
|
1283
|
+
echo " indistinguishable from not running it."
|
|
1284
|
+
return 1 ;;
|
|
1285
|
+
esac
|
|
1286
|
+
# 🟥 Measure INSIDE the parens. The old form took everything after the first `(`, so
|
|
1287
|
+
# `reflected() — <long tail>` passed on text that was never grounds (codex, diff axis).
|
|
1288
|
+
reason=$(printf '%s' "$line" | sed -E 's/^[^(]*\(//; s/\)[^)]*$//')
|
|
1289
|
+
# 🟥 agy (claims axis, 2026-08-21): this leg's own comment above condemns `${#var}` on
|
|
1290
|
+
# Korean text and then used `${#reason}` twelve lines later — [[feedback_citing_a_rule_is_not_obeying_it]],
|
|
1291
|
+
# verbatim. Inherited from the sibling legs, which does not excuse it here. Word count.
|
|
1292
|
+
_rwords=$(printf '%s' "$reason" | wc -w | tr -d ' '); _rwords=${_rwords:-0}
|
|
1293
|
+
if [ "$_rwords" -lt 3 ]; then
|
|
1294
|
+
echo " ❌ FAIL — '$val(...)' grounds are vacuous. Name what read the ①영혼 line back,"
|
|
1295
|
+
echo " against what, and what came back."
|
|
1296
|
+
return 1
|
|
1297
|
+
fi
|
|
1298
|
+
# ── the consistency check that makes this lane non-decorative ──────────────
|
|
1299
|
+
# Two fields in ONE record, mechanically decidable. Not a conclusion.
|
|
1300
|
+
case "$val" in
|
|
1301
|
+
reflected|violated)
|
|
1302
|
+
if [ "$soul_n" -eq 0 ] || [ "$soul_words" -lt 6 ]; then
|
|
1303
|
+
echo " ❌ FAIL — '$val' claims the ①영혼 line was read back, but this marker has"
|
|
1304
|
+
echo " no ①영혼 line with content (found: $soul_n line(s), $soul_words word(s))."
|
|
1305
|
+
echo " 🟥 Reflecting against an absent success-definition is the exact"
|
|
1306
|
+
echo " half-externalization this lane exists to catch. Write ①영혼 first,"
|
|
1307
|
+
echo " or record DEGRADED_NO_SOUL(<why>)."
|
|
1308
|
+
return 1
|
|
1309
|
+
fi ;;
|
|
1310
|
+
DEGRADED_NO_SOUL)
|
|
1311
|
+
if [ "$soul_n" -gt 0 ] && [ "$soul_words" -ge 6 ]; then
|
|
1312
|
+
echo " ❌ FAIL — 'DEGRADED_NO_SOUL' says there is nothing to reflect against, but"
|
|
1313
|
+
echo " this marker DOES carry an ①영혼 line with content ($soul_n line(s))."
|
|
1314
|
+
echo " A recorded absence contradicted by the same record is worse than silence."
|
|
1315
|
+
return 1
|
|
1316
|
+
fi ;;
|
|
1317
|
+
esac
|
|
1318
|
+
echo " ✅ soul-check leg: $val" ;;
|
|
1319
|
+
*)
|
|
1320
|
+
echo " ❌ FAIL — '$val' is not a member of the soul-check: enum."
|
|
1321
|
+
echo " Closed enum: reflected(<what came back — 어긋남 여부. NOT just who reviewed:
|
|
1322
|
+
that is crossfamily's column, and duplicating it double-counts one event>) ·"
|
|
1323
|
+
echo " violated(<which half broke — 성공정의 미달 / 절대안함 위반 — and what was done>) ·"
|
|
1324
|
+
echo " DEGRADED_NOT_RUN(<why>) · DEGRADED_NO_SOUL(<why>) · UNKNOWN · not-applicable(<why>)"
|
|
1325
|
+
echo " 🟥 'violated' is a legal value ON PURPOSE. A recorded violation is worth more"
|
|
1326
|
+
echo " than a hidden one, and a lane that only admits success trains erasure."
|
|
1327
|
+
return 1 ;;
|
|
1328
|
+
esac
|
|
1329
|
+
return 0
|
|
1330
|
+
}
|
|
1331
|
+
|
|
974
1332
|
|
|
975
1333
|
|
|
976
1334
|
# ── 4축 자기 대조 — «어느 축을 돌렸고 어느 축을 안 돌렸나» 형식 검사 ────────────
|
|
@@ -1292,7 +1650,27 @@ if [ "$GATE_MODE" = "full" ]; then
|
|
|
1292
1650
|
MARKER="$MARKER_DIR/.axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker"
|
|
1293
1651
|
|
|
1294
1652
|
if [ -f "$MARKER" ]; then
|
|
1295
|
-
|
|
1653
|
+
# Wave 1-D defense leg — floor tiers only (sonnet-floor / below-floor), on ANY full-gate marker.
|
|
1654
|
+
# 🟥 It was first wired inside the LOAD-BEARING block below, which made the real condition
|
|
1655
|
+
# "load-bearing AND floor tier" while the skill documented "floor tiers only" — a SKILL.md
|
|
1656
|
+
# change at sonnet-floor would have passed with no defense line. Found by cross-family review
|
|
1657
|
+
# (codex/gpt-5.5, 2026-08-20); self-detection 0. The scope the doctrine states is the scope
|
|
1658
|
+
# the hook now has.
|
|
1659
|
+
_DEFENSE_OK=1
|
|
1660
|
+
if grep -qE '^[[:space:]]*floor-status:[[:space:]]*(sonnet-floor|below-floor)' "$MARKER"; then
|
|
1661
|
+
validate_defense_leg "$MARKER" || _DEFENSE_OK=0
|
|
1662
|
+
fi
|
|
1663
|
+
# ⓒ 사전 선언 (①영혼) — scope is EVERY marker (CLAUDE.md §자기 대조 mandates ①영혼 on all of them), so it
|
|
1664
|
+
# belongs HERE and not in the load-bearing block below.
|
|
1665
|
+
# 🟥 It was first wired there, exactly like `validate_defense_leg` above — the real condition
|
|
1666
|
+
# would have been "load-bearing AND has a marker" while the doctrine says every marker. The
|
|
1667
|
+
# note recording that precedent is ~8 lines above this one and was in the file at the time;
|
|
1668
|
+
# the same defect was made one day later anyway ([[feedback_unedited_parts_of_my_own_file]]).
|
|
1669
|
+
# Caught by the operator asking «배선 완료되었나?», not by a lane and not by cross-family.
|
|
1670
|
+
_SOUL_OK=1
|
|
1671
|
+
validate_soul_present_leg "$MARKER" || _SOUL_OK=0
|
|
1672
|
+
validate_soul_check_leg "$MARKER" || _SOUL_OK=0
|
|
1673
|
+
if [ "$_DEFENSE_OK" -eq 1 ] && [ "$_SOUL_OK" -eq 1 ] && validate_marker_floor "$MARKER" && validate_marker_axes_run "$MARKER"; then
|
|
1296
1674
|
echo " ✅ PASS (marker confirmed + floor/axes fields valid:"
|
|
1297
1675
|
echo " .axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker)"
|
|
1298
1676
|
else
|
|
@@ -1827,6 +2205,24 @@ PTR_EOF
|
|
|
1827
2205
|
fi
|
|
1828
2206
|
fi
|
|
1829
2207
|
|
|
2208
|
+
# ── 상주 유입 게이트 (mandatory-pass — blocks) ────────────────────────────────
|
|
2209
|
+
# 왜 여기 있나 (2026-08-20 실측): 감량은 유출이 아니라 **유입** 문제다.
|
|
2210
|
+
# CLAUDE.md 23,839(05-26) → 70,758(07-20 «11.9% 감량» 직후) → 144,138(08-20)
|
|
2211
|
+
# 07-20 이후 31일 +73,380 = 약 2,367 bytes/일. 같은 기간 어블레이션 CUT 판정 **0건**.
|
|
2212
|
+
# 최대 감량 후보 절(22k)조차 유입 9일치 ⇒ 나가는 문만 당기면 구조적으로 진다.
|
|
2213
|
+
# 검사하는 것은 **기록의 속성**이지 결론이 아니다(§Mechanization Boundary):
|
|
2214
|
+
# 「이 문단이 상주할 가치가 있나」를 판정하지 않고 「저자가 유형을 선언했나」만 본다.
|
|
2215
|
+
# Anchor: scripts/test_residency_admission_lanes.sh (레인 10 · 컨트롤 2 · 가드 7 전수 되돌림 격리)
|
|
2216
|
+
RESID="$REPO_ROOT/scripts/residency_admission_check.sh"
|
|
2217
|
+
if [ -f "$RESID" ]; then
|
|
2218
|
+
echo "[Residency] 상주 유입 게이트..."
|
|
2219
|
+
resid_out=$(bash "$RESID" "$REPO_ROOT" 2>&1); resid_rc=$?
|
|
2220
|
+
printf '%s\n' "$resid_out" | sed 's/^/ /'
|
|
2221
|
+
if [ "$resid_rc" -ne 0 ]; then FAILED=1; fi
|
|
2222
|
+
else
|
|
2223
|
+
echo " ⚠️ 상주 유입 게이트 미설치 (scripts/residency_admission_check.sh) — skipped, not passed"
|
|
2224
|
+
fi
|
|
2225
|
+
|
|
1830
2226
|
# ── Verdict ───────────────────────────────────────────────────────────────────
|
|
1831
2227
|
echo ""
|
|
1832
2228
|
echo "══════════════════════════════════════════════"
|
|
@@ -357,10 +357,25 @@ PPRANGES
|
|
|
357
357
|
[ "${PUBLIC_SURFACE_OK:-0}" = "1" ] || exit 1
|
|
358
358
|
fi
|
|
359
359
|
if [ "${_pp_count:-0}" -gt 0 ]; then
|
|
360
|
-
|
|
360
|
+
# 🟥 rc 를 «비영» 으로 뭉개지 마라 (cross-family, 2026-08-21): 1=유출 · 3=계기 사망.
|
|
361
|
+
# 합치면 아래 PUBLIC_SURFACE_OK 가 **계기 사망까지 승인**한다 — push 는 비가역이다.
|
|
362
|
+
_pp_rc=0
|
|
363
|
+
printf '%s\n' "$_pp_added" | awk '
|
|
361
364
|
/^\+\+\+ b\// { f = substr($0, 7); next }
|
|
362
365
|
/^\+/ { print f "\t" substr($0, 2) }
|
|
363
|
-
' | psa_scan_tagged
|
|
366
|
+
' | psa_scan_tagged || _pp_rc=$?
|
|
367
|
+
if [ "$_pp_rc" = "3" ]; then
|
|
368
|
+
echo ""
|
|
369
|
+
echo "══════════════════════════════════════════════"
|
|
370
|
+
echo " ⛔ FH Pre-Publish Gate (pre-push) — INSTRUMENT DEAD, scan did NOT run"
|
|
371
|
+
echo "══════════════════════════════════════════════"
|
|
372
|
+
echo " rc=3 = NOT SCANNED. That is not «no token found» — nothing was measured."
|
|
373
|
+
echo " PUBLIC_SURFACE_OK does NOT cover this: an override approves a KNOWN mention,"
|
|
374
|
+
echo " never an unmeasured surface. Fix the scanner first (run under bash; psa_load)."
|
|
375
|
+
echo "══════════════════════════════════════════════"
|
|
376
|
+
exit 1
|
|
377
|
+
fi
|
|
378
|
+
if [ "$_pp_rc" = "0" ]; then
|
|
364
379
|
echo " ✅ FH Pre-Publish: no operator-private token in the TEXT added by ${_pp_count} commit(s)"
|
|
365
380
|
echo " (not covered: annotated-tag messages · binary blobs — named residuals)"
|
|
366
381
|
else
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": [
|
|
3
|
+
"prior-art 프롬프트 훅 스니펫 — install-wizard 가 사용자 로컬 .claude/settings.json 에 머지하는",
|
|
4
|
+
"tracked SOURCE 다. 이 레포의 .claude/settings*.json 은 전부 gitignored 라 **훅 등록 자체를",
|
|
5
|
+
"tracked 로 둘 수 없다.** 머지 단계가 없으면 훅은 돌지 않는다 —",
|
|
6
|
+
"**스크립트를 출하하는 것과 배선하는 것은 같은 명제가 아니다.**",
|
|
7
|
+
"",
|
|
8
|
+
"🟥 왜 이 래퍼가 뒤늦게 생겼나 (2026-08-22 실측, 컨트롤 동반):",
|
|
9
|
+
" 이 파일은 형제 스니펫 넷과 달리 `project_settings_json` 래퍼 **없이** 출하되고 있었다.",
|
|
10
|
+
" install-wizard 의 실제 머지 코드(install-wizard/SKILL_detail.md:642-645)는 그 키가 없으면",
|
|
11
|
+
" print(\"SKIP (no project_settings_json)\", ...); continue",
|
|
12
|
+
" 로 **조용히 건너뛴다.** 즉 이 훅은 「아직 안 돌렸다」가 아니라",
|
|
13
|
+
" **「install-wizard 를 돌려도 항상 스킵돼서 한 번도 등록된 적이 없다」** 였다.",
|
|
14
|
+
" 전수 실측: Compaction·FieldCanon·PreToolUse·SessionStart = 래퍼 O · PriorArt = 혼자 부재.",
|
|
15
|
+
" 머지 코드를 그대로 복사해 실행한 수리 전/후 대조로 SKIP→등록 전이를 확인했다.",
|
|
16
|
+
" ⇒ built_but_not_wired 보다 나쁘다. **설치기가 자기 출하물을 조용히 건너뛰는** 형태다.",
|
|
17
|
+
" 회귀 앵커: scripts/test_wizard_snippet_merge_lanes.sh 의 전수 래퍼 검사(이 수리와 같이 들어갔다).",
|
|
18
|
+
"",
|
|
19
|
+
"무엇을 하나: 새 메커니즘을 짓기 직전에 «책장 먼저, 없으면 도서관» 을 띄운다.",
|
|
20
|
+
"정체성 ④(프런티어 답습)의 **빌드 시점 격발 표면**이다.",
|
|
21
|
+
"🟥 명시된 잔여: matcher 가 `Write|Bash` 라도 **auto-mode 세션이 heredoc 으로 파일을 만드는",
|
|
22
|
+
"경로는 Bash 로 잡히지만**, 그 안에서 실제로 새 메커니즘을 짓는지까지는 훅이 모른다.",
|
|
23
|
+
"표면화까지가 훅의 몫이고 판정은 사람/세션이 한다 — 판정하는 훅을 만들면",
|
|
24
|
+
"§Mechanization Boundary 가 금하는 «결론을 단언하는 체크»가 된다.",
|
|
25
|
+
"",
|
|
26
|
+
"PRIVATE PATH 없음 → .claude/settings.json(프로젝트 로컬)에 속한다.",
|
|
27
|
+
"머지하되 덮어쓰지 마라: 기존 PreToolUse 엔트리를 보존하고 스크립트 이름을 키로 FH 것만 교체."
|
|
28
|
+
],
|
|
29
|
+
"project_settings_json": {
|
|
30
|
+
"hooks": {
|
|
31
|
+
"PreToolUse": [
|
|
32
|
+
{
|
|
33
|
+
"matcher": "Write|Bash",
|
|
34
|
+
"hooks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/scripts/prior_art_prompt.sh\""
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|