fix(kyberforge): restore the authoring rules the ADR-0020 trim dropped

Diffing each retrofitted SKILL.md against its replacement references/ files found
rules that existed on main and now existed nowhere — relocated in intent, deleted in
fact. A trim that loses a rule is not progressive disclosure, it is data loss with a
smaller word count.

Three had no survivor. The least-privilege guidance for `tools` kept its mechanics
and lost the "restrict to what the agent needs" half, so the remaining text read as
encouragement to omit the field. The improve flow lost its regression check, so
nothing compared the closing audit against the pre-edit state and a PASS quietly
becoming a SUGGESTION went unnoticed — restored on both halves of the author pair,
since agent-author had dropped its equivalent too. And agent bodies lost "would the
agent get this wrong without it?", which mattered more than it looks: ADR-0020
deliberately sets no body word gate for agents, three of the four already sit
between 933 and 1,199 words, and the delegation check only fires on procedure a
skill already owns. That heuristic was the only brake left.

Two more were reachable only from the wrong scope. agent-author tells the reader to
load only the file for the resolved scope, but the mcp__ glob syntax for
disallowedTools and the five tools no subagent ever receives had both landed in
project-user-scope.md. disallowedTools is the ONLY permitted fence at plugin/APM
scope, so the scope that needs the syntax most could not reach it, and a plugin-scope
run could write a body telling the agent to ask the user a question.

Two documents were actively wrong rather than merely thin. agent-audit told auditors
that validate.sh resolves boundary targets for skills only; it runs at both scopes,
so the auditor was hand-resolving what the script had already decided and could
contradict it. And skill-audit routed to its script-troubleshooting reference
whenever validate.sh "fails" — but it exits 1 on ordinary content FAILs, the normal
outcome for the whole #99 population, so 1,302 words loaded on nearly every audit.
A context-budget regression inside the skill that enforces the context budget.

Finally, two illustrations taught the shape the gate ERRORs on, unfenced, while an
adjacent rubric called it a hard ERROR.

LESSONS.md records the reference-chain depth rule flipping from "one level deep" to
"two hops, never three". ADR-0020 is silent on it and the reversal rode entirely on
the diff; the looser rule is what mandatory dispatch requires.

Refs: #99
ADR: 0020

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W3iwF9ncfRZddGBxsMCYi
This commit is contained in:
2026-08-16 19:49:38 +00:00
parent b0d6d08239
commit ede3f06689
21 changed files with 120 additions and 22 deletions

View File

@@ -111,9 +111,12 @@ Flag as FAIL if:
available). `Kyberforge.VagueWording` catches the known filler; imprecision outside that list is
judgment.
- **A boundary clause naming a target that does not resolve** to a real skill directory or agent
file in the authoring source. No script checks this for an agent file — `validate.sh` resolves
boundary targets for skills only, so resolve the name yourself against `plugins/*/.apm/skills/`
and `plugins/*/.apm/agents/`.
file in the authoring source. `validate.sh` resolves this for agent files at both scopes and
reports each unresolved target itself — take its verdict rather than re-resolving the name by
hand, because a hand-walk over a different universe can contradict it. What is left to you is
semantic and the script cannot reach it: whether a target that *does* resolve is the right
sibling to exclude, and whether a clause naming no target at all ("examine the files manually")
should have named one.
- **`Use proactively` in a Copilot or vendor-neutral description.**
`KyberforgeCopilot.ProactivePhrase` catches it. The phrase steers the Claude Code runtime and
does nothing anywhere else, so in a `.agent.md` it is preloaded text that buys no behaviour.

View File

@@ -53,6 +53,8 @@ Gates `agent-audit` enforces at every scope:
- **Body** — no word gate, and a delegation check in its place: name the skill to invoke rather than restating what it does.
- **Invocation** — decide whether the agent is model-delegated or reached only by name. Only Copilot's cloud/IDE format expresses that in frontmatter (`disable-model-invocation`, `user-invocable`).
At every scope, five tools reach no subagent whatever `tools` says — `AskUserQuestion`, `EnterPlanMode`, `ExitPlanMode`, `ScheduleWakeup`, `WaitForMcpServers`. Never write a body that has the agent ask the user a question or enter plan mode; it describes a turn the runtime cannot give it.
## Step 4 — Validate and close
Invoke `agent-audit` on each file written and resolve every FAIL before reporting done. It checks the field allowlist, name-to-stem match, leftover placeholders and template comments, the description budget and the Copilot body limit — do not hand-check those.

View File

@@ -25,12 +25,13 @@ description: FILL IN: Use when <trigger>. <One capability clause.> Not <thing> -
<!-- tools: Read, Bash, Grep
Optional. Allowlist of tool names: a comma-separated string or a YAML list.
Omit to inherit all tools from parent.
Restrict it to what the agent actually needs. Omit only when it needs them
all — omitting inherits every tool from the parent.
Use Agent(type1,type2) to restrict which subagent types this agent can spawn.
Omit Agent entirely to prevent this agent from spawning subagents.
Never available to subagents regardless of tools field:
AskUserQuestion, EnterPlanMode, ExitPlanMode, ScheduleWakeup, WaitForMcpServers
Exception: ExitPlanMode IS available when parent session runs in permissionMode: plan -->
Listing any of them is a finding: agent-audit enforces the flat rule. -->
<!-- model: sonnet
Optional. Aliases: sonnet, opus, haiku, fable. Or full model ID.

View File

@@ -57,6 +57,11 @@ procedure a skill it can invoke already owns is an `agent-audit` FAIL. When a si
missing procedure, check first whether an installed skill owns it and name that skill instead of
transcribing it. See `references/contract.md`.
The delegation check is not a length brake — it fires only on procedure an invocable skill already
owns, and says nothing about original prose. That brake is judgment, and it is the only one left:
for every sentence you add, ask "would the agent get this wrong without it?" and delete it if the
answer is no.
**Explain the why.** Reasoning-based instructions outperform rigid directives. A rule written in
all caps (ALWAYS/NEVER) is usually better reframed as why the behaviour matters, so the agent can
apply judgment at the edges.
@@ -73,4 +78,10 @@ that was already there.
If the edit adds or removes research-sourced content, update `source_keys` in the edited file and
the matching `sources.md` entry — the create flow's Step 3 has the rules.
**Check for regressions before handing back.** `SKILL.md` Step 4 tells you to resolve every FAIL,
which says nothing about a check that passed *before* these edits and no longer does. Compare the
closing `agent-audit` against the agent's pre-edit state — a PASS that has become a SUGGESTION, or
a SUGGESTION that has become a FAIL, is damage this flow caused and is in scope for it. Only the
improve flow can make that comparison; the create flow has no prior state to compare against.
Then return to `SKILL.md` Step 4.

View File

@@ -37,6 +37,11 @@ The rule is about a field's *shape*, not a fixed roster:
Claude Code honours it for plugin subagents; the three fields plugin agents do silently ignore
are `hooks`, `mcpServers` and `permissionMode`, and this is not one of them. Copilot's handling
of the key is unconfirmed, which ADR-0016 accepts as a stated risk.
Its syntax is the same at every scope, and this is the one scope that cannot reach it anywhere
else: MCP tools are denied as `mcp__<server>`, `mcp__<server>__*` or `mcp__*`; both a YAML list
and a delimited string are accepted, and this repo writes the comma-separated string form
(`disallowedTools: Edit, Write, NotebookEdit`) — match it.
- The Claude-only knobs (`isolation`, `maxTurns`, `effort`, `memory`, `permissionMode`, `skills`,
`color`, `initialPrompt`, `background`, `hooks`, `mcpServers`) have no Copilot equivalent and
are never written to this file at all. "Silently ignored at plugin scope" is the wrong framing:

View File

@@ -25,7 +25,10 @@ duplicate silently.
**`description`** — write it against `references/contract.md`. It is the primary signal for
autonomous delegation.
**`tools`** — an allowlist; omit it to inherit every tool from the parent. Use `Agent(type1,type2)`
**`tools`** — an allowlist. Write it, and restrict it to the tools the agent actually needs;
omitting it inherits every tool from the parent, which is the right value only when the agent
genuinely needs all of them. Least privilege is the default, not the exception. Use
`Agent(type1,type2)`
to restrict which subagent types this agent may spawn, and omit `Agent` entirely to stop it
spawning any. Five tools reach no subagent whatever this field says — `AskUserQuestion`,
`EnterPlanMode`, `ExitPlanMode`, `ScheduleWakeup` and `WaitForMcpServers` — so listing one buys

View File

@@ -35,7 +35,7 @@ scripts/vale-wrap.sh <skill-dir>/SKILL.md
`validate.sh` findings become the `### Structure` dimension — its FAILs and its SUGGESTIONs both.
If any of the three fails, cannot run, or reports something needing interpretation, read `references/validation-scripts.md` — it carries the manual fallback and the misleading exit codes.
If any of the three cannot run, or exits non-zero for a reason other than findings, read `references/validation-scripts.md` — it carries the manual fallback and the misleading exit codes. Ordinary content FAILs are the expected outcome here and need no fallback.
`validate-provenance.sh` prints nothing on success. Its FAIL and INFO findings become a separate `### Provenance` dimension, and it emits Why and Fix itself — surface those verbatim.

View File

@@ -28,8 +28,10 @@ Include content the agent lacks:
- The specific tools or sequences to use — not the full range of options
- One default per decision point with one escape hatch
Move to `references/`, behind an explicit "If X, read `references/file.md`" trigger — the literal
conditional form, never a generic pointer:
Move to `references/`, behind an explicit "If X, read `references/<file>.md`" trigger — the literal
conditional form, never a generic pointer. Write the real filename in the skill under audit; the
angle brackets are a placeholder here, and a literal `references/file.md` in a body is an ERROR
from the ADR-0020 gate because no such file exists on disk. Move:
- Lookup tables and spec restatements
- Output schemas, templates and example blocks

View File

@@ -32,8 +32,16 @@ read after the mistake.
inner fence as `` \`\`\` ``. An unescaped inner fence terminates the outer block and the remaining
instructions render as prose.
**Conditional references** state a specific trigger: "If the API returns a non-200 status, read
`references/api-errors.md`." The generic form — pointing at the directory and hoping — defeats
**Conditional references** state a specific trigger, naming a file that exists in the skill's own
`references/` directory:
```text
If the API returns a non-200 status, read `references/api-errors.md`.
```
That block is fenced because the filename in it is illustrative — an unfenced `references/` pointer
in a `SKILL.md` body must resolve on disk or the ADR-0020 gate reports a hard ERROR. The generic
form — pointing at the directory and hoping — defeats
progressive disclosure, because the agent either loads everything or loads nothing.
`Kyberforge.PaddingPhrase` catches the common generic phrasing deterministically; other malformed
forms are judgment.

View File

@@ -85,4 +85,10 @@ improvise the cuts — four dry runs invented six to ten different answers to th
If a signal points to a script or reference file, edit that file directly rather than adding a
workaround in SKILL.md.
**Check for regressions before handing back.** `SKILL.md` Step 4 tells you to resolve every FAIL,
which says nothing about a check that passed *before* these edits and no longer does. Compare the
closing audit against the skill's pre-edit state — a PASS that has become a SUGGESTION, or a
SUGGESTION that has become a FAIL, is damage this flow caused and is in scope for it. Only the
improve flow can make that comparison; the create flow has no prior state to compare against.
Then return to `SKILL.md` Step 4.

View File

@@ -111,9 +111,12 @@ Flag as FAIL if:
available). `Kyberforge.VagueWording` catches the known filler; imprecision outside that list is
judgment.
- **A boundary clause naming a target that does not resolve** to a real skill directory or agent
file in the authoring source. No script checks this for an agent file — `validate.sh` resolves
boundary targets for skills only, so resolve the name yourself against `plugins/*/.apm/skills/`
and `plugins/*/.apm/agents/`.
file in the authoring source. `validate.sh` resolves this for agent files at both scopes and
reports each unresolved target itself — take its verdict rather than re-resolving the name by
hand, because a hand-walk over a different universe can contradict it. What is left to you is
semantic and the script cannot reach it: whether a target that *does* resolve is the right
sibling to exclude, and whether a clause naming no target at all ("examine the files manually")
should have named one.
- **`Use proactively` in a Copilot or vendor-neutral description.**
`KyberforgeCopilot.ProactivePhrase` catches it. The phrase steers the Claude Code runtime and
does nothing anywhere else, so in a `.agent.md` it is preloaded text that buys no behaviour.

View File

@@ -53,6 +53,8 @@ Gates `agent-audit` enforces at every scope:
- **Body** — no word gate, and a delegation check in its place: name the skill to invoke rather than restating what it does.
- **Invocation** — decide whether the agent is model-delegated or reached only by name. Only Copilot's cloud/IDE format expresses that in frontmatter (`disable-model-invocation`, `user-invocable`).
At every scope, five tools reach no subagent whatever `tools` says — `AskUserQuestion`, `EnterPlanMode`, `ExitPlanMode`, `ScheduleWakeup`, `WaitForMcpServers`. Never write a body that has the agent ask the user a question or enter plan mode; it describes a turn the runtime cannot give it.
## Step 4 — Validate and close
Invoke `agent-audit` on each file written and resolve every FAIL before reporting done. It checks the field allowlist, name-to-stem match, leftover placeholders and template comments, the description budget and the Copilot body limit — do not hand-check those.

View File

@@ -25,12 +25,13 @@ description: FILL IN: Use when <trigger>. <One capability clause.> Not <thing> -
<!-- tools: Read, Bash, Grep
Optional. Allowlist of tool names: a comma-separated string or a YAML list.
Omit to inherit all tools from parent.
Restrict it to what the agent actually needs. Omit only when it needs them
all — omitting inherits every tool from the parent.
Use Agent(type1,type2) to restrict which subagent types this agent can spawn.
Omit Agent entirely to prevent this agent from spawning subagents.
Never available to subagents regardless of tools field:
AskUserQuestion, EnterPlanMode, ExitPlanMode, ScheduleWakeup, WaitForMcpServers
Exception: ExitPlanMode IS available when parent session runs in permissionMode: plan -->
Listing any of them is a finding: agent-audit enforces the flat rule. -->
<!-- model: sonnet
Optional. Aliases: sonnet, opus, haiku, fable. Or full model ID.

View File

@@ -57,6 +57,11 @@ procedure a skill it can invoke already owns is an `agent-audit` FAIL. When a si
missing procedure, check first whether an installed skill owns it and name that skill instead of
transcribing it. See `references/contract.md`.
The delegation check is not a length brake — it fires only on procedure an invocable skill already
owns, and says nothing about original prose. That brake is judgment, and it is the only one left:
for every sentence you add, ask "would the agent get this wrong without it?" and delete it if the
answer is no.
**Explain the why.** Reasoning-based instructions outperform rigid directives. A rule written in
all caps (ALWAYS/NEVER) is usually better reframed as why the behaviour matters, so the agent can
apply judgment at the edges.
@@ -73,4 +78,10 @@ that was already there.
If the edit adds or removes research-sourced content, update `source_keys` in the edited file and
the matching `sources.md` entry — the create flow's Step 3 has the rules.
**Check for regressions before handing back.** `SKILL.md` Step 4 tells you to resolve every FAIL,
which says nothing about a check that passed *before* these edits and no longer does. Compare the
closing `agent-audit` against the agent's pre-edit state — a PASS that has become a SUGGESTION, or
a SUGGESTION that has become a FAIL, is damage this flow caused and is in scope for it. Only the
improve flow can make that comparison; the create flow has no prior state to compare against.
Then return to `SKILL.md` Step 4.

View File

@@ -37,6 +37,11 @@ The rule is about a field's *shape*, not a fixed roster:
Claude Code honours it for plugin subagents; the three fields plugin agents do silently ignore
are `hooks`, `mcpServers` and `permissionMode`, and this is not one of them. Copilot's handling
of the key is unconfirmed, which ADR-0016 accepts as a stated risk.
Its syntax is the same at every scope, and this is the one scope that cannot reach it anywhere
else: MCP tools are denied as `mcp__<server>`, `mcp__<server>__*` or `mcp__*`; both a YAML list
and a delimited string are accepted, and this repo writes the comma-separated string form
(`disallowedTools: Edit, Write, NotebookEdit`) — match it.
- The Claude-only knobs (`isolation`, `maxTurns`, `effort`, `memory`, `permissionMode`, `skills`,
`color`, `initialPrompt`, `background`, `hooks`, `mcpServers`) have no Copilot equivalent and
are never written to this file at all. "Silently ignored at plugin scope" is the wrong framing:

View File

@@ -25,7 +25,10 @@ duplicate silently.
**`description`** — write it against `references/contract.md`. It is the primary signal for
autonomous delegation.
**`tools`** — an allowlist; omit it to inherit every tool from the parent. Use `Agent(type1,type2)`
**`tools`** — an allowlist. Write it, and restrict it to the tools the agent actually needs;
omitting it inherits every tool from the parent, which is the right value only when the agent
genuinely needs all of them. Least privilege is the default, not the exception. Use
`Agent(type1,type2)`
to restrict which subagent types this agent may spawn, and omit `Agent` entirely to stop it
spawning any. Five tools reach no subagent whatever this field says — `AskUserQuestion`,
`EnterPlanMode`, `ExitPlanMode`, `ScheduleWakeup` and `WaitForMcpServers` — so listing one buys

View File

@@ -35,7 +35,7 @@ scripts/vale-wrap.sh <skill-dir>/SKILL.md
`validate.sh` findings become the `### Structure` dimension — its FAILs and its SUGGESTIONs both.
If any of the three fails, cannot run, or reports something needing interpretation, read `references/validation-scripts.md` — it carries the manual fallback and the misleading exit codes.
If any of the three cannot run, or exits non-zero for a reason other than findings, read `references/validation-scripts.md` — it carries the manual fallback and the misleading exit codes. Ordinary content FAILs are the expected outcome here and need no fallback.
`validate-provenance.sh` prints nothing on success. Its FAIL and INFO findings become a separate `### Provenance` dimension, and it emits Why and Fix itself — surface those verbatim.

View File

@@ -28,8 +28,10 @@ Include content the agent lacks:
- The specific tools or sequences to use — not the full range of options
- One default per decision point with one escape hatch
Move to `references/`, behind an explicit "If X, read `references/file.md`" trigger — the literal
conditional form, never a generic pointer:
Move to `references/`, behind an explicit "If X, read `references/<file>.md`" trigger — the literal
conditional form, never a generic pointer. Write the real filename in the skill under audit; the
angle brackets are a placeholder here, and a literal `references/file.md` in a body is an ERROR
from the ADR-0020 gate because no such file exists on disk. Move:
- Lookup tables and spec restatements
- Output schemas, templates and example blocks

View File

@@ -32,8 +32,16 @@ read after the mistake.
inner fence as `` \`\`\` ``. An unescaped inner fence terminates the outer block and the remaining
instructions render as prose.
**Conditional references** state a specific trigger: "If the API returns a non-200 status, read
`references/api-errors.md`." The generic form — pointing at the directory and hoping — defeats
**Conditional references** state a specific trigger, naming a file that exists in the skill's own
`references/` directory:
```text
If the API returns a non-200 status, read `references/api-errors.md`.
```
That block is fenced because the filename in it is illustrative — an unfenced `references/` pointer
in a `SKILL.md` body must resolve on disk or the ADR-0020 gate reports a hard ERROR. The generic
form — pointing at the directory and hoping — defeats
progressive disclosure, because the agent either loads everything or loads nothing.
`Kyberforge.PaddingPhrase` catches the common generic phrasing deterministically; other malformed
forms are judgment.

View File

@@ -85,4 +85,10 @@ improvise the cuts — four dry runs invented six to ten different answers to th
If a signal points to a script or reference file, edit that file directly rather than adding a
workaround in SKILL.md.
**Check for regressions before handing back.** `SKILL.md` Step 4 tells you to resolve every FAIL,
which says nothing about a check that passed *before* these edits and no longer does. Compare the
closing audit against the skill's pre-edit state — a PASS that has become a SUGGESTION, or a
SUGGESTION that has become a FAIL, is damage this flow caused and is in scope for it. Only the
improve flow can make that comparison; the create flow has no prior state to compare against.
Then return to `SKILL.md` Step 4.