@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +18 -0
- package/CHEATSHEET.md +1 -1
- package/CLAUDE.md +11 -4
- package/README.md +19 -0
- package/knowledge/shared/harness-core/fh_detail_protocols.md +12 -0
- package/knowledge/shared/harness-core/ship_readiness_gate.md +7 -4
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +113 -1
- package/package.json +10 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/agents/quench-challenger.md +49 -23
- package/plugins/fh-commons/skills/convergence-loop/SKILL.md +14 -0
- package/plugins/fh-commons/skills/deliberation/SKILL.md +14 -0
- package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +10 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +36 -0
- package/plugins/fh-meta/agents/beginner.md +4 -1
- package/plugins/fh-meta/agents/challenger.md +7 -1
- package/plugins/fh-meta/agents/expert.md +1 -1
- package/plugins/fh-meta/agents/fact-checker.md +7 -1
- package/plugins/fh-meta/agents/hub-persona-auditor.md +2 -1
- package/plugins/fh-meta/agents/main-player.md +4 -1
- package/plugins/fh-meta/agents/persona-innovator.md +10 -2
- package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
- package/plugins/fh-meta/skills/apex-review/SKILL.md +5 -0
- package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +38 -8
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +16 -2
- package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +45 -10
- package/plugins/fh-meta/skills/corpus-grounding-expander/SKILL.md +14 -5
- package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +93 -30
- package/plugins/fh-meta/skills/deep-clarify/SKILL.md +28 -9
- package/plugins/fh-meta/skills/fh/SKILL.md +4 -0
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +64 -8
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +20 -7
- package/plugins/fh-meta/skills/goal-quench/SKILL.md +48 -15
- package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +58 -11
- package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +109 -33
- package/plugins/fh-meta/skills/harvest-loop/SKILL.md +6 -1
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +126 -17
- package/plugins/fh-meta/skills/install-doctor/SKILL.md +50 -14
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +26 -7
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +68 -21
- package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +64 -17
- package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +38 -4
- package/plugins/fh-meta/skills/persona-roster-expander/SKILL.md +15 -7
- package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +39 -11
- package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +24 -7
- package/plugins/fh-meta/skills/prompt-regression/SKILL.md +54 -11
- package/plugins/fh-meta/skills/salience-splitter/SKILL.md +120 -7
- package/plugins/fh-meta/skills/salience-splitter/SKILL_detail.md +46 -13
- package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +28 -3
- package/plugins/fh-meta/skills/steel-quench/SKILL.md +3 -1
- package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +72 -14
- package/scripts/compaction_probe.sh +153 -10
- package/scripts/consent_registry_check.sh +63 -0
- package/scripts/count_check.sh +47 -1
- package/scripts/degrade_direction_scan.sh +276 -6
- package/scripts/degrade_probe_capability.sh +105 -0
- package/scripts/lane_runner_check.sh +425 -0
- package/scripts/package_coverage_check.sh +190 -35
- package/scripts/psa_probe_capability.sh +78 -0
- package/scripts/psa_scan_lib.sh +164 -0
- package/scripts/public_surface_scan_files.sh +38 -0
- package/scripts/selfcheck.sh +346 -28
- package/scripts/test_capability_entrypoint_shipping.sh +132 -0
- package/scripts/test_consent_registry.sh +54 -0
- package/scripts/test_count_check_readme_format_lanes.sh +75 -0
- package/scripts/test_degrade_scan_shell_probes.sh +415 -0
- package/scripts/test_marker_crossfamily_lanes.sh +10 -1
- package/scripts/test_marker_floor_lanes.sh +25 -1
- package/scripts/test_psa_singlefile_lanes.sh +288 -0
- package/scripts/test_reviewer_capability_conformance.sh +10 -1
- package/scripts/test_selfcheck_state_lanes.sh +173 -0
- package/scripts/validate_yaml.sh +146 -0
- package/templates/degrade_direction_scan.sh +276 -6
- 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
|