The ADR was written against base commit `f9b919d` and then not updated as the implementation moved, so several of its numbers were measuring one thing and being read as another — the exact conflation the ADR exists to stop, reproduced inside it. Corrections, all reproducible now that each figure states its method: - The preload tax is 23,427 chars / ~5,900 tokens, not 23,612 / ~6,200. - `MAX_WORDS=2770` is a density proxy for the agentskills.io ~5,000-token ceiling, not "2× p90". Neither percentile reaches it: 2× the body-only p90 is 2,698 and 2× the whole-file p90 is 3,052. Reading it as a percentile pairs a whole-file gate against a body-only distribution. - `apm-workflow` is a 421-word body; 554 is its whole-file count. `skill-author` and `agent-author` were 2,623 and 2,582 body words — 2,760 and 2,758 whole-file, which is where "within twelve words of the gate" comes from. Two numbers for one file is the point, and only one of them is what either gate measures. - Every `file:line` citation now says it resolves against `f9b919d`, since this change rewrites most of the cited files. Three things the ADR asserted that no validator implemented are now filed by tier in an exhaustive enforcement table — deterministic, prose-pattern, or auditor judgment — because a rule filed under "Enforcement" that nothing enforces is the failure mode this ADR is most exposed to. The Gotchas entry count moves to SUGGESTION to match the script; the paraphrase FAIL is marked as an auditor's, since semantic equivalence is not pattern-matchable. Two gaps recorded rather than quietly left: - The agent body-gate exemption lives in `agent-audit`'s validator and in the `skill-size-check` hook's `SKILL.md`-only `files:` pattern — *not* in `scripts/skill-size-check.sh`, which measures whatever path it is handed and today reports 900-word body FAILs on `git-orchestrate` (933), `gitea-orchestrate` (1,199) and `apm-orchestrate` (1,080). Agents escape by file pattern, not because the script knows the difference, so widening that pattern would silently enforce a gate this ADR declines to set. - The `skill-audit`/`agent-audit` merge is deferred to #101. This change made the split deeper, not shallower: the dispatch retrofit took them from 3 and 4 reference files to 7 and 8, and their two same-named `description-quality.md` files now differ on 100 of ~120 lines after normalising skill/agent. The merge reopens ADR-0008 and touches every call site in `skill-author`, `agent-author` and `forge`, so it is its own change. #100 carries the dangling-target fixes. AGENTS.md and CONTEXT.md take the same corrections plus the two live setup changes: PyYAML is now a hard requirement rather than an optional accelerator (a fallback that mis-parses an unfamiliar scalar shape reports a clean pass on a file it never measured), and `.claude/settings.json`'s `pretty-format-json` exclusion is documented as load-bearing rather than as a tidy-up candidate. LESSONS.md's autofix entry is corrected on its own provenance, which it got wrong in both directions. `git log --date=iso` puts the introducing commit at 18:47 and the fix at 21:54 — three hours, not "weeks" — and `git branch -a --contains` puts the introducing commit on this branch only, not on main. It was manufactured inside the same PR that diagnosed it. The added lesson is that "pre-existing" is a claim about history and history is queryable: a defect found while working on a branch feels inherited, and the feeling is not evidence. Refs: ADR-0020, #99, #100, #101
398 lines
30 KiB
Markdown
398 lines
30 KiB
Markdown
# Skills and agents are authored against a context budget, not a spec ceiling
|
||
|
||
Every installed skill's `name` and `description` sits in every agent's context from the first token
|
||
of every session, whether or not the skill is ever invoked. Across this repo's 39 skills that is
|
||
23,427 characters — roughly 5,900 tokens — and the authoring rules that produced it optimised for
|
||
triggering reliability with no counter-pressure on size. This ADR sets the budget, the shape, and the
|
||
gates that hold them.
|
||
|
||
**Status: accepted (2026-08-14).**
|
||
|
||
## Context
|
||
|
||
Every `file:line` citation in this ADR is against the base commit the decision was taken on,
|
||
`f9b919d7e3bd5e6b51fbdf88b32ace0438b313e0`, not against current `HEAD`. The change that carries this
|
||
ADR rewrites several of the cited files, so a citation resolved against the worktree will land on
|
||
unrelated text. Use `git show f9b919d:<path>` to follow one.
|
||
|
||
Measured before any change, at that commit. Method, so the figures are reproducible: sum
|
||
`len(name) + len(description)` over the frontmatter of every `plugins/*/.apm/skills/*/SKILL.md`,
|
||
folding `>` block scalars to the value the host actually loads (most descriptions here are folded
|
||
scalars, so counting raw lines measures indentation instead); tokens at the standard
|
||
~4-characters-per-token approximation `scripts/skill-size-check.sh` uses. Word counts are
|
||
whitespace-separated tokens, and are stated as **body-only** or **whole-file** every time, never bare.
|
||
|
||
| | |
|
||
|---|---|
|
||
| 39 skill `name` + `description` | 23,427 chars, ~5,900 tokens, **preloaded every session** |
|
||
| 4 agent `name` + `description` | 1,325 chars, ~330 tokens, preloaded every session |
|
||
| skill bodies (body-only words) | median 684, mean 815, p90 1,349 |
|
||
| skill files (whole-file words) | median 816, mean 927, p90 1,526 |
|
||
| `MAX_WORDS` gate (`skill-audit/scripts/validate.sh:147`) | **2,770** whole-file — a density proxy, not a percentile |
|
||
|
||
That last row is worth stating plainly, because it is the first thing this ADR is about. 2,770 is not
|
||
derived from the corpus distribution at all: per the derivation comment in
|
||
`scripts/skill-size-check.sh`, it is 2,770 words at the densest observed 7.22 chars/word ≈ 20,000
|
||
chars ≈ the agentskills.io ~5,000-token ceiling. Neither percentile reaches it — 2× the body-only p90
|
||
is 2,698 and 2× the whole-file p90 is 3,052 — and reading it as "2× p90" would pair a whole-file gate
|
||
against a body-only distribution, which is exactly the conflation this ADR exists to stop.
|
||
|
||
Three findings drove this, none of which is "the descriptions drifted".
|
||
|
||
**The rules mandate the bloat.** `skill-author/SKILL.md:104` requires indirect triggers ("even if the
|
||
user doesn't mention X explicitly") and `skill-audit/references/description-quality.md:21` requires
|
||
authors to "err toward being pushy". Both are enforced. The one rule that would delete the waste —
|
||
`skill-author/SKILL.md:102`, "not the skill's internal mechanics" — is judgment-only and is absent
|
||
from the FAIL conditions at `description-quality.md:45-50`. The enforced rules inflate; the deflating
|
||
rule does not bite. The result is measurable: `gitea-files` spends 147 chars listing six verbs, then
|
||
301 chars re-quoting the same six as user phrasings, in the same order. `apm-workflow` does the same
|
||
with six capability clusters. Across the twelve longest descriptions, 30.7% is capability
|
||
enumeration and 11.6% is composition or implementation detail that cannot affect a routing decision.
|
||
|
||
**Capability enumeration in a description is a correctness hazard, not only a token cost.**
|
||
`plugins/kyberforge/docs/research/examples/skill-write/writing-skills/SKILL.md:154-158` reports a
|
||
measured failure: "when
|
||
a description summarizes the skill's workflow, an agent may follow the description instead of reading
|
||
the full skill content. A description saying 'code review between tasks' caused an agent to do ONE
|
||
review, even though the skill's flowchart clearly showed TWO reviews." `git-commits` is exactly that
|
||
shape — 74% of its description is capability enumeration, including a rules table (`header max 100
|
||
chars, lowercase subject, no trailing periods, 11 standard types`) an agent can act on without ever
|
||
loading the body.
|
||
|
||
**The upstream sources cannot settle this.** The four skill-writing references under
|
||
`plugins/kyberforge/docs/research/examples/skill-write/` disagree on what a description contains —
|
||
when-only (`writing-skills/SKILL.md:99`), what-and-when (`skill-creator/SKILL.md:67`,
|
||
`writing-skills/anthropic-best-practices.md:187`), triggers-only
|
||
(`writing-great-skills/SKILL.md:28`), and what-plus-when-plus-negative
|
||
(`write-skill/SKILL-TEMPLATE.md:5-6`). Those four paths are relative to that directory.
|
||
`writing-skills` and the Anthropic document it bundles contradict each other inside one skill
|
||
directory. They also disagree on whether
|
||
500 lines is binding, on the inline-versus-bundle threshold, and on the TOC threshold (>100 lines vs
|
||
>300 lines). "Grounded in the research" is therefore not available as a tiebreaker; a house choice is
|
||
required and this is it.
|
||
|
||
A fourth observation shaped the body half. The best progressive-disclosure ratio in the repo belongs
|
||
to `apm-workflow` — a 421-word body dispatching to 3,006 words of references — and the worst two
|
||
belong to the skills that define the house standard: `skill-author` (2,623-word body / 1,247 words of
|
||
references) and `agent-author` (2,582 / 1,664). Measured the other way, whole-file, those two are
|
||
2,760 and 2,758 words — ten and twelve words under the 2,770 gate their own plugin enforces. A
|
||
ceiling that nothing approaches is not a constraint; a ceiling that two files have grown into is a
|
||
target. The two numbers for one file are the point: 2,623 and 2,760 describe the same `skill-author`,
|
||
and only one of them is what either gate measures.
|
||
|
||
## Decision
|
||
|
||
### Descriptions
|
||
|
||
A description carries three things and nothing else: a **trigger clause**, at most one **capability
|
||
clause**, and a **boundary clause**. Capability enumeration, output-format detail, composition notes
|
||
("composes X rather than duplicating Y"), and implementation detail move to the body or to
|
||
`README.md`.
|
||
|
||
- **250 characters SUGGESTION, 400 FAIL.** The agentskills.io 1,024-character limit remains as an
|
||
unchanged spec backstop. The SUGGESTION tier is what moves the average; the FAIL tier only stops
|
||
outliers.
|
||
- **A missing, valueless or `null` `description:` is a hard FAIL** in all three validators. That
|
||
reads as a trivial precondition and is not: a `description:` line 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 it then early-returned on the genuinely empty folded
|
||
value — exit 0, zero output, on a blocking pre-push gate. Presence is decided on the YAML-folded
|
||
value and nowhere else. The field this contract is entirely about is the one field a gate must
|
||
never fail to notice is absent.
|
||
- **Boundary clauses compress** to `Not <thing> → <skill-name>.` and must name a target that
|
||
resolves to a real skill or agent. Resolution walks up **from the file being checked** to an
|
||
*authoring root* — the nearest ancestor holding `plugins/*/.apm/skills` or `plugins/*/.apm/agents`,
|
||
falling back to the nearest ancestor holding `.git`. Two passes rather than one interleaved walk,
|
||
so a nested `.git` (a submodule, a sub-package worktree) cannot beat a real monorepo root further
|
||
up. When an authoring root is found the universe is every skill and agent under
|
||
`<root>/plugins/*/`, plus the target's own apm package and the packages that package declares in
|
||
its own `apm.yml` `dependencies.apm`. Sibling plugins resolve against each other, which is what a
|
||
monorepo means. Deployed `.claude/`/`.agents/` trees are consulted **only** when no authoring root
|
||
exists — the consumer case, where there is no monorepo to read. What the resolver must never do is
|
||
derive the universe from its own location: a `${BASH_SOURCE}`-relative repo root leaked this repo's
|
||
39-skill universe into every consumer repo running the hook through pre-commit, so a consumer skill
|
||
routing to `skill-audit` resolved against a plugin it had never installed. Checked
|
||
deterministically. A description carrying **no** boundary clause at all is a SUGGESTION, for skills
|
||
and agents alike: most descriptions want one, some genuinely have no near-miss sibling to exclude,
|
||
and that judgment is not a script's to make.
|
||
- **The verdict must not depend on whether `apm install` has been run.** Deployed trees are
|
||
gitignored install output, present only on a machine that has run it. Four cross-plugin targets
|
||
here (`gitea-branches` → `git-branches`, `gitea-branches` → `git-history`, `gitea-issues` →
|
||
`git-branches`, `gitea-workflow` → `git-workflow`) once resolved through `.claude/skills/` alone,
|
||
so the same commit measured 2 dangling targets on a developer machine and 6 on a fresh clone. A
|
||
gate shipping hot with no baseline cannot give two answers. Under the walk-up those four resolve
|
||
because sibling plugins are in the universe — no plugin here declares a cross-plugin apm
|
||
dependency, and none needs to. Verified: a tree holding only `plugins/` and the root `apm.yml`,
|
||
with no `.claude/` or `.agents/` anywhere, now produces findings identical to the working tree —
|
||
26 description FAILs, 9 body FAILs, 2 dangling targets, 0 missing references, 58 SUGGESTIONs.
|
||
- **The blanket pushiness rules are deleted.** `skill-author/SKILL.md:104` and
|
||
`description-quality.md:21` are replaced by a conditional: add an indirect trigger only where the
|
||
user's natural phrasing genuinely omits the domain word — true for the `gitea-*` family, false for
|
||
`git-commits`. Stating the same trigger twice in two registers is a FAIL.
|
||
|
||
### Bodies
|
||
|
||
The body carries the **decision procedure only**: ordered steps, decision branches, gates, and which
|
||
reference to load when. Lookup tables, spec restatements, output schemas, templates, and rationale
|
||
prose move to `references/` behind an explicit "read X when Y" trigger.
|
||
|
||
- **600 words SUGGESTION, 900 FAIL, counted body-only** — everything after the closing `---` of the
|
||
frontmatter. The 2,770-word / 500-line spec backstop is unchanged, keeps its existing meaning
|
||
(conformance, not quality), and keeps counting the **whole file including frontmatter**. These are
|
||
two different gates measuring two different things, and conflating them is what produced the
|
||
current state.
|
||
- **Dispatch is mandatory at two or more mutually exclusive flows.** The body carries the dispatch
|
||
table and the gates that apply to every branch; each flow lives in its own self-contained
|
||
`references/` file. This is `apm-workflow/SKILL.md:33-41` promoted from accident to rule. "Two
|
||
mutually exclusive flows" is not decidable from file text, so this rule is auditor judgment — see
|
||
Enforcement below for what that means and does not mean.
|
||
- **Every `references/<file>.md` a body names must exist.** A dispatch table pointing at a file that
|
||
was never written is a silently dead branch. Checked deterministically.
|
||
- **Gotchas are constrained.** A Gotcha must state a fact that contradicts a reasonable default —
|
||
something the agent gets wrong by acting sensibly. More than five entries is a SUGGESTION, as is a
|
||
Gotchas section exceeding 25% of the body; both are countable and both are checked
|
||
deterministically. A Gotcha that paraphrases a step in the body below it is a FAIL, but a FAIL an
|
||
auditor issues, not a script — semantic equivalence is not pattern-matchable.
|
||
|
||
### Agents
|
||
|
||
Agents take the same description gates — they are preloaded identically — and **no body word gate**.
|
||
A skill body is loaded into the caller's context, competing with the live conversation; an agent body
|
||
becomes the system prompt of a fresh context. The rationale for the 900-word FAIL does not transfer.
|
||
|
||
That exemption is expressed in `agent-audit/scripts/validate.sh`, which has no body constant, and in
|
||
the `files:` pattern of the `skill-size-check` pre-commit hook, which is `SKILL.md`-only. It is *not*
|
||
expressed in `scripts/skill-size-check.sh` itself, which measures whatever path it is handed —
|
||
running it directly over `plugins/*/.apm/agents/*.agent.md` today reports 900-word body FAILs on
|
||
`git-orchestrate` (933), `gitea-orchestrate` (1,199) and `apm-orchestrate` (1,080). Agents escape by
|
||
file pattern, not by the script knowing the difference. Anyone widening that pattern to cover agents
|
||
would silently enforce a gate this ADR declines to set.
|
||
|
||
A plugin-scope agent is a single file with no sibling `references/` directory, so it cannot disclose
|
||
to itself — it can only delegate to skills. `agent-audit` therefore gains a **delegation check**: an
|
||
agent body that restates a procedure owned by a skill it can invoke is a FAIL, with the fix being
|
||
"invoke `<skill>` instead". Length falls out of delegation rather than being gated directly.
|
||
|
||
### Invocation as a design axis
|
||
|
||
`skill-author` asks whether a skill is model-invoked or hand-invoked before writing a description. A
|
||
hand-invoked skill sets `disable-model-invocation: true` and carries one plain human-facing sentence
|
||
with no trigger list.
|
||
|
||
Verified end-to-end rather than assumed: `plugins/bin/.apm/skills/zoom-out/SKILL.md:4` carries the
|
||
flag, apm passes it through verbatim to both `.claude/skills/zoom-out/SKILL.md:4` and the flat mirror
|
||
at `plugins/bin/skills/zoom-out/SKILL.md:4`, and `zoom-out` is the one installed skill absent from
|
||
the model-visible skill listing in a live session. It remains invocable as `/zoom-out`.
|
||
|
||
### Merging siblings
|
||
|
||
Two skills that share substantial content, name each other as near-misses, and differ only in the
|
||
type of input they take should be **one skill with a dispatch table**. This catches `skill-audit` +
|
||
`agent-audit` and is scoped to them; the author pair is explicitly excluded, because
|
||
`skill-author` and `agent-author` emit genuinely different artifacts (a skill directory versus a
|
||
one-or-two-file agent pair, per ADR-0005 and ADR-0016) and their overlap is in the improve flow
|
||
rather than the core job.
|
||
|
||
**DEFERRED — not implemented in the change that carries this ADR. Tracked as issue #101.** Both
|
||
skills still exist separately, and this change made the split deeper rather than shallower: retrofit
|
||
to the dispatch pattern took `skill-audit` from 3 reference files to 7 and `agent-audit` from 4 to 8,
|
||
and their two same-named `references/description-quality.md` files now differ on 100 of ~120 lines
|
||
after normalising `skill`/`agent`, where before they were closer. The merge stays the decision; it
|
||
reopens ADR-0008 (agent-audit's single-file invocation contract) and touches every call site in
|
||
`skill-author`, `agent-author` and `forge`, which is why it is its own change and not a rider on
|
||
this one. Recorded here rather than dropped, so the gap between the rule and the tree is deliberate
|
||
and dated instead of discovered later.
|
||
|
||
### Enforcement and rollout
|
||
|
||
Gates land where the existing gates already live — no new layer. The table below is exhaustive about
|
||
which tier each rule is in, because the failure this ADR is most exposed to is a rule filed under
|
||
"Enforcement" that no validator implements:
|
||
|
||
| Check | Applies to | Tier | Home |
|
||
|---|---|---|---|
|
||
| description characters (250 SUGGESTION / 400 FAIL) | skills, agents | deterministic | `scripts/skill-size-check.sh`; constants mirrored in `skill-audit/scripts/validate.sh` and `agent-audit/scripts/validate.sh` |
|
||
| body-only words (600 SUGGESTION / 900 FAIL) | skills | deterministic | `skill-size-check.sh`, `skill-audit/scripts/validate.sh` |
|
||
| description present and non-empty (ERROR) | skills, agents | deterministic | same |
|
||
| boundary target resolves to a real skill or agent (ERROR when written as `/name` or `-> name`, or when its own sentence names another target that resolves; SUGGESTION otherwise) | skills, agents | deterministic | same |
|
||
| boundary clause absent (SUGGESTION) | skills, agents | deterministic | same |
|
||
| Gotchas entry count over five (SUGGESTION) | skills | deterministic | same |
|
||
| Gotchas over 25% of the body (SUGGESTION) | skills | deterministic | same |
|
||
| every `references/<file>.md` a body names exists (ERROR) | skills | deterministic | same |
|
||
| description opener, composition notes in a description | skills, agents | prose pattern | `plugins/kyberforge/.apm/skills/*/assets/vale/styles/Kyberforge/` |
|
||
| a Gotcha paraphrasing a body step | skills | **auditor judgment** | `references/body-discipline.md` |
|
||
| dispatch at two or more mutually exclusive flows | skills | **auditor judgment** | `references/body-discipline.md` |
|
||
| delegation: an agent body restating a skill's procedure | agents | **auditor judgment** | `agent-audit` |
|
||
| capability enumeration, restatement, trigger quality | skills, agents | **auditor judgment** | `references/description-quality.md` |
|
||
|
||
The rows in bold are stated as FAILs in the Decision above and are FAILs an *auditor* issues. None of
|
||
them is countable: "does this Gotcha paraphrase step 4", "are these two flows mutually exclusive" and
|
||
"does this agent body restate what `git-commits` already owns" are semantic questions, and a script
|
||
that guessed at them would be a worse gate than no gate, because it would be believed. They are not
|
||
enforced, they are reviewed, and this table exists so that distinction is written down rather than
|
||
inferred from whether a validator happens to have been written yet.
|
||
|
||
Two of the deterministic rows are tuned for **false positives over recall**, and what they decline to
|
||
see is part of the contract. On target extraction: a bare hyphenated name counts only inside a
|
||
boundary sentence, and a single-word name is never matchable bare — `research`, `triage`, `forge`,
|
||
`prototype` and `tdd` are all real skill names *and* ordinary English, so it must be written
|
||
`` `forge` `` or `/forge` to be seen at all. Grammar then decides whether a recognised target may
|
||
raise an error: one followed by an ordinary lowercase noun is a compound **modifier**, not a route
|
||
("use pre-commit hooks instead of ad-hoc scripts", "invoke the pull-request template"), so it is
|
||
confirm-only — it still resolves and still counts as a route when the name exists, but it can never
|
||
dangle. Only a *terminal* target can. The compressed arrow form `→ <name>` is exempt from that
|
||
follower test and is always error-eligible, because nothing reads as a compound modifier after an
|
||
arrow; a `/slash` target reached through a route verb is **not** exempt and takes the same test. The
|
||
simpler rule — "only marked targets may dangle" — was available and would have been wrong here: both
|
||
live true positives are bare, `research`'s "(use neuledge-context)" and the `gitea-labels-` /
|
||
`milestones` fold. On the body-shape checks: a `## Gotchas` heading must *end* in "gotchas", not
|
||
merely contain the word, so `## Gotcha handling` and `## Why gotchas matter` are prose sections and
|
||
are skipped; fenced code blocks are masked out of heading detection and entry counting, so a fenced
|
||
example list is not mistaken for the section; and a `references/` pointer named on a line
|
||
that also says the file is gone ("removed", "deprecated", "no longer") is read as a historical
|
||
mention rather than a dead dispatch entry. Note the 25% fraction is deliberately *not* fence-masked
|
||
on either side — fenced lines are real body words, and the fraction is measured against the whole
|
||
body.
|
||
|
||
**The deterministic tier blocks immediately, with no baseline file.**
|
||
|
||
Three pre-existing contradictions are fixed in the same change, because they are the contract:
|
||
|
||
- `skill-audit/SKILL.md:58` asks whether the description opens with an action verb ("Audits…",
|
||
"Reviews…"), while `:56` defers the same question to `Kyberforge.DescriptionOpener` and
|
||
`skill-author/SKILL.md:101` requires an imperative "Use when…" opener. The criterion is
|
||
unsatisfiable against the house's own skills, both of which open with "Use when".
|
||
- `DescriptionOpener.yml` is anchored to `^This (skill|agent)\b`, which misses a plain `This …`
|
||
opener; it is widened here to `^This\b`. The anchor itself stays. Composition prose that sits
|
||
*mid*-description — `gitea-workflow`'s "This is the human-facing entry point…" at character 377,
|
||
`gitea-labels-milestones`'s "This is a cross-cutting shared skill…" at character 300 — was never in
|
||
the opener rule's scope and correctly is not: under `scope: text.frontmatter.description` the `^`
|
||
anchors to the start of the whole folded value, and un-anchoring to reach mid-description text was
|
||
measured at 5 hits and 5 false positives and rejected (`LESSONS.md`, 2026-08-14). The real gap is
|
||
that no rule covered that text at all, which a new token-list rule, `Kyberforge.CompositionNote`,
|
||
closes: 10 alerts across four `gitea-*` skills, 0 false positives.
|
||
- `description-quality.md:45-50` has no FAIL condition for internal-mechanics content, which is why
|
||
`skill-author/SKILL.md:102` never bit.
|
||
|
||
## Consequences
|
||
|
||
**Editing any non-compliant skill now requires retrofitting it first.** At decision time, 30 of 39
|
||
descriptions exceeded 400 characters and 13 of 39 bodies exceeded 900 words — the latter counted
|
||
body-only, which is what the new gate measures; the pre-existing 2,770-word gate counts the whole
|
||
file including frontmatter, and the two must not be conflated. The change that carries this ADR also
|
||
retrofits kyberforge's own four author/audit skills, so the figures on landing are **26 and 9**.
|
||
With the gate hot and no baseline, a one-line
|
||
fix to `gitea-prs` cannot be committed until that skill meets the contract. This is deliberate — it
|
||
guarantees convergence and avoids a half-state — but it means the retrofit is lazy and *mandatory*
|
||
rather than deferred. Issue #99 tracks it and should be prioritised accordingly, and the risk it
|
||
carries is the ordinary one for hot gates: a gate expensive enough to be inconvenient gets bypassed
|
||
with `SKIP=` and loses its authority.
|
||
|
||
**A second hot gate ships alongside it, and it is easy to miss.** `Kyberforge.CompositionNote` is
|
||
`level: error` like every other rule in that style, so `pre-commit run --all-files` is red on 10
|
||
alerts across `gitea-issues`, `gitea-labels-milestones`, `gitea-prs` and `gitea-workflow`
|
||
independently of anything `skill-size-check` reports. Someone scoping the #99 retrofit off the size
|
||
findings alone will fix those and still be blocked. The two gates want fixing together.
|
||
|
||
**A ceiling does not produce an average.** If every author writes to the 400-character FAIL, the
|
||
preload lands at 39 × 400 = 15,600 chars — a 33% cut off 23,427, not the ~50% intended. Writing to
|
||
the 250-character SUGGESTION instead lands at 9,750, a 58% cut. The halving depends entirely on the
|
||
250-character SUGGESTION tier being visible and respected. That tier works here in a way it does not
|
||
elsewhere in this repo: `skill-audit` already reports `PASS (N suggestions)` as a first-class
|
||
outcome. This is explicitly **not** the failure ADR-0013 records — Vale warnings are invisible
|
||
because vale's exit code keys on `error` alone, but these gates live in `validate.sh` and
|
||
`skill-audit`, where a SUGGESTION reaches the report. Realistic landing is somewhere in that 33-58%
|
||
band, not a guaranteed 50%.
|
||
|
||
**A word gate cannot detect the defect it is standing in for.** `git-commits` carries twelve Gotchas
|
||
of which four restate steps in its own Workflow (`:32` ≡ step 9, `:33` ≡ step 9, `:36` ≡ step 2,
|
||
`:31` ≡ the description). Its body is 1,102 words and its whole file 1,217, so it does fail the
|
||
900-word body FAIL — but for its length, not for the restatement. The four duplicated Gotchas are 114
|
||
words between them; delete every one and the file still fails, while a skill 250 words shorter with
|
||
the identical defect passes clean. The two properties are uncorrelated, which is why the counts are a
|
||
backstop to the dispatch rule and the Gotchas constraint — both of which are auditor judgment for the
|
||
semantic half, per the Enforcement table — and not a substitute for them. Reading the word gate as
|
||
the mechanism is the specific mistake this paragraph exists to prevent.
|
||
|
||
**Some skills legitimately need more description budget than others.** A tiered limit keyed to
|
||
sibling density was considered and rejected as too clever; the flat 250/400 pair means the `gitea-*`
|
||
and `git-*` families — where every sibling shares a keyword and boundary clauses do real routing work
|
||
— are the ones most likely to sit at the FAIL tier permanently. If the retrofit shows that family
|
||
routing degrades, the tier is the first thing to revisit.
|
||
|
||
**Four broken routing targets were found; two are fixed here and two are live.** Tracked as issue
|
||
#100.
|
||
|
||
- `skill-audit` routed to `/skill-improve` twice in its description plus `README.md:10`, and no such
|
||
skill exists — the real target is `skill-author`. **Fixed here**, as a side effect of retrofitting
|
||
kyberforge's own skills.
|
||
- `agent-author` said "Do not use for read-only review — examine agent files manually", routing away
|
||
from `agent-audit`, the correct sibling. **Fixed here**, same way. Note this one was never
|
||
detectable by the resolvable-target check and never will be: "examine agent files manually" names
|
||
no target, and a check that resolves names cannot see a name that is absent. A misroute to nowhere
|
||
is a review finding, not a gate finding.
|
||
- `research` routes to `neuledge-context`, which exists only inside that string. **Live.**
|
||
- `gitea-issues` carries the literal string `gitea-labels- milestones` in its folded description, a
|
||
stray space introduced by YAML wrapping mid-token, breaking the skill name in preloaded text.
|
||
**Live** — the check reports it as a dangling `gitea-labels`.
|
||
|
||
So the check fires on 3 of the 4 against the base commit and on 2 at the tip of this change, and
|
||
`tests/test-skill-size-check.sh` probes exactly those three by name rather than asserting a count, so
|
||
it degrades to SKIP as #100 lands rather than going stale.
|
||
|
||
**Duplication between `skill-author` and `agent-author` survives un-gated.** The merge rule
|
||
deliberately excludes the author pair, so the commit-verification argument in four near-copies, the
|
||
root-cause grouping rule in four copies, and the wholesale clone of the "Improving an existing X"
|
||
flow all remain. Cache isolation makes them structurally unavoidable
|
||
(`skill-audit/SKILL.md:95` forbids cross-skill references; `LESSONS.md:107` records why), so the
|
||
options are a sync gate or continued drift. This is an input to issue #101, which carries both halves
|
||
of the kyberforge duplication problem — the deferred audit-pair merge and this — not a solved
|
||
problem.
|
||
|
||
**Provenance frontmatter is explicitly out of scope.** `LESSONS.md:63` asserts that non-routing
|
||
frontmatter (`source_keys`, `category`, `version`) is loaded at agent startup, which would make the
|
||
7,352 characters of it across the corpus a third again on top of the description tax. Measured
|
||
against a live session on this Claude Code version, it is not: the model-visible skill listing
|
||
contains only `name` and `description`. That is host-observed rather than spec-guaranteed and says
|
||
nothing about Copilot CLI, but it is sufficient to establish that cutting `source_keys` would break
|
||
the ADR-0009 provenance machinery for no runtime gain. The metadata was added deliberately and stays.
|
||
|
||
## Alternatives considered
|
||
|
||
Upstream citations below are relative to
|
||
`plugins/kyberforge/docs/research/examples/skill-write/`, as in Context above.
|
||
|
||
- **Keep pushiness, raise the budget to ~500 chars.** Undertriggering is the worse failure mode — a
|
||
skill that never fires is worth nothing regardless of cost — and `skill-creator/SKILL.md:67`
|
||
explicitly recommends being "pushy" against an observed undertriggering tendency. Rejected because
|
||
that claim is an unmeasured assertion about an older model, and because the correctness hazard in
|
||
`writing-skills/SKILL.md:154-158` cuts the other way: a fat description is not merely expensive, it is a
|
||
shortcut agents take instead of reading the body. Would have landed a 35% cut.
|
||
- **A trigger-eval loop to set lengths empirically.** `skill-creator/SKILL.md:337-404` specifies 20
|
||
queries per skill, 8-10 positive and 8-10 near-miss, with a 60/40 train/test split selecting on
|
||
test score. This is the rigorous answer and the repo has deliberately never built it. Rejected
|
||
because it blocks the context cut behind a substantial new subsystem.
|
||
- **A repo-level aggregate preload budget** (≤12,000 chars across all skills, checked at pre-push).
|
||
The only option that measures the actual goal rather than a proxy. Rejected because it makes one
|
||
skill's edit fail on account of another skill's growth, and because it is meaningless for an
|
||
external consumer installing a subset of the plugins.
|
||
- **500-word body FAIL, matching `writing-skills/SKILL.md:217-221`.** Best-grounded in upstream and
|
||
would align this repo with the tightest source. Rejected because it fails 28 of 39 skills body-only
|
||
(35 of 39 measured whole-file), and a blunt gate gets satisfied by deleting content rather than
|
||
relocating it.
|
||
- **A shrinking baseline file** recording each non-compliant skill's current numbers, failing only on
|
||
growth. Would have made the retrofit a visible burn-down instead of a wall. Rejected in favour of
|
||
hot gates.
|
||
- **A sync gate over the duplicated spans** instead of a merge rule — generalising
|
||
`scripts/check-vale-style-sync.sh` to cover shared prose so duplication persists but drift cannot.
|
||
Rejected for the audit pair in favour of merging, which removes the duplication rather than
|
||
policing it, and removes a mutually-excluding near-miss pair from the router at the same time. It
|
||
remains the only available answer for the author pair.
|
||
- **Merging `skill-author` + `agent-author` as well**, taking kyberforge from seven skills to five.
|
||
Largest cut available. Rejected because it reopens ADR-0005, ADR-0008 and ADR-0016 together, and a
|
||
merged author skill would carry both the skill-directory scaffold and the dual-provider agent
|
||
scaffold behind one dispatch.
|
||
- **Demoting Gotchas** to the end of the body or into `references/gotchas.md`, removing its
|
||
position-based exemption from the dispatch rule. Maximum saving on the largest body construct
|
||
(6,830 words, 21% of all body text). Rejected because a gotcha read after the mistake is worthless.
|