feat(kyberforge): enforce the ADR-0020 context contract for skills and agents

Skill name+description pairs are preloaded into every session, costing
~6,200 tokens across 39 skills before any skill is invoked. The authoring
rules mandated that growth: skill-author:104 and description-quality.md:21
both required padding, while skill-author:102 (the deflating rule) had no
FAIL condition behind it.

Gates (blocking, no baseline file):
- description 250 chars SUGGESTION / 400 FAIL, measured on the folded
  YAML value
- body-only 600 words SUGGESTION / 900 FAIL, independent of the unchanged
  whole-file 2770-word / 500-line spec backstop
- every boundary-clause routing target must resolve to a real skill or
  agent; catches skill-improve, neuledge-context and gitea-labels
- agents take the description gates but deliberately no body gate; a test
  pins that absence

Vale: DescriptionOpener widened to ^This\b, new CompositionNote rule
banning architecture notes from descriptions. 10 hits, 0 false positives.

Kyberforge's own four skills retrofitted: descriptions 3,364 -> 938 chars
(-72%), bodies 8,306 -> 2,487 words (-70%), all via the apm-workflow
dispatch pattern. Fixes the skill-improve dangling route and the
agent-author misroute to manual review.

Also fixes a pre-existing false positive where any line-initial 'read '
was flagged as interactive input, which had already caused two scripts to
be rewritten around it.

Refs: ADR-0020
This commit is contained in:
2026-08-14 21:13:13 +00:00
parent 1c6eababb0
commit 4a5c3c0cff
104 changed files with 6272 additions and 1880 deletions

View File

@@ -1,32 +1,40 @@
# agent-audit
Audits an agent definition for correctness and quality — a single vendor-neutral file at
Audits an agent definition for correctness and quality against the Claude Code and Copilot agent
references and the house context-budget contract (ADR-0020) — a single vendor-neutral file at
plugin/APM scope, or a Claude Code and Copilot file pair at project/user scope.
## What it does
At **plugin/APM scope**, accepts the single `.apm/agents/<name>.agent.md` file — there is no
counterpart. Structural checks via `validate.sh` hard-`FAIL` any frontmatter field outside the
vendor-neutral allowlist, since `apm compile` copies frontmatter verbatim to both harnesses and an
unsafe field can't be silently dropped for just one of them. The allowlist itself lives in the
`apm-agent-allowlist` section of `references/field-inventory.md` and is read from there as data —
consult that section rather than any restatement of it, including this one. As of 2026-08-14 it
admits `name`, `description`, `model`, `source_keys`, and `disallowedTools`; `source_keys` is
provenance metadata checked separately by `validate-provenance.sh` against `sources.md`, and
`disallowedTools` is admitted because a denylist survives verbatim copy where the `tools` allowlist
does not (ADR-0016 and its 2026-08-14 amendment).
1. Runs `scripts/validate.sh` and `scripts/validate-provenance.sh` for structural and provenance
checks, plus `scripts/vale-wrap.sh` — a Vale prefilter that deterministically flags
non-imperative description openers, composition and architecture notes, vague wording, padding
phrases, "There is/are" sentence openers, and CC-specific "Use proactively" phrasing in a
Copilot or vendor-neutral description
2. Reads the agent file, and its counterpart when one exists, then loads the contract for its scope
3. Applies qualitative checks across description, body, delegation and comment discipline, loading
one rubric from `references/` per group
4. Outputs a compact findings report — findings only, grouped by dimension, each with Why and Fix —
and a result block with handoff to `agent-author`
At **project/user scope**, accepts either file in a CC `.md` / Copilot `.agent.md` pair, derives
the counterpart automatically, and validates both. Runs structural checks via `validate.sh`
(required fields, kebab-case name, no placeholders, no CC-only fields in the Copilot file, no
Copilot-only fields in the CC file), provenance chain validation via `validate-provenance.sh`
(checks `source_keys` against `sources.md` at the plugin root — plugin/APM scope only), then
qualitative checks on description phrasing and system prompt quality. Step 1 also runs a
Vale-based prose sub-check via `vale-wrap.sh` against both files of the pair, using the
`Kyberforge` style (both files) and `KyberforgeCopilot` style (Copilot file only) — every alert
is a `FAIL`, cited by rule ID — falling back to Step 2 judgment when the `vale` binary is
unavailable or reports `0 files` scanned. Produces a compact findings report in the same format
as `skill-audit`.
Two things follow from ADR-0020 and are easy to get backwards. Agents take the **same** description
gates a skill takes — 250 characters SUGGESTION, 400 FAIL, since a `name` + `description` is
preloaded into every session either way — and **no body word gate at all**, because an agent body
becomes the system prompt of a fresh context rather than competing with the caller's live
conversation. Body length is judged through the delegation check instead: an agent body that
restates a procedure owned by a skill it can invoke is a FAIL, because a plugin-scope agent has no
sibling `references/` directory to disclose to and can only delegate.
At **plugin/APM scope** the audit accepts the single `.apm/agents/<name>.agent.md` file — there is
no counterpart, and pair consistency does not apply. `validate.sh` hard-`FAIL`s any frontmatter
field outside the vendor-neutral allowlist, since `apm compile` copies frontmatter verbatim to both
harnesses and an unsafe field cannot be silently dropped for just one of them. The allowlist lives
in the `apm-agent-allowlist` section of `references/field-inventory.md`, is read from there as data
by the script, and is deliberately not restated anywhere else in this skill (ADR-0009).
At **project/user scope** the audit accepts either file in a CC `.md` / Copilot `.agent.md` pair,
derives the counterpart automatically, and validates both, including the field-leakage checks in
each direction.
## Usage
@@ -42,18 +50,23 @@ Pass the path to either agent file as the argument.
|------|---------|
| `SKILL.md` | Skill instructions for agents |
| `assets/vale/.vale.ini` | Vale config: scopes `Kyberforge` to `**/agents/*.md`, `Kyberforge`+`KyberforgeCopilot` to `**/*.agent.md` |
| `assets/vale/styles/Kyberforge/DescriptionOpener.yml` | Flags descriptions opening with "This skill/agent" instead of an imperative "Use when..." |
| `assets/vale/styles/Kyberforge/CompositionNote.yml` | Flags composition and architecture notes in a description ("cross-cutting", "entry point", "composes", "rather than duplicating") that belong in README.md |
| `assets/vale/styles/Kyberforge/DescriptionOpener.yml` | Flags descriptions opening with "This..." instead of an imperative "Use when..." |
| `assets/vale/styles/Kyberforge/PaddingPhrase.yml` | Flags generic "see references/ for info" pointers instead of specific file references |
| `assets/vale/styles/Kyberforge/SentenceOpenerThereIs.yml` | Flags sentences opening with "There is/are" instead of naming the subject directly |
| `assets/vale/styles/Kyberforge/VagueWording.yml` | Flags vague capability wording ("helps with", "utilize", "assists with", "used for") in descriptions |
| `assets/vale/styles/KyberforgeCopilot/ProactivePhrase.yml` | Flags CC-specific "Use proactively" phrasing with no effect in Copilot descriptions |
| `references/README.md` | Directory documentation for references/ |
| `references/description-quality.md` | Qualitative guide for borderline description findings |
| `references/description-quality.md` | Rubric for the description dimension — three-part shape, the 250/400-character budget, the hand-invoked contract, and the internal-mechanics FAIL |
| `references/body-and-delegation.md` | Rubric for the body, delegation and comment-discipline dimensions — the delegation FAIL and why agents take no body word gate |
| `references/scope-plugin-apm.md` | Scope contract for a single vendor-neutral APM agent file — allowlist, dimension routing, and the dimensions that do not apply |
| `references/scope-project-user.md` | Scope contract for a CC / Copilot pair — counterpart derivation, provider field rules, pair consistency |
| `references/validation-scripts.md` | Loaded only when a Step 1 script fails or cannot run — scope-detection walk-up, manual fallback checks, known script failures |
| `references/field-inventory.md` | Authoritative field lists read as data by `validate.sh`: valid CC and Copilot agent fields, and the vendor-neutral plugin/APM-scope allowlist |
| `references/sources.md` | Research provenance for skill content |
| `scripts/README.md` | Directory documentation for scripts/ |
| `scripts/validate.sh` | Structural validation script for agent file pairs |
| `scripts/validate-provenance.sh` | Provenance chain validation script for agent pairs against `sources.md` (plugin root) |
| `scripts/validate.sh` | Structural validator — required fields, name format, placeholder detection, the ADR-0020 description budget, and the field rules for the detected scope |
| `scripts/validate-provenance.sh` | Provenance chain validation against `sources.md` at the package root (plugin/APM scope only) |
| `scripts/vale-wrap.sh` | Drop-in `vale` wrapper that works around a frontmatter-description NLP scope limitation |
| `tests/README.md` | (source-only) Bats test dependency and run instructions |
| `tests/validate.bats` | (source-only) Bats tests for validate.sh |

View File

@@ -1,18 +1,10 @@
---
name: agent-audit
description: >
Use when the user wants to review an agent definition they wrote, says "audit this
agent", "check if my agent follows best practices", "review my agent file", or wants
to know if an agent pair is ready to ship — even if they don't use the word "audit".
Also invoke proactively after directly hand-editing an agent file pair outside
agent-author — an unaudited hand-edit is the same risk as unreviewed code.
Audits a Claude Code .md and Copilot .agent.md agent file pair across six dimensions:
structural validation, provider safety, description quality, body quality, comment
discipline, and pair consistency — plus provenance chain validation. Produces a
compact findings report
(findings only, no PASS noise) with Why and Fix per finding. Do not use to fix agent
files — use /agent-author instead. Do not use to audit SKILL.md files — use
/skill-audit instead.
Use when the user wants an agent definition audited — "audit this agent",
"review my agent file", "is this ready to ship" — or after hand-editing an
agent outside agent-author. Not applying fixes -> agent-author. Not a skill
directory -> skill-audit.
allowed-tools: Bash Read
metadata:
category: factory
@@ -26,80 +18,67 @@ metadata:
## Gotchas
- The unit of authoring at project/user scope is always a pair (CC `.md` + Copilot `.agent.md`). A missing counterpart is a FAIL under the kyberforge project convention at those scopes — neither the CC nor the Copilot platform itself requires a counterpart file. Label such findings as project convention violations, not platform spec failures. **At plugin/APM scope there is no pair** — the unit of authoring is a single vendor-neutral `.apm/agents/<name>.agent.md` file, and Pair Consistency does not apply there at all (see below).
- Scope is detected by walking up from the agent file's directory: at each level, if `apm.yml` exists AND contains a top-level `type: instructions|skill|hybrid|prompts` line, that directory is an APM package root — plugin/APM scope. A `type:`-less `apm.yml` is marketplace-only (see `docs/research/docs/microsoft-apm/monorepo-and-repo-shapes.md`) — skip it and keep walking up. Otherwise, if `.git` is a directory at that level, stop there — project scope. If neither is found before the filesystem root, fall back to user scope at `$HOME`. `plugin.json`/`.claude-plugin/plugin.json` are no longer scope signals for this skill — a directory with only a `plugin.json` and no `apm.yml` falls through to project (or user) scope.
- `references/field-inventory.md` must exist for `validate.sh` to run. The script exits with an error if it is missing.
- Do not output findings while auditing — gather internally, surface in Step 3 report.
- Do not narrate PASS/FAIL per check while auditing. Gather findings internally and surface them only in the Step 4 report. Narrating each check as you go is the default failure mode here.
- Agents take the same 250/400-character description gates as skills and **no body word gate at all** — an agent body becomes the system prompt of a fresh context, so the 900-word skill ceiling does not transfer. Judge an over-long agent body through the delegation check, never by word count.
- At plugin/APM scope the agent is a single vendor-neutral file by design: never raise a pair-consistency finding there, and provider safety stops meaning Claude-Code-versus-Copilot field leakage.
- Vale reporting `0 files` scanned means NOT RUN, not clean. Fall back to full Step 3 judgment for every dimension it would have covered.
## Step 1 — Run structural validation
## Step 1 — Deterministic checks
Resolve all three paths against this skill's own directory so they work from a repo checkout and an installed plugin cache alike. Run exactly:
```bash
bash scripts/validate.sh <path-to-agent-file>
bash scripts/validate-provenance.sh <path-to-agent-file>
scripts/vale-wrap.sh <path-to-cc-file> <path-to-copilot-file> # project/user scope
scripts/vale-wrap.sh <path-to-apm-agent-file> # plugin/APM scope — single file
bash scripts/validate.sh <agent-file>
bash scripts/validate-provenance.sh <agent-file>
scripts/vale-wrap.sh <agent-file> [<counterpart-file>]
```
The script accepts either the CC file, the Copilot file, or (at plugin/APM scope) the single `.apm/agents/<name>.agent.md` file. It detects provider from extension and scope from the walk-up above, then runs the checks for that scope.
`validate.sh` takes either half of a project/user-scope pair or the single plugin/APM-scope file, detects the provider from the extension and the scope by walking up, then checks required fields, kebab-case `name`, `FILL IN:` placeholders, template HTML comments left in frontmatter, the ADR-0020 description budget (250 chars SUGGESTION, 400 FAIL, measured on the folded YAML value) and the fields that scope permits. Its findings become the `### Structure` dimension — its FAILs and its SUGGESTIONs both — except the ones the Step 2 scope contract re-routes.
At **project/user scope** it derives the counterpart and runs the existing pair-based checks. Note FAILs and SUGGESTIONs for the `### Structure` and `### Provider safety` report dimensions. Findings about missing fields, bad name format, empty body, or missing frontmatter → `### Structure`. Findings about CC-only fields in a Copilot file, Copilot-only fields in a CC file, body length, or subagent-unavailable tools → `### Provider safety`. A missing counterpart file → `### Pair consistency`.
If a validation script fails or cannot run — Bash denied, `python3` or `vale` absent, `references/field-inventory.md` missing — read `references/validation-scripts.md`; what these scripts measure is not reproducible by reading.
At **plugin/APM scope** there is no counterpart — the script instead checks the single file's frontmatter against the `apm-agent-allowlist` in `references/field-inventory.md`. Read that section for the current list rather than reciting one here; it is the authoritative source and it changes. As of 2026-08-14 it is `name`, `description`, `model`, `source_keys`, `disallowedTools` — `source_keys` is provenance metadata, not a provider-specific field, and is validated separately by `validate-provenance.sh` against `sources.md`; `disallowedTools` is a denylist, admitted because denying a tool by name is safe under `apm compile`'s verbatim copy in a way the `tools` allowlist is not (ADR-0016's 2026-08-14 amendment, and the rationale recorded alongside the list itself). Findings about missing fields, bad name format, name/filename-stem mismatch, empty body, or missing frontmatter → `### Structure`, same as project/user scope. Findings about any field outside the allowlist (e.g. `tools`, or any Claude-only/Copilot-only field carried over from a hand-edit) and body length → `### Provider safety` — but the dimension's meaning shifts here: it is no longer a CC-vs-Copilot field-leakage check, it's a vendor-neutral-field-allowlist check, since `apm compile` verbatim-copies this file's frontmatter to every target and there is no per-target integrator to reconcile a CC-only or Copilot-only field (ADR-0016). `### Pair consistency` never applies at this scope — the script never emits a missing-counterpart FAIL here, because there is nothing to pair by design.
`validate-provenance.sh` prints nothing on success and runs at plugin/APM scope only, exiting 0 silently elsewhere. Its FAIL findings become a separate `### Provenance` dimension, and it emits Why and Fix itself — surface those verbatim.
`vale-wrap.sh` ships inside this skill's own `scripts/` — resolve it relative to this skill's directory the same way `scripts/validate.sh` is resolved above, so the invocation works whether this skill is running from this repo or from an installed plugin cache. Pass no `--config`: handed none, the wrapper loads its own sibling `assets/vale/.vale.ini`, located from the script's path rather than from the cwd. Adding an explicit relative `--config` breaks exactly the case the self-location covers — a resolved script path plus an unresolved config path yields `E100 Runtime error ... does not exist`, exit 2, which the fallback below then misreads as "vale unavailable". At project/user scope, run it against both files of the pair (not just the one passed in); at plugin/APM scope, run it against the single file. `Kyberforge` applies to all of these files via the `**/agents/*.md` glob; `KyberforgeCopilot` applies to any `*.agent.md` file — including the plugin/APM-scope file, which already has that extension — via the `**/*.agent.md` glob, since its one rule (`Use proactively`) flags CC-specific phrasing that's meaningless in a vendor-neutral or Copilot description. Every Vale alert is a `FAIL` — all rules are graded `error` — so report each one in the `### Description` / `### Body` dimensions citing its rule ID (e.g. `KyberforgeCopilot.ProactivePhrase`). Skip and fall back to Step 2 judgment if the `vale` binary is unavailable. If Vale reports `0 files` scanned, treat the pass as NOT RUN — not as clean — and fall back to full Step 2 judgment for the dimensions it would have covered.
`vale-wrap.sh` applies the bundled `Kyberforge` style as a prefilter. Pass no `--config`; the wrapper locates its own. At project/user scope pass both files of the pair, not only the one you were handed. Every rule is graded `error`, so every alert is a FAIL. Report each one citing its rule ID, filed under the dimension it belongs to, and do not re-derive it by judgment:
`validate-provenance.sh` operates at plugin/APM scope only — it walks up from the agent file's directory the same way `validate.sh` does (nearest ancestor `apm.yml` with a top-level `type:` field; skip a `type:`-less marketplace-only `apm.yml`; stop at `.git` or the filesystem root) and exits 0 silently if that walk doesn't land on a package root, or when no provenance data exists. When it does apply, it validates the chain between the single file's own `source_keys` and the package-scoped `sources.md` (package root — see ADR-0010). Note FAILs from this script for the `### Provenance` dimension — surface them verbatim with Why and Fix.
| Rule | Dimension |
|---|---|
| `Kyberforge.DescriptionOpener`, `Kyberforge.CompositionNote`, `Kyberforge.VagueWording`, `KyberforgeCopilot.ProactivePhrase` | description |
| `Kyberforge.SentenceOpenerThereIs`, `Kyberforge.PaddingPhrase` | body |
If the scripts cannot run (Bash denied, python3 unavailable), perform checks manually. At project/user scope: counterpart file exists, required fields present (`name`, `description`, non-empty body), `name` is kebab-case, Copilot CLI `.agent.md` `name` must match filename stem (CC files are exempt — the CC platform does not require name to match filename), no `FILL IN:` placeholders, no CC-only fields in Copilot file, no Copilot-only fields in CC file (read `references/field-inventory.md` for the authoritative field lists). At plugin/APM scope: required fields present (`name`, `description`, non-empty body), `name` is kebab-case and matches the filename stem, no `FILL IN:` placeholders, no frontmatter field outside the allowlist — read the `apm-agent-allowlist` section of `references/field-inventory.md` for it, do not work from memory (`source_keys` carries provenance metadata, checked separately by `validate-provenance.sh` against `sources.md`).
## Step 2 — Read the agent and load its scope contract
## Step 2 — Qualitative checks
Read the agent file end to end, and at project/user scope its counterpart too. A path containing `.apm/agents/` is plugin/APM scope; anything else is project or user scope. Each contract names the dimensions that apply there and where `validate.sh` findings other than Structure belong:
Read both agent files. Work through each dimension internally. Collect findings only; report in Step 3.
| Scope | Read |
|---|---|
| plugin/APM | `references/scope-plugin-apm.md` |
| project, user | `references/scope-project-user.md` |
**Description (both files):**
- Action-verb opening: description starts with a verb ("Reviews...", "Analyzes...", "Generates...") — FAIL if absent. Vale's `Kyberforge.DescriptionOpener` alert flags the specific known-bad "This agent..." opener directly; verifying an arbitrary opening word is genuinely a strong verb still requires judgment.
- Specificity: is the trigger condition stated precisely? — SUGGESTION if vague. Vale's `Kyberforge.VagueWording` alert covers known filler ("helps with", "utilize", ...) directly; report those as FAILs without re-deriving by judgment.
- `Use proactively` in a Copilot description: Vale's `KyberforgeCopilot.ProactivePhrase` alert (Copilot file only) flags this directly — report it without re-deriving by judgment.
## Step 3 — Qualitative audit
If a description finding is borderline, read `references/description-quality.md`.
Load a dimension's rubric before judging that dimension.
**Body:**
- Direct role instruction: system prompt opens with `You are a [role]. When invoked, [action].` — SUGGESTION if absent
- One job per agent: system prompt describes a single bounded task — SUGGESTION if scope appears unbounded
- Generic, non-specific reference pointers to the `references/` directory: Vale's `Kyberforge.PaddingPhrase` alert flags this directly — report it without re-deriving by judgment
- Sentences that open with "There is"/"There are": Vale's `Kyberforge.SentenceOpenerThereIs` alert flags this directly — report it without re-deriving by judgment
| Dimension | Read |
|---|---|
| description | `references/description-quality.md` |
| body, delegation, comment-discipline | `references/body-and-delegation.md` |
**Body/Frontmatter comments:**
- Inspect each comment block in the YAML frontmatter. For each comment, apply: *"Would the agent get this wrong without this comment?"* Flag any that answer "no" as padding.
- Look for patterns like `# Optional. <long explanation>` or extensive inline guidance (more than 1–2 lines per field) that should be condensed or removed before shipping.
- This mirrors skill-audit's body-discipline check but applies to template documentation in the frontmatter — template guidance belongs in development; agent-ready files should have minimal comments.
Cite file and line number for every finding.
**Pair consistency (cross-file) — project/user scope only:**
- Both files exist — FAIL if counterpart is missing (kyberforge project convention; not a platform requirement from either CC or Copilot — label as such)
- The following checks are covered automatically by `validate.sh`; apply them manually only when the script cannot run: both system prompt bodies non-empty — FAIL if either is empty
- **Does not apply at plugin/APM scope** — there is only one file, by design; do not raise a Pair Consistency finding there under any circumstance.
## Step 4 — Report
**Unexpressable Claude-only behavior — plugin/APM scope only:**
- Read the description and body. If either implies a need the vendor-neutral frontmatter can no longer express — tool restriction, `isolation`, `memory`, or another Claude-only behavior that a hand-authored CC file could have declared — flag it as a SUGGESTION, never a FAIL. This is a known upstream schema limitation (APM's agent primitive has no per-target compile integrator, so `tools:`/`isolation`/etc. can't be emitted safely to both CC and Copilot — ADR-0016), not an authoring mistake. The finding exists to give the author visibility into the gap, not to imply the schema can be made to do something it can't.
- Example: a body that says "only use Read and Grep, never Edit" but the frontmatter has no `tools` field to enforce it — SUGGESTION, not FAIL.
## Step 3 — Report
Open with a coverage line. At project/user scope:
Open with a coverage line naming every dimension checked. At project/user scope:
```text
Checked: structure · provider-safety · description · body · comment-discipline · pair-consistency · provenance
Checked: structure · provider-safety · description · body · delegation · comment-discipline · pair-consistency · provenance
```
At plugin/APM scope, omit `pair-consistency` — it does not apply when there is no pair:
At plugin/APM scope, drop `pair-consistency` — there is no pair to check.
```text
Checked: structure · provider-safety · description · body · comment-discipline · provenance
```
Then output only the dimensions that have findings, grouped under H3 headings, FAILs before SUGGESTIONs within each. Omit clean dimensions — their absence is what confirms they passed.
Then output only dimensions that have findings, grouped under H3 headings, FAILs before SUGGESTIONs within each dimension. Omit clean dimensions entirely. `### Provenance` findings are sourced verbatim from `validate-provenance.sh` output — copy them without rephrasing.
For each finding:
Each finding:
```text
FAIL/SUGGESTION <finding> — file:line
@@ -107,18 +86,4 @@ FAIL/SUGGESTION <finding> — file:line
Fix: <exact change — quote before/after where applicable>
```
Close with:
```text
## Result
PASS
PASS · P info
PASS (N suggestions)
PASS (N suggestions) · P info
FAIL (N fails · M suggestions)
FAIL (N fails · M suggestions) · P info
Run /agent-author to address findings.
```
Omit `Run /agent-author to address findings.` when there are no findings at all. Do not apply fixes — report and propose only.
Close with a `## Result` block holding one line: `PASS`, `PASS (N suggestions)`, or `FAIL (N fails · M suggestions)`, each optionally followed by ` · P info`. INFO findings are observational and never change PASS/FAIL; omit `· P info` when there are none. Add a second line, `Run agent-author to address findings.`, whenever there is at least one finding. Do not apply fixes — report and propose only.

View File

@@ -0,0 +1,13 @@
extends: existence
message: "Composition or architecture note in a description: '%s' — a description carries a trigger, one capability clause and a boundary clause only; move this to README.md"
level: error
scope: text.frontmatter.description
ignorecase: true
tokens:
- cross-cutting
- shared (skill|agent)
- human-facing
- entry[- ]point
- composes
- rather than duplicating
- replaces the (old|former|previous)

View File

@@ -4,4 +4,4 @@ level: error
scope: text.frontmatter.description
ignorecase: true
raw:
- '^This (skill|agent)\b'
- '^This\b'

View File

@@ -10,6 +10,10 @@ Additional documentation agents load on demand.
| File | Purpose |
|------|---------|
| `description-quality.md` | Qualitative guide for borderline description findings — action-verb rules, specificity criteria, proactive-use caveat, length limits. |
| `field-inventory.md` | Canonical list of valid CC and Copilot agent definition fields. Load when the script needs authoritative field lists for structural validation. |
| `description-quality.md` | Rubric for the description dimension — three-part shape, the 250/400-character budget, the hand-invoked contract, and the internal-mechanics FAIL. |
| `body-and-delegation.md` | Rubric for the body, delegation and comment-discipline dimensions — the delegation FAIL, why agents take no body word gate, and what an agent body is for. |
| `scope-plugin-apm.md` | Contract for a single vendor-neutral `.apm/agents/<name>.agent.md` file — allowlist, dimension routing, and the dimensions that do not apply. |
| `scope-project-user.md` | Contract for a Claude Code / Copilot file pair — counterpart derivation, provider field rules, and pair consistency. |
| `validation-scripts.md` | Loaded only when a Step 1 script fails or cannot run — scope-detection walk-up, manual fallback checks, and known script failures. |
| `field-inventory.md` | Authoritative field lists, read as data by `validate.sh`: valid CC and Copilot agent fields, and the vendor-neutral plugin/APM allowlist. |
| `sources.md` | Research provenance records for skill content. Load only when tracing the origin of a specific rule or field constraint. |

View File

@@ -0,0 +1,116 @@
---
source_keys:
- context7-websites-code-claude
- claude-code-plugins-docs
- claude-code-subagents-docs
- context7-github-en-copilot
- github-custom-agents-configuration
---
# Body, Delegation and Comment Discipline Reference
Upstream source: Claude Code subagent and plugin references, GitHub Copilot custom-agents
configuration. House contract: ADR-0020, the context budget.
Read this when judging the **body**, **delegation** and **comment-discipline** dimensions.
## The core test
For every sentence in the body, ask: **"Would the agent get this wrong without this instruction?"**
If no — cut it. The agent already knows it from general training. Adding it wastes tokens and
dilutes the signal of what matters.
## Agents take no body word gate
ADR-0020 gates a skill body at 600 words SUGGESTION / 900 FAIL and deliberately gates an agent body
at nothing. The two are not the same construct: a skill body is loaded into the caller's live
context and competes with the conversation already there, while an agent body *becomes* the system
prompt of a fresh context that has nothing else in it. The rationale for the 900-word ceiling does
not transfer, so:
- **Never report an agent body as too long on a word count.** There is no number to cite.
- **Never add such a gate to `scripts/validate.sh`.** `tests/validate.bats` pins its absence with a
body far past 900 words that must still pass, and adding one would contradict the ADR.
- The one length signal that does apply is the Copilot runtime's 30,000-character body limit, which
`validate.sh` already reports as a SUGGESTION because content past it is silently truncated.
Length is judged through the delegation check below instead, which is the defect a word count was
standing in for anyway.
## The delegation check
A plugin-scope agent is a single `.apm/agents/<name>.agent.md` file with no sibling `references/`
directory. It cannot progressively disclose to itself — it can only delegate to skills. So a
procedure spelled out in an agent body that a skill the agent invokes already owns is not a
shortcut: it is a second copy of that procedure, and the second copy drifts. This is the
characteristic agent defect, the way a stale README row is the characteristic skill defect.
**An agent body that restates a procedure owned by a skill it can invoke is a FAIL.** The Fix is
always the same shape: invoke `<skill>` instead.
How to apply it: for each procedural block in the body — a rule list, a numbered sequence, a
constraint table — ask which skill owns that procedure. If the agent names that skill anywhere (its
dispatch table, its routing prose, its frontmatter), the block is a restatement and the skill is
already there to be invoked.
Worked example. The three `*-orchestrate` agents exist to compose domain skills — `git-orchestrate`
(933 body words), `gitea-orchestrate` (1,199) and `apm-orchestrate` (1,080) — so any step they
spell out that the composed skill already owns is the defect. `git-orchestrate:24-31` carries a
"Hard rules" list (Conventional Commits types, atomic commits, never commit secrets, git trailers)
that `git-commits` owns and that `git-orchestrate:44` routes to by name; `:39` concedes the point
outright, noting the sub-skills "carry their own local copies of these rules". Two copies, one
authority, and nothing keeping them in step.
What is **not** a finding under this rule, because no skill owns it:
- The dispatch table itself — which operation routes to which skill.
- Safety gates the agent enforces before dispatching, and refusals it makes on its own authority.
- The input contract and the structured output the agent's caller consumes.
- Session state the agent carries across skill invocations.
## What the body is for
Include what the fresh context lacks:
- A direct role instruction opening the prompt: `You are a [role]. When invoked, [action].`
- One bounded job, stated so the agent knows what it must refuse.
- The dispatch, gates, inputs and outputs listed above.
- Non-obvious environment facts and project-specific conventions it cannot infer.
- One default per decision point with one escape hatch.
Do not include at all:
- Concepts the agent already knows (what JSON is, how HTTP works, what a CSV is)
- Exhaustive option lists — pick a default; the agent does not benefit from choosing
- Steps the agent handles independently — over-specifying leads to unproductive paths
- Restatements of the description, which is already in context
## Comment discipline
Inspect every comment block in the YAML frontmatter and apply the core test to each: *would the
agent get this wrong without this comment?* Template scaffolding — `# Optional. <long
explanation>`, more than a line or two of inline guidance per field — belongs to development, not
to a shipped file. At plugin/APM scope the stakes are higher than tidiness: `apm compile` copies
frontmatter verbatim to every target, `<!-- ... -->` is not valid YAML, and `validate.sh` FAILs a
frontmatter block that still contains one.
## Auditing guidance
Flag as FAIL if:
- The body restates a procedure owned by a skill the agent can invoke — Fix: invoke `<skill>`
instead
- A sentence answers "no" to the core test — it is padding
- A decision point presents a menu of options with no default
- An instruction repeats content already in the description
- Frontmatter comments are template scaffolding rather than instruction, or are HTML comments at
plugin/APM scope
- A prescriptive sequence is used where flexibility is fine, or the reverse
Flag as SUGGESTION if:
- The body does not open with a direct role instruction
- The job the agent describes is unbounded, or bounded only implicitly
- A rationale is missing from a rule the agent is expected to enforce — present but unexplained
- Comments are useful but verbose enough to bury the field they annotate

View File

@@ -1,7 +1,6 @@
---
source_keys:
- context7-websites-code-claude
- claude-code-plugins-docs
- claude-code-subagents-docs
- context7-github-en-copilot
- github-custom-agents-configuration
@@ -9,41 +8,120 @@ source_keys:
# Agent Description Quality Reference
Load this file when a description finding is borderline and you need to make a precise call.
Upstream source: Claude Code subagent reference, GitHub Copilot custom-agents configuration.
House contract: ADR-0020, the context budget. The house contract is narrower than either
platform's schema rather than a reinterpretation of it: where both speak, both must be satisfied.
## Action-verb opening
## Why the description is the expensive part
The description must open with an imperative or present-tense verb that describes what the agent does ("Reviews...", "Audits...", "Generates...", "Analyzes..."). Avoid:
- Noun phrases: "An agent that..." — no verb
- "This agent..." or "Use this when..." — passive framing
- "Helps with..." — too vague to be a clear verb
At startup an agent loads only the `name` and `description` of every installed skill and agent.
The body is never seen until the agent is invoked. The description therefore carries the entire
triggering burden **and** is paid for in every session, whether the agent fires or not.
**Borderline call:** "Validates and reviews..." is acceptable — two verbs is fine if both are specific. "Assists in reviewing..." is not — "assists" is vague filler.
A second cost is less obvious and is a correctness hazard rather than a token cost: a description
that summarises the workflow is a shortcut the caller takes *instead of* reading the body. A
measured failure upstream — a description saying "code review between tasks" — produced one review
where the body's flowchart specified two.
## Specificity of trigger condition
## Step 0 — establish which contract applies
The description must state what specifically triggers the agent. Generic phrasing fails:
- Too vague: "when the user needs help with agents"
- Acceptable: "when the user says 'audit this agent', 'check if my agent follows best practices', or wants to know if an agent pair is ready to ship"
Read the frontmatter before judging a single word.
Include indirect triggers: "even if they don't use the word 'audit'" or "even if the user doesn't phrase it as a review request". If the agent should activate on a recognisable user goal (not just literal keyword matches), name that goal.
- **`disable-model-invocation: true` or `user-invocable: false`** — the agent is hand-invoked. Its
description is never matched against user intent, so it is not a routing string. It carries **one
plain human-facing sentence** stating what the agent does. Audit it for that and nothing else.
Reporting a missing trigger clause, a missing boundary clause or absent indirect triggers on a
hand-invoked agent is a wrong finding, not a strict one. Both fields are Copilot-only and neither
is on the vendor-neutral APM allowlist, so this case arises in a Copilot `.agent.md` at
project/user scope and nowhere else. Its Claude Code counterpart has no equivalent field and stays
model-invoked, so the two halves of the pair carrying differently shaped descriptions is expected
there rather than a pair-consistency finding.
- **No such flag** — the agent is model-invoked and the rest of this file applies.
**Borderline call:** If the description covers direct triggers but omits common indirect phrasings that a user would plausibly use, mark as SUGGESTION (not FAIL) — the agent still activates, just less reliably.
## The three-part shape
## `Use proactively`
A model-invoked description carries exactly three things:
For CC files: including "Use proactively" signals the CC runtime to offer the agent unprompted when conditions are met. This is CC-specific — use it when the agent should activate without an explicit user request.
1. **Trigger clause.** When to invoke, phrased imperatively: `Use when ...`. Not `This agent ...` —
the caller is deciding whether to act, not reading a catalogue entry.
2. **At most one capability clause.** What it does, in one clause. Never an enumeration.
3. **Boundary clause.** Compressed form: `Not <thing> -> <skill-name>.` The target must resolve to
a real skill directory or agent file in the authoring source.
For Copilot files: this phrase has no effect. Use `user-invocable: false` / `disable-model-invocation: true` for equivalent Copilot behavior. Flag `Use proactively` in a Copilot description as a SUGGESTION (not FAIL) — it causes no harm, just has no effect.
Everything else belongs in the body or in the plugin's `README.md`.
## Length and hard limits
## Indirect triggers — conditional, never blanket
- CC agent descriptions: no documented character limit, but keep under 500 characters to avoid truncation in UI contexts.
- Copilot agent descriptions: no separate documented limit, but the overall 30,000-character body limit applies to the full file.
- Skill descriptions (SKILL.md): hard 1024-character limit enforced by the platform.
Add "even if the user doesn't say X" **only where the user's natural phrasing genuinely omits the
domain word.** True for the `gitea-*` family: people say "create an issue", not "create a Gitea
issue". False for `git-commits`: nobody asks for a commit without saying commit. A blanket
indirect-trigger clause on an agent whose domain word is unavoidable is padding charged to every
session.
## Do not use when
## Near-miss exclusions
Include a "Do not use when..." clause only if a near-miss agent or skill exists that could steal activations. Omitting it is not a finding. Including it is correct when there is a real confusion risk (e.g., `/agent-audit` vs `/skill-audit`).
Add a boundary clause only where a sibling skill or agent could plausibly steal the activation. Use
strong near-misses — queries that share keywords but need something different — not weak ones. One
boundary clause per genuine near-miss; a list of four is enumeration wearing a boundary's clothes.
**Borderline call:** If the "Do not use when" clause is present but the exclusion described is already obvious from context, mark as SUGGESTION to tighten or remove — not FAIL.
## Before / after
```yaml
# FAIL — a noun-phrase opener rather than a trigger, capability enumeration in
# place of one capability clause, and no boundary clause at all, preloaded into
# every session forever. (The live git-orchestrate description, 254 chars.)
description: Orchestrates git workflow operations for other agents. Invoke when a
caller needs a multi-step or destructive git operation (rebase, force-push, branch
deletion) coordinated across domain skills with safety gates, session context, and
structured results.
# PASS — trigger, one capability clause, boundary. The operation list and the
# safety-gate mechanics are the body's job; the router cannot act on them.
description: >
Use when an agent caller needs a multi-step or destructive git operation
dispatched and safety-gated. Not conversational git help -> git-workflow.
```
## Auditing guidance
Flag as FAIL if:
- **Over 400 characters.** Measured on the folded YAML value, not the raw source lines.
`validate.sh` reports the number; do not re-derive it, but do point the Fix at what to cut. Agent
descriptions have no platform-documented ceiling of their own — unlike a skill's 1,024-character
spec limit, the 400-character house ceiling is the only hard limit there is, so do not go looking
for a backstop behind it.
- **Internal mechanics appear in the description.** Any of:
- capability enumeration or a feature list;
- output-format detail ("Produces a compact findings report with Why and Fix per finding");
- composition or architecture notes ("composes X rather than duplicating Y", "a cross-cutting
shared agent", "the human-facing entry point", "replaces the old flat invocation");
- implementation detail ("self-validates via a bundled deterministic script").
None of it can change a routing decision and all of it is preloaded.
`Kyberforge.CompositionNote` catches the common phrasings deterministically; the rest is
judgment. This is the rule that deflates a description, so apply it before reaching for length.
- **The same trigger stated twice in two registers** — a verb list, then the same verbs re-quoted
as user phrasings, usually in the same order. One register, whichever routes better.
- **Descriptive rather than imperative phrasing** (`This agent ...`, `This is the ...`).
`Kyberforge.DescriptionOpener` catches any opener matching `^This`. There is no action-verb rule
here and never was a defensible one: an `Orchestrates ...` or `Audits ...` opener is a catalogue
entry, not a trigger.
- **Vague capabilities** ("helps with agents" where "audits an agent definition pair" was
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/`.
- **`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.
- **Trigger-list, boundary or indirect-trigger content on a hand-invoked agent** — see Step 0.
Flag as SUGGESTION if:
- **Over 250 characters** but at or under 400. This tier is what moves the corpus average; the FAIL
tier only stops outliers. Report it rather than treating a 399-character description as clean.
- A near-miss exclusion is present but targets a weak near-miss.
- An indirect trigger is present and warranted but could name the omitted phrasing more precisely.

View File

@@ -0,0 +1,58 @@
---
source_keys:
- claude-code-plugins-docs
- claude-code-subagents-docs
- github-custom-agents-configuration
---
# Plugin/APM Scope Contract
Read this when the agent file sits at `<package>/.apm/agents/<name>.agent.md` — a single
vendor-neutral file inside an APM package, with no counterpart anywhere.
## What is different here
`apm compile` copies an agent's frontmatter **verbatim** to every target harness. There is no
per-target integrator to reconcile a Claude-Code-only field with a Copilot-only one, so the file
cannot carry either (ADR-0016). That single fact drives everything below.
## Frontmatter allowlist
The permitted keys are the `apm-agent-allowlist` section of `references/field-inventory.md`. Read
them from there. Do not recite the list in a finding, do not work from memory, and do not trust any
restatement of it you find elsewhere in this repo: the list is data with one home (ADR-0009), it
has changed before, and `validate.sh` parses that same section at load time, so a recitation is a
copy that can disagree with the check the agent just ran.
`field-inventory.md` records why a denylist-shaped field is admitted where an allowlist-shaped one
is not. Read that note before arguing with a finding about it.
## Dimension routing
`validate.sh` findings land as follows at this scope:
| Finding | Dimension |
|---|---|
| any frontmatter key outside the allowlist; body over the 30,000-character Copilot limit | Provider safety |
| everything else — missing or malformed field, `name` not matching the filename stem, empty body, absent frontmatter, template HTML comments, description length | Structure |
| — | Pair consistency never applies |
**Provider safety means something else here.** At project/user scope it asks whether a field leaked
across the Claude Code / Copilot boundary. At this scope there is no boundary and no pair: it asks
whether every field survives a verbatim copy to *every* target. Report it in those terms — a
finding phrased as "CC-only field in a Copilot file" is the wrong finding here.
**Pair consistency never applies.** There is one file by design. `validate.sh` never emits a
missing-counterpart FAIL at this scope, and neither do you, under any circumstance. Drop
`pair-consistency` from the Step 4 coverage line rather than reporting it clean.
## Behaviour the schema cannot express
Read the description and body. If either implies a need the vendor-neutral frontmatter can no
longer express — a tool restriction, `isolation`, `memory`, or another Claude-only behaviour a
hand-authored CC file could have declared — flag it as a **SUGGESTION, never a FAIL**. This is a
known upstream schema limitation (ADR-0016), not an authoring mistake, and the finding exists to
give the author visibility into the gap rather than to imply the schema can be made to close it.
Example: a body saying "only use Read and Grep, never Edit" with no `tools` field to enforce it.
A denylist-shaped restriction is the available half of that — see `field-inventory.md`.

View File

@@ -0,0 +1,59 @@
---
source_keys:
- context7-websites-code-claude
- claude-code-subagents-docs
- context7-github-en-copilot
- github-custom-agents-configuration
---
# Project and User Scope Contract
Read this when the agent file is not under `.apm/agents/` — a Claude Code `.md` and Copilot CLI
`.agent.md` **pair**, at project scope (`<repo>/.claude/agents/` and `<repo>/.github/agents/`) or
user scope (`~/.claude/agents/` and `~/.copilot/agents/`). `validate.sh` derives the counterpart
from whichever half it was handed; audit both.
## The pair is a house convention
Neither platform requires a counterpart file. The pair is a kyberforge convention (ADR-0005), so a
missing counterpart is a FAIL against **this repo's** convention and must be labelled that way in
the finding, not presented as a platform spec failure.
## Dimension routing
`validate.sh` findings land as follows at these scopes:
| Finding | Dimension |
|---|---|
| a Claude-Code-only field in the Copilot file, a Copilot-only field in the CC file, a tool the runtime withholds from subagents, body over the 30,000-character Copilot limit | Provider safety |
| counterpart file not found | Pair consistency |
| everything else — missing or malformed field, name format, empty body, absent frontmatter, description length | Structure |
The two field lists are the `claude-code-only-fields` and `copilot-only-fields` sections of
`references/field-inventory.md`. Read them from there rather than from memory; `validate.sh` parses
those same sections, so any restatement is a copy that can disagree with the check (ADR-0009).
## Field and naming rules that differ by provider
- `name` must match the filename stem in a **Copilot CLI** `.agent.md`. Claude Code imposes no such
rule, so a CC file whose `name` differs from its filename is not a finding.
- A Copilot **cloud/IDE** agent — one under `.github/copilot/agents/` — may omit `name` entirely.
If it carries one, it still has to be kebab-case.
- `Use proactively` is meaningful in a CC description and steers the runtime to offer the agent
unprompted. In a Copilot description it does nothing; `KyberforgeCopilot.ProactivePhrase` flags
it. The Copilot equivalent is `disable-model-invocation` / `user-invocable`, which changes the
description contract entirely — see `references/description-quality.md`, Step 0.
## Pair consistency
Check that:
- Both files exist.
- Both system prompt bodies are non-empty (`validate.sh` covers this; do it by hand only when the
script could not run).
- The two files describe the **same job**. Divergent capability claims across the pair mean one
half was edited and the other was not, which is the defect this dimension exists to catch.
- Descriptions may legitimately differ in *shape* when the Copilot half is hand-invoked — that is
the Step 0 case in `references/description-quality.md`, not a pair-consistency finding.
Keep `pair-consistency` in the Step 4 coverage line at these scopes.

View File

@@ -14,7 +14,7 @@ source_keys:
- **URL:** context7:/websites/code_claude
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
- **Description:** Official Claude Code documentation site indexed by Context7 — plugin manifest schema, subagent definition types, marketplace JSON format, agent markdown file format
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md, references/body-and-delegation.md, references/scope-project-user.md
- **Status:** `extracted`
## claude-code-plugins-docs
@@ -22,7 +22,7 @@ source_keys:
- **URL:** https://code.claude.com/docs/en/plugins
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
- **Description:** Official Claude Code plugin authoring guide — plugin structure, manifest fields, loading methods, skill namespacing, agent activation, marketplace submission
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md
- **Contributing files:** SKILL.md, references/field-inventory.md, references/body-and-delegation.md, references/scope-plugin-apm.md, references/validation-scripts.md
- **Status:** `extracted`
## claude-code-subagents-docs
@@ -30,7 +30,7 @@ source_keys:
- **URL:** https://code.claude.com/docs/en/sub-agents
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
- **Description:** Official Claude Code subagent reference — definition format, all frontmatter fields, scope priority, built-in agents, CLI flags, environment variables, known limitations
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md, references/body-and-delegation.md, references/scope-plugin-apm.md, references/scope-project-user.md, references/validation-scripts.md
- **Status:** `extracted`
## context7-github-en-copilot
@@ -38,7 +38,7 @@ source_keys:
- **URL:** context7:/websites/github_en_copilot
- **Research doc:** plugins/kyberforge/docs/research/docs/github-copilot-plugins/sources.md
- **Description:** Official GitHub Copilot documentation indexed by Context7; covers CLI plugins, custom agents, SDK, and marketplace
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md, references/body-and-delegation.md, references/scope-project-user.md
- **Status:** `extracted`
## github-custom-agents-configuration
@@ -46,7 +46,7 @@ source_keys:
- **URL:** https://docs.github.com/en/copilot/reference/custom-agents-configuration
- **Research doc:** plugins/kyberforge/docs/research/docs/github-copilot-plugins/sources.md
- **Description:** Reference for cloud and IDE custom agent definition format — frontmatter fields, tool aliases, MCP server config, secrets interpolation, scoping hierarchy
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md
- **Contributing files:** SKILL.md, references/field-inventory.md, references/description-quality.md, references/body-and-delegation.md, references/scope-plugin-apm.md, references/scope-project-user.md, references/validation-scripts.md
- **Status:** `extracted`
## github-cli-plugin-reference

View File

@@ -0,0 +1,70 @@
---
source_keys:
- claude-code-plugins-docs
- claude-code-subagents-docs
- github-custom-agents-configuration
---
# Validation Scripts Reference
Read this when a Step 1 script fails, cannot run, or reports something that needs interpreting.
Nothing here is needed on a clean run.
## Report the gap, do not guess
If a script cannot run at all — Bash denied, `python3` unavailable, `vale` not installed — say so
as an **INFO** finding naming the script and the missing dependency, then fall back to the manual
checks below. An INFO never changes PASS/FAIL. Silently omitting the dimension a script would have
covered reports a clean audit that checked less than it claims to have checked.
## How the scripts detect scope
`validate.sh` and `validate-provenance.sh` walk up from the agent file's directory and stop at the
first of these:
1. An `apm.yml` carrying a top-level `type: instructions|skill|hybrid|prompts` line — **plugin/APM
scope**, and that directory is the package root. An `apm.yml` with no `type:` is a
marketplace-only manifest: skip it and keep walking.
2. `$HOME` — **user scope**, checked before `.git` so a dotfiles-managed home directory that is its
own repo cannot shadow it.
3. A `.git` directory or file — **project scope**.
4. The filesystem root — **project scope**.
`plugin.json` and `.claude-plugin/plugin.json` are not scope signals. A directory holding only a
`plugin.json` and no `apm.yml` falls through to project or user scope.
`validate-provenance.sh` exits 0 silently when that walk does not land on a package root, and again
when the package has no provenance data. Silence from it is a pass, not a skip you need to
investigate.
## Manual fallback
**Every scope:** required fields present (`name`, `description`, non-empty body); `name` is
kebab-case; no `FILL IN:` placeholders in the description or body; the description at or under 400
characters measured on the folded YAML value.
**Plugin/APM scope:** `name` matches the filename stem; no HTML comments left in the frontmatter;
no frontmatter key outside the `apm-agent-allowlist` section of `references/field-inventory.md` —
open that file, do not work from memory.
**Project/user scope:** the counterpart file exists; `name` matches the filename stem in the
Copilot `.agent.md` only (Claude Code files are exempt); no key from `claude-code-only-fields` in
the Copilot file and none from `copilot-only-fields` in the CC file, both read from
`references/field-inventory.md`.
## Script-specific failures
- **`Error: field-inventory.md not found` (exit 2).** `validate.sh` reads its field lists from
`references/field-inventory.md` at load time and refuses to run without it, rather than falling
back to a hardcoded list that could disagree with the file (ADR-0009). Restore the file; do not
work around it.
- **`vale` reports `0 files`.** Treat the pass as NOT RUN, not as clean, and fall back to full
Step 3 judgment for the dimensions it would have covered. The `Kyberforge` style is scoped to
`**/agents/*.md` and `**/*.agent.md`, and `KyberforgeCopilot` to `**/*.agent.md` alone — a file
outside those globs is silently not linted.
- **`E100 Runtime error ... does not exist` (exit 2) from `vale-wrap.sh`.** An explicit relative
`--config` was passed. Pass none: the wrapper locates its own `assets/vale/.vale.ini` from its
own path. Do not read this exit code as vale being unavailable.
- **A path argument that does not exist is a hard error** in `vale-wrap.sh`, deliberately: bare
`vale` would fall back to reading stdin and print a clean-looking `0 errors ... in stdin`, which
the `0 files` guard above does not catch.

View File

@@ -90,6 +90,23 @@ SUBAGENT_UNAVAILABLE_TOOLS = {
# Copilot body length limit (chars) — content beyond this is silently truncated
COPILOT_BODY_LIMIT = 30000
# ADR-0020 description budget. An agent's name + description is preloaded into
# every session exactly like a skill's, so agents take the SAME description
# gates. These two constants are DUPLICATED from scripts/skill-size-check.sh
# and skill-audit/scripts/validate.sh rather than shared from one file: a
# cache-installed plugin's scripts cannot read files outside their own plugin
# directory, so there is no single source to share (same rationale as
# vale-wrap.sh's per-plugin duplication). tests/test-skill-size-check.sh
# asserts all copies agree, so drift fails CI rather than silently diverging.
#
# Agents deliberately take NO body word gate, and adding one here would
# contradict ADR-0020: a skill body is loaded into the caller's context and
# competes with the live conversation, while an agent body becomes the system
# prompt of a fresh context. The rationale for the 900-word skill ceiling does
# not transfer. Agent body length falls out of the delegation rule instead.
DESC_SUGGEST_CHARS = 250
DESC_MAX_CHARS = 400
# --- Helpers (shared by every scope) ---
failed = False
suggestions = []
@@ -122,6 +139,80 @@ def get_frontmatter_keys(fm):
keys.add(m.group(1))
return keys
def normalize_scalar(value):
return re.sub(r'\s+', ' ', value).strip()
def fold_description_fallback(fm_text):
"""Resolve `description:` without PyYAML.
Not a YAML parser — it recognises exactly the shapes agent frontmatter
uses: an inline scalar (optionally quoted, optionally continued on
following indented lines) and a `>`/`|` block scalar with optional
indentation and chomping indicators.
"""
lines = fm_text.splitlines()
for i, line in enumerate(lines):
m = re.match(r'^description:[ \t]*(.*)$', line)
if not m:
continue
head = m.group(1).strip()
block = bool(re.match(r'^[>|][0-9]*[-+]?$|^[>|][-+]?[0-9]*$', head))
parts = [] if block else [head]
for nxt in lines[i + 1:]:
if not nxt.strip():
parts.append('')
continue
if not re.match(r'^[ \t]', nxt):
break
parts.append(nxt.strip())
value = ' '.join(parts)
if not block:
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in '"\'':
value = value[1:-1]
return value
return ''
def description_value(fm):
"""The description VALUE with YAML folding resolved.
extract_field() reads one raw line, which is the right shape for the
presence and placeholder checks but the wrong one for a length gate: a
`>`-folded description measured off its first raw line is not the value the
host preloads. Parse instead of regexing the raw text.
"""
try:
import yaml
data = yaml.safe_load(fm)
if isinstance(data, dict):
value = data.get('description')
if isinstance(value, str):
return normalize_scalar(value)
if value is not None:
return normalize_scalar(str(value))
return ''
except Exception:
pass
return normalize_scalar(fold_description_fallback(fm))
def check_description_budget(fm, local_fname):
"""ADR-0020 description gates — identical for every scope."""
value = description_value(fm)
if not value:
return
dlen = len(value)
if dlen > DESC_MAX_CHARS:
fail(f"description is {dlen} chars — exceeds the {DESC_MAX_CHARS}-character "
f"ADR-0020 ceiling. It is preloaded into every session whether or not the "
f"agent is invoked. Keep a trigger clause, at most one capability clause, "
f"and a boundary clause; move capability enumeration, output-format detail, "
f"composition notes and implementation detail to the body — {local_fname}")
elif dlen > DESC_SUGGEST_CHARS:
suggest(f"description is {dlen} chars — over the {DESC_SUGGEST_CHARS}-character "
f"ADR-0020 target (hard fail at {DESC_MAX_CHARS}). The SUGGESTION tier is "
f"what moves the corpus average; the FAIL tier only stops outliers "
f"— {local_fname}")
def extract_tools_list(fm):
"""Extract tool names from the tools frontmatter field (space or comma separated)."""
val = extract_field(fm, 'tools')
@@ -273,6 +364,7 @@ def check_apm_agent_file(fpath, allowlist, stem):
else:
if PLACEHOLDER_RE.search(desc_val):
fail(f"description contains unfilled FILL IN: placeholder — {local_fname}")
check_description_budget(fm, local_fname)
# body — required, non-empty, no placeholder; same Copilot truncation risk
# applies since this file compiles verbatim into a real Copilot file downstream.
@@ -346,6 +438,7 @@ def check_file(fpath, file_provider):
else:
if PLACEHOLDER_RE.search(desc_val):
fail(f"description contains unfilled FILL IN: placeholder — {local_fname}")
check_description_budget(fm, local_fname)
# body
if not body.strip():