@chrono-meta/fh-gate 3.2.0 → 3.5.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/registry/agent_cards.json +1 -1
- package/.claude/rules/fh_4axis_gate.md +25 -0
- package/.claude-plugin/marketplace.json +9 -3
- package/AGENTS.md +2 -2
- package/CATALOG.md +4 -4
- package/CHEATSHEET.md +1 -1
- package/CLAUDE.md +4 -4
- package/README.md +2 -2
- package/docs/OUTPUT_EVIDENCE.md +1 -1
- package/docs/STANDARDS_ALIGNMENT.md +1 -1
- package/docs/codex-compat.md +2 -2
- package/knowledge/shared/harness-core/agents_md_runtime_details.md +2 -2
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +30 -3
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +1 -1
- package/knowledge/shared/harness-core/skill_quality_rubric.md +1 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +120 -4
- package/knowledge/shared/rules/modes_and_value.md +2 -2
- package/package.json +9 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +2 -2
- package/plugins/fh-commons/README.md +38 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +117 -0
- package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +40 -0
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +1 -1
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +43 -5
- package/plugins/fh-meta/skills/{hub-cc-pr-reviewer → harness-pr-reviewer}/SKILL.md +75 -3
- package/plugins/fh-meta/skills/{hub-cc-pr-reviewer → harness-pr-reviewer}/SKILL_detail.md +2 -2
- package/plugins/fh-meta/skills/harvest-loop/SKILL_detail.md +2 -2
- package/plugins/fh-meta/skills/install-doctor/SKILL.md +1 -1
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +1 -1
- package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +1 -1
- package/plugins/fh-meta/skills/pipeline-conductor/SKILL.md +1 -1
- package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +1 -1
- package/plugins/fh-meta/skills/sim-conductor/SKILL.md +1 -1
- package/plugins/fh-preprep/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-preprep/README.md +24 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/SKILL.md +75 -3
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/mk_slide_fixtures.py +152 -1
- package/plugins/fh-preprep/skills/preprep/lane_attr_consistency.py +370 -0
- package/plugins/fh-preprep/skills/preprep/lane_geometry.py +339 -0
- package/plugins/fh-preprep/skills/preprep/lane_screen_parity.py +232 -0
- package/plugins/fh-preprep/skills/preprep/oox.py +305 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/preprep.py +52 -4
- package/plugins/fh-preprep/skills/preprep/safe_install.py +369 -0
- package/plugins/{fh-commons → fh-preprep}/skills/preprep/surfaces.example.yaml +29 -0
- package/plugins/fh-preprep/skills/preprep/test_preprep_lanes_rp.py +1417 -0
- package/plugins/fh-preprep/skills/preprep/usage_ledger.py +191 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +1 -1
- package/scripts/fh-run.sh +13 -0
- package/scripts/field_canon_preload.sh +1 -1
- package/scripts/finding_fleet.sh +391 -13
- package/scripts/finding_pipeline.sh +370 -11
- package/scripts/finding_verifier.sh +31 -2
- package/scripts/finding_verify.py +198 -13
- package/scripts/frontier_digest_autopilot.sh +3 -3
- package/scripts/gate_shape_scan.sh +16 -2
- package/scripts/selfcheck.sh +12 -8
- package/scripts/test_fh_run_resolver_lanes.sh +116 -0
- package/scripts/test_files_manifest_shipping_lanes.sh +31 -0
- package/scripts/test_finding_pipeline_lanes.sh +1556 -4
- package/scripts/test_gate_shape_scan_lanes.sh +11 -0
- package/scripts/test_marker_crossfamily_lanes.sh +75 -3
- package/scripts/test_marker_standpoint_lanes.sh +31 -6
- package/scripts/test_preprep_adjacent_dup_lanes.sh +1 -1
- package/scripts/test_preprep_diagram_lanes.sh +1 -1
- package/scripts/test_preprep_drift_anchor.sh +27 -12
- package/scripts/test_preprep_drift_anchor_lanes.sh +4 -2
- package/scripts/test_preprep_font_lanes.sh +7 -7
- package/scripts/test_preprep_progression_lanes.sh +1 -1
- package/scripts/test_preprep_promise_lanes.sh +1 -1
- package/scripts/test_preprep_retired_lanes.sh +45 -2
- package/scripts/test_preprep_slide_refs_lanes.sh +5 -5
- package/scripts/test_usage_ledger_lanes.sh +243 -0
- package/scripts/usage_report.py +211 -0
- package/templates/.git-hooks/pre-commit +152 -6
- package/templates/local_fh_context.md +1 -1
- package/templates/regression_guard.sh +1 -1
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +0 -181
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +0 -184
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/README.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/canon_terms.example.yaml +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/diagram_from_json.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/adjdup_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/adjdup_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/font_revert_probe.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_negative.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_positive.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/interslide_deps.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/jargon_terms.example.yaml +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_adjacent_dup.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_diagram.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_font.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_progression.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_promise.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_slide_refs.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/lane_slide_relations.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/ooxml/c1_baseline.txt +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/ooxml/gate.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/preprep_wire.py +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/presentation_checklist.md +0 -0
- /package/plugins/{fh-commons → fh-preprep}/skills/preprep/test_lane_font.py +0 -0
|
@@ -805,8 +805,25 @@ validate_defense_leg() { # $1 = marker path — fires ONLY at the floor tiers (s
|
|
|
805
805
|
# would block every in-flight branch and teach `--no-verify`, which disarms the Destructive-Op
|
|
806
806
|
# gate sharing this hook.
|
|
807
807
|
RESIDENCY_TOKEN_GRACE_DATE="2026-09-05"
|
|
808
|
+
# 🟥 `evidence=` — panel(...) 이 «무엇을 받았는지» 를 적게 한다. 2026-09-12 신설, 근거는 외부 실측이다:
|
|
809
|
+
# arXiv:2609.10969 (48 템플릿 · 2,880 시나리오 · **고정예산 2×2**) 에서 «같은 증거를 읽는 교차-모델
|
|
810
|
+
# 투표» 는 위험 제안의 **62.9 %** 를 승인하고 «독립 출처» 는 **22.9 %** — 출처 효과 40.9 %p 대
|
|
811
|
+
# 모델 다양성 11.3 %p, **3.6 배**. 즉 이 훅이 이미 하드 차단하는 축(계열)이 약한 쪽이고,
|
|
812
|
+
# `panel(codex, gemini)` 가 **같은 diff 를 읽은 패널**이면 그것은 62.9 % 팔인데 기록은 강한 값으로
|
|
813
|
+
# 남는다. 그 구분을 닫는 토큰이다. 🟥 새 필드도 새 게이트도 만들지 않는다 — residency= 와 같은
|
|
814
|
+
# 자리(crossfamily 의 grounds) 에 같은 문법으로 얹는다.
|
|
815
|
+
EVIDENCE_TOKEN_GRACE_DATE="2026-09-12"
|
|
816
|
+
# 🟥 `standpoint:` tier2+ grounds — 2026-09-12 부로 **advisory 에서 차단으로**. 근거가 바뀌었다:
|
|
817
|
+
# 이 carve-out 은 증거가 FH 자기 관찰 n=4+8 뿐일 때 합리적이었다. arXiv:2609.10969 이 같은 비교를
|
|
818
|
+
# n=2,880 에서 **40.9 %p 대 11.3 %p** 로 재면서, 「강한 축의 grounds 를 advisory 로 두고 약한 축을
|
|
819
|
+
# 하드 차단한다」가 더는 균형이 아니게 됐다. §Mechanization Boundary 검사는 여전히 통과한다 —
|
|
820
|
+
# «저자가 실행한 명령과 본 출력을 명명했는가» 는 **기록의 속성**(존재·비공허·귀속가능)이지 결론이 아니다.
|
|
821
|
+
# 🟥 정직한 범위: 이 게이트는 **기록의 형태**만 막는다. 그 실행이 진짜였는지는 여전히 안 본다 —
|
|
822
|
+
# 게임 가능성은 §Mechanization Boundary 가 사람에게 남긴 의도된 잔여이고 이 변경이 닫는 구멍이 아니다.
|
|
823
|
+
STANDPOINT_GROUNDS_GRACE_DATE="2026-09-12"
|
|
808
824
|
validate_crossfamily_leg() { # $1 = marker path
|
|
809
825
|
local m="$1" line val reason fams mdate _res_active _res_tokens _res_ntok _res_wellformed _res_kind
|
|
826
|
+
local _ev_active _ev_tokens _ev_ntok _ev_full _ev_nfull _ev_wellformed _ev_kind _ev_body _ev_grace _ev_strip
|
|
810
827
|
if [ ! -f "$m" ] || ! grep -qE '^[[:space:]]*crossfamily:[[:space:]]*[^[:space:]]' "$m"; then
|
|
811
828
|
echo " ❌ FAIL — load-bearing file staged with no 'crossfamily:' line in the Axes 2-3 marker."
|
|
812
829
|
echo " Verdict/gate/irreversible-surface code shares the author's blind spot with a"
|
|
@@ -869,6 +886,31 @@ validate_crossfamily_leg() { # $1 = marker path
|
|
|
869
886
|
_res_kind=""
|
|
870
887
|
[ "$_res_wellformed" -eq 1 ] && _res_kind=$(printf '%s' "$_res_full" | sed -E 's/^residency=([A-Za-z_]*)\(.*$/\1/')
|
|
871
888
|
|
|
889
|
+
# evidence= token — same grammar and same presence-first rule as residency= above. REQUIRED for
|
|
890
|
+
# panel(...) on markers dated >= EVIDENCE_TOKEN_GRACE_DATE; format-only-if-present elsewhere.
|
|
891
|
+
# 🟥 상수를 «먼저» 평가한다(cross-family agy 적발). 이걸 mdate 검사 뒤에 두면 두 결함이 같이 난다:
|
|
892
|
+
# ⓐ 미주입 시 `[ "$mdate" \< "" ]` 이 거짓이 되어 **grace 가 통째로 꺼지고** pre-grace 마커가
|
|
893
|
+
# 전부 막힌다(실측 확인) ⓑ mdate 가 비면 단축평가로 `:?` 가드가 아예 안 돈다.
|
|
894
|
+
_ev_grace="${EVIDENCE_TOKEN_GRACE_DATE:?EVIDENCE_TOKEN_GRACE_DATE unset — partial extraction of this hook? refusing to guess a grace date}"
|
|
895
|
+
_ev_active=1
|
|
896
|
+
if [ -n "$mdate" ] && [ "$mdate" \< "$_ev_grace" ]; then _ev_active=0; fi
|
|
897
|
+
_ev_tokens=$(printf '%s' "$reason" | grep -oE 'evidence=[^[:space:]]*' 2>/dev/null || true)
|
|
898
|
+
_ev_ntok=0
|
|
899
|
+
[ -n "$_ev_tokens" ] && _ev_ntok=$(printf '%s\n' "$_ev_tokens" | grep -c .)
|
|
900
|
+
# 🟥 `[^)]*` 는 중첩 괄호에서 안쪽 `)` 에 끊긴다 — `evidence=MIXED(codex(diff), gemini(repo))` 의
|
|
901
|
+
# 본문이 `codex(diff` 로 잘려 «공허» 판정을 받고 **정당한 기록이 하드 차단**됐다(실측).
|
|
902
|
+
# 한 겹 중첩을 허용한다(두 겹은 여전히 형식 위반 — 무한 중첩을 정규식으로 쫓지 않는다).
|
|
903
|
+
_ev_full=$(printf '%s' "$reason" | grep -oE 'evidence=(SHARED|INDEPENDENT|MIXED)\(([^()]|\([^()]*\))*\)' 2>/dev/null || true)
|
|
904
|
+
_ev_nfull=0
|
|
905
|
+
[ -n "$_ev_full" ] && _ev_nfull=$(printf '%s\n' "$_ev_full" | grep -c .)
|
|
906
|
+
_ev_wellformed=0
|
|
907
|
+
[ "$_ev_ntok" -eq 1 ] && [ "$_ev_nfull" -eq 1 ] && _ev_wellformed=1
|
|
908
|
+
_ev_kind=""; _ev_body=""
|
|
909
|
+
if [ "$_ev_wellformed" -eq 1 ]; then
|
|
910
|
+
_ev_kind=$(printf '%s' "$_ev_full" | sed -E 's/^evidence=([A-Z]*)\(.*$/\1/')
|
|
911
|
+
_ev_body=$(printf '%s' "$_ev_full" | sed -E 's/^evidence=[A-Z]*\(//; s/\)$//')
|
|
912
|
+
fi
|
|
913
|
+
|
|
872
914
|
case "$val" in
|
|
873
915
|
declined)
|
|
874
916
|
# `declined` means ONE thing: the OPERATOR declined sidecars (UAP), a chosen floor. It was
|
|
@@ -1022,6 +1064,81 @@ validate_crossfamily_leg() { # $1 = marker path
|
|
|
1022
1064
|
return 1
|
|
1023
1065
|
fi
|
|
1024
1066
|
fi
|
|
1067
|
+
# evidence= — WHAT the panel received. Same four checks in the same order as residency=
|
|
1068
|
+
# (duplicate → absent → shape → meaning). Why this exists at all: arXiv:2609.10969 measured
|
|
1069
|
+
# the two axes against each other at fixed call budget (48 templates, 2,880 scenarios) and a
|
|
1070
|
+
# cross-model vote over SHARED evidence approved 62.9% of unsafe proposals vs 22.9% with an
|
|
1071
|
+
# independent source — 40.9pp vs 11.3pp for model diversity. A `panel(codex, gemini)` that
|
|
1072
|
+
# read the same diff is that 62.9% arm while recording as this enum's STRONG value. The token
|
|
1073
|
+
# makes the distinction sayable; it does not make shared evidence illegal.
|
|
1074
|
+
# 🟥 grace 여부와 무관하게, 토큰이 «있으면» 형식은 본다 — residency 가 `DEGRADED_*` 에서 하는
|
|
1075
|
+
# 약속과 같은 모양이다. 이게 없으면 pre-grace 마커의 `evidence=PARTIAL(...)` 이 조용히
|
|
1076
|
+
# 통과한다(실측). 부재는 grace 가 면제하지만, **잘못 쓴 것은 면제하지 않는다.**
|
|
1077
|
+
if [ "$_ev_active" -eq 0 ] && [ "$_ev_ntok" -gt 0 ] && [ "$_ev_wellformed" -ne 1 ]; then
|
|
1078
|
+
echo " ❌ FAIL — 'evidence=' token present but malformed (pre-grace marker: the token is"
|
|
1079
|
+
echo " OPTIONAL here, but a token that IS written must be well-formed):"
|
|
1080
|
+
printf '%s\n' "$_ev_tokens" | sed 's/^/ /'
|
|
1081
|
+
echo " Closed set evidence=(SHARED|INDEPENDENT|MIXED)(...) with a closing paren."
|
|
1082
|
+
return 1
|
|
1083
|
+
fi
|
|
1084
|
+
if [ "$_ev_active" -eq 1 ]; then
|
|
1085
|
+
if [ "$_ev_ntok" -gt 1 ]; then
|
|
1086
|
+
echo " ❌ FAIL — grounds carries MORE THAN ONE 'evidence=' token."
|
|
1087
|
+
printf '%s\n' "$_ev_tokens" | sed 's/^/ /'
|
|
1088
|
+
echo " Same trap as two crossfamily: lines — the reader takes the first. Leave one."
|
|
1089
|
+
return 1
|
|
1090
|
+
fi
|
|
1091
|
+
if [ "$_ev_ntok" -eq 0 ]; then
|
|
1092
|
+
echo " ❌ FAIL — panel(...) with no 'evidence=' token in grounds."
|
|
1093
|
+
echo " State WHAT the panel received, because that axis carries ~3.6x the effect of"
|
|
1094
|
+
echo " family diversity (arXiv:2609.10969: shared evidence approves 62.9% of unsafe"
|
|
1095
|
+
echo " proposals vs 22.9% independent; 40.9pp vs 11.3pp). One of:"
|
|
1096
|
+
echo " evidence=SHARED(<what every family read — e.g. the same diff>)"
|
|
1097
|
+
echo " evidence=INDEPENDENT(<what each got separately — own checkout, own run>)"
|
|
1098
|
+
echo " evidence=MIXED(<which member got which>)"
|
|
1099
|
+
echo " e.g. crossfamily: panel($fams) — residency=CLEAN(files=3) · evidence=SHARED(same"
|
|
1100
|
+
echo " staged diff to both) · R1..R2, 4 findings"
|
|
1101
|
+
echo " 🟥 SHARED is a legal, common answer — it is not a failure. Recording it as if"
|
|
1102
|
+
echo " it were INDEPENDENT is the failure this token exists to stop."
|
|
1103
|
+
return 1
|
|
1104
|
+
fi
|
|
1105
|
+
if [ "$_ev_wellformed" -ne 1 ]; then
|
|
1106
|
+
echo " ❌ FAIL — 'evidence=' token present but not one well-formed"
|
|
1107
|
+
echo " evidence=(SHARED|INDEPENDENT|MIXED)(...) token:"
|
|
1108
|
+
printf '%s\n' "$_ev_tokens" | sed 's/^/ /'
|
|
1109
|
+
echo " Closed set with a closing paren — same shape as residency=, same reason: a"
|
|
1110
|
+
echo " malformed token must be malformed, never invisible."
|
|
1111
|
+
return 1
|
|
1112
|
+
fi
|
|
1113
|
+
# Meaning check, and it is deliberately only on SHARED/MIXED: those are the values whose
|
|
1114
|
+
# whole content is "which evidence did they actually share", so an empty body says nothing.
|
|
1115
|
+
# INDEPENDENT is self-describing; over-blocking it would train the override.
|
|
1116
|
+
# 🟥 본문 검사, 두 층 — cross-family agy 가 초판의 두 결함을 다 지목했다:
|
|
1117
|
+
# ⓐ **빈 본문과 자리표시자는 세 값 모두 차단**한다. 초판은 INDEPENDENT 를 통째로 면제해서
|
|
1118
|
+
# `evidence=INDEPENDENT()` 와 `evidence=INDEPENDENT(<what>)` 가 **통과했다**(실측 fail-open).
|
|
1119
|
+
# «자기서술적이라 본문 검사 면제» 는 «본문이 없어도 된다» 와 다른 말이었는데 초판이 접었다.
|
|
1120
|
+
# ⓑ 길이 바는 **문자 수**로 센다. `wc -w` 는 `same-diff` 도 `동일diff` 도 1 낱말로 세서
|
|
1121
|
+
# 정당한 한국어/하이픈 기록을 막았다(실측) — 한국어로 근거를 쓰는 이 레포에서 이건 상습 과차단이다.
|
|
1122
|
+
_ev_strip=$(printf '%s' "$_ev_body" | tr -d '[:space:]')
|
|
1123
|
+
if [ -z "$_ev_strip" ] \
|
|
1124
|
+
|| printf '%s' "$_ev_body" | grep -qE '^[[:space:]]*(TBD|tbd|N/?A|n/?a|없음|미정|-+|\?+|<[^>]*>)[[:space:]]*$'; then
|
|
1125
|
+
echo " ❌ FAIL — evidence=$_ev_kind(...) has an EMPTY or placeholder body: '$_ev_body'"
|
|
1126
|
+
echo " All three values need a body — 'self-describing' meant it is not length-checked,"
|
|
1127
|
+
echo " never that it can be blank. Name what each member actually received."
|
|
1128
|
+
return 1
|
|
1129
|
+
fi
|
|
1130
|
+
case "$_ev_kind" in
|
|
1131
|
+
SHARED|MIXED)
|
|
1132
|
+
# 문자 수 기준(공백 제외). 4자 미만이면 «diff» 같은 한 토큰이고, 그건 어느 산출물인지
|
|
1133
|
+
# 말하지 않는다. 🟥 낱말 수로 세지 않는다 — CJK 와 하이픈 복합어가 전부 1 낱말이 된다.
|
|
1134
|
+
if [ "$(printf '%s' "$_ev_strip" | wc -m | tr -d ' ')" -lt 6 ]; then
|
|
1135
|
+
echo " ❌ FAIL — evidence=$_ev_kind(...) body too thin to identify the artifact: '$_ev_body'"
|
|
1136
|
+
echo " Name it (\"same staged diff\", \"one findings.jsonl\", \"동일 프롬프트 파일\")."
|
|
1137
|
+
echo " A bare $_ev_kind(diff) records the weak arm as if it were documented."
|
|
1138
|
+
return 1
|
|
1139
|
+
fi ;;
|
|
1140
|
+
esac
|
|
1141
|
+
fi
|
|
1025
1142
|
echo " ✅ cross-family leg: $val${reason:+ — $(printf '%s' "$reason" | cut -c1-60)}" ;;
|
|
1026
1143
|
DEGRADED_SINGLE_FAMILY|DEGRADED_PANEL_UNUSED|UNKNOWN)
|
|
1027
1144
|
# A degrade is allowed; being silent about WHICH degrade, or about its grounds, is not.
|
|
@@ -1129,6 +1246,7 @@ validate_crossfamily_leg() { # $1 = marker path
|
|
|
1129
1246
|
# and 0 over-blocks. Anchor: scripts/test_marker_standpoint_lanes.sh
|
|
1130
1247
|
validate_standpoint_leg() { # $1 = marker path
|
|
1131
1248
|
local m="$1" line val reason n quoted=""
|
|
1249
|
+
local _sp_mdate _sp_block _sp_grace
|
|
1132
1250
|
n=$(grep -cE '^[[:space:]]*standpoint:' "$m" 2>/dev/null); n=${n:-0}
|
|
1133
1251
|
if [ "$n" -eq 0 ]; then
|
|
1134
1252
|
echo " ❌ FAIL — load-bearing file staged with no 'standpoint:' line in the Axes 2-3 marker."
|
|
@@ -1174,13 +1292,41 @@ validate_standpoint_leg() { # $1 = marker path
|
|
|
1174
1292
|
tier1b\(*\)|tier2\(*\)|tier2b\(*\)|tier3\(*\))
|
|
1175
1293
|
case "$val" in
|
|
1176
1294
|
tier2\(*\)|tier2b\(*\)|tier3\(*\))
|
|
1295
|
+
# 🟥 키워드에 «뒤따르는 공백» 을 박아두면 정당한 기록이 막힌다 — cross-family agy 적발,
|
|
1296
|
+
# 실측 확인: `cargo check` · `python scripts/eval.py` · `ran: ./ci.sh` 가 전부 BLOCK 이었다.
|
|
1297
|
+
# advisory 일 때는 경고 한 줄이었지만 **차단이 된 순간 이건 override 를 훈련시키는 결함**이다.
|
|
1298
|
+
# ⇒ ⓐ 경계를 «비-알파벳» 으로 바꾸고(콜론·세미콜론·줄끝 허용) ⓑ 흔한 러너를 넣고
|
|
1299
|
+
# ⓒ «출력» 의 형태(`30/30`, `rc=`, `./path`)도 실행의 증거로 받는다.
|
|
1300
|
+
# ⚠️ 방향: 이건 **느슨하게** 만드는 변경이다. 막고 싶은 것은 «명령도 출력도 안 적은 줄» 이고,
|
|
1301
|
+
# 길이 20 자 하한은 그대로 남는다.
|
|
1177
1302
|
if [ "${#reason}" -lt 20 ] || ! printf '%s' "$reason" \
|
|
1178
|
-
| grep -qiE 'ran
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1303
|
+
| grep -qiE '(^|[^[:alpha:]])(ran|run|runs|exec|invoke[ds]?|call|calls|called|exit|output|suite|test|tests|tested|bash|sh|zsh|grep|npm|pnpm|yarn|pytest|python|python3|cargo|make|go|gradle|mvn|dotnet|node|command)([^[:alpha:]]|$)|실행|돌렸|돌린|돌려|호출|출력|스위트|커맨드|rc=|[0-9]+/[0-9]+|\./'; then
|
|
1304
|
+
# Grace: markers dated before STANDPOINT_GROUNDS_GRACE_DATE keep the old advisory
|
|
1305
|
+
# behaviour (no retro-blocking), same pattern as the residency=/evidence= tokens.
|
|
1306
|
+
_sp_mdate=$(printf '%s' "$m" | sed -nE 's/.*_([0-9]{4}-[0-9]{2}-[0-9]{2})\.marker$/\1/p')
|
|
1307
|
+
_sp_block=1
|
|
1308
|
+
# Unreadable date → NOT exempt. The hook stamps TODAY into every marker filename it
|
|
1309
|
+
# creates, so an unparseable date is an unexpected shape, not evidence of age.
|
|
1310
|
+
# 🟥 상수를 «먼저» 평가한다 — 초판은 `[ -n "$_sp_mdate" ] && [ ... ${VAR:?} ]` 였고,
|
|
1311
|
+
# mdate 가 비면 단축평가로 `:?` 가드가 **아예 안 돌았다**(agy 적발). 가드가 있다고
|
|
1312
|
+
# 적어놓고 안 도는 것은 장식이다([[feedback_anchor_can_be_decorative]]).
|
|
1313
|
+
_sp_grace="${STANDPOINT_GROUNDS_GRACE_DATE:?STANDPOINT_GROUNDS_GRACE_DATE unset — partial extraction of this hook? refusing to guess a grace date}"
|
|
1314
|
+
if [ -n "$_sp_mdate" ] && [ "$_sp_mdate" \< "$_sp_grace" ]; then _sp_block=0; fi
|
|
1315
|
+
if [ "$_sp_block" -eq 1 ]; then
|
|
1316
|
+
echo " ❌ FAIL — standpoint leg: $val — the grounds do not NAME an execution."
|
|
1317
|
+
echo " ${val%%(*} asserts code RAN in the target. The discriminator is mechanical:"
|
|
1318
|
+
echo " name the command you ran and the output you saw. If you only read files,"
|
|
1319
|
+
echo " the honest rung is tier1b — weaker on purpose, so the owed arm stays visible."
|
|
1320
|
+
echo " standpoint: tier2(<harness>) — ran \`bash scripts/x.sh\` there, output: 30/30 PASS"
|
|
1321
|
+
echo " 🟥 BLOCKING since 2026-09-12 (was advisory). Why it changed:"
|
|
1322
|
+
echo " arXiv:2609.10969 measured evidence-source diversity at 40.9pp against 11.3pp"
|
|
1323
|
+
echo " for model diversity (n=2,880, fixed call budget) — this is the STRONGER axis,"
|
|
1324
|
+
echo " and leaving its grounds advisory while hard-blocking the weaker one was not a"
|
|
1325
|
+
echo " balance. Honest scope: this blocks on the RECORD's shape, never on whether the"
|
|
1326
|
+
echo " run was real."
|
|
1327
|
+
return 1
|
|
1328
|
+
fi
|
|
1329
|
+
echo " ⚠️ standpoint leg: $val — grounds name no execution (pre-$STANDPOINT_GROUNDS_GRACE_DATE marker, advisory)."
|
|
1184
1330
|
else
|
|
1185
1331
|
echo " ✅ standpoint leg: $val$quoted"
|
|
1186
1332
|
fi ;;
|
|
@@ -11,7 +11,7 @@ description: forge-harness path and skill list pointer — local only, do not co
|
|
|
11
11
|
**forge-harness path**: `~/path/to/forge-harness` (replace with your actual install path)
|
|
12
12
|
**Session records**: `{FH_ROOT}/tracks/_meta/`
|
|
13
13
|
|
|
14
|
-
**Available skills (fh-meta, 35)**: agent-composer · dialogue-harvest · fh · apex-review · asset-placement-gate · auto-decorrelation · context-doctor · contention-layer · corpus-grounding-expander · cross-ecosystem-synergy-detection · deep-clarify · edit-manifest · field-harvest · frontier-digest · goal-quench · harness-doctor · harvest-loop ·
|
|
14
|
+
**Available skills (fh-meta, 35)**: agent-composer · dialogue-harvest · fh · apex-review · asset-placement-gate · auto-decorrelation · context-doctor · contention-layer · corpus-grounding-expander · cross-ecosystem-synergy-detection · deep-clarify · edit-manifest · field-harvest · frontier-digest · goal-quench · harness-doctor · harvest-loop · harness-pr-reviewer · install-doctor · install-wizard · marketplace-gate · memory-hygiene · meta-prompt-builder · persona-roster-expander · phantom-quench · pipeline-conductor · plugin-recommender · prompt-regression · public-surface-audit · return-path-gate · sim-conductor · salience-splitter · steel-quench · verify-bidirectional · video-ingest
|
|
15
15
|
|
|
16
16
|
**Available skills (fh-commons)**: convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate
|
|
17
17
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# 구분하려면 stdout 의 `REGRESSION_GUARD_RESULT=skip` 을 보라. 종료코드만 보는 호출자는
|
|
16
16
|
# 미검사를 통과로 읽는다(2026-07-22: pre-commit 이 정확히 그랬고, AGENTS.md 변경이
|
|
17
17
|
# 그 경로로 '✅ PASS' 를 받고 지나갔다).
|
|
18
|
-
# 배선 현황: pre-commit ✅ / harness-doctor · harvest-loop ·
|
|
18
|
+
# 배선 현황: pre-commit ✅ / harness-doctor · harvest-loop · harness-pr-reviewer ·
|
|
19
19
|
# self_evolution_routine = **미배선(종료코드만 판정)** — 알려진 잔여.
|
|
20
20
|
#
|
|
21
21
|
# PR mode rationale: using 'main' as BASE_REF for a PR branch includes changes from OTHER
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""P1·P3 — 도형 배치 검사 둘. the private companion store's origin script (lane_geometry.py(2026-09-04,
|
|
3
|
-
36p 사고에서 역산)를 preprep 관례로 이식한 것 — 로직은 그대로다.
|
|
4
|
-
정본: `tracks-meta/fh_signal_2026-09-04_preprep-evolution.md` §2.
|
|
5
|
-
|
|
6
|
-
P1 build-jitter 같은 이름의 도형이 «연속한 두 장»에서 조금 다른 자리에 있다
|
|
7
|
-
→ 넘길 때 튄다. 실사고: 노란 게이트 막대가 직전 장 대비 35,000 EMU 밀려
|
|
8
|
-
회색 연결선과 겹쳤다. 큰 차이는 «의도한 이동»이라 제외한다.
|
|
9
|
-
P3 adjacency 한 도형의 «오른쪽 끝»과 다른 도형의 «왼쪽 끝»이 맞닿으려다 어긋났다
|
|
10
|
-
→ 실제 결함이 이 형태였다(회색 스텁 끝 ↔ 막대 시작, 35,000 겹침).
|
|
11
|
-
|
|
12
|
-
🟥 **P2(near-miss, 같은 종류 모서리끼리만 대조)는 미포함이다 — 채택하지 않는다.** 실사고가
|
|
13
|
-
못 잡히는 이유가 설계 근거였다: 실제 결함은 「회색 스텁 오른쪽 끝 ↔ 막대 왼쪽 끝」이라는
|
|
14
|
-
**다른 종류** 모서리의 인접 관계인데, P2 는 왼↔왼·오른↔오른만 봐서 구조적으로 못 잡는다.
|
|
15
|
-
그 관계는 P3 가 이미 덮는다. 새 레인을 짤 때 «같은 종류끼리»가 기본값이라는 것을 의심하라.
|
|
16
|
-
|
|
17
|
-
🟥 둘 다 advisory. 임계는 사람이 정한다(기본 JIT=200,000 EMU · ALN=63,500 EMU=5pt ·
|
|
18
|
-
1pt=12,700 EMU). `surfaces.yaml` 의 `geometry.jitter_emu` / `geometry.align_emu` 로 바꾼다.
|
|
19
|
-
🟥 «차이 0» 은 정상이고 «차이 큼» 도 정상이다. 이 검사가 보는 것은 그 사이뿐이다.
|
|
20
|
-
|
|
21
|
-
## 🟥 목록으로 읽지 마라 — 델타로 읽어라
|
|
22
|
-
|
|
23
|
-
실측: P3 는 원 코퍼스에서 기저 오탐이 76건이다(아이콘 무리 내부 · 맵 라벨의 «의도된» 겹침).
|
|
24
|
-
그 목록에서 진짜를 골라낼 방법이 없다. 그런데 편집 전/후를 대조하면 **차이가 정확히 그
|
|
25
|
-
한 줄**이었다. ⇒ 절대 목록은 회귀 도구로 못 쓰고 **편집 전후 델타는 정확하다.**
|
|
26
|
-
`surfaces.yaml` 의 `geometry.baseline`(이전 판 pptx 경로)이 있으면 이 레인은 델타 모드로 돈다
|
|
27
|
-
— 「지금 몇 건이냐」가 아니라 «내가 방금 뭘 어긋냈냐»를 묻는 회귀 도구다.
|
|
28
|
-
|
|
29
|
-
## 🟥 P1 이 «비교 안 함»을 «튐 없음»으로 속일 수 있는 자리 (계기 결함, 미리 적는다)
|
|
30
|
-
|
|
31
|
-
`shapes()` 는 **한 장 안에서 이름이 겹치는 도형을 통째로 버린다**(`len(v) == 1` 만 남긴다) —
|
|
32
|
-
겹치는 이름끼리는 대조할 대상을 하나로 못 정하기 때문이다. python-pptx 기본 이름
|
|
33
|
-
(`TextBox 1` 류)은 장마다 새로 매겨져 **이름이 우연히 안 겹칠 수 있고**, 대조 자체가 0건이
|
|
34
|
-
되는 입력에서도 「후보 0건」이 똑같이 출력된다 — 그 0 은 **「튐 없음」이 아니라 「비교 안 함」**
|
|
35
|
-
이다(이 저장소가 이름 붙인 「레인이 초록인 이유는 셋」의 ②). ⇒ `scan()` 은 **비교된 도형-쌍
|
|
36
|
-
수**를 항상 같이 낸다 — 0 이면 UNMEASURED 로 읽어야 한다는 뜻이다.
|
|
37
|
-
"""
|
|
38
|
-
import zipfile, re, os, collections
|
|
39
|
-
|
|
40
|
-
EMU_PT = 12700
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def shapes(z, sn):
|
|
44
|
-
"""한 슬라이드의 도형을 {이름: (x,y,cx,cy,text)} 로. 이름 중복은 대조 못 하니 뺀다."""
|
|
45
|
-
x = z.read('ppt/slides/slide%d.xml' % sn).decode('utf-8')
|
|
46
|
-
out = {}
|
|
47
|
-
for m in re.finditer(r'<p:(sp|cxnSp)>.*?</p:\1>', x, re.S):
|
|
48
|
-
b = m.group(0)
|
|
49
|
-
nm = re.search(r'name="([^"]*)"', b)
|
|
50
|
-
o = re.search(r'<a:off x="(-?\d+)" y="(-?\d+)"/><a:ext cx="(-?\d+)" cy="(-?\d+)"', b)
|
|
51
|
-
if not (nm and o):
|
|
52
|
-
continue
|
|
53
|
-
t = re.sub(r'\s+', ' ', ' '.join(re.findall(r'<a:t>([^<]*)</a:t>', b))).strip()
|
|
54
|
-
out.setdefault(nm.group(1), []).append(
|
|
55
|
-
(int(o.group(1)), int(o.group(2)), int(o.group(3)), int(o.group(4)), t))
|
|
56
|
-
return {k: v[0] for k, v in out.items() if len(v) == 1}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _order(z):
|
|
60
|
-
rels = dict(re.findall(r'Id="([^"]+)"[^>]*Target="slides/slide(\d+)\.xml"',
|
|
61
|
-
z.read('ppt/_rels/presentation.xml.rels').decode('utf-8')))
|
|
62
|
-
lst = re.search(r'<p:sldIdLst>.*?</p:sldIdLst>',
|
|
63
|
-
z.read('ppt/presentation.xml').decode('utf-8'), re.S).group(0)
|
|
64
|
-
return [int(rels[r]) for r in re.findall(r'<p:sldId id="\d+" r:id="([^"]+)"/>', lst)]
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def load_slides(path):
|
|
68
|
-
z = zipfile.ZipFile(path)
|
|
69
|
-
return [shapes(z, sn) for sn in _order(z)]
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def p1_lines(S, JIT):
|
|
73
|
-
"""build-jitter 후보. 반환: (lines, compared) — compared=0 이면 대조 대상이 아예 없었다는
|
|
74
|
-
뜻이라 «튐 0건»과 «UNMEASURED」를 갈라야 한다."""
|
|
75
|
-
lines, compared = [], 0
|
|
76
|
-
for i in range(1, len(S)):
|
|
77
|
-
for nm, cur in S[i].items():
|
|
78
|
-
prv = S[i - 1].get(nm)
|
|
79
|
-
if not prv:
|
|
80
|
-
continue
|
|
81
|
-
compared += 1
|
|
82
|
-
d = [cur[k] - prv[k] for k in range(4)]
|
|
83
|
-
mx = max(abs(v) for v in d)
|
|
84
|
-
if 0 < mx <= JIT:
|
|
85
|
-
lab = ['x', 'y', 'cx', 'cy']
|
|
86
|
-
moved = ' · '.join(f'{lab[k]} {d[k]:+d}' for k in range(4) if d[k])
|
|
87
|
-
lines.append(f" {i:>3}p→{i+1:<3}p {nm[:16]:16} {moved} ({mx/EMU_PT:.2f}pt) "
|
|
88
|
-
f"«{cur[4][:26]}»")
|
|
89
|
-
return lines, compared
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def p3_lines(S, ALN):
|
|
93
|
-
"""adjacency 후보 — A 오른쪽 끝 ↔ B 왼쪽 끝, 세로로 겹치는 띠에 있을 때만."""
|
|
94
|
-
lines = []
|
|
95
|
-
for i, sh in enumerate(S):
|
|
96
|
-
items = [(nm, v) for nm, v in sh.items() if v[2] > 0 and v[3] > 0]
|
|
97
|
-
for a in range(len(items)):
|
|
98
|
-
for b in range(len(items)):
|
|
99
|
-
if a == b:
|
|
100
|
-
continue
|
|
101
|
-
na, va = items[a]
|
|
102
|
-
nb, vb = items[b]
|
|
103
|
-
oy = min(va[1] + va[3], vb[1] + vb[3]) - max(va[1], vb[1])
|
|
104
|
-
if oy <= 0:
|
|
105
|
-
continue
|
|
106
|
-
gap = vb[0] - (va[0] + va[2]) # A 오른쪽 → B 왼쪽
|
|
107
|
-
if 0 < abs(gap) <= ALN:
|
|
108
|
-
kind = '겹침' if gap < 0 else '틈'
|
|
109
|
-
lines.append(f" {i+1:>3}p {na[:14]:14} 오른끝 → {nb[:14]:14} 왼끝 "
|
|
110
|
-
f"{kind} {abs(gap):>6} EMU ({abs(gap)/EMU_PT:.2f}pt)")
|
|
111
|
-
return lines
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def collect_lines(path, JIT, ALN):
|
|
115
|
-
"""(lines, compared) — P1+P3 합친 후보 줄과, P1 이 실제로 대조한 도형-쌍 수."""
|
|
116
|
-
S = load_slides(path)
|
|
117
|
-
l1, compared = p1_lines(S, JIT)
|
|
118
|
-
l3 = p3_lines(S, ALN)
|
|
119
|
-
return l1 + l3, compared
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def delta(base_path, cur_path, JIT, ALN):
|
|
123
|
-
"""편집 전/후 델타 — 절대 목록이 아니라 **차이**로 읽는 용법(정확했던 것). P3 는 SYS 를 안 쓰니
|
|
124
|
-
양쪽에 같은 규칙을 그대로 적용하면 된다(계기 자기결함이 P2 전용이라 여기 안 옮는다)."""
|
|
125
|
-
bl, cmp_b = collect_lines(base_path, JIT, ALN)
|
|
126
|
-
cur, cmp_c = collect_lines(cur_path, JIT, ALN)
|
|
127
|
-
b = set(bl)
|
|
128
|
-
new = [l for l in cur if l not in b]
|
|
129
|
-
gone = [l for l in bl if l not in set(cur)]
|
|
130
|
-
return new, gone, cmp_b, cmp_c
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _resolve(root, p):
|
|
134
|
-
return os.path.normpath(os.path.join(root, os.path.expanduser(p)))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def scan(cfg, root):
|
|
138
|
-
"""preprep.py 가 부르는 레인 진입점. (findings, notes) — 🟥 **advisory 고정**:
|
|
139
|
-
호출자는 findings 를 버리고 notes 만 취한다(L8·L11 관례)."""
|
|
140
|
-
deck_s = (cfg.get('surfaces_by_id') or {}).get('built_deck')
|
|
141
|
-
if not deck_s:
|
|
142
|
-
return [], ['P1/P3 geometry : built_deck 미선언 — NOT_CONFIGURED (0 아님)']
|
|
143
|
-
deck = _resolve(root, deck_s['path'])
|
|
144
|
-
if not os.path.exists(deck):
|
|
145
|
-
return [], [f'P1/P3 geometry : built_deck 실물 없음({deck}) — UNMEASURED (0 아님)']
|
|
146
|
-
spec = cfg.get('geometry') or {}
|
|
147
|
-
JIT = int(spec.get('jitter_emu', 200000))
|
|
148
|
-
ALN = int(spec.get('align_emu', 63500))
|
|
149
|
-
base_p = spec.get('baseline')
|
|
150
|
-
notes = []
|
|
151
|
-
|
|
152
|
-
if base_p:
|
|
153
|
-
base = _resolve(root, base_p)
|
|
154
|
-
if not os.path.exists(base):
|
|
155
|
-
return [], [f'P1/P3 geometry : baseline 실물 없음({base}) — UNMEASURED (0 아님)']
|
|
156
|
-
try:
|
|
157
|
-
new, gone, cmp_b, cmp_c = delta(base, deck, JIT, ALN)
|
|
158
|
-
except Exception as e:
|
|
159
|
-
return [], [f'P1/P3 geometry : 계기 오류({type(e).__name__}: {e}) — UNMEASURED (0 아님)']
|
|
160
|
-
notes.append(f'P1/P3 geometry(델타) : 기준 {os.path.basename(base)} 대비 '
|
|
161
|
-
f'새 어긋남 {len(new)}건 · 사라진 어긋남 {len(gone)}건 🟥 advisory '
|
|
162
|
-
f'(P1 대조된 도형-쌍: 기준 {cmp_b} · 현재 {cmp_c}'
|
|
163
|
-
+ ('' if cmp_b and cmp_c else ' — 🟥 0건이면 UNMEASURED, 「튐 없음」이 아니다') + ')')
|
|
164
|
-
for l in new:
|
|
165
|
-
notes.append(' + ' + l.strip())
|
|
166
|
-
for l in gone:
|
|
167
|
-
notes.append(' - ' + l.strip())
|
|
168
|
-
return [], notes
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
lines, compared = collect_lines(deck, JIT, ALN)
|
|
172
|
-
except Exception as e:
|
|
173
|
-
return [], [f'P1/P3 geometry : 계기 오류({type(e).__name__}: {e}) — UNMEASURED (0 아님)']
|
|
174
|
-
notes.append(f'P1/P3 geometry(목록) : 후보 {len(lines)}건 — 🟥 절대 목록으로 읽지 마라, '
|
|
175
|
-
f'P3 기저 오탐이 코퍼스마다 수십 건일 수 있다(원 코퍼스 실측 76건). '
|
|
176
|
-
f'`geometry.baseline` 을 쓰는 편집-델타 용법이 정확하다 '
|
|
177
|
-
f'(P1 대조된 도형-쌍 {compared}'
|
|
178
|
-
+ ('' if compared else ' — 🟥 UNMEASURED, 「튐 없음」이 아니다') + ')')
|
|
179
|
-
for l in lines:
|
|
180
|
-
notes.append(l)
|
|
181
|
-
return [], notes
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""R1~R5 · P1/P3 self-test — known-pair 계량 + 실물 재현.
|
|
3
|
-
|
|
4
|
-
exit 0 = 전부 통과 · 1 = 실패 있음 · 2 = 픽스처 생성 불가(python-pptx 부재 등, UNMEASURED)
|
|
5
|
-
|
|
6
|
-
구성:
|
|
7
|
-
① known-pair — `fixtures/mk_slide_fixtures.py` 로 R1·R2·R4·R5·P1·P3 를 테스트 시점에 만든다
|
|
8
|
-
(R3 는 예외 — 실물 구조 2장을 줄인 `fixtures/fixture_R3_{positive,negative}.pptx` 를 쓴다.
|
|
9
|
-
선두 줄 판별이 절대좌표·srgbClr 런 구조에 기대므로, 검출기 가정대로 «생성»한 픽스처는
|
|
10
|
-
검출기 자신을 검증 못 한다 — 자기참조. `tracks-meta` 원장이 지목한 방침)
|
|
11
|
-
② --baseline 델타 모드 — 편집 전/후 한 쌍에서 새 어긋남 1건만 잡히는지
|
|
12
|
-
③ 실물 재현 — 실제 백업 pptx(환경변수 PREPREP_REAL_CORPUS 가 가리키는 디렉터리, 있을 때만)에 돌려 원장이 인용한 실사고 4+1건이
|
|
13
|
-
그대로 뜨는지 확인한다. 그 코퍼스가 없는 머신에서는 SKIP(«통과»로 세지 않는다).
|
|
14
|
-
"""
|
|
15
|
-
import os
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
19
|
-
sys.path.insert(0, HERE)
|
|
20
|
-
sys.path.insert(0, os.path.join(HERE, 'fixtures'))
|
|
21
|
-
|
|
22
|
-
PASS, FAIL, SKIP = 0, 0, 0
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def ok(msg):
|
|
26
|
-
global PASS
|
|
27
|
-
PASS += 1
|
|
28
|
-
print(' ✅', msg)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def ng(msg):
|
|
32
|
-
global FAIL
|
|
33
|
-
FAIL += 1
|
|
34
|
-
print(' ❌', msg)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def sk(msg):
|
|
38
|
-
global SKIP
|
|
39
|
-
SKIP += 1
|
|
40
|
-
print(' ⏭ ', msg, '— SKIPPED (PASS 아님)')
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def codes_of(deck_path, LSR):
|
|
44
|
-
deck = LSR.Deck(deck_path)
|
|
45
|
-
F = LSR.analyze(deck)
|
|
46
|
-
return F, {k for k, _, _ in F}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def run_known_pairs(fx_dir):
|
|
50
|
-
print('\n[1] known-pair — R1·R2·R4·R5·P1·P3 (생성 픽스처) + R3 (실물 픽스처)')
|
|
51
|
-
import lane_slide_relations as LSR
|
|
52
|
-
import lane_geometry as LG
|
|
53
|
-
|
|
54
|
-
# R1
|
|
55
|
-
_, cp = codes_of(os.path.join(fx_dir, 'r1_pos.pptx'), LSR)
|
|
56
|
-
_, cn = codes_of(os.path.join(fx_dir, 'r1_neg.pptx'), LSR)
|
|
57
|
-
ok('R1 known-positive 검출') if 'R1' in cp else ng(f'R1 known-positive 미검출 ({cp})')
|
|
58
|
-
ok('R1 known-negative 무검출') if 'R1' not in cn else ng(f'R1 known-negative에서 오탐 ({cn})')
|
|
59
|
-
|
|
60
|
-
# R2
|
|
61
|
-
_, cp = codes_of(os.path.join(fx_dir, 'r2_pos.pptx'), LSR)
|
|
62
|
-
_, cn = codes_of(os.path.join(fx_dir, 'r2_neg.pptx'), LSR)
|
|
63
|
-
ok('R2 known-positive 검출') if 'R2' in cp else ng(f'R2 known-positive 미검출 ({cp})')
|
|
64
|
-
ok('R2 known-negative 무검출') if 'R2' not in cn else ng(f'R2 known-negative에서 오탐 ({cn})')
|
|
65
|
-
|
|
66
|
-
# R3 — 실물 픽스처 (자기참조 회피)
|
|
67
|
-
r3p = os.path.join(HERE, 'fixtures', 'fixture_R3_positive.pptx')
|
|
68
|
-
r3n = os.path.join(HERE, 'fixtures', 'fixture_R3_negative.pptx')
|
|
69
|
-
if os.path.exists(r3p) and os.path.exists(r3n):
|
|
70
|
-
_, cp = codes_of(r3p, LSR)
|
|
71
|
-
_, cn = codes_of(r3n, LSR)
|
|
72
|
-
ok('R3 known-positive(실물 2장) 검출') if 'R3' in cp else ng(f'R3 known-positive 미검출 ({cp})')
|
|
73
|
-
ok('R3 known-negative(실물 2장) 무검출') if 'R3' not in cn else ng(f'R3 known-negative에서 오탐 ({cn})')
|
|
74
|
-
else:
|
|
75
|
-
sk('R3 known-pair — fixture_R3_{positive,negative}.pptx 없음')
|
|
76
|
-
|
|
77
|
-
# R4
|
|
78
|
-
Fp, cp = codes_of(os.path.join(fx_dir, 'r4_pos.pptx'), LSR)
|
|
79
|
-
Fn, cn = codes_of(os.path.join(fx_dir, 'r4_neg.pptx'), LSR)
|
|
80
|
-
ok('R4 known-positive 검출(아웃라이어 500자)') if 'R4' in cp else ng(f'R4 known-positive 미검출 ({cp})')
|
|
81
|
-
ok('R4 known-negative 무검출(동값 분포)') if 'R4' not in cn else ng(f'R4 known-negative에서 오탐 ({cn})')
|
|
82
|
-
|
|
83
|
-
# R5
|
|
84
|
-
Fp, cp = codes_of(os.path.join(fx_dir, 'r5_pos.pptx'), LSR)
|
|
85
|
-
Fn, cn = codes_of(os.path.join(fx_dir, 'r5_neg.pptx'), LSR)
|
|
86
|
-
ok('R5 known-positive 검출(비율 30.0)') if 'R5' in cp else ng(f'R5 known-positive 미검출 ({cp})')
|
|
87
|
-
ok('R5 known-negative 무검출(동일 비율)') if 'R5' not in cn else ng(f'R5 known-negative에서 오탐 ({cn})')
|
|
88
|
-
|
|
89
|
-
# P1 — 3팔(임계 안 소폭 이동은 뜼다 · 임계 밖 큰 이동은 안 뜼다 · 무이동은 안 뜼다)
|
|
90
|
-
lines, compared = LG.collect_lines(os.path.join(fx_dir, 'p1.pptx'), 200000, 63500)
|
|
91
|
-
p1_lines = [l for l in lines if 'gate_bar' in l]
|
|
92
|
-
ok(f'P1 대조된 도형-쌍 {compared} > 0 (비교 안함 아님)') if compared > 0 else ng('P1 compared=0 — UNMEASURED')
|
|
93
|
-
ok(f'P1 후보 정확히 1건(임계 안 이동만)') if len(p1_lines) == 1 else ng(f'P1 후보 {len(p1_lines)}건 (want 1) — {p1_lines}')
|
|
94
|
-
if p1_lines:
|
|
95
|
-
ok('P1 유일 후보가 정확히 (1→2p) 소폭 이동 — 큰 이동(2→3p)·무이동(3→4p)은 안 뜬다') \
|
|
96
|
-
if '1p→2' in p1_lines[0].replace(' ', '') else ng(f'P1 후보가 엉뚱한 쌍을 가리킨다 {p1_lines}')
|
|
97
|
-
|
|
98
|
-
# P3
|
|
99
|
-
lines3, _ = LG.collect_lines(os.path.join(fx_dir, 'p3.pptx'), 200000, 63500)
|
|
100
|
-
p3_lines = [l for l in lines3 if 'stub' in l]
|
|
101
|
-
ok('P3 후보 정확히 1건(임계 안 틀/겹침만)') if len(p3_lines) == 1 else ng(f'P3 후보 {len(p3_lines)}건 (want 1) — {p3_lines}')
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def run_baseline_delta(fx_dir):
|
|
105
|
-
print('\n[2] --baseline 델타 모드 — 편집 전/후 한 쌍')
|
|
106
|
-
import lane_geometry as LG
|
|
107
|
-
new, gone, cmp_b, cmp_c = LG.delta(os.path.join(fx_dir, 'geo_base.pptx'),
|
|
108
|
-
os.path.join(fx_dir, 'geo_edited.pptx'), 200000, 63500)
|
|
109
|
-
ok('델타 새 어긋남 정확히 1건') if len(new) == 1 else ng(f'델타 새 어긋남 {len(new)}건 (want 1)')
|
|
110
|
-
ok('델타 사라진 어긋남 0건(깨끗한 기준본)') if len(gone) == 0 else ng(f'델타 사라진 어긋남 {len(gone)}건 (want 0)')
|
|
111
|
-
ok(f'기준본 대조 {cmp_b} > 0 · 현재본 대조 {cmp_c} > 0') if cmp_b > 0 and cmp_c > 0 \
|
|
112
|
-
else ng(f'기준본/현재본 대조 0 있음 — UNMEASURED (cmp_b={cmp_b}, cmp_c={cmp_c})')
|
|
113
|
-
# scan(cfg, root) 래퍼로도 동일하게 도는지 확인 (preprep.py 가 실제로 부르는 경로)
|
|
114
|
-
cfg = {'surfaces_by_id': {'built_deck': {'path': os.path.join(fx_dir, 'geo_edited.pptx')}},
|
|
115
|
-
'geometry': {'baseline': os.path.join(fx_dir, 'geo_base.pptx')}}
|
|
116
|
-
fP, nP = LG.scan(cfg, '.')
|
|
117
|
-
ok('scan(cfg, root) 래퍼가 델타 모드로 도고 새 어긋남 1건을 notes 에 낸다') \
|
|
118
|
-
if any('새 어긋남 1건' in n for n in nP) else ng(f'scan() 델타 출력 이상 — {nP}')
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
REAL_BASE = os.environ.get('PREPREP_REAL_CORPUS', '') # 기본값 없음 — 다른 머신에서 거짓 SKIP 방지(SKIP != PASS)
|
|
122
|
-
REAL_TARGETS = [
|
|
123
|
-
('ifkakao26_slides_v1.3_pre-engine-strip_20260904_210857.pptx', 'R1', 40,
|
|
124
|
-
'선두 줄이 «그러나»로 뒤집는데'),
|
|
125
|
-
('ifkakao26_slides_v1.3_pre-40p_20260904_213452.pptx', 'R2', 41,
|
|
126
|
-
'«이 셋/세 가지»라 부르는데 번호가 없다'),
|
|
127
|
-
('ifkakao26_slides_v1.3_pre-38trim_20260904_210428.pptx', 'R4', 38,
|
|
128
|
-
'화면 250자'),
|
|
129
|
-
('ifkakao26_slides_v1.3_pre-40p_20260904_213452.pptx', 'R5', 112,
|
|
130
|
-
'29.2자/초'),
|
|
131
|
-
]
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def run_real_corpus():
|
|
135
|
-
print('\n[3] 실물 재현 (PREPREP_REAL_CORPUS 백업, 있을 때만) — 원장이 인용한 실사고 4+1건')
|
|
136
|
-
if not os.path.isdir(REAL_BASE):
|
|
137
|
-
sk(f'실물 코퍼스 미지정/미존재({REAL_BASE or "PREPREP_REAL_CORPUS unset"}) — SKIP, 통과 아님')
|
|
138
|
-
return
|
|
139
|
-
import lane_slide_relations as LSR
|
|
140
|
-
import lane_geometry as LG
|
|
141
|
-
for fname, code, want_page, want_sub in REAL_TARGETS:
|
|
142
|
-
p = os.path.join(REAL_BASE, fname)
|
|
143
|
-
if not os.path.exists(p):
|
|
144
|
-
sk(f'{fname} 없음 — 재현 건너뜀')
|
|
145
|
-
continue
|
|
146
|
-
deck = LSR.Deck(p)
|
|
147
|
-
F = LSR.analyze(deck)
|
|
148
|
-
hit = [x for x in F if x[0] == code and abs(x[1] - want_page) <= 1 and want_sub in x[2]]
|
|
149
|
-
ok(f'{code} 실물 재현 — {fname} 약 {want_page}p — {hit[0][2][:60] if hit else ""}') \
|
|
150
|
-
if hit else ng(f'{code} 실물 미재현 — {fname}')
|
|
151
|
-
|
|
152
|
-
# P1/P3 36p (보너스 확인 — 원장이 명시적으로 요구한 4건에는 없지만 신호 자체의 원적 사건)
|
|
153
|
-
align36 = os.path.join(REAL_BASE, 'ifkakao26_slides_v1.3_pre-align36_222442.pptx')
|
|
154
|
-
if os.path.exists(align36):
|
|
155
|
-
lines, compared = LG.collect_lines(align36, 200000, 63500)
|
|
156
|
-
hit_p1 = any('s5017' in l and '35000' in l and '35p' in l.replace(' ', '') for l in lines)
|
|
157
|
-
hit_p3 = any('s5016' in l and 's5017' in l and '35000' in l for l in lines)
|
|
158
|
-
ok('P1 실물 재현 — 35p→36p s5017 x -35,000') if hit_p1 else ng('P1 실물 미재현')
|
|
159
|
-
ok('P3 실물 재현 — 36p s5016→s5017 겹침 35,000') if hit_p3 else ng('P3 실물 미재현')
|
|
160
|
-
else:
|
|
161
|
-
sk('ifkakao26_slides_v1.3_pre-align36_222442.pptx 없음 — P1/P3 보너스 재현 건너뜀')
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def main():
|
|
165
|
-
try:
|
|
166
|
-
import mk_slide_fixtures
|
|
167
|
-
except ImportError as e:
|
|
168
|
-
print(f'❌ python-pptx 또는 픽스처 모듈 부재 — UNMEASURED: {e}')
|
|
169
|
-
return 2
|
|
170
|
-
import tempfile
|
|
171
|
-
fx_dir = tempfile.mkdtemp(prefix='preprep_lanes_rp_')
|
|
172
|
-
mk_slide_fixtures.build_all(fx_dir)
|
|
173
|
-
print(f'픽스처 생성: {fx_dir}')
|
|
174
|
-
|
|
175
|
-
run_known_pairs(fx_dir)
|
|
176
|
-
run_baseline_delta(fx_dir)
|
|
177
|
-
run_real_corpus()
|
|
178
|
-
|
|
179
|
-
print(f'\n결과: {PASS} passed · {FAIL} failed · {SKIP} skipped (skip != pass)')
|
|
180
|
-
return 1 if FAIL else 0
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if __name__ == '__main__':
|
|
184
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_negative.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/progression_known_positive.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_negative.md
RENAMED
|
File without changes
|
/package/plugins/{fh-commons → fh-preprep}/skills/preprep/fixtures/promise_known_positive.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|