fix(kyberforge): reconcile the authoring rules the ADR-0020 trim left disagreeing

Six defects, each one a place where two files that an author reads in the same
sitting told them different things — or where the trim dropped a rule and nothing
noticed because no gate covers prose.

**"Use proactively" contradicted itself across the pair.** All three agent
templates said to add it where the runtime should delegate unprompted, while
`agent-audit`'s `KyberforgeCopilot.ProactivePhrase` rule grades it a hard FAIL in
any `*.agent.md` — which is the Copilot half of every project/user pair *and* the
vendor-neutral plugin-scope file, since that compiles to a real Copilot agent
downstream. Following the template produced a file the repo's own gate rejects.
The phrase is now permitted in exactly one place, the Claude Code `.md`, and
`references/contract.md` carries the per-file table plus the consequence authors
ask about next: a pair whose CC half has it and whose Copilot half does not is
correct, because `agent-audit` checks that both halves describe the same job, not
that they match word for word.

**The output-schema rule contradicted itself inside one file.** `contract.md`
said any content only one branch reaches moves to `references/`, and then offered
an "Output format template" body pattern with no qualification. Stated once now,
so it is not re-litigated: an output schema stays in the body only when every flow
produces it and it is roughly 50 words or less. No third option.

**Gotchas tiers disagreed with the script.** `validate.sh` emits the entry count
through `suggest()` and exits 0, while `skill-author` and `skill-audit` both
called more than five entries a FAIL. Whether a given gotcha earns its place is
judgment, so the prose moves to the script's tier rather than the reverse. The
paraphrase rule stays a FAIL and is explicitly marked as the auditor's call — no
script detects it.

**The dispatch exemplar was cited at the wrong number.** `apm-workflow`'s body is
421 words; 554 is its whole-file count. Both `contract.md` and `body-discipline.md`
cited 554 while describing a body budget, so an author calibrating against the
exemplar overshot by ~30% — the exact whole-file/body-only conflation those two
sections exist to warn against, reproduced inside the warning.

**"Error handling" came back as a required body element.** It was one of four and
is the one that gets dropped, and dropping it is not neutral: an agent handed
malformed input with no instruction invents a recovery, and a subagent's invented
recovery is invisible to its caller until the output is wrong. Restored in
`agent-audit`'s rubric as a SUGGESTION, in `agent-author`'s contract and both
scope checklists as a required element, and as an `## Errors` section in all three
templates.

**`skill-author` Step 4 gains the one check the audit misses.** An empty body
reports `PASS SKILL.md body word count 0` — a word gate cannot tell "concise"
from "absent". Step 4 now hand-checks for a non-empty section, and its commit
verification is conditioned on actually being inside a git worktree, which a skill
under `~/.claude/skills/` is not.

Also here: absolute repo paths removed from `skill-author`'s SKILL.md and
contract.md in favour of naming the skill (`zoom-out`'s description is quoted
inline instead of pointed at), the boundary-target universe documented to match
the resolver, a two-hops-from-SKILL.md limit on reference chains, and
`new-agent.sh`'s next-steps output naming the description budget and the
deliberate absence of an agent body gate.

Refs: ADR-0020
This commit is contained in:
2026-08-16 16:40:51 +00:00
parent 2540e50fcc
commit 311e7cd22c
26 changed files with 332 additions and 108 deletions

View File

@@ -47,11 +47,15 @@ explicitly" only where the user's natural phrasing genuinely omits the domain wo
for `git-commits`, where the user says "commit". Adding one everywhere is what inflated this
corpus, and it was deleted as a blanket rule.
**Boundary targets must resolve.** The name after the arrow is checked against real skill
directories under `plugins/*/.apm/skills/<name>/` and real agents under
`plugins/*/.apm/agents/<name>.agent.md`. A boundary clause naming a target that does not exist
sends the router nowhere and fails the audit. Check the target exists before writing it — do not
invent a plausible sibling name.
**Boundary targets must resolve.** Both forms are checked — the arrow and the prose form ("do not
use for X, use `y` instead") — so a typo dangles either way. Targets resolve against a universe
built by walking up **from the SKILL.md itself**: the nearest ancestor holding
`plugins/*/.apm/{skills,agents}` (or, failing that, the nearest ancestor holding `.git`) contributes
every skill and agent under `<root>/plugins/*/`, plus the skill's own apm package and the packages
that package declares in `apm.yml` under `dependencies.apm`. A sibling plugin in the same monorepo
therefore resolves; a skill in an unrelated repo does not. A boundary clause naming a target
outside that universe sends the router nowhere and fails the audit. Check the target exists before
writing it — do not invent a plausible sibling name.
**Length.** 250 characters SUGGESTION, 400 characters FAIL, counting the frontmatter value only
with YAML folding resolved. The agentskills.io 1,024-character spec limit is unchanged and sits
@@ -61,7 +65,13 @@ as the outlier stop.
**Hand-invoked skills are exempt.** A skill carrying `disable-model-invocation: true` is absent
from the model-visible listing and is reached only by the user typing `/name`. It takes one plain
human-facing sentence — no trigger clause, no boundary clause, no indirect triggers. Worked
example: `plugins/bin/.apm/skills/zoom-out/SKILL.md`.
example — the whole description of the `zoom-out` skill, which carries `disable-model-invocation`:
````markdown
Tell the agent to zoom out and give broader context or a higher-level perspective. Use when
you're unfamiliar with a section of code or need to understand how it fits into the bigger
picture.
````
## Body
@@ -90,6 +100,12 @@ blocks, rationale prose, and any content only one branch reaches. Each reference
self-contained for its concern, and every one is wired from the body with the literal conditional
form:
**The one exception, stated once so it is not re-litigated:** an output schema stays in the body
only when it applies to *every* flow and is short — roughly 50 words or less, which is the "Output
format template" pattern below. An output schema that is longer than that, or that only one flow
produces, moves to `references/` like any other schema. No third option exists, and the two rules
do not disagree.
````markdown
If <condition>, read `references/<file>.md`.
````
@@ -98,8 +114,9 @@ A generic pointer ("see references/ for details") is a Vale error — the agent
**Dispatch is mandatory at two or more mutually exclusive flows.** The body carries the dispatch
table and the gates common to every branch; each flow gets its own self-contained `references/`
file. Exemplar: `plugins/kyberforge/.apm/skills/apm-workflow/SKILL.md` — a 554-word body
dispatching to 3,006 words of references.
file. Exemplar: the `apm-workflow` skill — a **421-word body** dispatching to 3,006 words of
references. Calibrate against 421: that file's whole-file count is 554 words, and aiming at that
number instead overshoots the body budget by ~30%.
**Length.** 600 words SUGGESTION, 900 words FAIL, counting the **body only** — everything after
the frontmatter's closing `---`.
@@ -108,7 +125,7 @@ the frontmatter's closing `---`.
- Each entry must state a fact that **contradicts a reasonable default** — something the agent
gets wrong by acting sensibly. "Never commit secrets" is not one; the agent already knows.
- Maximum five entries.
- More than five entries is a SUGGESTION — five is the guideline, not a ceiling.
- A Gotcha that paraphrases a step in the body below it is a **FAIL**. If the rule is already a
step, it is not a gotcha.
- A Gotchas section exceeding 25% of the body is a SUGGESTION.
@@ -164,7 +181,9 @@ Do not modify flags.
| <condition> | <flow> | `references/<file>.md` |
````
**Output format template** (when the skill produces structured output):
**Output format template** (when the skill produces structured output on *every* flow, and the
schema is roughly 50 words or less — see the exception under Body above; anything longer or
flow-specific belongs in `references/`):
````markdown
Output format:
@@ -173,7 +192,8 @@ Output format:
```
````
For longer templates, place them in `assets/<name>.md` and reference conditionally.
For longer templates, place them in `references/<topic>.md` or `assets/<name>.md` and reference
conditionally.
## Embedding org-specific policy

View File

@@ -136,10 +136,16 @@ If no scripts are needed, delete `scripts/README.md` and the `scripts/` director
## Step 5 — Add references, assets, and tests (if needed)
**`references/`** — additional documentation loaded on demand. One topic per file. Reference
conditionally from SKILL.md with the literal form ``If <condition>, read `references/<file>.md` ``.
Keep reference chains one level deep — a reference file that references another reference file is
rarely loaded correctly.
**`references/`** — additional documentation loaded on demand. One topic per file, named in
kebab-case after the topic. Reference conditionally from SKILL.md with the literal form
``If <condition>, read `references/<file>.md` ``.
**Two hops from `SKILL.md`, never three.** A flow file may route on to a shared contract or
sub-topic file — that is the shipped pattern here (`SKILL.md` → `references/create.md` → this
file's own pointers to `contract.md`, `scripts.md` and `deployment-modes.md`). What does not work
is a third hop: a file reachable only through two intermediates is rarely loaded at the moment it
is needed. Every hop past the first also needs the same literal conditional form, so the agent
knows when to take it.
**`assets/`** — static resources: templates, schemas, lookup tables. Reference by relative path
from SKILL.md.