@chrono-meta/fh-gate 1.4.95 → 1.4.97

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 (76) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/AGENTS.md +18 -0
  3. package/CHEATSHEET.md +1 -1
  4. package/CLAUDE.md +11 -4
  5. package/README.md +19 -0
  6. package/knowledge/shared/harness-core/fh_detail_protocols.md +12 -0
  7. package/knowledge/shared/harness-core/ship_readiness_gate.md +7 -4
  8. package/knowledge/shared/learnings/subagent_invocations_log.yaml +113 -1
  9. package/package.json +10 -2
  10. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  11. package/plugins/fh-commons/agents/quench-challenger.md +49 -23
  12. package/plugins/fh-commons/skills/convergence-loop/SKILL.md +14 -0
  13. package/plugins/fh-commons/skills/deliberation/SKILL.md +14 -0
  14. package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +10 -1
  15. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  16. package/plugins/fh-meta/CHANGELOG.md +36 -0
  17. package/plugins/fh-meta/agents/beginner.md +4 -1
  18. package/plugins/fh-meta/agents/challenger.md +7 -1
  19. package/plugins/fh-meta/agents/expert.md +1 -1
  20. package/plugins/fh-meta/agents/fact-checker.md +7 -1
  21. package/plugins/fh-meta/agents/hub-persona-auditor.md +2 -1
  22. package/plugins/fh-meta/agents/main-player.md +4 -1
  23. package/plugins/fh-meta/agents/persona-innovator.md +10 -2
  24. package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
  25. package/plugins/fh-meta/skills/apex-review/SKILL.md +5 -0
  26. package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +38 -8
  27. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +16 -2
  28. package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +45 -10
  29. package/plugins/fh-meta/skills/corpus-grounding-expander/SKILL.md +14 -5
  30. package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +93 -30
  31. package/plugins/fh-meta/skills/deep-clarify/SKILL.md +28 -9
  32. package/plugins/fh-meta/skills/fh/SKILL.md +4 -0
  33. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +64 -8
  34. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +20 -7
  35. package/plugins/fh-meta/skills/goal-quench/SKILL.md +48 -15
  36. package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +58 -11
  37. package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +109 -33
  38. package/plugins/fh-meta/skills/harvest-loop/SKILL.md +6 -1
  39. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +126 -17
  40. package/plugins/fh-meta/skills/install-doctor/SKILL.md +50 -14
  41. package/plugins/fh-meta/skills/install-wizard/SKILL.md +26 -7
  42. package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +68 -21
  43. package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +64 -17
  44. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +38 -4
  45. package/plugins/fh-meta/skills/persona-roster-expander/SKILL.md +15 -7
  46. package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +39 -11
  47. package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +24 -7
  48. package/plugins/fh-meta/skills/prompt-regression/SKILL.md +54 -11
  49. package/plugins/fh-meta/skills/salience-splitter/SKILL.md +120 -7
  50. package/plugins/fh-meta/skills/salience-splitter/SKILL_detail.md +46 -13
  51. package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +28 -3
  52. package/plugins/fh-meta/skills/steel-quench/SKILL.md +3 -1
  53. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +72 -14
  54. package/scripts/compaction_probe.sh +153 -10
  55. package/scripts/consent_registry_check.sh +63 -0
  56. package/scripts/count_check.sh +47 -1
  57. package/scripts/degrade_direction_scan.sh +276 -6
  58. package/scripts/degrade_probe_capability.sh +105 -0
  59. package/scripts/lane_runner_check.sh +425 -0
  60. package/scripts/package_coverage_check.sh +190 -35
  61. package/scripts/psa_probe_capability.sh +78 -0
  62. package/scripts/psa_scan_lib.sh +164 -0
  63. package/scripts/public_surface_scan_files.sh +38 -0
  64. package/scripts/selfcheck.sh +346 -28
  65. package/scripts/test_capability_entrypoint_shipping.sh +132 -0
  66. package/scripts/test_consent_registry.sh +54 -0
  67. package/scripts/test_count_check_readme_format_lanes.sh +75 -0
  68. package/scripts/test_degrade_scan_shell_probes.sh +415 -0
  69. package/scripts/test_marker_crossfamily_lanes.sh +10 -1
  70. package/scripts/test_marker_floor_lanes.sh +25 -1
  71. package/scripts/test_psa_singlefile_lanes.sh +288 -0
  72. package/scripts/test_reviewer_capability_conformance.sh +10 -1
  73. package/scripts/test_selfcheck_state_lanes.sh +173 -0
  74. package/scripts/validate_yaml.sh +146 -0
  75. package/templates/degrade_direction_scan.sh +276 -6
  76. package/templates/goal-quench-settings-merged.json +30 -0
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env bash
2
+ # test_capability_entrypoint_shipping.sh — every typed CAPABILITY ENTRY POINT must be in the
3
+ # npm published file set.
4
+ #
5
+ # WHY THIS EXISTS (measured 2026-08-12, cross-family gpt-5.5 + governor widening)
6
+ # `scripts/degrade_probe_capability.sh` AND `scripts/psa_probe_capability.sh` were both absent from
7
+ # `package.json` `files[]`, while their VALIDATOR (`capability_registry_check.sh`) shipped. An npm
8
+ # consumer therefore received the thing that checks capabilities and none of the capabilities — the
9
+ # release's headline feature (scanning ```bash/```python fences) was unreachable on the typed path
10
+ # it advertises.
11
+ #
12
+ # WHY `package_coverage_check.sh` DID NOT CATCH IT — and why this is a separate check rather than a
13
+ # rule added there: that checker walks *references* (a shipped doc names a path → the path must
14
+ # ship). These two files are referenced by NOTHING but their own header. A reference-follower is
15
+ # structurally blind to an ORPHAN; you cannot fix that by adding another pattern to it. The
16
+ # discriminator here is not "is it referenced" but "is it an entry point", which is knowable from
17
+ # the filename convention alone.
18
+ #
19
+ # CLASS: third occurrence of "a shipped surface points outside files[]" in this release cycle
20
+ # 1. SKILL.md pointed at a runner that was not shipped (caught by CI only)
21
+ # 2. AGENTS.md pointed at validate_yaml.sh, not shipped (caught by CI only)
22
+ # 3. the capability entry points themselves, not shipped (caught by cross-family only)
23
+ # The standing rule is N>=3 → mechanize at the front instead of relying on the next reviewer.
24
+ #
25
+ # Usage: bash scripts/test_capability_entrypoint_shipping.sh
26
+ # Exit: 0 = every entry point ships; 1 = at least one is missing (or the check could not run).
27
+ set -uo pipefail
28
+
29
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
30
+ cd "$REPO_ROOT" || { echo "FAIL: cannot cd to repo root"; exit 1; }
31
+
32
+ pass=0; fail=0
33
+ ok() { printf ' \342\234\205 %s\n' "$1"; pass=$((pass+1)); }
34
+ bad() { printf ' \342\235\214 %s\n' "$1"; fail=$((fail+1)); }
35
+
36
+ echo "capability entry-point shipping check"
37
+
38
+ command -v node >/dev/null 2>&1 || {
39
+ echo " INSTRUMENT ERROR: node unavailable — files[] cannot be read. NOT a pass."; exit 1; }
40
+
41
+ # Discover entry points by convention. A DISCOVERY of zero is an instrument failure, not a clean
42
+ # result: this check exists precisely because the surface it guards is invisible to reference
43
+ # walking, so "found nothing to check" must never render as "everything ships".
44
+ ENTRIES=$(find scripts -maxdepth 1 -type f -name '*_capability.sh' 2>/dev/null | sort)
45
+ n=$(printf '%s\n' "$ENTRIES" | grep -c . || true)
46
+ n=$(( ${n:-0} + 0 ))
47
+ if [ "$n" -eq 0 ]; then
48
+ echo " INSTRUMENT ERROR: zero *_capability.sh files discovered — the scan did not reach its target."
49
+ echo " (A real zero is possible only if this repo has no typed capabilities; verify by hand before believing it.)"
50
+ exit 1
51
+ fi
52
+
53
+ FILES_JSON=$(node -e 'process.stdout.write(JSON.stringify(require("./package.json").files||[]))' 2>/dev/null) || {
54
+ echo " INSTRUMENT ERROR: could not read package.json files[]"; exit 1; }
55
+
56
+ printf '%s\n' "$ENTRIES" | while IFS= read -r e; do
57
+ [ -n "$e" ] || continue
58
+ if node -e 'const f=JSON.parse(process.argv[1]);process.exit(f.includes(process.argv[2])?0:1)' "$FILES_JSON" "$e"; then
59
+ echo " ok $e"
60
+ else
61
+ echo " MISSING $e"
62
+ fi
63
+ done > "${TMPDIR:-/tmp}/cap_entry_$$.txt"
64
+
65
+ missing=$(grep -c '^ MISSING ' "${TMPDIR:-/tmp}/cap_entry_$$.txt" || true)
66
+ missing=$(( ${missing:-0} + 0 ))
67
+ shipped=$(grep -c '^ ok ' "${TMPDIR:-/tmp}/cap_entry_$$.txt" || true)
68
+ shipped=$(( ${shipped:-0} + 0 ))
69
+ cat "${TMPDIR:-/tmp}/cap_entry_$$.txt"
70
+ rm -f "${TMPDIR:-/tmp}/cap_entry_$$.txt"
71
+
72
+ if [ "$missing" -eq 0 ]; then
73
+ ok "all $shipped capability entry point(s) are in package.json files[]"
74
+ else
75
+ bad "$missing capability entry point(s) absent from files[] — npm consumers get the validator without the capability"
76
+ fi
77
+
78
+ # REVERSE DIRECTION (added 2026-08-12 after a cross-family round broke the first version).
79
+ # The check above walks disk → files[]. That direction alone is blind to the failure that actually
80
+ # loses a capability: MOVE OR DELETE the entry-point file. Discovery then simply does not see it,
81
+ # every remaining entry is still listed, and the lane goes green while `npm pack` ships one fewer
82
+ # capability. So also walk files[] → disk: anything declared as a capability entry point must exist.
83
+ # (A capability removed from BOTH sides is an intentional deletion and correctly flags nothing.)
84
+ DECLARED=$(node -e '
85
+ const f=JSON.parse(process.argv[1]);
86
+ process.stdout.write(f.filter(p=>/^scripts\/.*_capability\.sh$/.test(p)).join("\n"));
87
+ ' "$FILES_JSON")
88
+ dangling=0
89
+ if [ -n "$DECLARED" ]; then
90
+ while IFS= read -r d; do
91
+ [ -n "$d" ] || continue
92
+ # `-f` alone is not enough, and both gaps were demonstrated (round 5): a SYMLINK satisfies `-f`
93
+ # but `npm pack` does not follow it — the tarball simply omits the file — and a ZERO-BYTE file
94
+ # satisfies every existence test while shipping an empty capability. Existence is the weakest
95
+ # of the three properties; assert all of them.
96
+ if [ ! -e "$d" ]; then
97
+ echo " DANGLING $d (declared in files[], absent on disk)"; dangling=$((dangling+1))
98
+ elif [ -L "$d" ]; then
99
+ echo " SYMLINK $d (npm pack does not follow symlinks — the tarball would omit it)"; dangling=$((dangling+1))
100
+ elif [ ! -f "$d" ]; then
101
+ # A DIRECTORY passes -e, is not a symlink, and `-s` reports non-zero size for it — so the
102
+ # three tests above all agreed a directory was a fine entry point (round 6). Regular-file-ness
103
+ # is the property actually being claimed; assert it rather than three proxies for it.
104
+ echo " NOT-A-FILE $d (declared as an entry point but is not a regular file)"; dangling=$((dangling+1))
105
+ elif [ ! -s "$d" ]; then
106
+ echo " EMPTY $d (zero bytes — ships an entry point that cannot run)"; dangling=$((dangling+1))
107
+ fi
108
+ done <<EOF
109
+ $DECLARED
110
+ EOF
111
+ fi
112
+ declared_n=$(printf '%s\n' "$DECLARED" | grep -c . || true); declared_n=$(( ${declared_n:-0} + 0 ))
113
+ if [ "$declared_n" -eq 0 ]; then
114
+ bad "files[] declares ZERO capability entry points — either the convention changed or the list was gutted; a disk-only check cannot notice that"
115
+ elif [ "$dangling" -eq 0 ]; then
116
+ ok "all $declared_n declared capability entry point(s) exist on disk (move/delete would be caught)"
117
+ else
118
+ bad "$dangling declared capability entry point(s) missing from disk — npm would ship a broken files[] and the disk-side check alone stays green"
119
+ fi
120
+
121
+ # CONTROL — the check must be able to say NO. A checker that only ever prints ok is indistinguishable
122
+ # from a checker that is not looking; assert the negative arm on a name that cannot be in files[].
123
+ if node -e 'const f=JSON.parse(process.argv[1]);process.exit(f.includes(process.argv[2])?0:1)' \
124
+ "$FILES_JSON" "scripts/definitely_not_shipped_$$.sh"; then
125
+ bad "CONTROL DEAD — a nonexistent path reported as shipped; the membership test is not testing"
126
+ else
127
+ ok "control alive — a nonexistent path is correctly reported as not shipped"
128
+ fi
129
+
130
+ echo "----"
131
+ echo "capability entry-point shipping: $pass passed, $fail failed (entries=$n, missing=$missing)"
132
+ [ "$fail" -eq 0 ] || exit 1
@@ -728,6 +728,60 @@ YAML
728
728
  lane "P-SPELL a grant WIDER than its class is still refused (the paired control)" 1 "R7"
729
729
 
730
730
 
731
+ # ── PROV : every verdict states what it measured WITH, and it states the TRUTH ──────────────────
732
+ # 2026-08-12. This gate rides selfcheck → prepublishOnly. A release shipped green from a session whose
733
+ # `python3` resolved to an unrelated project's venv that had PyYAML, while the machine's own python3
734
+ # did not. Nothing was bypassed — the PASS was simply not portable, and said nothing about what
735
+ # produced it.
736
+ #
737
+ # 🟥 The first version of this lane compared two arms, one of them "PyYAML hidden" via
738
+ # `env -i PATH=/usr/bin:/bin PYTHONNOUSERSITE=1`. It passed locally and FAILED IN CI — because
739
+ # `PYTHONNOUSERSITE` suppresses only the USER site directory. On this author's machine PyYAML was a
740
+ # `--user` install so it hid; on the CI runner it is a system `dist-packages` install so it did not,
741
+ # both arms returned the same value, and the lane called its own subject decorative.
742
+ # **The control worked only by accident of how the author happened to install a package** — which is
743
+ # the exact defect class the subject under test exists to catch, reproduced inside its own lane.
744
+ #
745
+ # So the lane no longer manufactures an environment. It compares the reported value against an
746
+ # INDEPENDENT ORACLE computed in the same run: whatever `find_spec` says here, the instrument line
747
+ # must say the same thing. That catches removal, hard-coding, truncation, and drift — everywhere,
748
+ # with no assumption about how PyYAML got installed.
749
+ _prov_line=$(bash "$CHK" 2>&1 | grep -o 'instrument (consent-registry): .*' | head -1)
750
+ _prov_oracle=$(python3 - <<'ORACLE' 2>/dev/null
751
+ import importlib.util, sys
752
+ s = importlib.util.find_spec("yaml")
753
+ print((s.origin or "namespace-package") if s is not None else "ABSENT")
754
+ ORACLE
755
+ )
756
+ if [ -z "$_prov_line" ]; then
757
+ echo " ❌ PROV-1 no instrument line on the ordinary path"; fail=$((fail+1))
758
+ elif [ -z "$_prov_oracle" ]; then
759
+ echo " ❌ PROV-1 oracle produced nothing — NOT RUN (unmeasured, not a pass)"; fail=$((fail+1))
760
+ elif ! printf '%s' "$_prov_line" | grep -q ': /'; then
761
+ echo " ❌ PROV-1 line does not name an absolute interpreter path: [$_prov_line]"; fail=$((fail+1))
762
+ elif ! printf '%s' "$_prov_line" | grep -qF "PyYAML $_prov_oracle"; then
763
+ echo " ❌ PROV-1 line disagrees with the oracle — reported [$_prov_line] vs actual [$_prov_oracle]"; fail=$((fail+1))
764
+ else
765
+ echo " ✅ PROV-1 instrument line matches an independent resolution of PyYAML [$_prov_oracle]"; pass=$((pass+1))
766
+ fi
767
+
768
+ # PROV-2 — the ABSENT branch, exercised DETERMINISTICALLY rather than by hiding a package.
769
+ # A `yaml.py` MODULE FILE placed first on PYTHONPATH wins by path order, so find_spec resolves to it
770
+ # — a different, predictable answer that does not depend on how the real PyYAML was installed.
771
+ # ⚠️ A `yaml/` DIRECTORY does NOT work and the first draft used one: a namespace package has LOWER
772
+ # precedence than a regular package, so Python keeps scanning the whole path and still finds the real
773
+ # one. Measured — the arm did not move, and the lane correctly called itself decorative.
774
+ _prov_tmp=$(mktemp -d); printf '# shadow module for the PROV-2 arm\n' > "$_prov_tmp/yaml.py"
775
+ _prov_shadow=$(PYTHONPATH="$_prov_tmp" bash "$CHK" 2>&1 | grep -o 'instrument (consent-registry): .*' | head -1)
776
+ rm -rf "$_prov_tmp"
777
+ if [ -z "$_prov_shadow" ]; then
778
+ echo " ❌ PROV-2 no instrument line under a shadowed yaml"; fail=$((fail+1))
779
+ elif [ "$_prov_shadow" = "$_prov_line" ]; then
780
+ echo " ❌ PROV-2 line did not move when the resolution moved — decorative [$_prov_line]"; fail=$((fail+1))
781
+ else
782
+ echo " ✅ PROV-2 line tracks the resolution when it changes (shadowed → different value)"; pass=$((pass+1))
783
+ fi
784
+
731
785
  echo "----"
732
786
  echo "consent-registry anchor: $pass passed, $fail failed"
733
787
  [ "$fail" -eq 0 ] || exit 1
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # test_count_check_readme_format_lanes.sh — known-pair anchor for the README format override guard
3
+ # in `scripts/count_check.sh`.
4
+ #
5
+ # WHY THIS EXISTS
6
+ # `count_check.sh` is a **mandatory-pass** gate wired into selfcheck → prepublishOnly, pre-commit
7
+ # and CI. Its README check renders a caller-supplied printf template and uses the RESULT as a
8
+ # `grep -qE` pattern. Two ways that goes wrong, both measured 2026-08-12:
9
+ # · a template rendering a NEWLINE turns the pattern into an OR search — a stale README (99/99)
10
+ # PASSED because the trailing fragment "8" matched "Node 18" elsewhere in the file;
11
+ # · `printf` failure and value-swallowing directives (`%.0s`) produced partial patterns, unchecked.
12
+ # The guard that closes this shipped with **zero** anchors in the same commit that added four
13
+ # anchors for a sibling fix — the exact "you can delete the feature and stay green" class that
14
+ # commit was closing. This file is that missing half.
15
+ #
16
+ # SCOPE, stated honestly: these lanes exercise the guard's DECISION LOGIC in isolation (render →
17
+ # inspect → accept/reject), not the whole count_check run. The full script needs a git worktree
18
+ # with a populated plugin tree; a fixture that lacks one fails for an unrelated reason ("0 active
19
+ # fh-meta skills") and every arm goes red for the SAME wrong cause — which is how a first attempt
20
+ # at this test nearly certified a guard that had not been exercised at all.
21
+ set -uo pipefail
22
+
23
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
24
+ SUBJECT="$REPO_ROOT/scripts/count_check.sh"
25
+ pass=0; fail=0
26
+ ok() { printf ' \342\234\205 %s\n' "$1"; pass=$((pass+1)); }
27
+ bad() { printf ' \342\235\214 %s\n' "$1"; fail=$((fail+1)); }
28
+
29
+ # The guard block is extracted from the subject rather than restated here: a copy drifts from the
30
+ # thing it claims to verify, which is the divergent-normalizer defect this repo has hit before.
31
+ GUARD=$(awk '/^ README_STR=\$\(printf/,/^ \[ -n "\$README_STR" \] && count_check/' "$SUBJECT")
32
+ if [ -z "$GUARD" ]; then
33
+ bad "guard block not found in count_check.sh — the anchor cannot verify what it cannot locate"
34
+ echo "----"; echo "count_check README-format lanes: $pass passed, $fail failed"; exit 1
35
+ fi
36
+
37
+ probe() { # $1 = template ; echoes rc + a reason token
38
+ local fmt="$1"
39
+ bash -c '
40
+ set -uo pipefail
41
+ total_sk=40; total_ag=8; fail=0
42
+ README_FMT="$1"
43
+ count_check() { echo "ACCEPTED:$3"; }
44
+ '"$GUARD"'
45
+ exit $fail
46
+ ' _ "$fmt" 2>&1
47
+ return $?
48
+ }
49
+
50
+ run() { # $1 = label ; $2 = template ; $3 = expect (accept|reject)
51
+ local out rc; out=$(probe "$2"); rc=$?
52
+ case "$3" in
53
+ accept) if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q 'ACCEPTED:'; then
54
+ ok "$1 — accepted (no over-block)"
55
+ else bad "$1 — legitimate template was REJECTED (rc=$rc): $out"; fi ;;
56
+ reject) if [ "$rc" -ne 0 ]; then ok "$1 — rejected (rc=$rc)"
57
+ else bad "$1 — a broken template PASSED the gate: $out"; fi ;;
58
+ esac
59
+ }
60
+
61
+ echo "== count_check README format override — known pair =="
62
+ # Accept arms. Two of them on purpose: the default and a localized one. The whole point of the
63
+ # override is downstream harnesses writing their own wording, so an English-only accept arm would
64
+ # not notice a guard that rejects every non-ASCII template.
65
+ run "A1 default English" '%s skills · %s agents' accept
66
+ run "A2 localized (Korean)" '스킬 %s개 · 에이전트 %s개' accept
67
+ run "A3 markdown decoration" '**%s** skills / **%s** agents' accept
68
+ # Reject arms — each must fail for its OWN reason, not a shared incidental one.
69
+ run "R1 renders a newline" '%s
70
+ %s' reject
71
+ run "R2 swallows a value" '%.0s%s agents' reject
72
+
73
+ echo "----"
74
+ echo "count_check README-format lanes: $pass passed, $fail failed"
75
+ [ "$fail" -eq 0 ] || exit 1