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 - **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 - **core** — authoring and auditing a repo's `AGENTS.md` and the provider adapter files that defer to it
- **lint** — configuring and running linters - **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 ## Prerequisites
@@ -81,12 +81,17 @@ A suite that exits 77 because a dependency is missing is reported as SKIPPED and
## Before pushing ## Before pushing
Run the whole pre-push gate locally in one command: Run the pre-push gate locally in one command:
```bash ```bash
pre-commit run --hook-stage pre-push --all-files 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. 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`: **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`. 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 ## 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. 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 | | Plugin | Scope |
|---|---| |---|---|
@@ -76,7 +76,7 @@ This repo also has a `CLAUDE.md` at its root — the Claude Code entry point for
## Reference conventions ## 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 ## Provider model

View File

@@ -14,7 +14,7 @@ of the oddities documented here are load-bearing and have already been re-litiga
| Command | Scope | | Command | Scope |
|---|---| |---|---|
| `pre-commit run --all-files` | the commit-stage hooks | | `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 | | `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 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, 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 `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 ## The pre-push gate
@@ -74,7 +85,7 @@ drift in generated text.
| Hook | Guards | | 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`, 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 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) ## Skill and agent context gates (ADR-0020)
The `skill-size-check` pre-commit hook, scoped to `^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$`, 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/` runs `scripts/skill-size-check.sh`. That scope means it never lints the
reference skills. It is also shipped to external repos as `kyberforge-skill-size-check` (see `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)). [External consumers](#external-consumers-the-root-pre-commit-hooksyaml)).
### Two independent gate families, neither replaced the other ### 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 | | `description` characters | 250 | 400 | the YAML-**folded** value |
| body words | 600 | 900 | **body only** — everything after the frontmatter's closing `---` | | 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 - **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 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 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 *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.) 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 - **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 file that was never written is a silently dead branch, and nothing else in the gate/audit/vale
stack notices it. 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 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. 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 ### Target resolution walk
Resolution walks up **from the file being checked** — never from the script's own location. Deriving 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 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 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. 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 = ADR-0020's preload arithmetic depends on it: writing to the 400-char FAIL delivers roughly half the
15,600 chars (a 33% cut off 23,427); writing to the 250-char SUGGESTION lands at 9,750 (58%). The cut that writing to the 250-char SUGGESTION does, so the intended saving depends entirely on that
halving depends entirely on that tier being visible. It costs nothing on a clean file — the script tier being visible. The numbers, and the measurement method behind them, are not restated here —
prints only findings. 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 ### 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 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 - `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 executable-trust gate, and the version-pinned allow key
- `docs/adr/0017` / `0015` / `0014` — plugin content sync, apm-generated manifests, committed Vale - `docs/adr/0017-plugin-content-mirror-bridges-apm-to-host-discovery.md`,
styles `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 - `docs/spec/architecture.md` — directory structure, install pipeline, what is generated and what is
hand-authored hand-authored
- `.pre-commit-config.yaml` — the hooks themselves, with inline rationale comments - `.pre-commit-config.yaml` — the hooks themselves, with inline rationale comments