Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
25 changes: 10 additions & 15 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,36 +32,31 @@ jobs:
**/*.yaml
!CHANGELOG.md
config: 'config/cspell.json'
detect-changed-skills:
name: 🔎 Detect Changed Skills
list-skills:
name: 🔎 List Skills
runs-on: ubuntu-latest
outputs:
skills: ${{ steps.changed-skills.outputs.skills }}
skills: ${{ steps.list-skills.outputs.skills }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get changed skills
id: changed-skills
- name: List all skills
id: list-skills
run: |
BASE=${{ github.event.pull_request.base.sha }}
HEAD=${{ github.event.pull_request.head.sha }}
SKILLS=$(git diff --name-only $BASE $HEAD | \
{ grep 'SKILL.md' || true; } | \
SKILLS=$(find -L skills -name SKILL.md -type f | \
xargs -I {} dirname {} | \
sort -u | \
jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "skills=$SKILLS" >> "$GITHUB_OUTPUT"
echo "Changed skills: $SKILLS"
echo "Skills: $SKILLS"
validate-skills:
name: 🔍 Validate Skills
needs: detect-changed-skills
if: needs.detect-changed-skills.outputs.skills != '[]'
needs: list-skills
if: needs.list-skills.outputs.skills != '[]'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
skill: ${{ fromJson(needs.detect-changed-skills.outputs.skills) }}
skill: ${{ fromJson(needs.list-skills.outputs.skills) }}
steps:
- uses: actions/checkout@v7
- name: Validate ${{ matrix.skill }}
Expand Down
11 changes: 8 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ hooks/
format.sh # Runs dart format on modified .dart files
vgv-cli-common.sh # Shared utilities for VGV CLI hook scripts
warn-missing-mcp.sh # Warns at session start if VGV CLI is missing/outdated
skills/
skills/ # every <skill>/ ships SKILL.md + agents/openai.yaml (Codex sidecar)
accessibility/SKILL.md
accessibility/references/
animations/SKILL.md
Expand Down Expand Up @@ -96,6 +96,8 @@ Every `SKILL.md` follows this structure:
3. **Core Standards** — enforced constraints, always first
4. **Content sections** — architecture, code examples, workflows, anti-patterns

Every skill also ships a Codex sidecar at `agents/openai.yaml` beside its `SKILL.md`, holding the skill-picker metadata `interface.display_name` and `interface.short_description`. Every skill in this plugin is **model-invoked** (the model may auto-activate it), so no skill sets `disable-model-invocation` or a Codex `policy` block; a user-invoked-only skill would set both, kept in sync. See `CONTRIBUTING.md` → Cross-harness portability.

## Writing Conventions

- Frame standards as clear directives — no soft language ("consider", "prefer")
Expand All @@ -107,7 +109,8 @@ Every `SKILL.md` follows this structure:

## Adding a New Skill

1. Create `skills/<skill_name>/SKILL.md` following the format above
1. Create `skills/<skill_name>/SKILL.md` following the format above, plus the Codex sidecar
`skills/<skill_name>/agents/openai.yaml` (`interface.display_name` + `interface.short_description`)
2. Create `evals/tests/<skill_name>.yaml` — eval cases with one prompt per major
workflow the skill covers, a `skill-used` assertion on each, and one
`not-skill-used` negative control. Routing assertions carry `weight: 3` so a routing
Expand Down Expand Up @@ -146,7 +149,9 @@ docs that describe them. When you touch any of the following, update the matchin
documentation in the same change:

- **Updating a skill's scope or description** — update the matching row in the
`README.md` skills table so the description stays in sync.
`README.md` skills table and the `interface.short_description` in the skill's
`agents/openai.yaml`, so all three stay in sync. Nothing checks them against
each other.
- **Changing what a skill teaches** — run the skill's eval cases to confirm the new
guidance actually lands in the model's output, and update any case that asserted
the old behavior. A failing case after a deliberate change means the case needs
Expand Down
106 changes: 100 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ Create `skills/<skill-name>/SKILL.md`. The file must begin with YAML frontmatter
---
name: <skill-name>
description: When this skill should be triggered — be specific.
allowed-tools: Read,Glob,Grep
allowed-tools: Read Glob Grep
argument-hint: "[file-or-directory]" # optional
---
```

| Field | Required | Rules |
| ----- | -------- | ----- |
| `name` | Yes | Must match the skill's folder name exactly; lowercase letters, numbers, and hyphens only |
| `name` | Yes | Lowercase letters, numbers, and hyphens only; no leading, trailing, or consecutive hyphen; 1-64 chars; **must match the skill's directory name** (enforced in CI by `validate-skill`) |
| `description` | Yes | Describes when the skill should be triggered |
| `allowed-tools` | Yes | Comma-separated list of tools the skill may use |
| `allowed-tools` | No | Space-separated list of tools the skill may use; a Claude Code permission hint, not a hard cap |
| `argument-hint` | No | Placeholder hint shown to the user |

After the frontmatter, structure the file as:
Expand All @@ -46,6 +46,15 @@ After the frontmatter, structure the file as:
2. **Core Standards** — enforced constraints, always first
3. **Content sections** — architecture, code examples, workflows, anti-patterns

Also create the Codex sidecar `skills/<skill-name>/agents/openai.yaml` with the skill-picker
metadata (see [Cross-harness portability](#cross-harness-portability) → Codex sidecar):

```yaml
interface:
display_name: "Skill Name"
short_description: "One short line for the picker"
```

### 2. Add eval cases

Create `evals/tests/<skill-name>.yaml` — prompts that prove the skill actually
Expand Down Expand Up @@ -95,6 +104,91 @@ eval documentation there rather than here.
- **Reference packages by full name** (e.g., `package:mocktail`, not just "mocktail").
- **Show anti-patterns alongside correct patterns** when helpful, so readers understand both what to do and what to avoid.

## Cross-harness portability

Skills are authored for Claude Code but target the [Agent Skills open
standard](https://agentskills.io/specification) (the `npx skills` format, supported by
many agents), so they should degrade gracefully on non-Claude harnesses such as Codex,
Gemini CLI, and OpenCode without changing Claude Code behavior. Under that standard a skill
is a **static instruction set**: the agent loads it by matching its `description`, then reads
the body — there is no argument or template substitution. `$ARGUMENTS` and
`${CLAUDE_SKILL_DIR}` are Claude Code conveniences, not spec features, so a body that uses
them must still work when they arrive unsubstituted.

**`$ARGUMENTS`** — not a spec concept; on a plain Agent Skill it is never substituted and
stays literal. Always pair it with a fallback that fires when it is empty *or still shows
the literal text* `$ARGUMENTS`:

```markdown
<feature_description>$ARGUMENTS</feature_description>

**If the feature description above is empty or still shows the literal text
`$ARGUMENTS` (the host did not substitute it), ask the user** for it (or read it
from the conversation).
```

**`${CLAUDE_SKILL_DIR}`** — no skill here uses it today (the hooks use
`${CLAUDE_PLUGIN_ROOT}`, resolved by Claude Code, not by skill bodies). If a future skill
references a bundled file, prefer the spec form — a **relative path from the skill root**
(`scripts/x.sh`) — and add a fallback for hosts that do not substitute the absolute form.

**Frontmatter** — an agent silently skips a skill whose frontmatter is malformed. Keep the
opening `---` on line 1, close the block with `---`, and include a non-empty `name:`
(kebab-case, **matching the directory name**) and `description:`. The spec also allows
`license`, `compatibility`, `metadata`, and `allowed-tools`. This plugin's Claude Code
extras (`when_to_use`, `argument-hint`, `effort`, `model`) are not spec fields, but
`npx skills` and other agents ignore unknown frontmatter keys — keep them top-level so
Claude Code reads them and nothing else breaks. (The spec's optional `skills-ref` linter is
stricter, rejecting any top-level field outside the six it allows; `npx skills` does not run
it, and nesting these extras under `metadata:` is the escape hatch if strict conformance is
ever needed.) The `Skill validation` CI job (`Flash-Brew-Digital/validate-skill@v1`) enforces
the spec (including name-matches-directory) across every skill on each pull request.

**MCP references** — this plugin registers two MCP servers in `.mcp.json`: `dart` (Dart and
Flutter actions) and `very-good-cli` (scaffolding, tests, license checks). On Claude Code
they are the primary execution path, and the `check-vgv-cli.sh` / `block-cli-workarounds.sh`
hooks deliberately steer the quality gates through the MCP tools instead of the raw CLI — do
not weaken that on Claude Code. Those hooks do not run on other hosts and the MCP servers may
not be connected there, so every skill that drives an MCP tool must name the equivalent
`very_good` / `dart` / `flutter` CLI command as a fallback and never block when the server is
absent. The `dart-flutter-sdk-upgrade` and `very-good-analysis-upgrade` skills already phrase
this as "use the MCP tool if available; otherwise Bash" — match that.

**Subagents** — subagents are not part of the Agent Skills standard, and no skill in this
plugin dispatches one. The `flutter-reviewer` agent (`agents/flutter-reviewer.md`) is a
Claude Code construct; on a host without a subagent mechanism its four preloaded standards
(`bloc`, `testing`, `static-security`, `accessibility`) still apply — run the review inline
against those skills instead of dispatching the agent.

**`AskUserQuestion` and `allowed-tools`** — both are Claude Code conveniences. A skill that
asks the user a structured question carries its own inline fallback: invoke whatever
equivalent user-question tool the host provides, and drop to plain numbered text only where
the host has none (see `accessibility` and `create-project`). Treat a narrow `allowed-tools`
list as a permission hint for Claude Code, not a hard cap — a skill uses whatever tools its
task needs.

**Own your references** — a skill's reference files live inside that skill's own
`references/` directory. Do not share a reference across skills by symlink or a cross-folder
`../other-skill/…` link: those do not survive every install path, and skills.sh copies each
skill on its own. Keep shared prose short enough to inline, or lift author-facing guidance
into this file rather than shipping it as a runtime reference in two places.

**Codex sidecar (`agents/openai.yaml`)** — every skill ships an `agents/openai.yaml` beside
its `SKILL.md`, carrying the Codex skill-picker metadata: `interface.display_name`, which has no
frontmatter equivalent, and `interface.short_description`, which takes precedence over the
spec-legal `metadata: short-description` key. The `SKILL.md` body stays the one
source of truth; the sidecar is thin, with no build step. Add one for every new skill.

**Invocation** — every skill in this plugin is **model-invoked**: the model may reach for it
autonomously when the context fits (that is the point of a best-practice skill), so neither
`disable-model-invocation` (Claude Code) nor a `policy` block (Codex) is set. Most skills here
keep their trigger phrasing in `when_to_use`, which only Claude Code reads: every other host
parses `name` and `description` only. Trigger wording that has to survive off Claude Code
belongs in `description`. If you add a skill only a human should fire, make
it **user-invoked**: set `disable-model-invocation: true` in the frontmatter and
`policy.allow_implicit_invocation: false` in its `agents/openai.yaml`, and keep the two in
sync — a skill is user-invoked in both harnesses or neither.

## Testing Locally

Editing a skill or hook and pushing straight to a PR only tells you the files
Expand Down Expand Up @@ -177,8 +271,8 @@ claude plugin validate .
```

This validates the manifest, skill frontmatter, hook JSON, MCP config, and file
references. It is static, so it confirms structure but does not replace the live
checks above.
references. It is static, so it confirms structure but does not replace the live checks
above.

### Troubleshooting

Expand All @@ -198,7 +292,7 @@ Every pull request runs the following checks automatically:
| ----- | ------------ | ------ |
| Markdown quality | Lints all `*.md` files with markdownlint-cli2 | `config/custom.markdownlint.jsonc` |
| Spelling | Runs cspell on all `*.md` files | `config/cspell.json` |
| Skill validation | Validates `SKILL.md` frontmatter and structure for changed skills | `Flash-Brew-Digital/validate-skill@v1` |
| Skill validation | Validates **every** `SKILL.md`'s frontmatter and structure against the Agent Skills spec, so a malformed skill fails the build instead of silently vanishing on another host | `Flash-Brew-Digital/validate-skill@v1` |
| Plugin validation | Validates and test-installs the plugin | `claude plugin validate .` |
| Script tests | Runs the hook scripts' own test suites | `hooks/scripts/*_test.sh` |

Expand Down
5 changes: 3 additions & 2 deletions agents/flutter-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ Determine the change set adaptively, from the repository root:
per affected package.

Read the changed files with `Read`/`Grep` to review their full context, not just the diff hunks.
You may use `mcp__dart__analyze_files` to corroborate a skill-based judgment, but analyzer output is
not itself a findings source (see "What not to report").
When the Dart MCP server is connected, you may use `mcp__dart__analyze_files` to corroborate a
skill-based judgment, but analyzer output is not itself a findings source (see "What not to
report"). If it is unavailable, rely on the four preloaded standards alone.

### When scoping fails

Expand Down
3 changes: 3 additions & 0 deletions config/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
"words": [
"activatable",
"adversarial",
"agentskills",
"antipattern",
"Automator",
"Bidirectionality",
"Bienvenido",
"bypassable",
"Codex",
"CSPRNG",
"Cupertino",
"cupertino",
Expand Down Expand Up @@ -41,6 +43,7 @@
"mocktail",
"monorepo",
"Mundo",
"opencode",
"pasteable",
"prefs",
"promptfoo",
Expand Down
2 changes: 2 additions & 0 deletions skills/accessibility/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Apply these standards to all accessibility work:

Every accessibility engagement follows four phases in sequence. Do not skip Phase 1 or Phase 2.

> **Cross-harness note.** Phases 1, 2, and 4 below use `AskUserQuestion`. On a host without it, invoke whatever equivalent user-question tool the host provides; if it has none, ask the same question as plain numbered text. Either way, wait for the reply before proceeding.

### Phase 1: Conformance Level Selection

Use `AskUserQuestion` to ask:
Expand Down
3 changes: 3 additions & 0 deletions skills/accessibility/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Accessibility"
short_description: "Audit Flutter widgets for WCAG 2.2 accessibility"
2 changes: 1 addition & 1 deletion skills/animations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ when_to_use: >
Also use for custom route transitions — `CustomTransitionPage`, a `buildPage` override on
a `GoRouteData` subclass, or a `Hero` transition. Motion between routes is animation work
even when the surrounding code is `go_router`.
allowed-tools: Read,Glob,Grep
allowed-tools: Read Glob Grep
argument-hint: "[file-or-directory]"
---

Expand Down
3 changes: 3 additions & 0 deletions skills/animations/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Animations"
short_description: "Flutter animation and motion best practices"
3 changes: 3 additions & 0 deletions skills/bloc/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Bloc"
short_description: "Bloc state management conventions for Flutter"
2 changes: 2 additions & 0 deletions skills/create-project/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ model: haiku

Scaffold a new Dart or Flutter project using Very Good CLI templates.

> **Cross-harness fallbacks.** This skill drives the Very Good CLI MCP server and asks the user structured questions. On a host without this plugin's Bash hooks and without that MCP server connected, run the equivalent `very_good create …` and `very_good packages get` commands directly. On a host without `AskUserQuestion`, invoke whatever equivalent user-question tool the host provides; if it has none, ask the same questions as plain numbered text.

---

## Core Standards
Expand Down
3 changes: 3 additions & 0 deletions skills/create-project/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Create Project"
short_description: "Scaffold a Dart or Flutter project"
5 changes: 3 additions & 2 deletions skills/dart-flutter-sdk-upgrade/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ versions, not one: the Flutter release being targeted, and the Dart release it s
3. Note the Dart version listed alongside it

The Flutter target comes from `$ARGUMENTS` when the user supplied one. If `$ARGUMENTS` is
empty, take the latest Flutter stable from that same page. For pure Dart packages, no mapping
is involved: the Dart version is whatever `$ARGUMENTS` specifies or the latest Dart stable.
empty or still shows the literal text `$ARGUMENTS` (the host did not substitute it), take the
latest Flutter stable from that same page. For pure Dart packages, no mapping is involved: the
Dart version is whatever `$ARGUMENTS` specifies or the latest Dart stable.

**When the archive is out of reach**, which is the common case in a session with no network
access, do not fill the gap from memory. A recalled pairing is wrong often enough to break
Expand Down
3 changes: 3 additions & 0 deletions skills/dart-flutter-sdk-upgrade/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Dart/Flutter SDK Upgrade"
short_description: "Bump Dart and Flutter SDK constraints"
16 changes: 13 additions & 3 deletions skills/green-gate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ description: >
coverage. Exits only when a single final iteration proves all four pass with
observed numbers. Also owns how those gates are configured — which tool runs
each one, the arguments it takes, the order they run in, the coverage target,
and what leaves the coverage denominator.
and what leaves the coverage denominator. Also owns what to do when the loop
stalls: when the same failures repeat round after round, it stops, escalates
and reports rather than retrying forever.
when_to_use: >
Use when the user wants a Dart or Flutter package driven to a fully passing
state, or says things like "green gate", "make it green", "get this package
Expand All @@ -17,7 +19,11 @@ when_to_use: >
through the plan before you touch anything", "confirm the package is green",
"just re-check coverage", "should I add a coverage ignore comment", "what
should be excluded from coverage", or "can we drop the coverage threshold to
90". Answer those from this skill instead of improvising a shell-command plan.
90". Use it too when a run has stopped making progress — "the same errors came
back three rounds running", "it keeps reporting the same failures", "what do you
do next", "keep retrying for as long as it takes" — because deciding when to stop
and escalate is part of this loop. Answer those from this skill instead of
improvising a shell-command plan.
Prefer this over the single-gate testing or analysis skills whenever the
request spans multiple gates, asks to fix and re-verify until clean, or asks
how one of the four gates is configured.
Expand Down Expand Up @@ -51,7 +57,11 @@ Apply these to ALL green-gate work:
through a shell command. The Bash test path (`very_good test`, `flutter test`,
`dart test`) is hook-blocked by `block-cli-workarounds.sh` and will be denied, and
`dart analyze` / `dart format` via Bash are redundant with the MCP tools.
**Bash is reserved for parsing `coverage/lcov.info` — nothing else.**
**Bash is reserved for parsing `coverage/lcov.info` — nothing else.** This MCP-only
rule is a Claude Code constraint enforced by that hook; the hook does not run on
other hosts. **Cross-harness fallback:** on a host without the hook and without the
MCP servers connected, run the equivalent `dart analyze`, `dart format`, and
`very_good test` CLI commands instead — never block on a missing MCP server.
- **A plan-only request is still this skill's job** — when the user asks which
tools, which arguments, or what order the gates run in and does not want a run
yet, answer from this skill: the same tool calls (`mcp__dart__analyze_files`
Expand Down
3 changes: 3 additions & 0 deletions skills/green-gate/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: "Green Gate"
short_description: "Drive a package to fully green"
Loading
Loading