refactor(lint): cache manifest parsing, single-pass size check

Two more efficiency findings from the same code-review pass:

- check-vale-style-sync.sh's hook_file_regexes() reparsed both
  pre-commit manifests from scratch on every call. The final
  validation loop calls it once per probe (3 probes: skill-audit once,
  agent-audit twice for its two file shapes), so agent-audit's regex
  set was being parsed twice for no reason. Now cached per skill in a
  lazily-populated associative array, with a separate "seen" map so an
  empty result isn't mistaken for "not yet computed."
- skill-size-check.sh read the target file twice (separate awk and
  wc -w calls) to get line and word counts; now a single awk pass
  returns both. Also documented, next to MAX_LINES/MAX_WORDS, why
  those constants are duplicated against skill-audit/scripts/
  validate.sh's Python implementation rather than unified — same
  cross-language/cross-context tradeoff as vale-wrap.sh's duplication,
  guarded by tests/test-skill-size-check.sh's drift check.

Verified: test-check-vale-style-sync.sh 20/20, test-skill-size-check.sh
9/9, full suite 12/12, pre-commit --all-files clean.
This commit is contained in:
2026-08-09 20:14:36 +00:00
parent 680aa4f43c
commit e62f68a1cc
2 changed files with 53 additions and 24 deletions

View File

@@ -35,6 +35,13 @@ set -euo pipefail
# tokenization and does not replace one. Re-measure the corpus before treating
# any of these numbers as still current.
# These constants are intentionally duplicated in
# skill-audit/scripts/validate.sh (Python) rather than shared from one file:
# this script is a standalone bash pre-commit hook, that one is an in-skill
# Python validator invoked in a different context (same rationale as
# vale-wrap.sh's per-plugin duplication — see its own header comment).
# tests/test-skill-size-check.sh asserts both files agree on these values, so
# drift between them fails CI rather than silently diverging.
MAX_LINES=500
MAX_WORDS=2770
FAIL=0
@@ -42,16 +49,19 @@ FAIL=0
for f in "$@"; do
[[ -f "$f" ]] || continue
# awk's NR counts the final line even without a trailing newline, matching
# Python's splitlines() semantics (used by skill-audit/scripts/validate.sh
# for its own line count) — `wc -l` undercounts by 1 in that case.
lines=$(awk 'END{print NR}' "$f")
# Single awk pass computes both line count and word count, avoiding a
# second read of the file. NR counts the final line even without a
# trailing newline, matching Python's splitlines() semantics (used by
# skill-audit/scripts/validate.sh for its own line count) — `wc -l`
# undercounts by 1 in that case. Word count uses awk's default
# whitespace-splitting NF, matching `wc -w` semantics.
read -r lines words <<< "$(awk '{w += NF} END{print NR, w+0}' "$f")"
if (( lines > MAX_LINES )); then
echo "ERROR: $f has $lines lines, exceeding the $MAX_LINES-line ceiling (agentskills.io skill-authoring.md)" >&2
FAIL=1
fi
words=$(wc -w < "$f")
if (( words > MAX_WORDS )); then
echo "ERROR: $f has $words words (proxy for tokens), exceeding the $MAX_WORDS-word ceiling (~5,000 tokens, agentskills.io skill-authoring.md)" >&2
FAIL=1