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

@@ -34,10 +34,13 @@ description: FILL IN: Use when <trigger>. <One capability clause.> Not <thing> -
boundary clause naming a real sibling skill or agent.
250 characters is the target, 400 the hard ceiling (ADR-0020).
Do not open with an action verb ("Reviews...", "Analyzes...") — that rule was
deleted. Add "Use proactively" only if the runtime should delegate here without
the user naming this agent.
deleted.
Never write "Use proactively" here. It steers the Claude Code runtime and does
nothing anywhere else, and this file compiles to a Copilot `.agent.md` too, where
agent-audit's KyberforgeCopilot.ProactivePhrase rule grades it a hard FAIL.
The phrase is CC-only; at this scope, a precise trigger clause does that job.
Example: "Use when a diff needs checking for injected credentials before it
merges. Not general code review -> code-reviewer." -->
merges. Not prose or style linting -> `lint-runner`." -->
<!-- model: sonnet
Optional. Aliases: sonnet, opus, haiku, fable. Or full model ID.
@@ -80,3 +83,10 @@ FILL IN: Steps the agent takes. Be specific about ordering if it matters.
## Output
FILL IN: What does the agent produce? Format, location, structure.
## Errors
FILL IN: What does the agent do on malformed, missing or contradictory input?
State whether it stops and reports, or degrades to a named fallback — and what it
tells the caller either way. An agent with no error handling invents a recovery,
and an invented recovery is invisible until the output is wrong.

View File

@@ -14,10 +14,14 @@ description: FILL IN: Use when <trigger>. <One capability clause.> Not <thing> -
boundary clause naming a real sibling skill or agent.
250 characters is the target, 400 the hard ceiling (ADR-0020).
Do not open with an action verb ("Reviews...", "Analyzes...") — that rule was
deleted. Add "Use proactively" only if the runtime should delegate here without
the user naming this agent.
deleted.
"Use proactively" is valid HERE and only here: it steers the Claude Code runtime
to offer this agent unprompted. Add it only if that is what you want. If you add
it, leave it OUT of the Copilot half of the pair — the phrase does nothing there
and agent-audit's KyberforgeCopilot.ProactivePhrase grades it a hard FAIL. The
pair must describe the same job; it does not have to be byte-identical.
Example: "Use when a diff needs checking for injected credentials before it
merges. Not general code review -> code-reviewer." -->
merges. Not prose or style linting -> `lint-runner`." -->
<!-- tools: Read, Bash, Grep
Optional. Allowlist of tool names: a comma-separated string or a YAML list.
@@ -99,3 +103,10 @@ FILL IN: Steps the agent takes. Be specific about ordering if it matters.
## Output
FILL IN: What does the agent produce? Format, location, structure.
## Errors
FILL IN: What does the agent do on malformed, missing or contradictory input?
State whether it stops and reports, or degrades to a named fallback — and what it
tells the caller either way. An agent with no error handling invents a recovery,
and an invented recovery is invisible until the output is wrong.

View File

@@ -19,9 +19,13 @@ description: FILL IN: Use when <trigger>. <One capability clause.> Not <thing> -
boundary clause naming a real sibling skill or agent.
250 characters is the target, 400 the hard ceiling (ADR-0020).
Do not open with an action verb ("Reviews...", "Analyzes...") — that rule was deleted.
Keep it identical in wording to the Claude Code half of the pair.
Never write "Use proactively" here. It steers the Claude Code runtime and does nothing
in Copilot, and agent-audit's KyberforgeCopilot.ProactivePhrase grades it a hard FAIL.
Otherwise keep the wording matched to the Claude Code half of the pair: agent-audit
checks that both halves describe the same job, not that they are byte-identical, so
dropping the CC-only phrase here is not a pair-consistency finding.
Example: "Use when a diff needs checking for injected credentials before it
merges. Not general code review -> code-reviewer." -->
merges. Not prose or style linting -> `lint-runner`." -->
<!-- tools: ["read", "search", "edit"]
Optional. Array of tool names. Omit = all available tools. [] = no tools.
@@ -68,3 +72,10 @@ FILL IN: Steps the agent takes. Be specific about ordering if it matters.
## Output
FILL IN: What does the agent produce? Format, location, structure.
## Errors
FILL IN: What does the agent do on malformed, missing or contradictory input?
State whether it stops and reports, or degrades to a named fallback — and what it
tells the caller either way. An agent with no error handling invents a recovery,
and an invented recovery is invisible until the output is wrong.

View File

@@ -44,15 +44,31 @@ Banned from a description; move it to the body or to `README.md`:
and ADR-0020 deleted it: the opener is `Use when`, matching every skill in this corpus, so one
router reads one shape.
**"Use proactively" is conditional.** Add it only where the runtime should delegate without the
user naming the agent — an agent invoked by name does not need it, and it costs activations
elsewhere when added by reflex. The same conditional governs indirect triggers ("even if the user
doesn't say X"): add one only where the user's natural phrasing genuinely omits the domain word.
**"Use proactively" is Claude Code-only, and conditional even there.** The phrase steers the
Claude Code runtime to offer an agent unprompted and does nothing anywhere else, so where it may
appear depends on the file:
**Boundary targets must resolve.** The name after the arrow is checked against real skills under
`plugins/*/.apm/skills/<name>/` and real agents under `plugins/*/.apm/agents/<name>.agent.md`. A
target that does not exist sends the router nowhere. Verify it before writing it — do not invent a
plausible sibling.
| File | Rule |
|---|---|
| Claude Code `.md` (project/user scope) | Allowed. Add it only where the runtime should delegate without the user naming the agent — an agent invoked by name does not need it, and it costs activations elsewhere when added by reflex. |
| Copilot `.agent.md` (project/user scope) | **Never.** Inert there, and `KyberforgeCopilot.ProactivePhrase` grades it a hard FAIL. |
| Vendor-neutral `.apm/agents/<name>.agent.md` (plugin/APM scope) | **Never.** Same Vale rule, same hard FAIL — the file matches the `**/*.agent.md` glob, and it compiles to a real Copilot agent downstream. |
A pair whose Claude Code half carries the phrase and whose Copilot half omits it is correct, not
inconsistent: `agent-audit` checks that both halves describe the same job, not that they match
word for word.
Indirect triggers ("even if the user doesn't say X") take a similar conditional at every scope:
add one only where the user's natural phrasing genuinely omits the domain word.
**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 agent file 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 agent'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 target outside that universe sends the
router nowhere. Verify it before writing it — do not invent a plausible sibling.
**Length.** 250 characters SUGGESTION, 400 characters FAIL, counting the frontmatter value only
with YAML folding resolved. Treat 250 as the target: the SUGGESTION tier is what moves the corpus
@@ -73,8 +89,18 @@ You are a <role>. When invoked, <primary action>.
## Output
<what it produces: format, location, structure>
## Errors
<what to do on malformed, missing or contradictory input: report and stop, or
which fallback to take — and what to say to the caller either way>
````
Four required elements: **inputs expected, process steps, output format, error handling.** The
last is the one that gets dropped, and dropping it is not neutral: an agent given a malformed
input and no instruction invents a recovery, and a subagent's invented recovery is invisible to
the caller until the output is wrong. Say explicitly whether the agent stops and reports, or
degrades to a named fallback.
One job per agent. An agent covering two jobs gets delegated to for the wrong one.
**Delegation discipline replaces the word gate.** A plugin/APM agent is a single file with no

View File

@@ -63,5 +63,7 @@ to installed skills instead of transcribed procedure.
- [ ] No `FILL IN:` placeholder and no `<!-- ... -->` template comment anywhere in the file
- [ ] System prompt body non-empty, and a read-only agent says so in prose as well as in
`disallowedTools`
- [ ] Body covers all four required elements: inputs expected, process steps, output format,
**error handling** — what the agent does on malformed, missing or contradictory input
Then return to the flow reference you came from.

View File

@@ -95,6 +95,8 @@ Both files:
- [ ] `name` present and kebab-case; `description` written to `references/contract.md`
- [ ] System prompt body present, non-empty and equivalent across the pair
- [ ] Body covers all four required elements: inputs expected, process steps, output format,
**error handling** — what the agent does on malformed, missing or contradictory input
- [ ] No `FILL IN:` placeholder and no `<!-- ... -->` template comment left
Copilot file only:

View File

@@ -285,6 +285,8 @@ else
if [[ "$SCOPE" == "plugin" ]]; then
echo " 1. Fill in $APM_FILE — replace every FILL IN: placeholder. Optional fields are" >&2
echo " scaffolded there as commented blocks; uncomment the ones that apply." >&2
echo " Description: 250 chars target / 400 ceiling (ADR-0020). The body has no" >&2
echo " word gate — delegate to a skill instead of restating what it does." >&2
echo " 2. Populate $SOURCES_DIR/sources.md with research sources, or delete it" >&2
echo " 3. Validate: $VALIDATE_HINT $APM_FILE" >&2
echo " It checks the frontmatter against the apm-agent-allowlist section of" >&2
@@ -292,6 +294,8 @@ else
else
echo " 1. Fill in $CC_FILE — replace every FILL IN: placeholder. Optional fields are" >&2
echo " scaffolded there as commented blocks; uncomment the ones that apply." >&2
echo " Description: 250 chars target / 400 ceiling (ADR-0020). The body has no" >&2
echo " word gate — delegate to a skill instead of restating what it does." >&2
echo " 2. Fill in $CP_FILE — same, and heed its closing comment: the Claude Code-only" >&2
echo " fields it names must not cross over from the file above." >&2
echo " 3. Validate: run $VALIDATE_HINT on each file" >&2