docs: make ADR-0020 match what actually shipped, and record what did not

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
This commit is contained in:
2026-08-16 16:41:45 +00:00
parent d02765d595
commit 64ffb9f35a
4 changed files with 254 additions and 80 deletions

View File

@@ -2,7 +2,7 @@
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,612 characters — roughly 6,200 tokens — and the authoring rules that produced it optimised for
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.
@@ -10,14 +10,32 @@ gates that hold them.
## Context
Measured before any change:
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,612 chars, ~6,200 tokens, **preloaded every session** |
| 4 agent `name` + `description` | 1,325 chars, ~350 tokens, preloaded every session |
| skill bodies | median 684 words, mean 815, p90 1,349 |
| `MAX_WORDS` gate (`skill-audit/scripts/validate.sh:147`) | **2,770** — 2× p90 |
| 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".
@@ -32,7 +50,8 @@ with six capability clusters. Across the twelve longest descriptions, 30.7% is c
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.**
`docs/research/examples/skill-write/writing-skills/SKILL.md:154-158` reports a measured failure: "when
`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
@@ -41,21 +60,25 @@ chars, lowercase subject, no trailing periods, 11 standard types`) an agent can
loading the body.
**The upstream sources cannot settle this.** The four skill-writing references under
`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`,
`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`). `writing-skills` and the Anthropic
document it bundles contradict each other inside one skill directory. They also disagree on whether
`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 554-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,760 body / 1,247 references)
and `agent-author` (2,758 / 1,664). Both sit within twelve words of 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.
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
@@ -69,8 +92,39 @@ clause**, and a **boundary clause**. Capability enumeration, output-format detai
- **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 under `plugins/*/.apm/skills/`. This is checked deterministically.
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
@@ -89,11 +143,16 @@ prose move to `references/` behind an explicit "read X when Y" trigger.
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.
`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. Maximum five entries. A Gotcha that paraphrases
a step in the body below it is a FAIL. A Gotchas section exceeding 25% of the body is a
SUGGESTION.
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
@@ -101,6 +160,14 @@ Agents take the same description gates — they are preloaded identically — an
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
@@ -126,26 +193,84 @@ type of input they take should be **one skill with a dispatch table**. This catc
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:
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 | Home |
|---|---|
| description and body counts, resolvable boundary targets | `skill-audit/scripts/validate.sh`, `scripts/skill-size-check.sh` |
| prose patterns (composition-note openers, restatement) | `plugins/kyberforge/.apm/skills/*/assets/vale/styles/Kyberforge/` |
| judgment calls | `references/description-quality.md`, `references/body-discipline.md` |
| 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` |
**Blocking immediately, with no baseline file.**
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 `:101` and `DescriptionOpener.yml` require an imperative "Use when…" opener. The
criterion is unsatisfiable against the house's own skills, both of which open with "Use when".
- `DescriptionOpener.yml`'s regex is anchored to `^This (skill|agent)\b`, so `gitea-workflow` ("This
is the human-facing entry point…") and `gitea-labels-milestones` ("This is a cross-cutting shared
skill…") both violate the rule and pass the linter.
"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.
@@ -159,24 +284,35 @@ retrofits kyberforge's own four author/audit skills, so the figures on landing a
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. The follow-up retrofit issue should be prioritised accordingly, and the risk it
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 15,600 chars — a 34% cut, not the ~50% intended. The halving depends entirely on 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 34-55% down, not a
guaranteed 50%.
`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 thirteen
Gotchas of which four restate steps in its own Workflow (`:32` ≡ step 9, `:33` ≡ step 9, `:36` ≡ step
2, `:31` ≡ the description) — 1,217 words that pass any plausible gate. The counts are a backstop to
the dispatch rule and the Gotchas constraint, not a substitute for them, and should not be read as
the mechanism.
**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-*`
@@ -184,22 +320,34 @@ and `git-*` families — where every sibling shares a keyword and boundary claus
— 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 are live and are not fixed here.** `skill-audit` routes to
`/skill-improve` twice in its description plus `README.md:10`, and no such skill exists — the real
target is `skill-author`. `research` routes to `neuledge-context`, which exists only inside that
string. `agent-author` says "Do not use for read-only review — examine agent files manually",
routing away from `agent-audit`, the correct sibling. `gitea-issues` contains the literal string
`gitea-labels- milestones`, a stray space introduced by YAML folding, breaking the skill name in
preloaded text. The resolvable-target check added here will fail on all four the moment those files
are touched; fixing them is split into its own issue.
**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 the kyberforge-bodies follow-up
issue, not a solved problem.
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
@@ -211,11 +359,14 @@ the ADR-0009 provenance machinery for no runtime gain. The metadata was added de
## 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:154-158` cuts the other way: a fat description is not merely expensive, it is a
`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
@@ -226,8 +377,9 @@ the ADR-0009 provenance machinery for no runtime gain. The metadata was added de
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 30 of 39 skills, and a
blunt gate gets satisfied by deleting content rather than relocating it.
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.