Files
holocron/scripts/check-vale-style-sync.sh
Defame1297 56cc173f65 fix: re-anchor doc citations that the CONTEXT.md trim broke
Why: eight comments and one status note cited CONTEXT.md or AGENTS.md text that
b9c7762 and 1929ffd moved or deleted. All are inert at runtime, but they are the
rationale comments that tell the next maintainer why an assertion exists, and
they now name a file that no longer explains it.

Implementation notes: re-anchored by what the citation is for, not uniformly.
- Four sites quoted facts ADR-0013 owns — every rule is `level: error` with no
  ignorable tier (ADR-0013:59-70), and KyberforgeCopilot's `.agent.md`-only
  scope (ADR-0013:43-46). These now cite ADR-0013. ADRs are append-only here;
  the spec docs are refactored, which is what caused this rot.
- Two sites quoted the glob location-independence property, which no ADR owns.
  The quote was already inline and carried the full rationale, so the citation
  added a rot surface and no information — dropped, statement kept.
- sync-marketplace-mirror.sh's header attributed the mirror-not-a-profile fact
  to CONTEXT.md; the parenthetical beside it already carries the evidence, so
  the attribution is dropped rather than re-pointed.
- .pre-commit-config.yaml cited an AGENTS.md instruction that no longer exists;
  generalised to "the documented instruction".
- LESSONS.md:29 misquoted AGENTS.md's current session-start line.

Also corrects a pre-existing misattribution at tests/test-vale-wrap.sh:454:
AGENTS.md has never named bash 3.2 as a repo target (`git log -S'3.2'` on it is
empty). LESSONS.md and the script headers do.

Impact: no behaviour change. test-check-vale-style-sync.sh and test-vale-wrap.sh
both pass (42 passed, 0 failed).
2026-08-17 10:09:51 +00:00

414 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Kyberforge's Vale prefilter is duplicated into skill-audit and agent-audit's own
# scripts/assets (per plugins/kyberforge/.apm/skills/skill-author/references/deployment-modes.md's
# no-cross-skill-path rule: a plugin's cache-install copy only includes each skill's own files).
# agent-audit's copy is canonical — it's the superset (Kyberforge + KyberforgeCopilot) that the
# repo root's own pre-commit hook and .pre-commit-hooks.yaml both consume. This fails the build
# if skill-audit's copy has drifted from it, since nothing else would catch a rule fix landing in
# only one of the two. Run from repo root or pass REPO_ROOT as arg.
REPO_ROOT="${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
# A nonexistent REPO_ROOT must fail loudly, not fall through to the "neither
# copy present" no-op below — that guard exists for a repo that legitimately
# has no kyberforge plugin installed, not for a typo'd or stale path, and a
# clean exit 0 here would read as "checked, in sync" when nothing ran at all.
if [[ ! -d "$REPO_ROOT" ]]; then
echo "Vale style sync check failed: REPO_ROOT '$REPO_ROOT' is not a directory." >&2
exit 1
fi
# Absolutized because the glob probe below `cd`s into a scratch tree, where a
# relative --config path would stop resolving.
REPO_ROOT="$(cd "$REPO_ROOT" && pwd)"
FAIL=0
err() { echo " FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
SKILL_AUDIT="$REPO_ROOT/plugins/kyberforge/.apm/skills/skill-audit"
AGENT_AUDIT="$REPO_ROOT/plugins/kyberforge/.apm/skills/agent-audit"
# Floor on the hardcoded `plugins/kyberforge/.apm/...` paths above. Neither copy
# present is only a legitimate no-op for a repo that has no kyberforge plugin at
# all. If `plugins/kyberforge/` IS here and the `.apm/` targets under it are not,
# the paths in this script have gone stale — a rename or relocation of `.apm/`
# would otherwise turn every assertion below into a silent exit 0, which reads as
# "checked, in sync" exactly like the REPO_ROOT case above. That matters most for
# the change that introduced these paths: a path rewrite is precisely the edit
# this would survive unnoticed.
if [[ ! -d "$SKILL_AUDIT" && ! -d "$AGENT_AUDIT" ]]; then
if [[ -d "$REPO_ROOT/plugins/kyberforge" ]]; then
echo "Vale style sync check failed: $REPO_ROOT/plugins/kyberforge exists but neither $SKILL_AUDIT nor $AGENT_AUDIT does — this script's .apm/ paths have gone stale, so nothing was checked. Update them to wherever the audit skills now live." >&2
exit 1
fi
exit 0
fi
# Exactly one present is drift, not absence: the missing copy can't be in sync
# with the surviving one, and treating it as a no-op is how a deleted or
# renamed copy would slip through silently.
if [[ ! -d "$SKILL_AUDIT" ]]; then
echo "Vale style sync check failed: $AGENT_AUDIT exists but $SKILL_AUDIT does not — run scripts/sync-vale-styles.sh to regenerate skill-audit's copy." >&2
exit 1
fi
if [[ ! -d "$AGENT_AUDIT" ]]; then
echo "Vale style sync check failed: $SKILL_AUDIT exists but $AGENT_AUDIT does not — agent-audit holds the canonical copy, so restore it before syncing." >&2
exit 1
fi
if ! diff -q "$SKILL_AUDIT/scripts/vale-wrap.sh" "$AGENT_AUDIT/scripts/vale-wrap.sh" >/dev/null 2>&1; then
err "scripts/vale-wrap.sh differs between skill-audit and agent-audit"
fi
if ! diff -rq "$SKILL_AUDIT/assets/vale/styles/Kyberforge" "$AGENT_AUDIT/assets/vale/styles/Kyberforge" >/dev/null 2>&1; then
err "assets/vale/styles/Kyberforge differs between skill-audit and agent-audit"
fi
# --- .vale.ini coverage ------------------------------------------------------
# The two .vale.ini files are deliberately NOT identical — agent-audit's carries
# an extra [**/*.agent.md] section and the KyberforgeCopilot style — so they
# cannot be diffed like the styles above. Nothing else in the repo read them at
# all, and that is what let a one-character glob typo silently disable the
# prefilter for a whole file type: the hook still MATCHES the file via its
# `files:` regex, so pre-commit reports neither `Skipped` nor an error; vale
# lints zero files, prints `0 errors ... in 1 file` and exits 0, and the hook
# shows `Passed`. So check the parts that must hold in both, not equality.
SKILL_INI="$SKILL_AUDIT/assets/vale/.vale.ini"
AGENT_INI="$AGENT_AUDIT/assets/vale/.vale.ini"
# Counted, not assumed. The summary line at the bottom used to hardcode `2
# .vale.ini file(s) checked` in both branches. That was true on any clean run --
# a missing or unreadable file errs below and the script never reaches the
# summary -- but the line's whole purpose is to say what this run actually
# inspected, and a constant says what the author expected. Nothing asserted it,
# so it would have survived becoming false.
INIS_CHECKED=0
for ini in "$SKILL_INI" "$AGENT_INI"; do
rel_ini="${ini#"$REPO_ROOT"/}"
# `-e`, not `-f`: a path that exists but is not a readable regular file (a
# directory sitting where the file should be, say) is not "missing", and
# reporting it as missing sends you looking for a deleted file. It belongs to
# the unreadable case below, which is the one that describes what actually
# went wrong.
if [[ ! -e "$ini" ]]; then
err "$rel_ini is missing — without it vale falls back to an upward config search and lints with whatever it finds"
continue
fi
# Present but unreadable is its own case: every assertion below is a grep, and
# grep exits 2 on a read error. The override capture swallows that into an
# empty result, which would read as "no findings" rather than "not checked",
# and the two greps above it report "has no StylesPath"/"names no Kyberforge"
# for a file that may well have both — a misdiagnosis, not a missed one.
#
# Decided by ACTUALLY READING the file, not by `[[ -r ]]`. `-r` is access(2),
# which answers "would the permission bits allow it" — and for uid 0 that is
# yes even on a mode-000 file (verified). This hook runs at pre-push, and this
# repo's dev environment is root, so an `[[ ! -r ]]` guard could never fire in
# the one place it exists to fire: it was untestable because it was dead. A
# read attempt is also the stricter question, catching EISDIR and EIO, which
# access(2) reports on neither. `cat`, not a bare `< "$ini"` redirect: opening
# a directory for reading succeeds, only the read fails.
if ! cat "$ini" >/dev/null 2>&1; then
err "$rel_ini exists but could not be read — none of its assertions could run, and an unreadable file cannot be distinguished from a clean one downstream"
continue
fi
# Counted here, past both `continue`s: the file exists and its bytes were
# readable, so every assertion below it really does run against it.
INIS_CHECKED=$((INIS_CHECKED + 1))
# StylesPath is resolved relative to the .vale.ini, which is the only reason
# the bundled styles are found from a consuming repo's clone prefix.
if ! grep -Eq '^[[:space:]]*StylesPath[[:space:]]*=[[:space:]]*styles[[:space:]]*$' "$ini"; then
err "$rel_ini has no 'StylesPath = styles' — the bundled styles/ directory would not be found"
fi
# Matches `Kyberforge` as a whole name, so `KyberforgeCopilot` alone does not
# satisfy it. Avoids \b, which is a GNU grep extension.
if ! grep -Eq '^[[:space:]]*BasedOnStyles[[:space:]]*=.*Kyberforge([[:space:],]|$)' "$ini"; then
err "$rel_ini has no section whose BasedOnStyles names Kyberforge — every rule the audit prefilters on lives in that style"
fi
# Per-rule overrides are the third way to retire a rule without touching a
# style file or a glob. Per ADR-0013, every rule is `level: error` and every
# alert is a FAIL — there is no ignorable tier. Vale's exit
# code keys on `error` alerts alone, so any override that leaves a rule at
# anything other than `error` still lints the file, still exits 0, and still
# shows `Passed` in pre-commit. The glob probe below cannot backstop this: it
# keys on one `Kyberforge.VagueWording` alert, so DescriptionOpener,
# PaddingPhrase, SentenceOpenerThereIs and ProactivePhrase can each be retired
# underneath a passing probe.
#
# Asserted as an ALLOWLIST, not a blocklist of `NO|warning|suggestion`, because
# that is vale 3.15.2's own semantic: only the exact tokens `YES` and `error`
# keep a rule blocking. `warning`/`suggestion` downgrade it (alert still
# printed, exit 0 — invisible, since pre-commit swallows a passing hook's
# output); every other value — `NO`, `false`, `0`, `off`, `n`, empty,
# `garbage`, and lowercase `yes`, `true`, `1`, `on` — silences the rule
# outright. Lowercase `yes` is the trap a blocklist cannot cover: it reads as
# "enabled" to a human and disables the rule. Verified by enumerating the
# value space against vale 3.15.2.
#
# The allowlist demands a BARE `YES`/`error` with nothing after it, which also
# rejects `error # note` and `error ; note`. Vale itself strips those — a
# whitespace-preceded `#` or `;` comment is removed and the rule stays live —
# so rejecting them is deliberately stricter than vale, not a workaround for
# it. Uniformity is worth more here than the ability to annotate a line that
# should not exist: no shipped `.vale.ini` has any override line at all, and
# the failure mode is a loud false positive rather than a silent pass. The
# genuine hazard is the no-space form — `error# note` and `error; note` are
# NOT stripped and silence the rule outright — and a rule that demands a bare
# token catches those without having to reimplement vale's comment parsing.
#
# `[A-Za-z0-9_-]` on both halves of the name, not `[A-Za-z]`: a rule named
# `Kyberforge.Vague2` is genuinely silenced by `= NO` (verified: 1 error ->
# 0 errors), so an alpha-only class would let a digit-bearing rule name slip
# past the gate. All five current rule names are pure alpha, so this is
# forward cover, not a live hole.
bad_overrides="$(
grep -E '^[[:space:]]*Kyberforge[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+[[:space:]]*=' "$ini" \
| grep -Ev '^[[:space:]]*Kyberforge[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+[[:space:]]*=[[:space:]]*(YES|error)[[:space:]]*$' \
|| true
)"
# No `grep -q` in that pipeline on purpose: `-q` exits on its first match, and
# under `set -o pipefail` the resulting SIGPIPE on the upstream grep would make
# the whole pipeline report 141 and read as "no findings".
if [[ -n "$bad_overrides" ]]; then
err "$rel_ini overrides a Kyberforge rule to something other than a bare YES or error (first: '${bad_overrides%%$'\n'*}') — every rule in this prefilter is level: error and every alert is a FAIL, and any other value downgrades or silences the rule while vale still exits 0. A trailing comment is rejected too: vale strips a spaced '# ...' but not 'error# ...', so this asks for the bare token rather than guessing which form you meant"
fi
done
# KyberforgeCopilot is agent-audit's alone — ADR-0013 scopes it to `.agent.md`
# files only, for the Copilot-only 'Use proactively has no effect' check, and
# records that it must not be extended to `.md` files. The loop above
# deliberately asserts only `Kyberforge`, since
# skill-audit's copy legitimately has no Copilot style, so dropping
# `, KyberforgeCopilot` from agent-audit's `[**/*.agent.md]` section unloaded the
# whole style silently: no glob broke, the styles/ diff above stayed clean (the
# style directory is still shipped, just never loaded), the two .vale.ini files
# are deliberately unequal so no equality check applies, and the probe below
# still passed because it keys on a Kyberforge alert. Assert the style is loaded
# whenever it is shipped.
if [[ -d "$AGENT_AUDIT/assets/vale/styles/KyberforgeCopilot" && -f "$AGENT_INI" ]]; then
if ! grep -Eq '^[[:space:]]*BasedOnStyles[[:space:]]*=.*KyberforgeCopilot([[:space:],]|$)' "$AGENT_INI"; then
err "${AGENT_INI#"$REPO_ROOT"/} ships a styles/KyberforgeCopilot style but no section's BasedOnStyles names it — the style is never loaded, so its Copilot-only rules lint nothing"
fi
fi
# Prints the `files:` regex of every hook, in ONE manifest ($2), 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.
#
# Deliberately kept per-manifest rather than unioned across both files: the
# validation loop below needs to know whether a probe path is in scope of
# .pre-commit-hooks.yaml (the canonical, external-facing manifest) and
# .pre-commit-config.yaml (this repo's own dev-time copy of the same hook)
# *independently*. A union here previously let a probe that matched only the
# older, looser .pre-commit-hooks.yaml pattern read as "in scope" even after
# .pre-commit-config.yaml's copy of the same hook had been narrowed away from
# it — silently masking exactly the kind of hook-rescoping drift this script
# exists to catch.
#
# Deliberately NOT memoized. The probe loop below calls this 12 times over the
# same two small manifests, which measures at 14ms against a ~870ms run (the six
# vale invocations are the wall clock). A previous memoization attempt was inert
# anyway: every call site is `x="$(hook_file_regexes ...)"`, a command
# substitution, so the cache writes landed in a subshell and the lookup never
# hit. Re-parsing is the honest, working version of a saving too small to buy.
hook_file_regexes() {
local skill="$1" manifest="$2" raw
if [[ -f "$manifest" ]]; then
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" | 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
fi
}
# True if $1 matches at least one newline-delimited regex in $2.
matches_any_regex() {
local rel="$1" regexes="$2" re
[[ -n "$regexes" ]] || return 1
while IFS= read -r re; do
[[ -n "$re" ]] || continue
if printf '%s\n' "$rel" | grep -Eq "$re"; then
return 0
fi
done <<EOF_RE
$regexes
EOF_RE
return 1
}
# Asks vale — the thing that actually applies these globs — whether a config
# covers a path, rather than reimplementing doublestar matching. The probe file
# carries a description with a token Kyberforge.VagueWording flags, so a config
# whose glob matches but whose BasedOnStyles lost Kyberforge fails too: it would
# lint the file and report nothing.
# On a miss, the caller reports a glob defect -- but a miss is also what a failed
# exec, an OOM-killed vale, or a full TMPDIR looks like, and discarding vale's rc
# and output made those indistinguishable and evidence-free. A flake seen once in
# this probe could not be diagnosed afterwards for exactly that reason. The rc and
# output are now stashed for the caller to attach to its message; VALE_PROBE_DIAG
# is set on every call, so a stale value from an earlier probe can never be
# reported against a later one.
VALE_PROBE_DIAG=""
vale_flags_path() {
local cfg="$1" rel="$2" tmp out rc=0
tmp="$(mktemp -d)"
mkdir -p "$tmp/$(dirname "$rel")"
{
echo "---"
echo "name: probe"
echo "description: Use when the caller wants a probe that helps with things."
echo "---"
echo ""
echo "Body."
} > "$tmp/$rel"
out="$(cd "$tmp" && vale --config "$cfg" "$rel" 2>&1)" || rc=$?
rm -rf "$tmp"
if printf '%s\n' "$out" | grep -qF "Kyberforge.VagueWording"; then
VALE_PROBE_DIAG=""
return 0
fi
# vale exits nonzero merely for *having* alerts, so rc alone proves nothing --
# it is evidence only alongside the absent alert.
VALE_PROBE_DIAG="vale exited $rc; output: ${out:-<empty>}"
return 1
}
# Missing vale is a HARD FAILURE, not a warning. Six of this script's assertions
# — one glob probe per path below — are `vale --config` invocations, and they are
# the only ones that catch the defect the whole `.vale.ini coverage` section was
# written for: the one-character glob typo (`[**/SKILL.md]` -> `[**/SKILLS.md]`)
# that leaves every text-level assertion clean while vale lints zero files. As a
# warning this self-disabled on exactly that mutation and exited 0, and since
# pre-commit swallows a passing hook's output the stderr line was never seen —
# the pre-push hook reported `Passed`. That is the same "clean exit 0 reads as
# 'checked, in sync' when nothing ran" failure the REPO_ROOT guard at the top of
# this file already refuses to allow.
#
# The opt-out exists for a machine that genuinely cannot install vale, and it is
# an env var that has to be set on purpose — never mere absence of the binary.
# Setting it downgrades the run to text-level assertions only and says so.
VALE_AVAILABLE=true
if ! command -v vale >/dev/null 2>&1; then
VALE_AVAILABLE=false
if [[ "${CHECK_VALE_STYLE_SYNC_ALLOW_MISSING_VALE:-}" == "1" ]]; then
echo " WARNING: vale is not installed and CHECK_VALE_STYLE_SYNC_ALLOW_MISSING_VALE=1 — .vale.ini glob coverage was NOT verified, only the text-level assertions ran. A clean result here does not mean the globs cover what their hooks lint." >&2
else
err "vale is not installed, so none of the .vale.ini glob-coverage probes ran — a glob typo that silently lints zero files is invisible without them. Install it (https://vale.sh/docs/vale-cli/installation/), or set CHECK_VALE_STYLE_SYNC_ALLOW_MISSING_VALE=1 to accept a text-only run"
fi
fi
# One representative path per file shape the prefilter is supposed to cover,
# tagged with whether that shape is expected to be in scope of BOTH manifests
# ("shared") or only the external-facing .pre-commit-hooks.yaml ("hooks-only"
# — e.g. a Copilot .agent.md file living outside this repo's own plugins/.apm/
# layout, which .pre-commit-config.yaml's repo-scoped regex has no reason to
# cover). Each probe is checked against the two manifests' `files:` regexes
# *separately*, not unioned: a path that goes stale because a hook was
# rescoped fails loudly here instead of quietly probing a shape nothing lints
# any more, and a "shared" path the two manifests disagree on fails loudly
# too — that disagreement is exactly how .pre-commit-config.yaml's regex can
# narrow out of sync with .pre-commit-hooks.yaml's without either manifest's
# own hook breaking (each still matches real files on its own), so nothing
# else would catch it.
PROBES_CHECKED=0
while IFS='|' read -r skill rel scope; do
[[ -n "$skill" ]] || continue
dir="$REPO_ROOT/plugins/kyberforge/.apm/skills/$skill"
ini="$dir/assets/vale/.vale.ini"
[[ -f "$ini" ]] || continue
PROBES_CHECKED=$((PROBES_CHECKED + 1))
hooks_regexes="$(hook_file_regexes "$skill" "$REPO_ROOT/.pre-commit-hooks.yaml")"
config_regexes="$(hook_file_regexes "$skill" "$REPO_ROOT/.pre-commit-config.yaml")"
in_hooks=false
matches_any_regex "$rel" "$hooks_regexes" && in_hooks=true
in_config=false
matches_any_regex "$rel" "$config_regexes" && in_config=true
if [[ "$in_hooks" == false && "$in_config" == false ]]; then
err "$rel matches no 'files:' regex of any $skill hook — the probe path is stale, or the hook was rescoped away from a shape it still needs to lint"
elif [[ "$scope" == "shared" && "$in_hooks" != "$in_config" ]]; then
err "$rel is in scope of $skill's hook in .pre-commit-hooks.yaml but not .pre-commit-config.yaml (or vice versa: hooks=$in_hooks, config=$in_config) — the local and canonical 'files:' regexes have drifted out of sync for this hook"
fi
if [[ "$VALE_AVAILABLE" == true ]] && ! vale_flags_path "$ini" "$rel"; then
err "$skill/assets/vale/.vale.ini raises no Kyberforge alert on $rel — its glob sections do not cover a path its own pre-commit hook is scoped to, so the hook passes that shape without linting it [$VALE_PROBE_DIAG]"
fi
# `demo.md` (bare, no `.agent.md` suffix) is `hooks-only` rather than
# `shared`: it exists only to exercise agent-audit's `[**/agents/*.md]` glob
# section in isolation from `[**/*.agent.md]` (test-check-vale-style-sync.sh's
# case 10), not because any real file under `.apm/agents/` still has that
# shape — per ADR-0016 every `.apm/agents/*` file is named `*.agent.md`, so
# `.pre-commit-config.yaml`'s regex correctly no longer matches it and that's
# not drift. `demo.agent.md` is the real, current shape and is `shared`.
#
# The two `.claude/`-prefixed probes carry the location-independence property: a
# `SKILL.md` outside `plugins/` (e.g. project-scope
# `.claude/skills/foo/SKILL.md`) still matches `[**/SKILL.md]` and gets linted
# normally — the globs constrain filename shape, not location. Every other
# probe here starts with `plugins/`, so narrowing a glob to a `plugins/`-shaped
# path (`[**/SKILL.md]` -> `[**/.apm/skills/*/SKILL.md]`) left all of them
# matching while the project-scope shape started linting as `0 errors ... in 0
# files` — the exact "vale lints zero files, hook shows Passed" failure the
# comment at the top of this section describes. Both are `hooks-only`: only
# `.pre-commit-hooks.yaml` is layout-agnostic, and `.pre-commit-config.yaml`
# pinning this repo's own `plugins/**/.apm/` layout is by design, not drift.
done <<'EOF_PROBE'
skill-audit|plugins/demo/.apm/skills/demo/SKILL.md|shared
skill-audit|.claude/skills/demo/SKILL.md|hooks-only
agent-audit|plugins/demo/.apm/agents/demo.md|hooks-only
agent-audit|plugins/demo/.apm/agents/demo.agent.md|shared
agent-audit|.claude/agents/demo.md|hooks-only
agent-audit|copilot/demo.agent.md|hooks-only
EOF_PROBE
# Second floor, on the probe TABLE rather than on the directory paths. Every row
# `continue`s when the `.vale.ini` of the skill its first column names is absent,
# so the table can verify nothing while FAIL stays 0. Two states do that, and no
# other assertion in this file sees either:
#
# * the `EOF_PROBE` heredoc gutted — a bad merge, a truncated edit, or a
# wholesale delete of the rows. The loop body never runs at all.
# * every row's skill column drifting away from the directory names on disk
# (`skill-audit|` -> `skill-auditX|`), which is what a skill rename plus a
# half-applied find/replace leaves behind.
#
# Both give a clean exit 0 from a section that checked nothing, which is why the
# guard is worth having. What it is NOT reachable by is a relocation of
# `assets/vale/`: PROBES_CHECKED only reaches 0 that way if BOTH `.vale.ini`
# files are gone, and the loop at the top of the `.vale.ini coverage` section
# errs on each of them first, so that state is already FAIL >= 2 and this guard
# is never the cause. The message therefore names the table, not the files —
# describing it as "every probe skill's .vale.ini is missing" misdiagnosed the
# one thing that can actually trigger it.
if [[ $PROBES_CHECKED -eq 0 ]]; then
err "no probe path was checked — the probe table is empty, or no row's first column names a skill directory under plugins/kyberforge/.apm/skills/ that has an assets/vale/.vale.ini, so the glob-coverage section verified nothing at all"
fi
if [[ $FAIL -gt 0 ]]; then
echo "Vale style sync check failed: $FAIL error(s). For a drifted wrapper or style, agent-audit's copy is canonical — run scripts/sync-vale-styles.sh to regenerate skill-audit's copy, then commit both. A .vale.ini finding is not drift and sync-vale-styles.sh will not fix it: edit that file's own StylesPath, BasedOnStyles or glob sections." >&2
exit 1
fi
# A clean run says what it actually inspected. Silence is what let the vacuous
# passes above look identical to real ones, and it is what made "did this script
# do any work against the real repo?" untestable from outside — the counts below
# are what tests/test-check-vale-style-sync.sh asserts a non-zero floor on.
if [[ "$VALE_AVAILABLE" == true ]]; then
echo "Vale style sync check passed: $INIS_CHECKED .vale.ini file(s) checked, $PROBES_CHECKED glob probe(s) verified with vale."
else
echo "Vale style sync check passed (text-level only, vale unavailable): $INIS_CHECKED .vale.ini file(s) checked, 0 glob probe(s) verified."
fi