Files
holocron/scripts/vale-wrap.sh
Defame1297 792d3e1852 fix(lint): resolve round-1 and round-2 review findings on the Vale prefilter
Addresses PR #85's outstanding review items after grilling the open
questions against ADR-0013/CONTEXT.md/ADR-0010:

Blocking fixes:
- vale-wrap.sh: replace json.dumps() escaping (which silently defeated
  Vale's frontmatter scope on any description containing a quote,
  backslash, or non-ASCII char — ~58% of the corpus) with a single-quoted
  YAML scalar, substituting a Unicode right single quote for embedded
  apostrophes rather than '' doubling (Vale's frontmatter scanner isn't a
  full YAML parser and silently truncates on '' too).
- vale-wrap.sh: fix a blank-line-inside-a-folded-description truncation
  bug via indentation-based, blank-line-tolerant body capture; narrow
  flattening to `>`-style scalars only (`|` already works unflattened).
- skill-audit/agent-audit Step 1: make the vale-wrap.sh invocation
  cwd-independent via git rev-parse --show-toplevel, fixing a bug where
  no single cwd satisfied all three Step 1 commands.
- styles/Kyberforge/VagueQualifier.yml: prune 17 tokens verified
  false-positive-dominated on this repo's own voice via a real corpus
  sweep (obvious, clearly, usually, several, simple, easy, completely,
  simply, tiny, etc.), keep 13 with real or unattested noise. Revert the
  28 prose "fixes" those tokens drove across 14 skill files back to their
  original, correct wording, including a functional regression to
  caveman/SKILL.md's own filler-word list (a mention, not a use) — now
  guarded with vale-off comments against recurrence.

Gaps:
- --minAlertLevel=warning on the pre-commit hook and Step 1 invocation
  so warning-level rules actually surface, without collapsing the
  FAIL/SUGGESTION severity mapping skill-audit/agent-audit rely on.
- vale-wrap.sh: fix --config=<path> equals-form, absolute-path silent
  no-op, and a zero-file-argument stdin hang.
- Route vale-run and lint-runner through a documented wrapper script
  when a target repo has one, instead of unconditionally recommending
  bare `vale`.
- Wire Kyberforge.VagueQualifier/SentenceOpenerThereIs into skill-audit/
  agent-audit's dimension-mapping prose (Body discipline).
- Add plugins/lint/sources.md provenance for lint-runner (ADR-0010).
- Sync both marketplace.json lint-entry descriptions with plugin.json.
- Retune skill-size-check.sh's MAX_WORDS 5000->2900 (measured ~1.6-1.7
  tokens/word on this repo's corpus, the old value gated at ~8,500
  tokens against a stated 5,000 ceiling); fix the >/>= line-count
  boundary and wc -l undercount on files with no trailing newline.
- Document the vale binary as a Setup prerequisite in AGENTS.md.
- Fix SentenceOpenerThereIs's dead regex alternative and add a real
  sentence-start anchor/scope.
- Fix a stale docs/research/docs/vale/ index pointer in kyberforge's
  docs README (moved to plugins/lint/ in e1a5403).
- Rewrite ADR-0013's Consequences section past-tense to describe what
  actually landed, and record the styles-portability limitation
  (repo-root placement stays intentional; deferred to a separate
  session per this PR's review).

Test coverage: 9 new vale-wrap.sh fixtures (quotes, backslash/unicode,
blank-line paragraphs, --config= form, zero-arg/absolute-path handling,
literal-block no-regression) and boundary-pair tests for
skill-size-check.sh's line/word ceilings.

bash tests/run-tests.sh: 9 scripts + 125 bats assertions, all passing.
scripts/check-manifests.sh and claude plugin validate --strict: clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCQ648fLSFXPHGZdQ8gn58
2026-08-08 20:21:21 +00:00

125 lines
4.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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" == --config=* ]]; then
cfg="${arg#--config=}"
if [[ "$cfg" == /* ]]; then
vale_args+=("--config=$cfg")
else
vale_args+=("--config=$repo_root/$cfg")
fi
continue
fi
if [[ "$arg" != -* && -f "$repo_root/$arg" ]]; then
files+=("$arg")
elif [[ "$arg" == /* && -f "$arg" && "$arg" == "$repo_root"/* ]]; then
files+=("${arg#"$repo_root"/}")
else
vale_args+=("$arg")
fi
done
if [[ ${#files[@]} -eq 0 ]]; then
exec vale "${vale_args[@]}" < /dev/null
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 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)
# Only `>`/`>-`/`>+` (folded) scalars break Vale's frontmatter-description
# scope. `|`/`|-`/`|+` (literal) scalars already work fine with bare vale,
# so they're deliberately left unmatched here.
header_m = re.search(r'^description:[ \t]*(>[+-]?)[ \t]*\n', fm, re.MULTILINE)
if header_m:
# Body capture is indentation-based and blank-line-tolerant, per YAML
# block-scalar rules: a blank line (any amount of whitespace) always
# stays inside the block; the indent is set by the first content line;
# the block ends at the first line indented less than that, or EOF.
rest = fm[header_m.end():]
indent = None
body_lines = []
for line in rest.splitlines(keepends=True):
text = line.rstrip('\n')
if text.strip() == '':
body_lines.append(line)
continue
line_indent = len(text) - len(text.lstrip(' \t'))
if indent is None:
indent = line_indent
elif line_indent < indent:
break
body_lines.append(line)
raw = ''.join(body_lines)
if raw.count('\n') >= 2:
flat = re.sub(r'\s+', ' ', raw).strip()
# YAML single-quoted scalars have no backslash-escape mechanism at
# all, so wrapping in single quotes sidesteps the backslash-escape
# bug entirely for embedded double quotes, backslashes, and
# non-ASCII text. The one YAML-spec-correct way to embed a literal
# apostrophe is to double it ('') — but Vale's own frontmatter
# scanner isn't a full YAML parser and doesn't understand that
# doubling: empirically, it silently truncates the value at the
# first ' it sees, hiding everything after it from the NLP scope
# (a different flavor of the same bug this whole script exists to
# work around). Since this copy is scratch-only and never written
# back, sidestep it by substituting a Unicode right single
# quotation mark (U+2019) for any literal apostrophe instead of
# doubling it — visually a smart quote, but never triggers a YAML
# escape sequence at all.
flat_q = "'" + flat.replace("'", "’") + "'"
pad = '\n' * raw.count('\n')
start = header_m.start()
end = header_m.end() + len(raw)
new_fm = fm[:start] + f'description: {flat_q}\n{pad}' + fm[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[@]}"