fix(lint): flatten multi-line frontmatter descriptions before Vale runs
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
This commit is contained in:
79
scripts/vale-wrap.sh
Executable file
79
scripts/vale-wrap.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/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[@]}"
|
||||
Reference in New Issue
Block a user