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

@@ -80,26 +80,45 @@ done
# Prints the `files:` regex of every hook, in either manifest, whose entry is
# $1's vale-wrap.sh. Records are delimited by their `- id:` line, so the check
# does not depend on `entry:` preceding `files:` within a record.
#
# Cached per skill (in HOOK_REGEX_CACHE, populated lazily) because the final
# validation loop below probes agent-audit twice — once for its CC agent-file
# shape, once for its Copilot .agent.md shape — and both probes need the same
# regex set. Without the cache, that pair of calls would each re-parse both
# manifest files from scratch for no new information. HOOK_REGEX_CACHE_SEEN is
# a separate array so a skill with no matching hooks (empty result) is still
# recognized as already computed, rather than re-parsed every call.
declare -A HOOK_REGEX_CACHE=()
declare -A HOOK_REGEX_CACHE_SEEN=()
hook_file_regexes() {
local skill="$1" manifest raw
for manifest in "$REPO_ROOT/.pre-commit-hooks.yaml" "$REPO_ROOT/.pre-commit-config.yaml"; do
[[ -f "$manifest" ]] || continue
awk -v skill="$skill" '
function flush() {
if (entry ~ skill "/scripts/vale-wrap.sh" && files != "") print files
entry = ""; files = ""
}
/^[ \t]*-[ \t]*id:/ { flush() }
/^[ \t]*entry:/ { entry = $0 }
/^[ \t]*files:/ { files = $0; sub(/^[ \t]*files:[ \t]*/, "", files) }
END { flush() }
' "$manifest"
done | while IFS= read -r raw; do
# Strip the surrounding YAML quotes; the regex itself never carries them.
raw="${raw%\'}"; raw="${raw#\'}"
raw="${raw%\"}"; raw="${raw#\"}"
printf '%s\n' "$raw"
done
local skill="$1" manifest raw result
if [[ -n "${HOOK_REGEX_CACHE_SEEN[$skill]:-}" ]]; then
printf '%s' "${HOOK_REGEX_CACHE[$skill]}"
return
fi
result="$(
for manifest in "$REPO_ROOT/.pre-commit-hooks.yaml" "$REPO_ROOT/.pre-commit-config.yaml"; do
[[ -f "$manifest" ]] || continue
awk -v skill="$skill" '
function flush() {
if (entry ~ skill "/scripts/vale-wrap.sh" && files != "") print files
entry = ""; files = ""
}
/^[ \t]*-[ \t]*id:/ { flush() }
/^[ \t]*entry:/ { entry = $0 }
/^[ \t]*files:/ { files = $0; sub(/^[ \t]*files:[ \t]*/, "", files) }
END { flush() }
' "$manifest"
done | while IFS= read -r raw; do
# Strip the surrounding YAML quotes; the regex itself never carries them.
raw="${raw%\'}"; raw="${raw#\'}"
raw="${raw%\"}"; raw="${raw#\"}"
printf '%s\n' "$raw"
done
)"
HOOK_REGEX_CACHE[$skill]="$result"
HOOK_REGEX_CACHE_SEEN[$skill]=1
printf '%s' "$result"
}
# Asks vale — the thing that actually applies these globs — whether a config