docs(spec): correct eight claims the trim carried into the new files

Why: the relocation into gates.md and architecture.md moved text faster than it
verified it, and a review found eight assertions that do not survive contact
with the repo.

- gates.md cited docs/research/examples/, which does not exist; the tree is at
  plugins/kyberforge/docs/research/examples/. The same wrong string survives at
  ADR-0013:98, where it originated, and is tracked separately.
- gates.md's description-budget arithmetic was built on 23,427, a figure
  ADR-0020 measured at f9b919d and pins there, and compared a name-inclusive
  total against a description-only ceiling. Dropped rather than re-measured,
  following the precedent this branch set for CONTEXT.md's token figure: a
  second live copy is a second thing to go stale.
- gates.md listed "every boundary-clause routing target must resolve" as a hard
  FAIL. skill-size-check.sh emits unresolved targets as SUGGESTION by default,
  promotes to ERROR only when terminal and either route-notated or corroborated
  by a resolving target in the same sentence, and reports INFO DID NOT RUN when
  no universe resolves. Verified with five fixtures; replaced with a
  three-verdict table.
- gates.md and README called `pre-commit run --hook-stage pre-push --all-files`
  the whole push gate. check-release-needed.sh exits 0 unless
  PRE_COMMIT_REMOTE_BRANCH is refs/heads/main, which only the real git hook
  exports -- its own header comment titles this a "Known gap".
- "repo-defined" was loose: the two meta hooks are also declared here, and what
  separates them is repo: meta from repo: local.
- architecture.md claimed `when:` appears in two of 39 skill sources. Exactly
  one SKILL.md carries it; the second hit is a META.md. The likely origin is
  skill-implementation-workflow.md:233, which claims META.md sits alongside
  every skill when one exists in the whole tree.
- architecture.md said core/AGENTS.md is "deployed beside" CLAUDE.md.
  deploy-manifest.sh sends them to ~/.claude/ and ~/.agents/ respectively; the
  relation is import, not adjacency.
- gates.md's see-also cited ADRs as bare stems where its neighbours use paths.

Implementation notes: architecture.md's closing pointer sent readers to the
published description "for what a consumer actually gets", which was true
against an enumeration and is not after ADR-0021. It now names the boundary
relationship and points at the skills tree and README for an inventory. README's
bin bullet gains caveman and zoom-out, since ADR-0021 leaves it the only place
an inventory lives.

Impact: 16/16 pre-push hooks pass. Deliberately not fixed here and tracked
separately: the 12 pre-commit-stage hooks gates.md does not document, its own
SentenceOpenerThereIs violation, and README's missing scripts/install.sh step.

Refs: #105

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TmFqzpuExLJv3m114XVE9w
This commit is contained in:
2026-08-17 12:29:16 +00:00
parent c7ba3d2ccf
commit c68e864159
3 changed files with 59 additions and 18 deletions

View File

@@ -22,7 +22,7 @@ The six plugins:
- **gitea** — issues, pull requests, labels, milestones, releases, branches, files
- **core** — authoring and auditing a repo's `AGENTS.md` and the provider adapter files that defer to it
- **lint** — configuring and running linters
- **bin** — cross-cutting workflow skills not yet split into a focused plugin: research, documentation, TDD, prototyping, triage, diagnosis, architecture review, requirement grilling
- **bin** — cross-cutting workflow skills not yet split into a focused plugin: research, documentation, TDD, prototyping, triage, diagnosis, architecture review, requirement grilling, compressed output (`caveman`), and re-orienting mid-task (`zoom-out`)
## Prerequisites
@@ -81,12 +81,17 @@ A suite that exits 77 because a dependency is missing is reported as SKIPPED and
## Before pushing
Run the whole pre-push gate locally in one command:
Run the pre-push gate locally in one command:
```bash
pre-commit run --hook-stage pre-push --all-files
```
One caveat: `check-release-needed` is a silent no-op under this invocation. It exits 0 unless
`PRE_COMMIT_REMOTE_BRANCH` is `refs/heads/main`, and pre-commit exports that only from the real
pre-push git hook during an actual `git push` — so the hook reports `Passed` having checked nothing.
Every other pre-push hook does run.
See [`docs/spec/gates.md`](docs/spec/gates.md) for what each hook enforces and why.
**Offline?** Exactly two pre-push hooks need the network, because root `apm.yml`'s marketplace contains one remote package entry that must be resolved with `git ls-remote`:

View File

@@ -21,7 +21,7 @@ project repo (local overrides)
Skills are **not** deployed by `install.sh`. They are distributed as plugins and installed separately — in this repo by `apm install` against the `dependencies.apm` entries in the root `apm.yml`, which lands them in `.claude/skills/` and `.claude/agents/` (ADR-0018); elsewhere by `claude plugin install <name>@holocron`.
`~/.claude/CLAUDE.md` is a thin adapter, not a content source. It imports `~/.agents/AGENTS.md` (always-on rules) and `governance.md` (always-on governance) and carries nothing else — the content index of on-demand instruction files sits in `core/AGENTS.md`, deployed beside it. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication.
`~/.claude/CLAUDE.md` is a thin adapter, not a content source. It imports `~/.agents/AGENTS.md` (always-on rules) and `governance.md` (always-on governance) and carries nothing else — the content index of on-demand instruction files sits in `core/AGENTS.md`, deployed to `~/.agents/AGENTS.md` and imported by it. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication.
## Plugin model
@@ -31,7 +31,7 @@ Which plugin a new skill belongs in follows from what each one is scoped to. The
The second boundary worth stating is `git` vs `gitea`, because both own things called branches and both touch pull requests: `git` is whatever works over the git wire protocol against a local clone, `gitea` is whatever goes through the forge's HTTP API. That is why `git-branches` and `gitea-branches` both exist and are not duplicates.
These are routing boundaries, not inventories — they answer "where does a new skill go", so they deliberately do not enumerate what each plugin ships today. For what a consumer actually gets, read the plugin's published `description` in its `apm.yml`.
These are routing boundaries, not inventories — they answer "where does a new skill go", so they deliberately do not enumerate what each plugin ships today. The plugin's published `description` in its `apm.yml` states the same boundary for a consumer deciding whether to install (ADR-0021); neither carries an inventory. For what a plugin ships today, read `plugins/<name>/.apm/skills/` or the plugin list in `README.md`.
| Plugin | Scope |
|---|---|
@@ -76,7 +76,7 @@ This repo also has a `CLAUDE.md` at its root — the Claude Code entry point for
## Reference conventions
The stated convention is that files referencing other files declare those references explicitly: the referencing file carries the forward reference (the content index in `core/AGENTS.md`, `references:` in frontmatter), the referenced file carries a `when:` field describing when it is loaded, and divergence between the two signals staleness. It is aspirational, not a description of the repo today — no file under `core/instructions/` carries frontmatter at all, `when:` appears in two of the 39 skill sources under `plugins/*/.apm/skills/`, and the reference scanner script meant to derive the reverse map ("what files reference this file?") does not exist; `docs/notes/skill-implementation-workflow.md` still lists it as unbuilt work. Treat it as intent for instruction files, skills, and workflow documents, not as a rule the repo enforces.
The stated convention is that files referencing other files declare those references explicitly: the referencing file carries the forward reference (the content index in `core/AGENTS.md`, `references:` in frontmatter), the referenced file carries a `when:` field describing when it is loaded, and divergence between the two signals staleness. It is aspirational, not a description of the repo today — no file under `core/instructions/` carries frontmatter at all, `when:` appears in exactly one of the 39 `SKILL.md` sources under `plugins/*/.apm/skills/`, and the reference scanner script meant to derive the reverse map ("what files reference this file?") does not exist; `docs/notes/skill-implementation-workflow.md` still lists it as unbuilt work. Treat it as intent for instruction files, skills, and workflow documents, not as a rule the repo enforces.
## Provider model

View File

@@ -14,7 +14,7 @@ of the oddities documented here are load-bearing and have already been re-litiga
| Command | Scope |
|---|---|
| `pre-commit run --all-files` | the commit-stage hooks |
| `pre-commit run --hook-stage pre-push --all-files` | the whole push gate, one command |
| `pre-commit run --hook-stage pre-push --all-files` | the push gate, one command — with one caveat below |
| `pre-commit run skill-size-check --all-files` | just the ADR-0020 size/context gates |
Install hooks via `pc-run`, wiring **all three stages**. This repo's `.pre-commit-config.yaml` has no
@@ -23,7 +23,18 @@ and `pre-push` (everything below).
The pre-push command reports **16** hooks, not 14. The extra two are pre-commit's own `meta` hooks,
`check-hooks-apply` and `check-useless-excludes`: they declare no `stages:`, so they run at every
stage including this one. Fourteen is the count of repo-defined pre-push hooks.
stage including this one. Both are declared in this repo's `.pre-commit-config.yaml` like everything
else — what separates them is `repo: meta` (pre-commit's own built-ins) from `repo: local`. Fourteen
is the count of hooks this repo authors itself.
**The caveat: one of those 14 is a silent no-op under that invocation.**
`check-release-needed` exits 0 immediately unless `PRE_COMMIT_REMOTE_BRANCH` equals
`refs/heads/main`, and pre-commit exports that variable only from the real pre-push git hook during
an actual `git push`. Running the stage by hand — or from a CI runner — therefore reports it
`Passed` having checked nothing. That is by design for feature branches — pushing WIP must not be
blocked on cutting a premature tag — but it means `--hook-stage pre-push --all-files` is a full
rehearsal of 13 hooks and a skip of the fourteenth. The script's own header records the same gap for
a PR merged through Gitea's merge button, where no local push happens at all.
## The pre-push gate
@@ -74,7 +85,7 @@ drift in generated text.
| Hook | Guards |
|---|---|
| `check-release-needed` | on push to `main` only — fails if files exposed via `.pre-commit-hooks.yaml` changed since the last tag |
| `check-release-needed` | on a real `git push` to `main` only — fails if files exposed via `.pre-commit-hooks.yaml` changed since the last tag. A no-op everywhere else, including under `pre-commit run --hook-stage pre-push` (see [the caveat above](#running-the-gates)) |
Four of these shell out to `apm`: `apm-marketplace-check`, `apm-audit-ci`, `apm-pack-check-clean`,
and `check-plugin-content-sync` (via `scripts/sync-plugin-content.sh`, which wraps `apm pack`). The
@@ -87,8 +98,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`. That scope means it never lints `docs/research/examples/`
reference skills. It is also shipped to external repos as `kyberforge-skill-size-check` (see
runs `scripts/skill-size-check.sh`. That scope means it never lints the
`plugins/kyberforge/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
@@ -107,14 +119,13 @@ reference skills. It is also shipped to external repos as `kyberforge-skill-size
| `description` characters | 250 | 400 | the YAML-**folded** value |
| body words | 600 | 900 | **body only** — everything after the frontmatter's closing `---` |
Plus three hard FAILs with no suggestion tier:
Plus two hard FAILs with no suggestion tier:
- **A missing, valueless or `null` `description:`.** Not a skip. The description is the one field
preloaded into every session, so a gate that declines to measure it reports green. (This is not
hypothetical: `description:` with no value followed by `model: sonnet` let a line regex capture the
*next* key, which looked non-empty, so the "missing or empty" branch never fired and every gate
below early-returned on the genuinely empty folded value — exit 0, zero output, on a blocking gate.)
- **Every boundary-clause routing target must resolve** to a real skill or agent.
- **Every `references/<file>.md` a body names must exist** on disk. A dispatch table pointing at a
file that was never written is a silently dead branch, and nothing else in the gate/audit/vale
stack notices it.
@@ -122,6 +133,26 @@ Plus three hard FAILs with no suggestion tier:
A file can sit well inside one family and fail the other. 2,770 whole-file words is a conformance
backstop; 900 body-only words is a quality gate. Conflating them is what produced the current state.
### An unresolved routing target is not automatically a FAIL
A boundary-clause target that resolves to no skill or agent has **three** possible verdicts, not one
(`unresolved_targets()` in `scripts/skill-size-check.sh`):
| Verdict | When |
|---|---|
| **SUGGESTION** — the default | the target does not resolve and neither promotion condition below holds |
| **blocking ERROR** | the target is **terminal** (not a compound modifier) **and** either written in route notation (`/name`, `-> name`) **or** corroborated by another target in the same sentence that *does* resolve |
| **INFO, "DID NOT RUN"** | no skill universe could be determined for the path at all — the targets are named and left unchecked, exit 0 |
The default is deliberately soft because a hyphenated word in a boundary clause is as likely to be a
tool, a file format or an English compound as a route: "pre-commit hooks" is prose about a tool and
never reaches the check at all, being a compound modifier rather than a terminal name. The
SUGGESTION text says how to opt in — write it as `/name` or `-> name` and it gets checked properly.
Corroboration is what makes the soft default safe: a sentence whose *other* target resolves is
demonstrably a routing sentence, so a sibling that does not resolve is a typo rather than a noun, and
gets promoted.
### Target resolution walk
Resolution walks up **from the file being checked** — never from the script's own location. Deriving
@@ -176,10 +207,13 @@ Three more, deterministic to measure but judgment to act on:
The hook is declared `verbose: true` so the SUGGESTION tier is audible. pre-commit prints nothing at
all for a passing hook, and a SUGGESTION deliberately does not fail — without verbose every
suggestion is swallowed, which is exactly the invisibility ADR-0013 records for Vale warnings.
ADR-0020's arithmetic depends on it: writing to the 400-char FAIL lands the preload at 39 × 400 =
15,600 chars (a 33% cut off 23,427); writing to the 250-char SUGGESTION lands at 9,750 (58%). The
halving depends entirely on that tier being visible. It costs nothing on a clean file — the script
prints only findings.
ADR-0020's preload arithmetic depends on it: writing to the 400-char FAIL delivers roughly half the
cut that writing to the 250-char SUGGESTION does, so the intended saving depends entirely on that
tier being visible. The numbers, and the measurement method behind them, are not restated here —
they live in ADR-0020's Consequences section, under "A ceiling does not produce an average", whose
figures are pinned to the base commit the decision was taken on (`f9b919d`). Quoting them here would
just create a second copy to go stale. It costs nothing on a clean file — the script prints only
findings.
### Duplicated constants
@@ -637,8 +671,10 @@ remote *before* any network call, so it does not join the pair above.
enforcement table (deterministic vs. auditor judgment), and every rejected alternative
- `docs/adr/0019-session-start-hook-keeps-the-apm-install-current.md` — the `SessionStart` hook, the
executable-trust gate, and the version-pinned allow key
- `docs/adr/0017` / `0015` / `0014` — plugin content sync, apm-generated manifests, committed Vale
styles
- `docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md`,
`docs/adr/0015-apm-replaces-plugin-marketplace-authoring.md`,
`docs/adr/0014-vale-prefilter-ships-from-the-plugin.md` — plugin content sync, apm-generated
manifests, committed Vale styles
- `docs/spec/architecture.md` — directory structure, install pipeline, what is generated and what is
hand-authored
- `.pre-commit-config.yaml` — the hooks themselves, with inline rationale comments