Vale's text.frontmatter.description scope silently stops matching once the description is a YAML block scalar spanning 2+ physical lines — the style used by most skills/agents in this repo. scripts/vale-wrap.sh flattens the description to one line in a scratch copy (preserving the repo-relative path and total line count) before invoking real vale, and both audit skills plus the pre-commit hook now call it instead of vale directly. Also tightens the pre-commit hook's file glob to single path segments so it can't cross into docs/research examples or asset templates the way the audit skills' scoped invocations already avoid. Addresses PR #85 review feedback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxG5T8EJDgkABXxuneuFfn
80 lines
2.3 KiB
Bash
Executable File
80 lines
2.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Works around a Vale limitation: the `text.frontmatter.description` NLP scope
|
|
# silently stops matching once the `description:` value is a YAML block scalar
|
|
# (`>`/`|`) spanning 2+ physical lines — the style used by most skills/agents in
|
|
# this repo. Flattens the description to one physical line in a scratch copy
|
|
# (padding with blank lines so every other line number is unchanged), then runs
|
|
# the real `vale` binary against the copies. Drop-in replacement for calling
|
|
# `vale` directly: same args, same exit code.
|
|
|
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
|
|
vale_args=()
|
|
files=()
|
|
config_next=false
|
|
for arg in "$@"; do
|
|
if [[ "$config_next" == true ]]; then
|
|
if [[ "$arg" == /* ]]; then
|
|
vale_args+=("$arg")
|
|
else
|
|
vale_args+=("$repo_root/$arg")
|
|
fi
|
|
config_next=false
|
|
continue
|
|
fi
|
|
if [[ "$arg" == "--config" ]]; then
|
|
vale_args+=("$arg")
|
|
config_next=true
|
|
continue
|
|
fi
|
|
if [[ "$arg" != -* && -f "$repo_root/$arg" ]]; then
|
|
files+=("$arg")
|
|
else
|
|
vale_args+=("$arg")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#files[@]} -eq 0 ]]; then
|
|
exec vale "${vale_args[@]}"
|
|
fi
|
|
|
|
tmpdir="$(mktemp -d)"
|
|
trap 'rm -rf "$tmpdir"' EXIT
|
|
|
|
for rel in "${files[@]}"; do
|
|
dest="$tmpdir/$rel"
|
|
mkdir -p "$(dirname "$dest")"
|
|
python3 - "$repo_root/$rel" "$dest" <<'PYTHON'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
src, dest = sys.argv[1], sys.argv[2]
|
|
with open(src) as fh:
|
|
content = fh.read()
|
|
|
|
fm_match = re.match(r'^(---\n)(.*?\n)(---\n)', content, re.DOTALL)
|
|
if fm_match:
|
|
fm = fm_match.group(2)
|
|
desc_m = re.search(r'^description:\s*([>|][+-]?)\n((?:[ \t]+.+\n?)+)', fm, re.MULTILINE)
|
|
if desc_m and desc_m.group(2).count('\n') >= 2:
|
|
raw = desc_m.group(2)
|
|
flat = re.sub(r'\s+', ' ', raw).strip()
|
|
# JSON string escaping is a valid subset of YAML double-quoted scalar
|
|
# escaping, so this is always a well-formed YAML value regardless of
|
|
# colons, quotes, or backslashes in the description text.
|
|
flat_q = json.dumps(flat)
|
|
pad = '\n' * raw.count('\n')
|
|
new_fm = fm[:desc_m.start()] + f'description: {flat_q}\n{pad}' + fm[desc_m.end():]
|
|
content = fm_match.group(1) + new_fm + fm_match.group(3) + content[fm_match.end():]
|
|
|
|
with open(dest, 'w') as fh:
|
|
fh.write(content)
|
|
PYTHON
|
|
done
|
|
|
|
cd "$tmpdir"
|
|
vale "${vale_args[@]}" "${files[@]}"
|