Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,15 @@ jobs:
with:
schemafile: plugins/skill-quality/reference/evals.schema.json
files: plugins/*/skills/*/evals/evals.json
- name: Test the shared listing-budget reporter
run: bash plugins/skill-quality/scripts/check-listing-budget.test.sh
# Report-only (always exit 0 — see the script's own header): pools every
# plugin's skills root into ONE shared aggregate, the shape a consumer who
# installs the whole marketplace actually experiences. Runs on every
# event, unlike the PR-diff-gated step above, since the aggregate is a
# whole-repo property, not a per-change one.
- name: Report the shared skill-listing budget across every plugin
run: bash plugins/skill-quality/scripts/check-listing-budget.sh plugins/*/skills

# Portability lint: skills declared ecosystem/forge/tracker-agnostic must not
# ship bare hardcoded stack/forge/branch/tracker defaults — the coupling class
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ user opts in with `/plugin enable`; an existing install is never flipped by cata
- [`rate-limit-guard`](plugins/rate-limit-guard) — Shared rate-limit guard for loop lanes: a statusline wrapper tees the subscription rate-limit windows to a fixed machine-scope file, a StopFailure hook records rate-limit stops reactively, and a reader contract fixes how consuming sessions pause and resume.
- [`context-guard`](plugins/context-guard) — Per-session context-window observability: a statusline wrapper tees each session's context_window fields to a per-session snapshot file, a zone resolver classifies usage into smart/acceptable/dumb bands (zones.json SSOT with shipped defaults), and a reader contract fixes how consuming sessions interpret the snapshots.
- [`plugin-quality`](plugins/plugin-quality) — Post-use behavioral audit of Claude Code plugin components: a six-step audit workflow (evidence capture, grounded mapping in a fresh subagent, blindspot pass, interactive contract lock, presence-gated review seams, work-item emit with draft+confirm) over any skill, agent, hook, command, or config you have actually used — zone-informed by context-guard snapshots when present, conservative when not.
- [`skill-quality`](plugins/skill-quality) — Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.
- [`skill-quality`](plugins/skill-quality) — Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, per-skill listing-entry cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration), a shared skill-listing budget reporter across a set of skills, and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.

### Autonomy

Expand Down
4 changes: 2 additions & 2 deletions plugins/skill-quality/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "skill-quality",
"version": "0.10.2",
"description": "Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, listing-budget cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration) and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.",
"version": "0.11.0",
"description": "Skill-authoring QA tooling: a static contract checker that runs twenty deterministic checks over a Claude Code skill (frontmatter, per-skill listing-entry cap, trigger-keyword preservation, line caps, broken internal refs, markdownlint, gotchas surface, evals presence, precompute opportunity, injection shell-declaration), a shared skill-listing budget reporter across a set of skills, and a bundled evals.json schema for validation. Runs against any repo's skills directory via the convention-resolution ladder — no baked layout.",
"author": {
"name": "Melodic Software",
"email": "info@melodicsoftware.com"
Expand Down
59 changes: 59 additions & 0 deletions plugins/skill-quality/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,65 @@
All notable changes to the `skill-quality` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.11.0]

### Added

- **`listing-budget` action + `check-listing-budget.sh`: reports the SHARED skill-listing budget,
the aggregate limit nothing in the gate previously checked (#1404).** `check-skill.sh` check 2 only
ever guarded the per-skill entry cap (`skillListingMaxDescChars`, 1536 chars); the shared budget
every loaded skill draws from together (`skillListingBudgetFraction`, default 1% of the model's
context window) had no check at all — measured evidence found the aggregate overflowing by a large
multiple with no gate ever reporting it. The new script pools one or more skills roots into one
aggregate estimate against a documented, overridable default (8000 chars — the harness's own
`SLASH_COMMAND_TOOL_CHAR_BUDGET` fallback) and reports the biggest contributors on overflow. It is
always advisory (exit 0) since the live budget depends on a model's context window and a consumer's
own settings, neither of which a static check can observe — never hardcode
`skillListingBudgetFraction`'s documented default as a resolved live value; `/doctor` is the
authoritative source per machine. Wired into this repo's `skill-quality-gate` CI job as a report-only
step pooling every plugin's `skills/` root into one marketplace-wide aggregate.

The report counts only **listing-eligible** skills. A skill with `disable-model-invocation: true`
is skipped: the invocation-control table at <https://code.claude.com/docs/en/skills> records
"Description not in context" for that frontmatter, and "Hide individual skills" states it
"removes the skill from Claude's context entirely" — such a skill spends none of the shared
description budget, so counting it overstates the aggregate. A consumer's `skillOverrides` can
free further descriptions via `"name-only"`, which repository content cannot reveal, so the
figure is an upper bound for anyone who sets it. On this marketplace the filter excludes 51 of the
183 `SKILL.md` files, leaving a reported **132 listing-eligible skills / 83,611 characters** as
measured at this commit — still an order of magnitude over the 8000-char default, so the finding
the check exists to surface is unchanged. (A figure without its commit goes stale: the population
itself moves, so re-measure rather than quoting this one forward.)

The flag is read through a normalizing comparison rather than an exact string match, since valid
YAML can spell the same boolean as `true # manual-only`, `"true"`, `TRUE`, or with surrounding
whitespace, and a bare `== "true"` silently re-counted every one of those. Comment-stripping is
scoped to the boolean and deliberately never applied to `description` / `when_to_use`, where a
whitespace-preceded `#` is content rather than a comment. YAML 1.1's `yes` / `on` aliases are not
folded — the documented spelling is `true`, and over-matching would risk dropping a skill over a
value the harness may read as a plain string.

Input handling is fail-closed on operator error, while the budget verdict stays advisory: every
numeric override is validated as a positive number and every explicit root must exist, both
reported as the documented environment error (exit 2). Previously a nonnumeric override was
either coerced to zero by `awk` — fabricating a zero-character budget and a bogus overflow WARN
while still exiting 0 — or crashed with an undocumented exit 1, and a misspelled root among
several was silently skipped while its subtree vanished from an "OK" aggregate. A fixed
`CHECK_SKILL_LISTING_BUDGET_CHARS` now takes precedence over the token/fraction reconstruction as
its own documentation always claimed, announcing the ignored input rather than discarding it
silently, and is labelled an override instead of the "documented default". The report header
counts roots actually scanned rather than arguments given, and `--help` derives its range from the
header block so editing that block can no longer clip or overrun the help text.

### Fixed

- **Check 2 now counts the description/when_to_use joiner (#1404).** The harness assembles a skill's
listing entry as `description` + `" - "` + `when_to_use` — a literal 3-character joiner. Check 2
summed only `len(description) + len(when_to_use)`, under-counting by 3 whenever `when_to_use` is
populated, so an entry sitting exactly at the boundary could pass a cap it had actually crossed. Not
currently binding at present description lengths in this repo, but wrong in exactly the direction
the listing-budget work is about.

## [0.10.2]

### Fixed
Expand Down
37 changes: 28 additions & 9 deletions plugins/skill-quality/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
# skill-quality

A Claude Code plugin for **skill-authoring QA**: it runs a static, deterministic contract gate over a
skill directory and validates a skill's `evals.json` against a bundled schema. No model invocation in
the gate — the same eighteen checks run identically in a session, a pre-commit hook, or CI.
skill directory, reports the shared listing-budget estimate across a set of skills, and validates a
skill's `evals.json` against a bundled schema. No model invocation in the gate — the same twenty checks
run identically in a session, a pre-commit hook, or CI.

The one failure static analysis catches best is a rewrite silently dropping a `description` trigger
phrase, which quietly degrades a skill's auto-invocation. Check 3 compares the trigger phrases against
`HEAD` and fails on a regression.

| Skill | What it does |
|---|---|
| `/skill-quality:check` | Runs the contract gate (`check`) or schema-validates evals (`validate-evals`), for one skill or every skill. |
| `/skill-quality:check` | Runs the contract gate (`check`), reports the shared listing budget (`listing-budget`), or schema-validates evals (`validate-evals`) — for one skill, a set of roots, or every skill. |
| `/skill-quality:setup` | `check` (default) resolves and verifies the skills directory; `apply` routes a non-default `skills_root` change through Claude Code. |

## Checks

`check` runs `check-skill.sh` — eighteen checks, reported as `FAIL:` (blocking) or `WARN:` (advisory):
`check` runs `check-skill.sh` — twenty checks, reported as `FAIL:` (blocking) or `WARN:` (advisory):

- Frontmatter parses; `name` + `description` present.
- `description` + `when_to_use` within the 1536-char listing budget (overflow truncates the listing).
- `description` + `when_to_use` within the 1536-char **per-skill** listing-entry cap (overflow
truncates that entry) — a different, narrower limit from the shared budget below.
- Trigger-keyword preservation vs `HEAD` (skipped for a new, uncommitted skill).
- `SKILL.md` under 500 lines (hard) / 200 lines (soft, advisory).
- Backtick- and link-cited skill-internal supporting files resolve.
Expand All @@ -30,11 +32,28 @@ phrase, which quietly degrades a skill's auto-invocation. Check 3 compares the t
- Precompute opportunity (advisory) — a fenced shell block gathers read-only context the skill could
inline at load time via [`!` injection](https://code.claude.com/docs/en/skills#inject-dynamic-context)
instead of a per-invocation tool call.
- Dynamic-context injection portability (a bash-only `!` command with no `shell:` declared) and
defensive-fallback presence (`|| <fallback>` on every injected command).

`listing-budget` runs `check-listing-budget.sh` — an always-advisory report on the **shared** budget
every loaded skill draws from together (`skillListingBudgetFraction`, default 1% of the model's context
window). This is the aggregate limit `check`'s per-skill cap above does not cover: nothing else in the
gate checks it, so a marketplace's skill count can silently overflow the live listing with no local
signal. It never asserts a live value it cannot observe (the model's context window and a consumer's
settings are both unknowable statically) — it reports against a documented, overridable default and
always exits 0.

Only **listing-eligible** skills are counted: `disable-model-invocation: true` keeps a skill's
description out of the model-visible listing entirely, so it spends none of the shared budget. A
consumer's `skillOverrides` can free further descriptions with `"name-only"`, which repository
content cannot reveal — so the reported figure is an upper bound for anyone who sets it.

```shell
/skill-quality:check my-skill # gate one skill
/skill-quality:check # gate every skill under the resolved root
/skill-quality:check validate-evals my-skill # schema-check evals.json
/skill-quality:check my-skill # gate one skill
/skill-quality:check # gate every skill under the resolved root
/skill-quality:check validate-evals my-skill # schema-check evals.json
/skill-quality:check listing-budget # report the shared budget over the resolved root
/skill-quality:check listing-budget plugins/*/skills # pool every plugin's root into one aggregate
```

## Skills directory — never baked in
Expand Down Expand Up @@ -65,4 +84,4 @@ Evals are warranted, not mandatory — a skill shipping none is not a failure.
- A git repository — several checks read `git show HEAD:` / `git ls-files`; outside a repo the script
exits 2.
- `npx` (Node) is optional; without it the markdownlint check downgrades to a warning and the other
seventeen still gate.
nineteen still gate.
Loading
Loading