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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user