@chrono-meta/fh-gate 1.4.89 → 1.4.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/judgment_circuits.txt +14 -0
- package/.claude/rules/fh_4axis_gate.md +7 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +25 -0
- package/CLAUDE.md +179 -12
- package/knowledge/shared/harness-core/dispatch_conditional_prohibition.md +105 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +165 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +100 -0
- package/knowledge/shared/harness-core/onboarding_acceleration_autopilot.md +3 -1
- package/knowledge/shared/harness-core/ship_readiness_gate.md +181 -13
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +528 -0
- package/package.json +16 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +56 -8
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +33 -0
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +104 -15
- package/scripts/chamber_run.sh +64 -2
- package/scripts/chamber_witness.sh +439 -0
- package/scripts/compaction_probe.sh +456 -0
- package/scripts/digest_landing_check.sh +385 -0
- package/scripts/directional_diff_gate.sh +459 -0
- package/scripts/judgment_circuit_lint.sh +239 -0
- package/scripts/novelty_claim_check.sh +193 -0
- package/scripts/relay_channel.sh +645 -0
- package/scripts/reviewer_capability_corpus.tsv +124 -0
- package/scripts/selfcheck.sh +66 -0
- package/scripts/test_marker_crossfamily_lanes.sh +132 -0
- package/scripts/test_marker_floor_lanes.sh +9 -8
- package/scripts/test_relay_channel_lanes.sh +583 -0
- package/scripts/test_reviewer_capability_conformance.sh +173 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +104 -11
- package/scripts/utterance_landing_check.sh +209 -0
- package/templates/.git-hooks/pre-commit +253 -13
- package/templates/settings.Compaction.snippet.json +56 -0
|
@@ -469,6 +469,144 @@ marker_recreate_hint() {
|
|
|
469
469
|
echo " printf 'axis2-engine: quench-challenger\\naxis2-model: opus\\nfloor-status: at-floor\\naxis2-evidence: PASS no-S\\n' > \"$1\""
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
# ── Cross-family leg — typed verdict (see the load-bearing block below for WHY) ──
|
|
473
|
+
# Grammar: crossfamily: <VALUE>[ — <reason>]
|
|
474
|
+
# VALUE is a closed enum; the three degrade values additionally require a non-vacuous reason
|
|
475
|
+
# on the SAME line (keeping the established one-line marker convention rather than adding a
|
|
476
|
+
# second field). Returns 0 = accepted, 1 = block.
|
|
477
|
+
validate_crossfamily_leg() { # $1 = marker path
|
|
478
|
+
local m="$1" line val reason fams
|
|
479
|
+
if [ ! -f "$m" ] || ! grep -qE '^[[:space:]]*crossfamily:[[:space:]]*[^[:space:]]' "$m"; then
|
|
480
|
+
echo " ❌ FAIL — load-bearing file staged with no 'crossfamily:' line in the Axes 2-3 marker."
|
|
481
|
+
echo " Verdict/gate/irreversible-surface code shares the author's blind spot with a"
|
|
482
|
+
echo " same-family reviewer. State the answer, whatever it is — the gate blocks silence:"
|
|
483
|
+
echo " crossfamily: panel(codex, gemini) — R1..R4, 16 findings, 15 fixed 1 refuted"
|
|
484
|
+
echo " crossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy/gemini, 0 capable reachable"
|
|
485
|
+
echo " crossfamily: DEGRADED_PANEL_UNUSED — codex+agy reachable, not recruited (<why>)"
|
|
486
|
+
echo " crossfamily: UNKNOWN — panel not probed this round (<why>)"
|
|
487
|
+
echo " crossfamily: declined — operator declined sidecars (UAP), chosen floor"
|
|
488
|
+
echo " Append one line to: $m"
|
|
489
|
+
return 1
|
|
490
|
+
fi
|
|
491
|
+
# Duplicate guard (cross-family finding, codex 2026-08-08): a marker is an APPEND-ONLY
|
|
492
|
+
# file in practice, and `grep -m1` takes the FIRST match — so a later corrected/degraded
|
|
493
|
+
# value is shadowed by a stale permissive one and the lane reads the wrong verdict. Two
|
|
494
|
+
# crossfamily lines is an ambiguous marker, not a resolvable one; fail closed rather than
|
|
495
|
+
# pick a winner, because either choice silently discards a stated verdict.
|
|
496
|
+
if [ "$(grep -cE '^[[:space:]]*crossfamily:' "$m")" -gt 1 ]; then
|
|
497
|
+
echo " ❌ FAIL — marker carries MORE THAN ONE 'crossfamily:' line."
|
|
498
|
+
grep -nE '^[[:space:]]*crossfamily:' "$m" | sed 's/^/ /'
|
|
499
|
+
echo " A later line does not supersede an earlier one here — the reader takes the"
|
|
500
|
+
echo " first, so an appended correction would be silently shadowed. Delete the stale"
|
|
501
|
+
echo " line and leave exactly one."
|
|
502
|
+
return 1
|
|
503
|
+
fi
|
|
504
|
+
line=$(grep -m1 -E '^[[:space:]]*crossfamily:' "$m" \
|
|
505
|
+
| sed -E 's/^[[:space:]]*crossfamily:[[:space:]]*//')
|
|
506
|
+
# Split on the em-dash separator, NOT on first whitespace: a family list legitimately
|
|
507
|
+
# contains ", " (`panel(codex, gemini)`), and splitting on whitespace truncated it to
|
|
508
|
+
# `panel(codex,` — a well-formed panel rejected as malformed. An over-block on the
|
|
509
|
+
# correct shape is the failure that teaches --no-verify, so it is a defect, not strictness.
|
|
510
|
+
val=$(printf '%s' "$line" | sed -E 's/[[:space:]]*(—|--).*$//' | sed -E 's/[[:space:]]+$//')
|
|
511
|
+
reason=$(printf '%s' "$line" | sed -E 's/^[^—-]*(—|--)[[:space:]]*//')
|
|
512
|
+
[ "$reason" = "$line" ] && reason=""
|
|
513
|
+
|
|
514
|
+
case "$val" in
|
|
515
|
+
declined)
|
|
516
|
+
echo " ✅ cross-family leg: declined (operator-chosen floor, first-class — not a degrade)" ;;
|
|
517
|
+
panel*)
|
|
518
|
+
if ! printf '%s' "$val" | grep -qE '^panel\([^)]+\)$'; then
|
|
519
|
+
echo " ❌ FAIL — panel claim without a family list: '$val'"
|
|
520
|
+
echo " Write panel(<families>), e.g. panel(codex, gemini). Source the list from"
|
|
521
|
+
echo " scripts/sidecar_calibrate.sh's PANEL line rather than recall — that line"
|
|
522
|
+
echo " records a pin verified for THIS run."
|
|
523
|
+
return 1
|
|
524
|
+
fi
|
|
525
|
+
fams=$(printf '%s' "$val" | sed -E 's/^panel\(//; s/\)$//')
|
|
526
|
+
# (a) AUTHOR-FAMILY EXCLUSION. The point of this field is decorrelation; a panel of
|
|
527
|
+
# the author's own family is a same-family pass wearing a panel label — the exact
|
|
528
|
+
# substitution the load-bearing block exists to forbid. FH's governor is Claude.
|
|
529
|
+
if printf '%s' "$fams" | grep -qiE '(^|[,[:space:]])(claude|opus|sonnet|haiku|fable)'; then
|
|
530
|
+
echo " ❌ FAIL — panel names the AUTHOR'S OWN family: '$fams'"
|
|
531
|
+
echo " A Claude-family auditor shares the governor's blind spots, so it is not"
|
|
532
|
+
echo " decorrelation. If that is genuinely all that was reachable, the honest value"
|
|
533
|
+
echo " is DEGRADED_SINGLE_FAMILY with a reason — not a panel claim."
|
|
534
|
+
return 1
|
|
535
|
+
fi
|
|
536
|
+
# (b) INELIGIBLE-FIRST. Tokens overlap: GLM-OCR matches both `ocr` and `glm`,
|
|
537
|
+
# Qwen3-Embedding matches both `embed` and `qwen`. Test capability-eligibility first
|
|
538
|
+
# and both pass silently (pmh-dev #41 design decision 3, anchored there and here).
|
|
539
|
+
if printf '%s' "$fams" | grep -qiE 'embed|rerank|[^a-z]rank|ocr|safeguard|moderat|classif|whisper|dall-e|-rm([^a-z]|$)|bge-|voyage|gte-|minilm'; then
|
|
540
|
+
echo " ❌ FAIL — panel names a review-INCAPABLE member: '$fams'"
|
|
541
|
+
echo " Embedding / reranker / reward-model / moderation / OCR / speech classes emit"
|
|
542
|
+
echo " vectors, scores or labels — they cannot return an adversarial finding, so"
|
|
543
|
+
echo " counting them inflates BOTH panel size and family-diversity. If nothing capable"
|
|
544
|
+
echo " remains, the honest value is DEGRADED_SINGLE_FAMILY with a reason."
|
|
545
|
+
return 1
|
|
546
|
+
fi
|
|
547
|
+
# (c) DEFAULT DENY on unknown families (pmh-dev #41 design decision 1: unmeasured is
|
|
548
|
+
# not eligible). A denylist alone cannot know `voyage-3` or `armorm` are not reviewers,
|
|
549
|
+
# so an unrecognised token is UNDECIDABLE, never assumed capable. Matching is by FAMILY
|
|
550
|
+
# PATTERN, not id enumeration (#41 decision 2), so a new build of a known family passes
|
|
551
|
+
# without rewiring — which is the reason the CLI route was chosen at all.
|
|
552
|
+
local unknown="" tok
|
|
553
|
+
for tok in $(printf '%s' "$fams" | tr ',' ' '); do
|
|
554
|
+
printf '%s' "$tok" | grep -qiE 'codex|gpt|openai|o[0-9]|gemini|agy|antigravity|glm|qwen|deepseek|mistral|llama|grok|command-|cohere|kimi|copilot|ollama|phi-|yi-' \
|
|
555
|
+
|| unknown="${unknown:+$unknown }$tok"
|
|
556
|
+
done
|
|
557
|
+
if [ -n "$unknown" ]; then
|
|
558
|
+
echo " ❌ FAIL — panel names unrecognised family/families: '$unknown'"
|
|
559
|
+
echo " Default-deny: an unrecognised token is UNDECIDABLE, not capable — that is how"
|
|
560
|
+
echo " 'panel(none)' and 'panel(unprobed)' would otherwise launder a non-run into a"
|
|
561
|
+
echo " pass. Either use the family pattern this runtime belongs to, or add the"
|
|
562
|
+
echo " pattern here in the same commit that first uses it."
|
|
563
|
+
return 1
|
|
564
|
+
fi
|
|
565
|
+
echo " ✅ cross-family leg: $val${reason:+ — $(printf '%s' "$reason" | cut -c1-60)}" ;;
|
|
566
|
+
DEGRADED_SINGLE_FAMILY|DEGRADED_PANEL_UNUSED|UNKNOWN)
|
|
567
|
+
# A degrade is allowed; being silent about WHICH degrade, or about its grounds, is not.
|
|
568
|
+
# Two conditions, because either alone is porous: enough substance to be a statement
|
|
569
|
+
# (length) AND a token naming what was actually looked at.
|
|
570
|
+
if [ "${#reason}" -lt 20 ] \
|
|
571
|
+
|| ! printf '%s' "$reason" | grep -qiE 'prob(e|ed|ing)|reachab|unreach|not reachable|0 |none reachable|empty|absent|declin|consent|codex|gemini|agy|copilot|gateway|cli|panel|scope|budget|offline'; then
|
|
572
|
+
echo " ❌ FAIL — $val without substantive grounds on the same line."
|
|
573
|
+
case "$val" in
|
|
574
|
+
UNKNOWN) echo " UNKNOWN = the panel was never probed. Undecidable, not zero." ;;
|
|
575
|
+
DEGRADED_SINGLE_FAMILY) echo " = probed, nothing CAPABLE reachable (a constraint)." ;;
|
|
576
|
+
DEGRADED_PANEL_UNUSED) echo " = a capable panel WAS reachable and was not recruited (a choice)." ;;
|
|
577
|
+
esac
|
|
578
|
+
echo " Name what you probed and what came back — the defect this field exists for"
|
|
579
|
+
echo " was a line that announced a state without naming its grounds:"
|
|
580
|
+
echo " crossfamily: $val — probed codex/agy/gemini, <what came back>"
|
|
581
|
+
return 1
|
|
582
|
+
fi
|
|
583
|
+
echo " ⚠️ cross-family leg: $val — recorded, not silent (weekly audit re-queues these)"
|
|
584
|
+
echo " $(printf '%s' "$reason" | cut -c1-80)" ;;
|
|
585
|
+
single-family|none|none-this-round|N/A|n/a)
|
|
586
|
+
# `single-family` is deliberately NOT accepted HERE. This block runs only on
|
|
587
|
+
# load-bearing changes, where "decorrelation not required" is a contradiction — and
|
|
588
|
+
# as a no-ack, no-warning pass it was a free bypass of the whole lane (any degrade
|
|
589
|
+
# could take this exit instead of DEGRADED_*). `none` is worse: it merges three states.
|
|
590
|
+
echo " ❌ FAIL — '$val' is not an accepted value on a LOAD-BEARING change."
|
|
591
|
+
echo " This block only runs when gate/verdict/irreversible-surface code is staged, so"
|
|
592
|
+
echo " 'decorrelation not required' does not apply — and a no-ack pass here would let"
|
|
593
|
+
echo " any degrade take this exit instead of naming itself. Say which state holds:"
|
|
594
|
+
echo " DEGRADED_SINGLE_FAMILY probed, nothing capable reachable (could not)"
|
|
595
|
+
echo " DEGRADED_PANEL_UNUSED panel reachable, not recruited (did not)"
|
|
596
|
+
echo " UNKNOWN never probed (did not look)"
|
|
597
|
+
echo " declined operator declined sidecars (chosen floor)"
|
|
598
|
+
return 1 ;;
|
|
599
|
+
*)
|
|
600
|
+
echo " ❌ FAIL — invalid crossfamily value: '$val'"
|
|
601
|
+
echo " Allowed: panel(<families>) | declined |"
|
|
602
|
+
echo " DEGRADED_SINGLE_FAMILY | DEGRADED_PANEL_UNUSED | UNKNOWN"
|
|
603
|
+
echo " Free prose is rejected on purpose: a prose verdict cannot be consumed by a gate,"
|
|
604
|
+
echo " and a later session cites it as though it were measured. That has happened."
|
|
605
|
+
return 1 ;;
|
|
606
|
+
esac
|
|
607
|
+
return 0
|
|
608
|
+
}
|
|
609
|
+
|
|
472
610
|
validate_marker_floor() {
|
|
473
611
|
local m="$1" fs model
|
|
474
612
|
# Leading whitespace tolerated on field lines (defense-in-depth vs indented writes).
|
|
@@ -801,9 +939,28 @@ fi
|
|
|
801
939
|
# the Surface-Class Degrade Invariant the fail-closed leg of this gate belongs at the MERGE boundary,
|
|
802
940
|
# not here; forcing a sidecar dispatch per commit would over-block and train the escape into reflex.
|
|
803
941
|
# It DOES forbid being SILENT about it. The marker must carry a `crossfamily:` line — either naming
|
|
804
|
-
# the engine and its verdict, or explicitly recording
|
|
805
|
-
# below-floor-ack: the gate blocks on an unstated answer, never on the answer itself.
|
|
806
|
-
# the marker's job; provenance is not (a fabricated line is the weekly-audit residual
|
|
942
|
+
# the engine and its verdict, or explicitly recording the degrade with a reason. Same shape as the
|
|
943
|
+
# existing below-floor-ack: the gate blocks on an unstated answer, never on the answer itself.
|
|
944
|
+
# Non-vacuity is the marker's job; provenance is not (a fabricated line is the weekly-audit residual).
|
|
945
|
+
#
|
|
946
|
+
# ── TYPED (2026-08-08) — why presence-only was not enough ─────────────────────────────────────
|
|
947
|
+
# The original check accepted any non-empty value, so the answer was FREE PROSE. That is how a
|
|
948
|
+
# false one got in: a sibling harness's marker recorded `crossfamily: none this round — 도달 불가`
|
|
949
|
+
# (unreachable), the claim was later found FALSE, and a subsequent session CITED it as grounds.
|
|
950
|
+
# A prose verdict laundered itself into evidence — the failure `[[feedback_typed_verdict_channel]]`
|
|
951
|
+
# names. Presence-checking catches silence; it cannot catch a confident wrong answer.
|
|
952
|
+
#
|
|
953
|
+
# The load-bearing split is that `none` is THREE different states, and merging them hides a
|
|
954
|
+
# different thing each time:
|
|
955
|
+
# DEGRADED_SINGLE_FAMILY probed, nothing CAPABLE reachable — could not
|
|
956
|
+
# DEGRADED_PANEL_UNUSED panel reachable, not recruited — did not (a choice, not a limit)
|
|
957
|
+
# UNKNOWN never probed — did not look
|
|
958
|
+
# `UNKNOWN` collapsed into `none` renders an unrun probe as a zero finding (not-found-is-not-zero).
|
|
959
|
+
# `DEGRADED_PANEL_UNUSED` collapsed into it renders an unused panel as an unavailable one — the
|
|
960
|
+
# distinction a sibling harness stated about itself in its own words: "못 한 것이 아니라 안 한 것이다".
|
|
961
|
+
#
|
|
962
|
+
# Scope is unchanged: still load-bearing-only, still reversible-surface (blocks the unstated answer,
|
|
963
|
+
# never demands a dispatch). Only the ANSWER is now typed. Fixtures: scripts/test_marker_crossfamily_lanes.sh
|
|
807
964
|
# The degrade lint runs as an ADVISORY pre-screen, per its own doctrine (FP-tolerant, never a solo block).
|
|
808
965
|
LOADBEARING=$(printf '%s\n%s\n' "$GATE_IMPL" "$UGUARD_IMPL" | grep -v '^[[:space:]]*$' | sort -u || true)
|
|
809
966
|
if [ -n "$LOADBEARING" ]; then
|
|
@@ -823,18 +980,101 @@ if [ -n "$LOADBEARING" ]; then
|
|
|
823
980
|
echo " ⚠️ degrade lint unavailable (scripts/degrade_direction_scan.sh missing) — advisory leg skipped"
|
|
824
981
|
fi
|
|
825
982
|
MARKER_LB="$REPO_ROOT/tracks/_meta/.axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker"
|
|
826
|
-
if
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
983
|
+
if validate_crossfamily_leg "$MARKER_LB"; then :; else FAILED=1; fi
|
|
984
|
+
fi
|
|
985
|
+
|
|
986
|
+
# ── staged-blob materializer (advisory linters) ───────────────────────────────
|
|
987
|
+
# 두 advisory 린터는 파일을 직접 grep/sed 한다. 워킹트리 경로를 넘기면 **커밋되는 내용이 아니라
|
|
988
|
+
# 지금 디스크에 있는 내용**을 재게 된다 — 스테이징 후 워킹트리에서 고치면 훅은 깨끗하다 보고하고
|
|
989
|
+
# 문제 있는 blob 이 커밋된다(high 리뷰 #6, 낙관 방향 fail-open). 이 훅의 다른 슬라이스는 전부
|
|
990
|
+
# `git diff --cached` / `git show :file` 로 추론한다. 여기만 예외였다.
|
|
991
|
+
_stage_blob() { # $1=repo-relative path → echoes temp file holding the STAGED content
|
|
992
|
+
local _p="$1" _t
|
|
993
|
+
_t="$(mktemp "${TMPDIR:-/tmp}/fh_staged.XXXXXX")" || return 1
|
|
994
|
+
if git -C "$REPO_ROOT" show ":$_p" > "$_t" 2>/dev/null; then printf '%s' "$_t"
|
|
995
|
+
else rm -f "$_t"; return 1; fi
|
|
996
|
+
}
|
|
997
|
+
_STAGE_TMPS=()
|
|
998
|
+
_STAGE_MAP=() # "tmpfile=repopath" — 보고를 사람이 읽을 수 있게 되돌리는 데 쓴다
|
|
999
|
+
# 히트를 익명 mktemp 경로로 인쇄하면, 그 파일은 _stage_cleanup 이 곧 지워서 **읽는 순간 이미 없다**.
|
|
1000
|
+
# 대상이 둘 이상이면 어느 파일 얘긴지 귀속조차 안 된다. advisory 의 목적은 사람을 그 줄로 보내는 것이다.
|
|
1001
|
+
_unmap() { local _o="$1" _m; for _m in "${_STAGE_MAP[@]:-}"; do
|
|
1002
|
+
[ -n "$_m" ] && _o="${_o//${_m%%=*}/${_m#*=}}"; done; printf '%s' "$_o"; }
|
|
1003
|
+
_stage_cleanup() { [ "${#_STAGE_TMPS[@]}" -gt 0 ] && rm -f "${_STAGE_TMPS[@]}" 2>/dev/null; }
|
|
1004
|
+
|
|
1005
|
+
# ── Judgment-circuit lint (advisory — the PROMPT-layer sibling of the degrade lint above) ─────
|
|
1006
|
+
# Same proposition, different corpus: degrade_direction_scan reads .py/.sh verdict code and skips
|
|
1007
|
+
# .md by construction, so identity/circuit DOCUMENTS had no instrument at all. Measured basis
|
|
1008
|
+
# (105 runs, 2026-08-07~08): role-assignment ("너는 ~이다") is a net loss on the weak tier and
|
|
1009
|
+
# intensity orders ("꼼꼼히") multiply phantoms 3.1x — both are prose granting discretion with an
|
|
1010
|
+
# unconstrained degrade direction, the doctrine's prompt-layer form.
|
|
1011
|
+
#
|
|
1012
|
+
# Trigger is an EXPLICIT REGISTRY, not a guess: `.claude/judgment_circuits.txt` (one path per line).
|
|
1013
|
+
# Which documents are judgment circuits is a decision someone makes, not something a glob infers —
|
|
1014
|
+
# an inferred trigger here would fire on every .md and get itself disabled.
|
|
1015
|
+
# ADVISORY by construction: grep-heuristic, false positives expected, never blocks a commit.
|
|
1016
|
+
JC_REG="$REPO_ROOT/.claude/judgment_circuits.txt"
|
|
1017
|
+
JC_LINT="$REPO_ROOT/scripts/judgment_circuit_lint.sh"
|
|
1018
|
+
if [ -f "$JC_REG" ] && [ -f "$JC_LINT" ]; then
|
|
1019
|
+
# Array, not a space-joined string: a tracked path CAN contain a space, and the string form
|
|
1020
|
+
# silently splits `docs/first known.md` into two nonexistent args (cross-family, 2026-08-08).
|
|
1021
|
+
_jc_targets=()
|
|
1022
|
+
while IFS= read -r _jc_line; do
|
|
1023
|
+
case "$_jc_line" in ''|'#'*) continue ;; esac
|
|
1024
|
+
if printf '%s\n' "$STAGED" | grep -qxF "$_jc_line" 2>/dev/null; then
|
|
1025
|
+
_b="$(_stage_blob "$_jc_line")" && { _jc_targets+=("$_b"); _STAGE_TMPS+=("$_b"); _STAGE_MAP+=("$_b=$_jc_line"); }
|
|
1026
|
+
fi
|
|
1027
|
+
done < "$JC_REG"
|
|
1028
|
+
if [ "${#_jc_targets[@]}" -gt 0 ]; then
|
|
1029
|
+
echo "[Gate] registered judgment-circuit doc staged — circuit lint (advisory)..."
|
|
1030
|
+
_jc_out="$(bash "$JC_LINT" "${_jc_targets[@]}" 2>&1)"; _jc_rc=$?
|
|
1031
|
+
printf '%s\n' "$(_unmap "$_jc_out")" | sed 's/^/ /'
|
|
1032
|
+
echo " (경로는 스테이징 blob 을 레포 경로로 되돌려 표기했다 — 줄번호는 staged 내용 기준)"
|
|
1033
|
+
# exit 10 = the scanner's own HARNESS-ERROR (bad input / unreadable target). Neither PASS nor
|
|
1034
|
+
# FAIL — surfacing it is the whole point of having a typed exit; swallowing it would render a
|
|
1035
|
+
# dead instrument as a clean run.
|
|
1036
|
+
[ "$_jc_rc" = "10" ] && echo " 🟥 circuit lint HARNESS-ERROR (rc=10) — instrument did not measure"
|
|
1037
|
+
echo " (advisory — does not block. A hit means 'prove this is a circuit, not a persona'.)"
|
|
1038
|
+
fi
|
|
1039
|
+
fi
|
|
1040
|
+
|
|
1041
|
+
# ── Novelty-claim ledger (advisory) ───────────────────────────────────────────
|
|
1042
|
+
# "Ask the world before asserting novelty" is an INTENT trigger — un-hookable, like the
|
|
1043
|
+
# repo-go-public surface. So this does not force the asking; it makes NOT having asked visible
|
|
1044
|
+
# at commit time (shift-left ledger, not one more rule: the rule already existed and still failed).
|
|
1045
|
+
# Direction matters: defect claims (FAIL) have long required source-grounding while novelty/strength
|
|
1046
|
+
# claims (PASS) passed unchecked — the optimistic direction is the one that leaks. And an absence
|
|
1047
|
+
# claim is structurally worse: with nothing cited, phantom-detection cannot engage at all.
|
|
1048
|
+
# Scope = the same substantive doc surface the 4-axis carve-out already treats as claim-bearing.
|
|
1049
|
+
NC_CHECK="$REPO_ROOT/scripts/novelty_claim_check.sh"
|
|
1050
|
+
if [ -f "$NC_CHECK" ]; then
|
|
1051
|
+
_nc_targets=()
|
|
1052
|
+
while IFS= read -r _nc_line; do
|
|
1053
|
+
_b="$(_stage_blob "$_nc_line")" && { _nc_targets+=("$_b"); _STAGE_TMPS+=("$_b"); _STAGE_MAP+=("$_b=$_nc_line"); }
|
|
1054
|
+
done < <(printf '%s\n' "$STAGED" | grep -E '^(knowledge/|docs/).*\.md$' || true)
|
|
1055
|
+
if [ "${#_nc_targets[@]}" -gt 0 ]; then
|
|
1056
|
+
_nc_raw=$(bash "$NC_CHECK" "${_nc_targets[@]}" 2>&1); _nc_rc=$?
|
|
1057
|
+
# Same typed-exit rule as above: rc=10 is a dead instrument, not a clean corpus.
|
|
1058
|
+
[ "$_nc_rc" = "10" ] && echo " 🟥 novelty check HARNESS-ERROR (rc=10) — instrument did not measure"
|
|
1059
|
+
_nc_raw="$(_unmap "$_nc_raw")"
|
|
1060
|
+
# 부분 미스캔 경고를 **같이** 건진다. 호출부가 `🟥 무앵커` 만 grep 해서, 스크립트가 옳게 낸
|
|
1061
|
+
# "전수 스캔이 아니다" 를 통째로 버렸다 — 수리가 스크립트 안으로 옮겨졌을 뿐 이 표면에선
|
|
1062
|
+
# 미스캔 문서가 여전히 깨끗한 문서와 구분 불가였다(high 재리뷰 #5).
|
|
1063
|
+
_nc_warn=$(printf '%s\n' "$_nc_raw" | grep -F '전수 스캔이 아니다' | head -3 || true)
|
|
1064
|
+
if [ -n "$_nc_warn" ]; then
|
|
1065
|
+
echo "[Gate] novelty check: 부분 미스캔 (advisory):"
|
|
1066
|
+
printf '%s\n' "$_nc_warn" | sed 's/^/ /'
|
|
1067
|
+
echo " → 이 문서는 전수 스캔이 아니다. 제외된 줄에 실제 주장이 있을 수 있다."
|
|
1068
|
+
fi
|
|
1069
|
+
_nc_out=$(printf '%s\n' "$_nc_raw" | grep -E '🟥 무앵커' | head -5 || true)
|
|
1070
|
+
if [ -n "$_nc_out" ]; then
|
|
1071
|
+
echo "[Gate] novelty/absence claim without an external anchor (advisory):"
|
|
1072
|
+
printf '%s\n' "$_nc_out" | sed 's/^/ /'
|
|
1073
|
+
echo " Each = 'you asserted this about the world without asking it'. Does not block."
|
|
1074
|
+
fi
|
|
836
1075
|
fi
|
|
837
1076
|
fi
|
|
1077
|
+
_stage_cleanup
|
|
838
1078
|
|
|
839
1079
|
# SKILL.md ONLY here, deliberately (2026-07-26): the 07-26 sweep widened `SKILL\.md` →
|
|
840
1080
|
# `SKILL(_detail)?\.md` in the HEAVY and doc-coupling terms above, but NOT here. This slice asks
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": [
|
|
3
|
+
"Compaction hook snippet — TRACKED, so it arrives with a clone. Every .claude/settings*.json path",
|
|
4
|
+
"in this repo is gitignored (confirm with `git check-ignore -v .claude/settings.json`), so the hook",
|
|
5
|
+
"registration itself cannot be tracked. This is the tracked SOURCE that install-wizard merges into",
|
|
6
|
+
"the user's local settings. Without the merge step the hooks do not run — shipping the script is not",
|
|
7
|
+
"the same proposition as wiring it.",
|
|
8
|
+
"",
|
|
9
|
+
"What it does: seals a POINTER ledger of what the session holds just BEFORE compaction, then",
|
|
10
|
+
"re-injects it once on the next prompt. FH's structural advantage over generic compaction memory is",
|
|
11
|
+
"that the originals are already on disk — what compaction destroys is not the context but the",
|
|
12
|
+
"pointers to it, and a pointer is cheap to restore.",
|
|
13
|
+
"",
|
|
14
|
+
"Why the second hook is UserPromptSubmit and not PostCompact — this is a MEASURED constraint, not a",
|
|
15
|
+
"style choice: PostCompact does NOT support `additionalContext` (official hooks reference, verified",
|
|
16
|
+
"2026-08-08), so a PostCompact hook physically cannot put anything back into the session. It can only",
|
|
17
|
+
"write files. UserPromptSubmit is one of the three events whose exit-0 stdout becomes context Claude",
|
|
18
|
+
"can read. Wiring the re-injection to PostCompact would install a hook that runs and changes nothing.",
|
|
19
|
+
"",
|
|
20
|
+
"Both entries carry NO private path, so both belong in .claude/settings.json (project-local).",
|
|
21
|
+
"Merge, do not overwrite: preserve any PreCompact / UserPromptSubmit entries the user already has;",
|
|
22
|
+
"replace only the FH ones, keyed by script name.",
|
|
23
|
+
"",
|
|
24
|
+
"Never blocks: PreCompact CAN block compaction (exit 2) and this deliberately does not — compaction",
|
|
25
|
+
"is a reversible surface, and a gate that fires in the user's way is a gate that gets switched off.",
|
|
26
|
+
"The script always exits 0 for the same reason a non-zero hook exit discards its own stdout."
|
|
27
|
+
],
|
|
28
|
+
"project_settings_json": {
|
|
29
|
+
"hooks": {
|
|
30
|
+
"PreCompact": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "bash -c 'HUB=\"${CLAUDE_PROJECT_DIR:-$HOME/projects/forge-harness}\"; bash \"$HUB/scripts/compaction_probe.sh\" seal; exit 0'",
|
|
37
|
+
"timeout": 15
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"UserPromptSubmit": [
|
|
43
|
+
{
|
|
44
|
+
"matcher": "",
|
|
45
|
+
"hooks": [
|
|
46
|
+
{
|
|
47
|
+
"type": "command",
|
|
48
|
+
"command": "bash -c 'HUB=\"${CLAUDE_PROJECT_DIR:-$HOME/projects/forge-harness}\"; bash \"$HUB/scripts/compaction_probe.sh\" digest; exit 0'",
|
|
49
|
+
"timeout": 10
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|