docs: trim CONTEXT.md to a glossary and demote VISION.md

Why: CONTEXT.md is an instructed every-session read at 33,799 bytes (~8,450
tokens), and CONTEXT.md:17 layered docs/VISION.md on top. After the AGENTS.md
trim, that left ~9,650 tokens of instructed reads sitting behind a file whose
own format contract — grill-with-docs/CONTEXT-FORMAT.md:43, "Keep definitions
tight. One sentence max." — it was not following. 22 glossary entries averaged
190 words; the top four were 55% of the file, and one ("Vale audit prefilter")
was 1,444 words of pre-commit forensics filed as a definition.

Implementation notes:
- CONTEXT.md rewritten to CONTEXT-FORMAT.md's prescribed template: ## Language
  (five clusters, 22 terms with _Avoid_ aliases), ## Relationships,
  ## Example dialogue, ## Flagged ambiguities. 33,799 -> 9,762 bytes.
- Vale prefilter forensics moved to docs/spec/gates.md under nine subsections:
  the two-copy style layout, vale-wrap.sh and the Vale 3.15.2
  text.frontmatter.description limitation, the --config argv defect (E100),
  glob scoping, the rule inventory, and 0-files-means-NOT-RUN.
- Instruction-file format and the bidirectional-reference principle moved to
  docs/spec/architecture.md, the latter flagged as aspirational: no instruction
  file carries a `when:` field, only 2 of 39 skills do, and the reference
  scanner script it names does not exist.
- Added a per-plugin scope table to architecture.md so "which plugin owns this
  skill" survives the trim, and corrected its claim that the content index
  lives in providers/claude-code/CLAUDE.md — that file is now two import lines
  and the index sits in core/AGENTS.md.
- "Repo and Gitea are the only source of truth" moved from CONTEXT.md's
  Principles into AGENTS.md session rules; it was operative guidance in a
  glossary. docs/VISION.md and LESSONS.md added to the on-demand list.
- The apm-consumed install entry (432 words) was deleted outright: all four
  consequences are already carried by README.md, ADR-0018, gates.md and
  AGENTS.md.
- ADR-0013, ADR-0015 and ADR-0017 each pointed at CONTEXT.md sections that
  moved; amended with dated supersession notes rather than left dangling.

Impact: the every-session set drops from ~15,700 to ~4,140 tokens across both
passes (-74%). The deleted "lint plugin" entry was also stale on a safety
claim — it said lint-runner inherits Edit, but ADR-0016 restored
`disallowedTools: Edit, Write, NotebookEdit` and CONTEXT.md was never updated.

Refs: #99
This commit is contained in:
2026-08-17 09:28:01 +00:00
parent 1929ffd2da
commit b9c7762463
7 changed files with 361 additions and 72 deletions

View File

@@ -87,7 +87,9 @@ loudly (`Error: jq is required but not installed`).
## Skill and agent context gates (ADR-0020)
The `skill-size-check` pre-commit hook, scoped to `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$`,
runs `scripts/skill-size-check.sh`.
runs `scripts/skill-size-check.sh`. That scope means it never lints `docs/research/examples/`
reference skills. It is also shipped to external repos as `kyberforge-skill-size-check` (see
[External consumers](#external-consumers-the-root-pre-commit-hooksyaml)).
### Two independent gate families, neither replaced the other
@@ -279,6 +281,81 @@ is needed: the `Kyberforge` styles are **committed** under
`plugins/kyberforge/.apm/skills/{skill-audit,agent-audit}/assets/vale/styles/`, not downloaded
packages (ADR-0014).
### Two copies, one canonical
Wiring Vale as a deterministic prefilter for `skill-audit`/`agent-audit`'s Description dimension
(motivation: issue #84) is repo-specific, not part of the generic `lint` plugin, so it does not live
in `plugins/lint/` — and per ADR-0014 it no longer lives at the repo root either. It lives **twice**,
one copy per skill, both under `plugins/kyberforge/.apm/skills/`:
| Copy | Styles | `.vale.ini` sections |
|---|---|---|
| `agent-audit/assets/vale/` — **canonical** | `Kyberforge`, `KyberforgeCopilot` | `[**/agents/*.md]`, `[**/*.agent.md]` |
| `skill-audit/assets/vale/` — smaller duplicate | `Kyberforge` | `[**/SKILL.md]` |
Duplicated rather than shared because a plugin's cache-install copies only each skill's own files —
there is no cross-skill sharing to point at. `check-vale-style-sync` at pre-push is what keeps them
from drifting; `KyberforgeCopilot` is the one deliberate inequality, being scoped only to `.agent.md`
files for the Copilot-only "`Use proactively` has no effect" check.
### What Vale owns, and what stays LLM judgment
Eleven rule files across the two copies, six distinct rules:
| Rule | Vale scope | Bans | From |
|---|---|---|---|
| `Kyberforge.DescriptionOpener` | `text.frontmatter.description` | non-imperative openers ("This skill/agent…") | issue #84 |
| `Kyberforge.VagueWording` | `text.frontmatter.description` | vague capability wording ("helps with", "utilize", …) | issue #84 |
| `Kyberforge.PaddingPhrase` | `text` | generic "see `references/` for details" padding | issue #84 |
| `KyberforgeCopilot.ProactivePhrase` | `text.frontmatter.description` | `Use proactively` (no effect in Copilot) | issue #84 |
| `Kyberforge.SentenceOpenerThereIs` | `sentence` | "There is/are" sentence openers | ADR-0013 |
| `Kyberforge.CompositionNote` | `text.frontmatter.description` | architecture and composition prose in a description | ADR-0020 |
Vale covers the **pattern-matchable** sub-checks named in issue #84 plus, per ADR-0013, one
cherry-picked body-wide prose-pattern rule. Everything else stays LLM judgment: defaults-vs-menus,
why-rationale, the non-pattern-matchable body-discipline calls, near-miss exclusion strength, and
control calibration. New rules land directly in `styles/Kyberforge` and block immediately — there is
no trial tier.
The cherry-pick record, so it is not re-litigated:
- `Kyberforge.SentenceOpenerThereIs` **landed** — 22 held-out hits, both in-corpus hits clean
rewrites, zero suppressions needed.
- `Kyberforge.VagueQualifier` was cherry-picked and then **deleted**. 2 hits across the corpus as it
stood on 2026-08-08 (before the `.apm/` restructure): one marginal, and one unfixable false
positive — `caveman/SKILL.md` quotes `of course` as an example of filler, a mention rather than a
use — which forced the repo's only Vale suppression comments.
- `governance.md` and `CONTROLS.md` were evaluated as rule sources and **excluded**: nothing
prose-pattern-matchable to mine.
### Why every rule is `level: error`
Every alert is a FAIL, with no ignorable tier — same all-or-nothing model as shellcheck, the test
suite, and conventional-pre-commit. Graded severities do not work here: **Vale's exit code keys on
`error` alerts alone**, so a `warning` or `suggestion` rule exits 0, and pre-commit swallows a
passing hook's output. Such a rule would be invisible and would block nothing.
`MinAlertLevel` and `--minAlertLevel` are correspondingly **absent** from both `.vale.ini` files and
from the hook definitions. Under this model they are no-ops; adding one is not a missing knob.
The `verbose: true` escape hatch that makes `skill-size-check`'s SUGGESTION tier audible has no
analogue here — Vale has no tier to make audible.
### External consumers: the root `.pre-commit-hooks.yaml`
The root `.pre-commit-hooks.yaml` exposes both Vale copies (`kyberforge-vale-audit-skill`,
`kyberforge-vale-audit-agent`) plus `kyberforge-skill-size-check`, so any external repo can enforce
the same rules with `repo: <this-repo-url>, rev: <tag>` in its own `.pre-commit-config.yaml`.
pre-commit clones the pinned rev into its own cache, independent of whether Claude Code or the
`kyberforge` plugin is installed at all; the same mechanism covers CI via `pre-commit run
--all-files`. `skill-size-check` has no external asset dependency, so it needed no relocation under
ADR-0014 — only exposure.
This repo's own `vale-audit-prefilter-skill` / `-agent` hooks consume the **identical**
plugin-bundled copies via `repo: local`. Deliberately not a third root copy, and deliberately **not a
pinned self-reference** — a pinned self-reference would lint working-tree edits against the last
tagged release rather than against the change being made.
### Pre-commit
Two prefilter hooks, with `.apm/`-scoped `files:` patterns:
@@ -292,6 +369,92 @@ Only the **authoring source** triggers them. A `SKILL.md` in the generated flat
neither pattern, so prose findings surface only when you edit the file you are supposed to be
editing. Without the binary the hooks fail with a bare "command not found" and no install pointer.
**Two hooks, not one combined hook.** Both manifests split the prefilter in two precisely because a
single hook can point at only one copy, and that copy would silently 0-file-skip the other file
shape (see [A 0-file Vale run is NOT RUN](#a-0-file-vale-run-is-not-run)).
### The `.vale.ini` globs do no scoping
Each `.vale.ini`'s section globs are **path-agnostic** — `[**/SKILL.md]` for skill-audit's copy,
`[**/agents/*.md]` and `[**/*.agent.md]` for agent-audit's — and constrain filename *shape*, not
location: Vale's `*` crosses `/`. A `SKILL.md` outside `plugins/` (a project-scope
`.claude/skills/foo/SKILL.md`, say) still matches `[**/SKILL.md]` and gets linted normally.
All scoping therefore comes from the pre-commit hook's own `files:` regex and from the audit skills
passing one explicit file per invocation. The two manifests scope **differently on purpose**:
| Manifest | `-skill` | `-agent` |
|---|---|---|
| `.pre-commit-config.yaml` (pins this repo's layout) | `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$` | `^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$` |
| `.pre-commit-hooks.yaml` (layout-agnostic for consumers) | `(^\|/)SKILL\.md$` | `(^\|/)agents/[^/]+\.md$\|\.agent\.md$` |
Narrowing a `.vale.ini` glob to a `plugins/`-shaped path to "tighten" it breaks the consumer case,
and `check-vale-style-sync`'s probe set is built to catch exactly that.
### `vale-wrap.sh`, never bare `vale`
Both audit skills' Step 1 and both pre-commit hooks call **each copy's own**
`scripts/vale-wrap.sh`, not `vale`. It works around a confirmed **Vale 3.15.2** limitation:
`text.frontmatter.description` silently stops matching on most — not all — multi-line descriptions.
Verified by reproduction on a deliberately-bad fixture, not assumed:
| Description scalar spanning 2+ lines | Vale's behaviour |
|---|---|
| `>` folded block | 0 alerts, exit 0 — **broken** |
| plain (unquoted) continuation lines | 0 alerts, exit 0 — **broken** |
| single- or double-quoted, wrapped | 0 alerts, exit 0 — **broken** |
| `\|` literal block | alerts fire, exit 1 — lints normally |
The wrapper flattens the three broken forms to a single-line scalar in a scratch copy — or, for the
rare value no inline scalar can spell verbatim, a `|-` block with one content line — padding with
blank lines so **every other line number is unchanged**. `|` literal blocks and single-line
descriptions pass through untouched. Most descriptions in this repo are `>` blocks, so before the
wrapper a bad description in any of the three broken forms sailed straight through the prefilter.
### The `--config` argv defect
Handed **no `--config` at all**, the wrapper falls back to its own sibling `assets/vale/.vale.ini`,
located from `${BASH_SOURCE[0]}` rather than from the cwd. That is why both manifests' `entry:` is
now the bare script path with **no argument after it**.
pre-commit prefixes only `entry[0]` with the hook-repo clone path (`cmd = (prefix.path(cmd[0]),
*cmd[1:])`), so every later argument resolves against the **consuming** repo's root. A `--config` in
`.pre-commit-hooks.yaml` therefore pointed at a path no consumer has and hard-failed every external
run with `E100 [--config] Runtime error`.
`.pre-commit-config.yaml` drops the argument too, deliberately keeping the two entries identical.
The local `repo: local` hook resolved its `--config` correctly only because the consuming repo *was*
this repo — and that divergence is why three review rounds exercised a path no external consumer
takes and missed the defect. **Do not reintroduce a `--config` to either manifest to make the local
run "explicit".**
An explicit `--config` from any other caller still wins, in all three argv forms (`--config X`,
`--config=/abs`, `--config=rel`), and a relative one resolves against the caller's cwd — matching
bare `vale`, not the repo root.
Both audit skills' Step 1 passes no `--config` either. Step 1 resolves the script relative to the
skill's own directory so the call works from an installed plugin cache; a relative `--config`
alongside it would resolve against the cwd instead, yielding `E100 Runtime error … does not exist`
and exit 2 — which both skills' fallback misreads as "vale unavailable" and silently downgrades to
full LLM judgment.
`tests/test-vale-wrap.sh` regression-tests this against **skill-audit's** copy specifically: its
fixtures are all `SKILL.md`-shaped, and only skill-audit's `.vale.ini` carries that glob section.
### A 0-file Vale run is NOT RUN
Vale reports 0 files only when the path it is handed matches **no glob section at all** — a
differently-named file, or a directory argument holding nothing that matches. That run prints
```
✔ 0 errors ... in 0 files.
```
and exits 0, indistinguishable from a clean pass. Both audits therefore treat a 0-file Vale run as
**NOT RUN** and fall back to full LLM judgment rather than reporting the Description dimension
clean.
### Pre-push
`vale` is a **pre-push** dependency too, not only pre-commit. `check-vale-style-sync` runs **six
@@ -314,6 +477,16 @@ Use it only on a machine that genuinely cannot install `vale`, and read that lin
was not checked", not as a pass. The hook is `verbose: true` for exactly that reason — its clean
output is a single line, so it costs one line per push.
### Mentioning banned phrasing without tripping the rule
House convention: banned phrasing that must be **mentioned** rather than used goes in backticks or a
fenced code block. Vale skips code spans and fences, so no suppression is needed — which is why this
document quotes `Use proactively` and "There is/are" the way it does.
Inline `<!-- vale Rule = NO -->` is the fallback **only** where backticking is impossible. Use the
HTML-comment form; the MDX `{/* */}` form does not work in plain Markdown. The one time a rule forced
suppression comments, the rule was deleted instead (see the `VagueQualifier` entry above).
## Tests
```