Why A six-agent review of the two preceding commits found their code sound -- the differential claim holds, the published hook contract is byte-unchanged -- but their prose drifted from it in three ways: statements of fact the code contradicts, markers in a convention this repo does not use, and figures that went stale when the merge changed what they counted. Implementation Notes ADR-0025's edge-path table is rewritten around one stated doctrine: exit 0 is audited and clean, exit 1 is audited with findings or a target present but unreadable, exit 2 is that nothing was audited. Its old row 1 promised "one generic matches-neither message" for three different inputs; there are three distinct messages, and the missing-path case exited 1 until the preceding commit fixed it. Rows are added for the preflight and CDPATH changes, because a table claiming to enumerate every entry-point behaviour change reproduces its own "an earlier revision of this ADR said they were behaviour-neutral" failure if it omits any. ADR-0025 also gains a Consequences supersession record in ADR-0016's form: partially-superseded entries for 0008, 0014, 0020 and 0021, and explicit "is not superseded" entries with reasoning for the rest. Twelve ADRs are amended and it previously listed none. ADR-0008 moves from an amendment note to partially superseded. Its contract genuinely narrowed -- an agent .md outside an agents/ directory was audited before the merge and is refused now -- and ADR-0020 already recorded that the merge "reopens ADR-0008". Its detector description said "a path under .apm/agents/", the phrasing ADR-0025 rejects as wider than the script and circular; the shipped rule is a .md whose immediate parent is named agents/, at any scope. ADR-0020's amendment claimed the boundary resolver is sourced by validate-provenance.sh. It is not, and never was; only validate.sh sources it, once per mode branch. Three Home-column entries pointed at reference filenames the merge renamed, one of which now resolves to two files because its row covers skills and agents. Five ADRs opened with "Skill renamed per ADR-0025", a form this repo does not use, in the same commit that used the conventional "Amended by ADR-0025" twice. They are normalized. "Renamed" was also wrong: the BREAKING-CHANGE trailer says the skills were removed and their flows merged. SIMPLIFICATION-AUDIT.md had 2026-09-15 notes attached to headlines that were never updated, against its own convention of correcting in place with strikethrough. Every figure here was re-derived at HEAD by command, and several differed from the review's own numbers, so the notes record the basis rather than the result alone. LESSONS.md asserted the two review-time suite failures were the SIGPIPE race. The commit that fixed that race explicitly declined to claim it -- the suite was running while agents edited live config files -- so the hedge is restored. Impact No code, test or configuration change; documentation only. Suites stay 20/20 strict with 0 skipped and 374/374 bats. No gate parses ADR or gates.md content, so nothing here is load-bearing for a hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD
10 KiB
Vale audit prefilter expands into a plugin-content harness, scoped to prose-pattern rules only
Issue #84 wired Vale as a deterministic prefilter for skill-audit/agent-audit (merged into
factory-audit's two flows by ADR-0025; read every mention of the pair below that way), scoped to
exactly four pattern-matchable checks (imperative description opener, vague capability wording,
generic reference-pointer padding, Copilot's dead Use proactively phrasing), documented only in
CONTEXT.md's "Vale audit prefilter" section — never its own ADR — and explicitly excluding body
discipline, near-miss exclusion strength, and control calibration as non-goals. This ADR records a
deferred PR #85 review item to broaden that coverage, retroactively captures #84's own rationale
(since it was never recorded as a decision in its own right), and layers the expansion on top
without reversing or weakening the original four rules.
2026-08-17 amendment. The CONTEXT.md section named above no longer holds that documentation.
CONTEXT.md was cut back to a glossary and the prefilter's mechanics — the two-copy style layout,
vale-wrap.sh, the --config argv defect, the rule inventory, and the 0-files-means-NOT-RUN
fallback — moved to docs/spec/gates.md. Read that file, not CONTEXT.md, for the harness itself;
this ADR still owns the scope decision.
File scope stays the same. SKILL.md plus agent files (**/agents/*.md,
**/*.agent.md) only — matching the existing prefilter's globs. Skill-level
README.md files and plugin.json manifests are not added: README.md files are navigational, not
spec-governed content, and plugin.json is JSON, not prose Vale can meaningfully lint.
Rule categories are prose-pattern-matchable only. Structural, schema, and security concerns
stay out of this Vale-based harness because this repo already has dedicated tools for them:
skill-frontmatter (required frontmatter fields), validate-marketplace
(claude plugin validate --strict, schema), and gitleaks/detect-private-key (secrets).
(ADR-0024 removed the companion validate-plugins gate along with the per-plugin manifests it
checked; the argument here is unaffected.)
Duplicating those concerns as Vale rules would fight tools that already own them better.
Governance docs are excluded as a rule source. docs/research/governance_principles/CONTROLS.md
and governance.md were investigated and found to contribute nothing minable: CONTROLS.md is
org/CI-infrastructure controls (secret scanning, dependency/license scanning, agent permission
scoping, audit logging, human approval gates, periodic reviews) — none of it is a prose pattern
expressible as a Vale rule against SKILL.md/agent-file text, and what it does cover is either
already handled elsewhere (gitleaks) or genuinely out of scope for a plugin-content prose harness
(dependency/license scanning is a code-dependency concern, not skill authoring).
Spec-derived custom rules stay mostly as-is. Re-reading agentskills.io's
optimizing-descriptions.md and skill-authoring.md, plus claude-code-plugins/agent-definition.md
and github-copilot-plugins/agent-definition.md, found that the existing four Kyberforge rules
already cover the pattern-matchable surface those specs describe. The remaining spec guidance —
calibrating control vs. giving freedom, avoiding menus of options, coherent skill scope, moderate
detail level — is semantic judgment, already skill-audit's job via LLM review (now
factory-audit's skill flow, see ADR-0025), not new lintable
rules. One confirmation surfaced: Claude Code's Use proactively phrasing is meaningful for .md
agent files (it triggers auto-invocation), unlike Copilot's .agent.md files where it's dead
phrasing — so KyberforgeCopilot/ProactivePhrase's existing .agent.md-only scope is correct and
must not be extended to .md files.
write-good/alex are trialed, not adopted wholesale. These built-in/third-party Vale
packages are tuned for general blog-style prose (passive voice, weasel words, wordy phrases) and
are expected to be noisy against this repo's terse, imperative instruction-file corpus. Only
individual rules proven low-noise against the existing corpus get cherry-picked into
styles/Kyberforge; the packages are never referenced wholesale in BasedOnStyles.
A new non-Vale check closes a real gap. skill-authoring.md states SKILL.md should stay
under 500 lines / 5,000 tokens — currently unenforced anywhere in this repo. This is a whole-file
length ceiling, not a text pattern, so it isn't a Vale rule — it becomes a new deterministic script
and pre-commit hook, sibling to the existing skill-frontmatter hook.
Rules land directly in styles/Kyberforge, enforcing immediately. No trial/report-only tier
is introduced (see Considered Options). "Enforcing immediately" holds only because every rule in
both styles is level: error: Vale's exit code keys on error-level alerts alone, so a
warning- or suggestion-level rule prints an alert and still exits 0, and pre-commit suppresses
output from hooks that pass — such a rule is invisible and blocks nothing. Every Vale alert is
therefore a FAIL, in the audit skills and in the blocking pre-commit hook alike, with no ignorable
tier; that matches every other gate in this repo (shellcheck, the test suite,
conventional-pre-commit). The implementation pass finalizes the cherry-picked
write-good/alex rules and any new spec-derived rule wording, runs the full set against the
existing SKILL.md/agent-file corpus, fixes any resulting violations across that corpus, and lands
the rule changes and the corpus fixes as one atomic commit — the same enforcement model as the
original four rules, never a partial or opt-in state.
Considered options
Phased rollout via a separate trial style + config (rejected). A styles/KyberforgeTrial/
directory plus a parallel .vale.trial.ini (mirroring the root config's globs but with
BasedOnStyles = Kyberforge, KyberforgeTrial) would let new rules be swept report-only via
lint-runner/vale-run before promotion into the enforcing styles/Kyberforge + root
.vale.ini. This was considered because BasedOnStyles = Kyberforge activates every rule file
under that directory automatically — there's no partial/opt-in application within a style, so a
rule dropped straight into styles/Kyberforge goes live in the blocking pre-commit hook
immediately. Rejected in favor of finalizing rules directly and fixing violations via subagent
before committing: simpler, no new trial-config machinery to build or maintain — at the cost of no
standing report-only tier for future candidate rules. Note that the first implementation shipped
graded severities (error/warning/suggestion) and thereby recreated the rejected option by
accident: the five non-error rules never affected an exit code and never surfaced output through
a passing pre-commit hook, so they were a report-only tier that reported to nobody. Flattening
every rule to level: error is what actually implements this decision.
Consequences
styles/Kyberforge/gained one new rule file, cherry-picked fromwrite-good/alexas low-noise against this repo's corpus:SentenceOpenerThereIs.yml(22 hits across 273 held-out markdown files; both in-corpus hits were clean rewrites, needing no suppression).- A second candidate,
VagueQualifier.yml, was cherry-picked and then dropped. Against the 41 skill/agent files it hit twice: one marginal real finding (prototype/SKILL.md, "very different" → "fundamentally different") and one false positive (caveman/SKILL.md, which quotesof courseas an example of filler — a mention, not a use) that no rewrite could clear, forcing the repo's only Vale suppression comments. Of its 15 held-out hits, 9 were indocs/research/examples/(out-of-scope upstream material) and the remaining 6 were the word "very" in two idioms in a single research doc, each already adjacent to the hard number carrying the fact. One marginal catch does not pay for a permanent suppression, so the rule is deleted and this ADR's "cherry-picked rules" is one rule, not two. - A new pre-commit hook,
skill-size-check(scripts/skill-size-check.sh), enforces the 500-line/5,000-tokenSKILL.mdceiling, sibling toskill-frontmatter. Both halves of that ceiling are blocking gates, not just the line count:MAX_LINES=500, andMAX_WORDS=2770as a word-count proxy for the 5,000-token limit (calibrated to the densest prose this repo measured, 1.81 tokens per word, so a worst-caseSKILL.mdat the ceiling still lands under 5,000 tokens —wc -wis not BPE tokenization). Either one exceeded fails the hook. Both are inclusive: a file at exactly 500 lines or exactly 2,770 words passes, and only one past a ceiling fails.skill-audit/scripts/validate.sh(nowfactory-audit/scripts/validate.sh, see ADR-0025) enforces the same pair on the same inclusive terms, so the audit and the commit hook cannot disagree about whether a givenSKILL.mdis over size. styles/KyberforgeTrial/and.vale.trial.iniwere deliberately not created — noted here so a future reader doesn't wonder if a trial tier was forgotten.- The styles-portability question — whether
styles/and.vale.inishould move intoplugins/lint/so the prefilter also works for repos that installkyberforge@holocronas an external plugin, rather than living at this repo's root — was deliberately deferred, not fixed, in this pass. This repo-root placement remains intentional: this ADR's "File scope stays the same" framing is specific to Kyberforge's own authoring conventions in this repo, not a genericlint-plugin feature. Portability is a known limitation, tracked for a separate future session, not silently forgotten.
What this ADR's implementation pass did: synced and trialed write-good/alex against the
existing SKILL.md/agent-file corpus, cherry-picked the one low-noise rule above into
styles/Kyberforge, wrote scripts/skill-size-check.sh and its pre-commit hook, fixed the
resulting corpus violations, and landed the rule changes and corpus fixes as one atomic commit —
matching the enforcement model described above (no partial or opt-in state), with every rule at
level: error so that model is real rather than nominal.