@chrono-meta/fh-gate 1.4.72 → 1.4.74

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.
Files changed (51) hide show
  1. package/.claude/rules/.public-surface-patterns.defaults +44 -0
  2. package/.claude/rules/fh_4axis_gate.md +207 -0
  3. package/.claude-plugin/marketplace.json +2 -2
  4. package/AGENTS.md +26 -2
  5. package/CATALOG.md +31 -0
  6. package/docs/ETHOS.md +106 -0
  7. package/docs/OUTPUT_EVIDENCE.md +118 -0
  8. package/docs/WHY.md +42 -0
  9. package/knowledge/patterns/ensemble_union_detection_task_pattern.md +125 -0
  10. package/knowledge/shared/GLOSSARY.md +77 -0
  11. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +10 -0
  12. package/knowledge/shared/learnings/subagent_invocations_log.yaml +554 -0
  13. package/knowledge/shared/patterns/multi-persona-review.md +88 -0
  14. package/knowledge/shared/plugin-catalog/recommended_plugins.md +117 -0
  15. package/package.json +42 -1
  16. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
  18. package/plugins/fh-meta/CHANGELOG.md +617 -0
  19. package/plugins/fh-meta/skills/context-doctor/SKILL.md +42 -4
  20. package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +38 -0
  21. package/scripts/below_floor_scan.sh +91 -0
  22. package/scripts/chamber_candidate_collect.sh +223 -0
  23. package/scripts/chamber_run.sh +184 -0
  24. package/scripts/degrade_direction_scan.sh +222 -0
  25. package/scripts/fh_env_delta_scan.sh +108 -0
  26. package/scripts/fh_session_load.sh +202 -0
  27. package/scripts/gate_pathspec_check.sh +166 -0
  28. package/scripts/package_coverage_check.sh +119 -0
  29. package/scripts/prepush_guard_check.sh +374 -0
  30. package/scripts/psa_scan_lib.sh +153 -0
  31. package/scripts/public_surface_scan_files.sh +157 -0
  32. package/scripts/selfcheck.sh +28 -0
  33. package/scripts/session_close_check.sh +171 -0
  34. package/scripts/substrate_jump_detector.sh +60 -0
  35. package/scripts/test_degrade_scan_shell_probes.sh +185 -0
  36. package/scripts/test_marker_floor_lanes.sh +45 -0
  37. package/scripts/test_prepush_stdin_integrity.sh +119 -0
  38. package/scripts/tier_census_grep.sh +54 -0
  39. package/scripts/universal_guard_check.sh +280 -0
  40. package/templates/.claude/rules/mcp_tool_gating.md +157 -0
  41. package/templates/.claude/rules/session.md +153 -0
  42. package/templates/.git-hooks/pre-commit +848 -0
  43. package/templates/.git-hooks/pre-push +585 -0
  44. package/templates/PRE-PUBLISH-CHECKLIST.md +85 -0
  45. package/templates/contrib_session.md +34 -0
  46. package/templates/degrade_direction_scan.sh +222 -0
  47. package/templates/goal-quench-hook-setup.md +152 -0
  48. package/templates/predelete_check.sh +72 -0
  49. package/templates/regression_guard.sh +563 -0
  50. package/templates/starter_profile.md +83 -0
  51. package/templates/temper_check.sh +46 -0
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # gate_pathspec_check.sh — known-pair regression anchor for gate PATH COVERAGE.
3
+ #
4
+ # WHY THIS EXISTS
5
+ # The gate-locality class has now recurred four times: scripts/ (2026-06-26), AGENTS.md
6
+ # inheritance (#111/#117), agent definitions (2026-06-27), and SKILL_detail.md (2026-07-26).
7
+ # Every instance had the same shape: an asset class the canonical rule *declared* covered, which
8
+ # the gate implementation's path term did not actually match — and the miss rendered as PASS,
9
+ # because "no file matched" and "all files passed" are indistinguishable downstream.
10
+ #
11
+ # The 07-26 instance was the sharpest: the term was the literal `SKILL\.md`, and the string
12
+ # `SKILL_detail.md` does not contain `SKILL.md` (the underscore breaks it). 17 files, 208,710 B,
13
+ # 27.7% of the skill-spec surface, 16 of 17 holding fenced code blocks — ungated. It leaked twice
14
+ # for real (371c04f, e661931: single-file edits to a GATE SKILL's own behavioral spec).
15
+ #
16
+ # So this is not a style check. It is the mechanical anchor for the fix, per the FH rule that a
17
+ # harness edit is a draft until a check fails when the mistake recurs.
18
+ #
19
+ # METHOD — known-pair, per CLAUDE.md §Instrument-Calibration. Every case asserts BOTH directions:
20
+ # a known-positive that MUST match and a known-negative that MUST NOT. A checker that only ever
21
+ # confirms positives cannot tell "covers everything" from "matches everything".
22
+ #
23
+ # Usage: bash scripts/gate_pathspec_check.sh # exit 0 = all pairs hold, 1 = a pair broke
24
+ set -uo pipefail
25
+
26
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
27
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
28
+ GUARD="$REPO_ROOT/templates/regression_guard.sh"
29
+
30
+ fail=0
31
+ pass=0
32
+
33
+ # Extract a live regex from the implementation instead of restating it here. A copy would drift
34
+ # from the thing it claims to verify — which is the very defect class this file exists to catch.
35
+ extract_term() { # $1 = file, $2 = variable-assignment marker
36
+ grep -A2 "^${2}=" "$1" 2>/dev/null | grep -oE '\| grep -E "[^"]+"' | head -1 \
37
+ | sed -E 's/^\| grep -E "//; s/"$//'
38
+ }
39
+
40
+ check() { # $1 = label, $2 = regex, $3 = should-match path, $4 = should-NOT-match path
41
+ local label="$1" re="$2" pos="$3" neg="$4" ok=1 rc
42
+ # grep exits 2 on a MALFORMED regex. `if ! grep -q` would negate that 2 into "true" and report
43
+ # it as "known-positive not covered" — fail-closed in direction, but it misnames the cause, and
44
+ # a checker that misreports why it failed sends the next reader to fix the wrong thing.
45
+ echo "$pos" | grep -qE "$re"; rc=$?
46
+ if [ "$rc" -eq 2 ]; then
47
+ echo " ❌ $label — extracted pattern is not a valid regex (instrument error, NOT a coverage result)"
48
+ echo " pattern: $re"
49
+ fail=$((fail + 1)); return
50
+ fi
51
+ [ "$rc" -ne 0 ] && { echo " ❌ $label — known-POSITIVE not covered: $pos"; ok=0; }
52
+ if echo "$neg" | grep -qE "$re"; then
53
+ echo " ❌ $label — known-NEGATIVE wrongly covered: $neg"; ok=0
54
+ fi
55
+ if [ "$ok" -eq 1 ]; then
56
+ echo " ✅ $label"; pass=$((pass + 1))
57
+ else
58
+ fail=$((fail + 1))
59
+ fi
60
+ }
61
+
62
+ echo "gate_pathspec_check — known-pair coverage anchors"
63
+ echo
64
+
65
+ # ── 1. pre-commit HEAVY term ──────────────────────────────────────────────────
66
+ HEAVY_RE="$(extract_term "$HOOK" HEAVY)"
67
+ if [ -z "$HEAVY_RE" ]; then
68
+ echo " ❌ could not extract HEAVY term from $HOOK — instrument error, NOT a pass"
69
+ exit 1
70
+ fi
71
+ # The 07-26 regression: detail files must be HEAVY. Negative: a tracks/ record must not be.
72
+ check "HEAVY covers SKILL_detail.md" "$HEAVY_RE" \
73
+ "plugins/fh-meta/skills/frontier-digest/SKILL_detail.md" \
74
+ "tracks/_meta/fh_signal_2026-07-26_ai.md"
75
+ check "HEAVY still covers SKILL.md" "$HEAVY_RE" \
76
+ "plugins/fh-meta/skills/frontier-digest/SKILL.md" \
77
+ "README.md"
78
+ # Prior gate-locality instances — anchored so a future edit cannot silently drop them.
79
+ check "HEAVY covers agent definitions (seam #3)" "$HEAVY_RE" \
80
+ "plugins/fh-meta/agents/challenger.md" \
81
+ "docs/README.md"
82
+ check "HEAVY covers scripts/*.sh (seam #1)" "$HEAVY_RE" \
83
+ "scripts/gate_pathspec_check.sh" \
84
+ "tracks/_audit/session_2026_07_26_agentsmith-sister.md"
85
+
86
+ # ── 2. regression_guard GUARD_PATHSPEC ────────────────────────────────────────
87
+ # Read the array as the guard itself defines it; match with the same glob semantics git uses.
88
+ # NOTE: no `mapfile` — macOS ships bash 3.2, where it does not exist. This is the documented
89
+ # bash-3.2 portability class; a 4.x-only builtin here would make the anchor itself the thing that
90
+ # breaks on the operator's own machine.
91
+ SPEC=()
92
+ while IFS= read -r line; do
93
+ [ -n "$line" ] && SPEC+=("$line")
94
+ done < <(sed -n '/^GUARD_PATHSPEC=(/,/^)/p' "$GUARD" | grep -oE "'[^']+'" | tr -d "'")
95
+ if [ "${#SPEC[@]:-0}" -eq 0 ]; then
96
+ echo " ❌ could not extract GUARD_PATHSPEC from $GUARD — instrument error, NOT a pass"
97
+ exit 1
98
+ fi
99
+ spec_matches() { # $1 = path
100
+ local p="$1" g
101
+ for g in "${SPEC[@]}"; do
102
+ # shellcheck disable=SC2254
103
+ case "$p" in $g) return 0 ;; esac
104
+ done
105
+ return 1
106
+ }
107
+ for pair in \
108
+ "plugins/fh-meta/skills/frontier-digest/SKILL_detail.md|tracks/_meta/x.md|PATHSPEC covers SKILL_detail.md" \
109
+ "plugins/fh-meta/skills/frontier-digest/SKILL.md|README.md|PATHSPEC still covers SKILL.md" \
110
+ "CLAUDE.md|CLAUDE.local.md|PATHSPEC covers CLAUDE.md but not the local override"
111
+ do
112
+ IFS='|' read -r pos neg label <<< "$pair"
113
+ ok=1
114
+ spec_matches "$pos" || { echo " ❌ $label — known-POSITIVE not covered: $pos"; ok=0; }
115
+ spec_matches "$neg" && { echo " ❌ $label — known-NEGATIVE wrongly covered: $neg"; ok=0; }
116
+ if [ "$ok" -eq 1 ]; then echo " ✅ $label"; pass=$((pass + 1)); else fail=$((fail + 1)); fi
117
+ done
118
+
119
+ # ── 3. Canonical-vs-implementation parity ─────────────────────────────────────
120
+ # regression_guard.sh's own comment: "두 목록이 갈리면 갈린 쪽이 조용히 무검사 구간이 된다."
121
+ # Anchor that warning mechanically for the asset class that just broke.
122
+ CANON="$REPO_ROOT/.claude/rules/fh_4axis_gate.md"
123
+ # Scope the match to the ASSET-LIST sentence, not the whole file. A bare whole-file grep would go
124
+ # green on a line that says "SKILL_detail.md is excluded" — i.e. it would certify parity against
125
+ # documentation that contradicts the code. Match the declaration line itself.
126
+ if grep -q 'Whenever the AI modifies FH assets.*SKILL_detail\.md' "$CANON" 2>/dev/null; then
127
+ echo " ✅ canonical rule declares SKILL_detail.md (in the asset-list line)"; pass=$((pass + 1))
128
+ else
129
+ echo " ❌ canonical rule ($CANON) no longer declares SKILL_detail.md — the two lists diverged,"
130
+ echo " which is exactly the silent no-check condition this anchor exists to prevent."
131
+ fail=$((fail + 1))
132
+ fi
133
+
134
+ # ── 4. Enumeration sweep — the anti-guessing check ────────────────────────────
135
+ # Every fix above answers "is THIS name covered?" — which only ever closes the names someone
136
+ # thought of. This one inverts it: enumerate what actually EXISTS under plugins/*/skills/ and
137
+ # assert the HEAVY term covers all of it. A new companion-file convention (SKILL_summary.md,
138
+ # a nested docs/ page, a deeper skill directory) then fails HERE, at introduction, instead of
139
+ # waiting for someone to notice the naming gap years later. Reality is the input, not a guess.
140
+ # (Adversarial credit: an Axis-2 sidecar pass argued the name-by-name fixes could not, in
141
+ # principle, close the class — correct, and this is the answer to it.)
142
+ uncovered=""
143
+ while IFS= read -r f; do
144
+ [ -z "$f" ] && continue
145
+ echo "$f" | grep -qE "$HEAVY_RE" || uncovered="$uncovered$f
146
+ "
147
+ done < <(cd "$REPO_ROOT" && find plugins -path '*/skills/*' -name '*.md' -type f 2>/dev/null | sort)
148
+ if [ -z "$uncovered" ]; then
149
+ echo " ✅ enumeration: every .md under plugins/*/skills/ is covered by the HEAVY term"
150
+ pass=$((pass + 1))
151
+ else
152
+ echo " ❌ enumeration: files exist under plugins/*/skills/ that NO gate term covers —"
153
+ printf '%s' "$uncovered" | sed 's/^/ /'
154
+ echo " Either widen the gate term, or state in fh_4axis_gate.md why this class is exempt."
155
+ fail=$((fail + 1))
156
+ fi
157
+
158
+ echo
159
+ if [ "$fail" -eq 0 ]; then
160
+ echo "gate_pathspec_check: PASS ($pass pairs)"
161
+ exit 0
162
+ fi
163
+ echo "gate_pathspec_check: FAIL ($fail broken, $pass ok)"
164
+ echo "A gate path term stopped covering an asset class it is declared to cover."
165
+ echo "Do NOT relax the anchor to make it green — fix the term, or retire the pair deliberately."
166
+ exit 1
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # package_coverage_check.sh — a shipped document must not point at a file the package omits.
3
+ #
4
+ # WHY (measured 2026-07-28): the npm tarball shipped CLAUDE.md, README, CATALOG and the knowledge/
5
+ # base while omitting much of what they instruct the reader to open — 35 distinct paths existed in
6
+ # the repo, were named by a shipped document, and were absent from the tarball. `CLAUDE.md` told
7
+ # consumers to run `templates/predelete_check.sh` before a destructive op; that file did not ship.
8
+ # A gate you are told to run and cannot run is worse than one you were never told about.
9
+ #
10
+ # This is the ANTI-REGROWTH instrument for that class. Closing the 35 once is worth little: the set
11
+ # regrows every time a doc gains a reference or files[] gains an entry. So the check is mechanical
12
+ # and the exceptions are ENUMERATED, never implicit.
13
+ #
14
+ # SOURCE-TREE ONLY. Inside an installed package the un-shipped files are legitimately absent and
15
+ # package.json's files[] may not even be present in a comparable form, so the check self-skips.
16
+ #
17
+ # Usage: bash scripts/package_coverage_check.sh
18
+ # Exit: 0 = every referenced path is either shipped or explicitly accepted; 1 = a new phantom.
19
+ set -uo pipefail
20
+
21
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ cd "$REPO_ROOT" || exit 1
23
+
24
+ if [ ! -d .git ] || [ ! -f package.json ]; then
25
+ echo "SKIP package-coverage (not a source checkout)"
26
+ exit 0
27
+ fi
28
+
29
+ # ── Accepted-absent, with the reason each one is NOT a defect ────────────────────────────────
30
+ # Adding a line here is a decision, not a silencer: each entry states why shipping it would be
31
+ # wrong. If you cannot write that sentence, the file probably belongs in files[].
32
+ #
33
+ # .claude/registry/LOCAL_SKILL_REGISTRY.md — per-environment, generated by the registry scan.
34
+ # Its consumers probe it with `ls … 2>/dev/null` and handle absence; shipping one machine's
35
+ # registry would hand every consumer a false map of skills they do not have.
36
+ # .claude/regression/probes.md — per-environment prompt-regression baselines. The
37
+ # skill explicitly prints NO_CUSTOM_PROBES when absent. Shipping FH's baselines would make
38
+ # a consumer's regression run compare against someone else's harness.
39
+ # scripts/sync-to-be.sh — operator-private companion-store sync. Never ships.
40
+ # (The reference that flags it is a TEST FIXTURE in prepush_guard_check.sh which *creates*
41
+ # a file of that name to exercise the LOW allowlist — not a pointer to this file at all.)
42
+ # scripts/sync_guard_check.sh — anchor for that same operator-private mirror sync;
43
+ # no shipped hook invokes it.
44
+ ACCEPTED_ABSENT=(
45
+ ".claude/registry/LOCAL_SKILL_REGISTRY.md"
46
+ ".claude/regression/probes.md"
47
+ "scripts/sync-to-be.sh"
48
+ "scripts/sync_guard_check.sh"
49
+ )
50
+
51
+ out=$(python3 - "${ACCEPTED_ABSENT[@]}" <<'PY'
52
+ import re, os, json, sys
53
+ accepted = set(sys.argv[1:])
54
+ files = json.load(open('package.json'))['files']
55
+
56
+ def covered(p):
57
+ return any(p == f or p.startswith(f.rstrip('/') + '/') for f in files)
58
+
59
+ shipped = []
60
+ for f in files:
61
+ if os.path.isfile(f):
62
+ shipped.append(f)
63
+ elif os.path.isdir(f):
64
+ for root, _, names in os.walk(f):
65
+ shipped.extend(os.path.join(root, n) for n in names)
66
+
67
+ # Only text surfaces can carry a reference a human or agent would follow.
68
+ shipped = [s for s in shipped if s.endswith(('.md', '.sh', '.js', '.json', '.yaml', '.yml'))]
69
+
70
+ pat = re.compile(
71
+ r'(?<![\w/.-])((?:scripts|templates|bin|docs|knowledge|plugins|\.claude)'
72
+ r'/[A-Za-z0-9_./-]+\.(?:sh|py|js|md|yaml|yml|json|defaults))'
73
+ )
74
+
75
+ phantom = {}
76
+ for s in shipped:
77
+ try:
78
+ text = open(s, encoding='utf-8', errors='ignore').read()
79
+ except OSError:
80
+ continue
81
+ for m in set(pat.findall(text)):
82
+ # Only a path that REALLY EXISTS here but is left out of the tarball is this defect.
83
+ # A path that exists nowhere is the ordinary phantom-reference class the ref-path
84
+ # check above already owns; a path outside files[] that is also absent is nothing.
85
+ if os.path.exists(m) and not covered(m) and m not in accepted:
86
+ phantom.setdefault(m, set()).add(s)
87
+
88
+ # Impossible-zero guard: this repo always has shipped docs. Zero scanned means the extractor
89
+ # broke — report that as a failure rather than letting a dead check print a pass
90
+ # (same rule as count_check.sh and the ref-path extractor).
91
+ if not shipped:
92
+ print("EXTRACTOR_BROKE")
93
+ raise SystemExit(2)
94
+
95
+ for p, srcs in sorted(phantom.items(), key=lambda kv: (-len(kv[1]), kv[0])):
96
+ print(f"{p}\t{len(srcs)}\t{sorted(srcs)[0]}")
97
+ raise SystemExit(1 if phantom else 0)
98
+ PY
99
+ )
100
+ rc=$?
101
+
102
+ if [ "$rc" -eq 2 ] || [ "$out" = "EXTRACTOR_BROKE" ]; then
103
+ echo "FAIL package-coverage: extractor scanned 0 shipped docs — the check broke, it did not pass"
104
+ exit 1
105
+ fi
106
+
107
+ if [ "$rc" -ne 0 ]; then
108
+ echo "FAIL package-coverage: shipped document(s) point at file(s) the package omits:"
109
+ printf '%s\n' "$out" | while IFS=$'\t' read -r path n src; do
110
+ [ -z "$path" ] && continue
111
+ printf ' %s (named by %s shipped doc(s), e.g. %s)\n' "$path" "$n" "$src"
112
+ done
113
+ echo " Fix: add the path to package.json files[], OR list it in ACCEPTED_ABSENT here"
114
+ echo " with a one-sentence reason why shipping it would be wrong."
115
+ exit 1
116
+ fi
117
+
118
+ echo "PASS package-coverage: every referenced path is shipped or explicitly accepted (${#ACCEPTED_ABSENT[@]} accepted)"
119
+ exit 0