@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.
- package/.claude/rules/.public-surface-patterns.defaults +44 -0
- package/.claude/rules/fh_4axis_gate.md +207 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +26 -2
- package/CATALOG.md +31 -0
- package/docs/ETHOS.md +106 -0
- package/docs/OUTPUT_EVIDENCE.md +118 -0
- package/docs/WHY.md +42 -0
- package/knowledge/patterns/ensemble_union_detection_task_pattern.md +125 -0
- package/knowledge/shared/GLOSSARY.md +77 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +10 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +554 -0
- package/knowledge/shared/patterns/multi-persona-review.md +88 -0
- package/knowledge/shared/plugin-catalog/recommended_plugins.md +117 -0
- package/package.json +42 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
- package/plugins/fh-meta/CHANGELOG.md +617 -0
- package/plugins/fh-meta/skills/context-doctor/SKILL.md +42 -4
- package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +38 -0
- package/scripts/below_floor_scan.sh +91 -0
- package/scripts/chamber_candidate_collect.sh +223 -0
- package/scripts/chamber_run.sh +184 -0
- package/scripts/degrade_direction_scan.sh +222 -0
- package/scripts/fh_env_delta_scan.sh +108 -0
- package/scripts/fh_session_load.sh +202 -0
- package/scripts/gate_pathspec_check.sh +166 -0
- package/scripts/package_coverage_check.sh +119 -0
- package/scripts/prepush_guard_check.sh +374 -0
- package/scripts/psa_scan_lib.sh +153 -0
- package/scripts/public_surface_scan_files.sh +157 -0
- package/scripts/selfcheck.sh +28 -0
- package/scripts/session_close_check.sh +171 -0
- package/scripts/substrate_jump_detector.sh +60 -0
- package/scripts/test_degrade_scan_shell_probes.sh +185 -0
- package/scripts/test_marker_floor_lanes.sh +45 -0
- package/scripts/test_prepush_stdin_integrity.sh +119 -0
- package/scripts/tier_census_grep.sh +54 -0
- package/scripts/universal_guard_check.sh +280 -0
- package/templates/.claude/rules/mcp_tool_gating.md +157 -0
- package/templates/.claude/rules/session.md +153 -0
- package/templates/.git-hooks/pre-commit +848 -0
- package/templates/.git-hooks/pre-push +585 -0
- package/templates/PRE-PUBLISH-CHECKLIST.md +85 -0
- package/templates/contrib_session.md +34 -0
- package/templates/degrade_direction_scan.sh +222 -0
- package/templates/goal-quench-hook-setup.md +152 -0
- package/templates/predelete_check.sh +72 -0
- package/templates/regression_guard.sh +563 -0
- package/templates/starter_profile.md +83 -0
- 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
|