@chrono-meta/fh-gate 3.1.3 → 3.2.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 +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +6 -0
- package/CLAUDE.md +50 -6
- package/README.ja.md +3 -2
- package/README.ko.md +3 -2
- package/README.md +3 -2
- package/README.zh.md +3 -2
- package/docs/OUTPUT_EVIDENCE.md +1 -1
- package/knowledge/shared/harness-core/claude_md_gate_details.md +26 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +1 -1
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +46 -0
- package/knowledge/shared/harness-core/governance_engineering_definition.md +89 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +68 -0
- package/knowledge/shared/rules/auto_project_mapping.md +1 -1
- package/package.json +11 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +23 -0
- package/plugins/fh-commons/skills/preprep/fixtures/font_revert_probe.py +92 -0
- package/plugins/fh-commons/skills/preprep/lane_font.py +462 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +16 -1
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +17 -0
- package/plugins/fh-commons/skills/preprep/test_lane_font.py +452 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +91 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +1 -1
- package/scripts/doc_claim_triad_scan.py +303 -0
- package/scripts/finding_fleet.sh +180 -0
- package/scripts/finding_pipeline.sh +213 -0
- package/scripts/finding_verifier.sh +144 -0
- package/scripts/finding_verify.py +283 -0
- package/scripts/gate_pathspec_check.sh +1 -0
- package/scripts/gate_shape_scan.sh +106 -0
- package/scripts/selfcheck.sh +50 -0
- package/scripts/test_doc_claim_triad_lanes.sh +124 -0
- package/scripts/test_finding_pipeline_lanes.sh +459 -0
- package/scripts/test_gate_shape_scan_lanes.sh +36 -0
- package/scripts/test_heavy_classifier_lanes.sh +13 -3
- package/scripts/test_preprep_font_lanes.sh +87 -0
- package/templates/.git-hooks/pre-commit +6 -5
- package/templates/PRE-PUBLISH-CHECKLIST.md +29 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_preprep_font_lanes.sh — preprep L15(서체 일관성)의 계약을 고정한다.
|
|
3
|
+
#
|
|
4
|
+
# WHY: 발표 템플릿을 주는 조직은 거의 항상 서체를 규정하는데, 서체 이탈은 **렌더링에서만**
|
|
5
|
+
# 드러나 사람이 눈으로 훑기 전에는 안 보인다. preprep 의 이웃 레인들은 배치(P1/P3)와 말의
|
|
6
|
+
# 정합(L1·L5·L8·R1~R5)을 보지, 「무슨 서체로 찍혔나」는 어느 레인도 안 봤다.
|
|
7
|
+
#
|
|
8
|
+
# 🟥 이 레인이 증명해야 하는 것은 «이탈을 잡는다»가 아니라 **«상속분을 안 잡는다»** 다.
|
|
9
|
+
# 실측(2026-09-11, 조직 배포 템플릿 기반 발표 덱 120장): 순진하게 세면 `Helvetica Neue`
|
|
10
|
+
# **85건**이 «위반»으로 나오는데, 그 85건은 전부 `slideMasters/` 안에 있고 **배포 템플릿의
|
|
11
|
+
# 마스터에도 정확히 같은 85건**이 있다(해시 동일). 저자는 그 글자를 찍은 적이 없다.
|
|
12
|
+
# 진짜 이탈은 하나뿐이었고 그건 의도였다(코드 데모의 고정폭 서체).
|
|
13
|
+
# ⇒ known-negative 를 «서체 하나뿐인 덱»으로 두면 그 오탐을 **구조적으로 못 잡는다.**
|
|
14
|
+
# 그래서 K1 은 «마스터에 이질 서체가 있지만 템플릿과 동일한 덱» 이다.
|
|
15
|
+
#
|
|
16
|
+
# 종료코드: 0 pass · 1 레인 실패 · 2 대상 부재 · 10 setup 실패
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
cd "$(dirname "$0")/.." || exit 10
|
|
19
|
+
LANE=plugins/fh-commons/skills/preprep/lane_font.py
|
|
20
|
+
SELFTEST=plugins/fh-commons/skills/preprep/test_lane_font.py
|
|
21
|
+
PROBE=plugins/fh-commons/skills/preprep/fixtures/font_revert_probe.py
|
|
22
|
+
[ -f "$LANE" ] || { echo "ⓘ $LANE absent — subject missing (NOT a pass)"; exit 2; }
|
|
23
|
+
[ -f "$SELFTEST" ] || { echo "ⓘ $SELFTEST absent — subject missing (NOT a pass)"; exit 2; }
|
|
24
|
+
[ -f "$PROBE" ] || { echo "ⓘ $PROBE absent — subject missing (NOT a pass)"; exit 2; }
|
|
25
|
+
command -v python3 >/dev/null 2>&1 || { echo "ⓘ python3 absent — setup broke"; exit 10; }
|
|
26
|
+
# 🟥 스테일 바이트코드가 뮤턴트/복원을 가린다 — 이웃 레인이 실측한 사고다(2026-09-06).
|
|
27
|
+
rm -rf plugins/fh-commons/skills/preprep/__pycache__
|
|
28
|
+
PASS=0; FAIL=0
|
|
29
|
+
ok(){ echo " ✅ $1"; PASS=$((PASS+1)); }
|
|
30
|
+
ng(){ echo " ❌ $1"; FAIL=$((FAIL+1)); }
|
|
31
|
+
|
|
32
|
+
echo "== K1~K13 known-pair (레인 자체 self-test) =="
|
|
33
|
+
OUT=$(python3 "$SELFTEST" 2>&1); RC=$?
|
|
34
|
+
echo "$OUT" | sed 's/^/ /'
|
|
35
|
+
if [ "$RC" -eq 0 ] && echo "$OUT" | grep -q 'FAIL 0'; then
|
|
36
|
+
ok "self-test 전항 통과 (rc=$RC)"
|
|
37
|
+
elif [ "$RC" -eq 2 ]; then
|
|
38
|
+
echo " ⏭ 픽스처 생성 불가 — UNMEASURED (통과 아님)"; FAIL=$((FAIL+1))
|
|
39
|
+
else
|
|
40
|
+
ng "self-test 실패 (rc=$RC)"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "== 배선·퇴화 — self-test 밖에서 한 번 더 =="
|
|
44
|
+
OUT=$(python3 -c "
|
|
45
|
+
import sys; sys.path.insert(0,'plugins/fh-commons/skills/preprep')
|
|
46
|
+
import lane_font as L
|
|
47
|
+
print(L.scan({}, '.')[1][0])
|
|
48
|
+
print(L.scan({'surfaces_by_id':{'built_deck':{'path':'/nope/none.pptx'}}}, '.')[1][0])
|
|
49
|
+
" 2>&1)
|
|
50
|
+
echo "$OUT" | grep -q 'NOT_CONFIGURED' && echo "$OUT" | grep -q 'UNMEASURED' \
|
|
51
|
+
&& ok "미선언 → NOT_CONFIGURED · 실물 없음 → UNMEASURED (둘 다 0 아님)" \
|
|
52
|
+
|| ng "퇴화 표기가 0 으로 접힌다: $OUT"
|
|
53
|
+
|
|
54
|
+
grep -q "import lane_font" plugins/fh-commons/skills/preprep/preprep.py \
|
|
55
|
+
&& ok "배선: preprep.py 가 이 레인을 부른다" \
|
|
56
|
+
|| ng "배선 없음 — 정의만 있고 아무도 안 부른다(built-but-not-wired)"
|
|
57
|
+
|
|
58
|
+
grep -q "lane_font" plugins/fh-commons/skills/preprep/SKILL.md \
|
|
59
|
+
&& ok "명세: SKILL.md 가 이 레인을 적는다(고아 구현 아님)" \
|
|
60
|
+
|| ng "SKILL.md 에 없다 — 명세 없는 구현"
|
|
61
|
+
|
|
62
|
+
# 🟥 이 프로브가 «template 미선언» 조건에서 도는 이유는 프로브 파일 머리에 적혀 있다. 요약:
|
|
63
|
+
# 한때 방어가 둘이었고(영역 분리 · 템플릿-자체-서체 강등) 해시 동일 부품에서는 ②가 ①을
|
|
64
|
+
# 완전히 덮어, 둘 다 켠 채 ①만 죽이면 «분리가 장식»이라는 **거짓 판정**이 났다.
|
|
65
|
+
# 그 뒤 ②는 cross-family 2라운드에서 **fail-open 으로 판정돼 걷혔다**(억제 → 맥락 주석).
|
|
66
|
+
# 지금 방어는 ① 하나뿐이고, 이 조건이 그 하나를 정확히 겨눈다.
|
|
67
|
+
echo "== 되돌림 — 영역 분리를 죽이면 오탐이 돌아오는가 (template 미선언 = 방어 하나) =="
|
|
68
|
+
OUT=$(python3 "$PROBE" separation 2>&1)
|
|
69
|
+
if echo "$OUT" | grep -q 'BASE 0' && echo "$OUT" | grep -qE 'MUT [1-9]'; then
|
|
70
|
+
ok "되돌림: 분리를 죽이면 마스터 상속 서체가 오탐으로 돌아온다 ($OUT) — 분리가 하중을 진다"
|
|
71
|
+
else
|
|
72
|
+
ng "되돌림 실패 — 분리를 죽여도 결과가 같다(그 분기가 장식이다): $OUT"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# 🟥 «템플릿도 쓰는 이름» 을 노트로 **억제**하면 fail-open 이다 — 2라운드가 그렇게 지목해서
|
|
76
|
+
# 억제를 걷어내고 맥락 주석만 남겼다. 이 레인이 그 되돌림을 고정한다.
|
|
77
|
+
echo "== 템플릿도 쓰는 이름이 억제되지 않고 finding 으로 남는가 =="
|
|
78
|
+
OUT=$(python3 "$PROBE" annotation 2>&1)
|
|
79
|
+
if echo "$OUT" | grep -qE 'FINDING [1-9]' && echo "$OUT" | grep -qE 'ANNOTATED [1-9]'; then
|
|
80
|
+
ok "억제 아님: finding 은 남고 맥락 주석만 붙는다 ($OUT)"
|
|
81
|
+
else
|
|
82
|
+
ng "억제로 되돌아갔다 — 저자가 그 이름으로 본문을 찍어도 종료코드가 안 움직인다: $OUT"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
echo "── preprep font lanes: PASS=$PASS FAIL=$FAIL"
|
|
86
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
87
|
+
exit 0
|
|
@@ -213,10 +213,11 @@ fi
|
|
|
213
213
|
# dlp-filter.sh, …) — the gate's OWN tooling. Leaving it ungated was a gate-locality
|
|
214
214
|
# seam: the engine that enforces the 4-axis could itself ship unverified. Gated broadly,
|
|
215
215
|
# not by an allowlist, so a NEW gate script cannot silently slip in ungated (the drift
|
|
216
|
-
# that would re-open the seam). Scope is `scripts
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
216
|
+
# that would re-open the seam). Scope is `scripts/**/*.{sh,py}` (2026-09-07: `.py` joined
|
|
217
|
+
# — a 252-line python instrument landed with no marker while 7 shipped python files sat
|
|
218
|
+
# outside this term the whole time; the heavy-classifier lane had it PINNED as `uncovered`).
|
|
219
|
+
# A governance script with yet another extension or placed elsewhere (bin/) is still NOT
|
|
220
|
+
# auto-caught and must be hand-gated — broaden this term if that changes. Axis 1 (regression_guard) skips non-markdown, so the live effect on a
|
|
220
221
|
# script is Axes 2-3 (adversarial design + phantom), NOT behavioral regression — that is
|
|
221
222
|
# covered downstream (selfcheck.sh / count_check.sh at publish), not by this commit gate.
|
|
222
223
|
# seam #3 (2026-06-27): agent definitions (plugins/*/agents/*.md, .claude/agents/*.md) are
|
|
@@ -242,7 +243,7 @@ fi
|
|
|
242
243
|
# «no FH asset staged» 로 빠져 4축 게이트를 통째로 건너뛰었다. tenet 을 바꾸는 것은
|
|
243
244
|
# 이 레포에서 가장 하중 큰 편집 중 하나다 — LIGHT 는커녕 CARVEOUT 도 아니다.
|
|
244
245
|
HEAVY=$(echo "$STAGED" \
|
|
245
|
-
| grep -E "(SKILL(_detail)?\.md|^plugins/[^/]+/skills/[^/]+/.*\.md$|\.claude/rules/|^knowledge/shared/rules/|templates/|CLAUDE\.md|^scripts/.*\.sh$|^plugins/[^/]+/agents/.*\.md$|^\.claude/agents/.*\.md$|^\.claude/soul_tenets\.txt$)" || true)
|
|
246
|
+
| grep -E "(SKILL(_detail)?\.md|^plugins/[^/]+/skills/[^/]+/.*\.md$|\.claude/rules/|^knowledge/shared/rules/|templates/|CLAUDE\.md|^scripts/.*\.(sh|py)$|^plugins/[^/]+/agents/.*\.md$|^\.claude/agents/.*\.md$|^\.claude/soul_tenets\.txt$)" || true)
|
|
246
247
|
|
|
247
248
|
# Carve-out candidates: knowledge/ docs, docs/*.md, AGENTS.md. Heavy only if substantive.
|
|
248
249
|
CARVEOUT=$(echo "$STAGED" \
|
|
@@ -24,6 +24,8 @@ Any first-time public action:
|
|
|
24
24
|
- `gh repo edit --visibility public` (private → public flip)
|
|
25
25
|
- first `git push` to a **new public remote**
|
|
26
26
|
- `npm publish` · `python -m build && twine upload dist/*` · any registry publish
|
|
27
|
+
- **scholarly deposit** — Zenodo / figshare / OSF publish, DOI mint, new version under a concept DOI,
|
|
28
|
+
arXiv `submit` / `replace`. A DOI outlives an npm version; its machine fields outlive the PDF.
|
|
27
29
|
|
|
28
30
|
## Step 0 — Cheap mechanical pre-flags (10 seconds)
|
|
29
31
|
|
|
@@ -64,6 +66,33 @@ git ls-files | grep -iE '<internal-acronym>'
|
|
|
64
66
|
- **Generated artifacts count.** An exported HTML/PDF carrying a username is a real public-surface leak —
|
|
65
67
|
fix = regenerate from a sanitized source, not hand-edit.
|
|
66
68
|
|
|
69
|
+
## Step 1b — Scholarly deposit only (Zenodo / DOI / arXiv): the form is not the server
|
|
70
|
+
|
|
71
|
+
Run **after** filling the form and **before** pressing Publish. Every line is an API read, not a screen read.
|
|
72
|
+
|
|
73
|
+
- [ ] Read the draft through the **service's draft-read API** — Zenodo (InvenioRDM) `/api/records/<id>/draft`,
|
|
74
|
+
Zenodo legacy `/api/deposit/depositions/<id>`, figshare `/v2/account/articles/<id>`, OSF its node/preprint
|
|
75
|
+
endpoint — and compare `description`, `creators`, `license`, `keywords` against **the text you pasted
|
|
76
|
+
into the form** (keep that text in a file; the comparison is string-vs-JSON, not screen-vs-screen).
|
|
77
|
+
(measured 2026-09-07: description + companion DOI were in the form and **absent from the server** —
|
|
78
|
+
the rich-text editor had not flushed)
|
|
79
|
+
- [ ] File **md5** equals the local artifact (size alone is not identity)
|
|
80
|
+
- [ ] **Corrective release?** Then the correction must land in the machine fields too — `references`,
|
|
81
|
+
`related identifiers`, `dates` — not only in the PDF body (measured: a corrective PDF shipped
|
|
82
|
+
with all eleven wrong attributions still in `references`)
|
|
83
|
+
- [ ] **After** Publish (a detector, not a gate — the record is already public): read the public
|
|
84
|
+
record API once; `updated` ≥ the wall-clock time you pressed Publish, the fields above still present,
|
|
85
|
+
file checksum unchanged. On a **moderated** service (OSF preprints, arXiv) a pending/announce state
|
|
86
|
+
is expected, not a failure. If a field is wrong here, the remedy is a **new corrective version**
|
|
87
|
+
(never silent) — this line cannot un-publish anything, which is why the three lines above run first.
|
|
88
|
+
|
|
89
|
+
**Completion**: Step 0 + Step 1 + **Step 1b** (when the surface is a scholarly deposit) — 1b is not
|
|
90
|
+
optional on that surface.
|
|
91
|
+
|
|
92
|
+
**Why no hook here**: the deposit lives on another service; nothing in this repo runs at that boundary.
|
|
93
|
+
This list is the floor. A `zenodo_draft_check.sh` that diffs draft-vs-form is the first thing to build
|
|
94
|
+
on the *second* real demand — 2026-09-07 was the first.
|
|
95
|
+
|
|
67
96
|
## Step 2 — If LEAK: scrub on a PRIVATE copy, then publish
|
|
68
97
|
|
|
69
98
|
Do **not** "publish then fix". If the repo is already created, keep it **private** until clean.
|