# Kyberforge's Vale prefilter ships from the plugin, with `.pre-commit-hooks.yaml` for external git-hook/CI enforcement **Resolves:** ADR-0013's deferred "styles-portability" consequence — `.vale.ini`/`styles/` moving out of the repo root was deliberately deferred there, not fixed. ADR-0013's other content (rule scope, `level: error` model, `SentenceOpenerThereIs`/`VagueQualifier` trial outcomes) is unaffected and remains in force. `skill-audit`/`agent-audit`'s Step 1 called `"$(git rev-parse --show-toplevel)/scripts/vale-wrap.sh" --config "$(git rev-parse --show-toplevel)/.vale.ini"` — which resolves to whichever repo the skill happens to be running in. Inside `ai-development` that's this repo; in any external repo that installs `kyberforge@holocron` as a plugin, it's that repo's own root, which has no `.vale.ini` or `vale-wrap.sh`. The prefilter silently fell back to full LLM judgment every time outside this repo — the exact gap ADR-0013 named and deferred. ## Decision **Runtime (a live Claude Code session):** the Vale config, styles, and wrapper script move into the plugin itself, following the no-cross-skill-path rule already established in `skill-author/references/deployment-modes.md` (a plugin's cache-install only copies each skill's own files; there is no plugin-level shared directory). `agent-audit` needs both `Kyberforge` and `KyberforgeCopilot` (it lints `.agent.md` files), so `plugins/kyberforge/skills/agent-audit/assets/vale/` is the canonical, superset copy. `skill-audit` needs a second, smaller copy (`plugins/kyberforge/skills/skill-audit/assets/vale/`, `Kyberforge` only) since it cannot reference agent-audit's copy across the skill boundary. Both skills' Step 1 now resolve `scripts/vale-wrap.sh`/`assets/vale/.vale.ini` relative to their own directory, the same way `scripts/validate.sh ` already does — no new resolution mechanism, just applying the existing one consistently. **git hooks / CI outside a Claude Code session** have no plugin cache and no `${CLAUDE_PLUGIN_ROOT}` — a CI runner in particular is guaranteed not to have one. The mechanism that works there for any consumer, with or without Claude Code installed, is pre-commit's own hook-repo protocol: this repo now ships a root-level `.pre-commit-hooks.yaml` exposing `kyberforge-vale-audit-skill`, `kyberforge-vale-audit-agent`, and `kyberforge-skill-size-check`. Any external repo adds `repo: , rev: ` to its own `.pre-commit-config.yaml` and gets all three, fully decoupled from Claude Code. CI is the identical `pre-commit run --all-files` call, so the same manifest covers "possibly CI" from the original ask. **This repo's own dev-time gate** consumes the same plugin-bundled copies instead of a third root-level copy — per explicit instruction, this repo should be set up like any other consumer would be, not dogfood a special root-only path. The existing `repo: local` hook is retargeted (not removed): `entry:` now points at `plugins/kyberforge/skills/{skill-audit,agent-audit}/scripts/vale-wrap.sh`. `repo: local` is kept rather than switching to a pinned self-reference (`repo: , rev: `) — a pinned self-reference would lint working-tree edits against the *last tagged release*, not the change actually being made, which is wrong for the repo that *is* the source of the hook. This mirrors standard practice among hook-author repos (pre-commit's own `pre-commit-hooks`, `shellcheck-py`): `repo: local` for self-consumption, `.pre-commit-hooks.yaml` for everyone else, same underlying files and commands either way. **One hook per file-scope, not one combined hook.** The old root `.vale.ini` had both the `[**/SKILL.md]` and `[**/agents/*.md]`/`[**/*.agent.md]` glob sections in a single file, so one pre-commit hook covered both. Splitting the config into two skill-scoped copies means a single hook entry pointed at only one copy would silently 0-file-skip the other file type. Both the local `.pre-commit-config.yaml` hooks and the external-facing `.pre-commit-hooks.yaml` therefore define separate `-skill`/`-agent` hook IDs, each with a `files:` regex matching exactly what its target copy's glob covers. (Confirmed empirically before deleting the root files: retargeting a single hook at agent-audit's copy silently scanned 0 SKILL.md files.) **Vale's `StylesPath` resolves relative to the `.vale.ini` file's own location**, confirmed against `docs.vale.sh/keys/stylespath` — so `--config /.vale.ini` correctly finds that ini's sibling `styles/` regardless of the caller's cwd, with no extra path-juggling needed beyond what `vale-wrap.sh` already does for its cwd-relative `--config`/file-argument handling. **A sync-check catches drift between the two copies.** `scripts/check-vale-style-sync.sh` diffs `scripts/vale-wrap.sh` and `assets/vale/styles/Kyberforge/` between skill-audit and agent-audit (not `.vale.ini` — those legitimately differ, scoped to different glob sections), wired at `pre-push` alongside `check-manifests`. `.vale.ini` itself isn't diffed since divergence there is by design. **External `.pre-commit-hooks.yaml` consumers pin `rev:` to a tag, not a commit SHA.** This repo had no tags before this change; going forward, a `vX.Y.Z` tag is cut whenever hook-relevant files change, matching how every other `repo:` entry in this repo's own `.pre-commit-config.yaml` already pins (`v2.4.0`, `v8.21.2`, ...). ## Considered options **Keep a third root-level copy, dogfooded specially (rejected).** Simpler in that this repo's own hook wouldn't need retargeting at all. Rejected on explicit instruction: this repo should consume the same portability path an external repo would, not carve out a special root-only case that never gets exercised the way external consumers exercise it. **Publish styles as a hosted Vale package via `Packages = ` (deferred, not rejected).** Vale supports fetching a style from a direct `.zip` URL via `vale sync`, fully decoupled from Claude Code and from pre-commit's hook-repo protocol — usable by any repo, even ones that never install `kyberforge` at all. This is a larger, separate investment (a release/versioning pipeline for the package itself) not required to satisfy the current ask; noted here so a future reader doesn't wonder if it was overlooked. ## Consequences - Root `.vale.ini`, `styles/`, `scripts/vale-wrap.sh` are deleted. Two copies remain: `plugins/kyberforge/skills/agent-audit/assets/vale/` (canonical, superset) and `plugins/kyberforge/skills/skill-audit/assets/vale/` (subset, `Kyberforge` only). - `plugins/kyberforge`'s `plugin.json` and `.claude-plugin/plugin.json` both patch-bump to `1.2.5` for the shipped content change (per ADR-0006's version-parity invariant). - `tests/test-vale-wrap.sh` now exercises skill-audit's copy specifically — its fixtures are all `SKILL.md`-shaped, and only skill-audit's `.vale.ini` has the matching glob section. - The first `vX.Y.Z` tag is cut once this change and its tests pass, giving external `.pre-commit-hooks.yaml` consumers something to pin. - **Cutting the tag is not left to memory.** `scripts/check-release-needed.sh`, wired at `pre-push`, hard-fails — but only when `PRE_COMMIT_REMOTE_BRANCH` (set by pre-commit's `hook-impl` for pre-push hooks) is `refs/heads/main` — if any path `.pre-commit-hooks.yaml` exposes changed since the last tag reachable from `HEAD`. It is a silent no-op on every other branch: hard-failing on feature-branch pushes mid-review would force a premature tag on a commit that might not survive a squash-merge, the exact problem `repo: local` (above) already avoids for this repo's own dev-time gate. A tag not existing at all is also a hard fail on `main`, covering the very first release. This is deterministic tooling, not a standing instruction to remember — consistent with `check-manifests.sh`/`check-vale-style-sync.sh` already using the same pre-push, main-agnostic-elsewhere pattern.