From 230dac6a9ac3156569222254c8739afee5a8c8cb Mon Sep 17 00:00:00 2001 From: Dominik Simonik Date: Fri, 24 Jul 2026 12:48:59 +0200 Subject: [PATCH 1/4] feat: cross-harness skill portability fallbacks Replicate the Phase 0 skill-portability work from VeryGoodOpenSource/vgv-wingspan#230 for this plugin: make skills degrade gracefully on non-Claude harnesses (Codex, Gemini CLI, OpenCode) via the Agent Skills open standard, without changing Claude Code behavior. - CI: validate every skill each run (list-skills) and add a frontmatter guard job (scripts/ci/check-frontmatter.sh) covering UTF-8 BOM detection and agents/**/*.md frontmatter, which no other check covers. - $ARGUMENTS literal-text fallbacks in very-good-analysis-upgrade and dart-flutter-sdk-upgrade. - AskUserQuestion fallbacks in accessibility and create-project, backed by a shared skills/shared/references/interaction-fallbacks.md symlinked into each consuming skill. - MCP graceful degradation: the dart / very-good-cli MCP tools fall back to the equivalent very_good / dart / flutter CLI off Claude Code (create-project, license-compliance, testing, layered-architecture, ui-package, flutter-reviewer). green-gate keeps its hook-enforced MCP-only mandate on Claude Code and only adds the off-Claude fallback. - CONTRIBUTING gains a Cross-harness portability section; CLAUDE.md structure block and cspell words updated. Refs VeryGoodOpenSource/vgv-wingspan#230 Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yaml | 35 ++++---- CLAUDE.md | 7 ++ CONTRIBUTING.md | 89 +++++++++++++++++-- agents/flutter-reviewer.md | 5 +- config/cspell.json | 3 + scripts/ci/check-frontmatter.sh | 89 +++++++++++++++++++ skills/accessibility/SKILL.md | 2 + .../references/interaction-fallbacks.md | 1 + skills/create-project/SKILL.md | 2 + .../references/interaction-fallbacks.md | 1 + skills/dart-flutter-sdk-upgrade/SKILL.md | 7 +- skills/green-gate/SKILL.md | 6 +- skills/layered-architecture/SKILL.md | 2 + skills/license-compliance/SKILL.md | 2 + .../references/interaction-fallbacks.md | 26 ++++++ skills/testing/SKILL.md | 1 + skills/ui-package/SKILL.md | 2 + skills/very-good-analysis-upgrade/SKILL.md | 3 +- 18 files changed, 254 insertions(+), 29 deletions(-) create mode 100755 scripts/ci/check-frontmatter.sh create mode 120000 skills/accessibility/references/interaction-fallbacks.md create mode 120000 skills/create-project/references/interaction-fallbacks.md create mode 100644 skills/shared/references/interaction-fallbacks.md diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d07d78e..0af0ec5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,36 +30,31 @@ jobs: **/*.md !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 }} @@ -76,6 +71,16 @@ jobs: echo "Valid: ${{ steps.validate.outputs.valid }}" echo "Errors: ${{ steps.validate.outputs.errors }}" echo "Warnings: ${{ steps.validate.outputs.warnings }}" + frontmatter-guard: + name: 🛡️ Frontmatter Guard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + # Covers what validate-skills cannot: UTF-8 BOM detection (Gemini-fatal, + # passes validate-skill) and agents/**/*.md frontmatter (validate-skill + # only accepts files named SKILL.md). + - name: Check frontmatter portability + run: bash scripts/ci/check-frontmatter.sh plugin-validate: name: 📦 Plugin Validation runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index 995793f..68d8033 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,9 @@ 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 +scripts/ + ci/ + check-frontmatter.sh # CI frontmatter guard: UTF-8 BOM + agents/**/*.md frontmatter skills/ accessibility/SKILL.md accessibility/references/ @@ -38,6 +41,7 @@ skills/ bloc/SKILL.md bloc/references/ create-project/SKILL.md + create-project/references/interaction-fallbacks.md # symlink → skills/shared/references/ dart-flutter-sdk-upgrade/SKILL.md green-gate/SKILL.md green-gate/references/ @@ -48,6 +52,9 @@ skills/ license-compliance/SKILL.md material-theming/SKILL.md navigation/SKILL.md + shared/ + references/ + interaction-fallbacks.md # AskUserQuestion + allowed-tools fallbacks; symlinked into consuming skills static-security/SKILL.md static-security/references/ testing/SKILL.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a070aa1..9fe4189 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,6 +69,74 @@ Add the new skill directory and files to the repository structure tree in `CLAUD - **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). 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 +$ARGUMENTS + +**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 — keep them top-level so Claude +Code reads them and nothing else breaks. 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, and +`scripts/ci/check-frontmatter.sh` guards the gaps it leaves — a UTF-8 BOM (Gemini-fatal but +passes `validate-skill`) and `agents/**/*.md` frontmatter, which no other check covers. + +**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 or declares a narrow `allowed-tools` list carries the +cross-harness fallbacks in [`skills/shared/references/interaction-fallbacks.md`](skills/shared/references/interaction-fallbacks.md): +ask the same question as plain numbered text where `AskUserQuestion` is absent, and treat +`allowed-tools` as a permission hint rather than a hard cap. + +**Shared content via symlinks** — references shared across skills live once under +`skills/shared/references/` and are symlinked into each consuming skill's `references/` +directory (for example `interaction-fallbacks.md`). `npx skills` dereferences symlinks when +it copies a cloned or local skill, so those install paths work as-is. Before relying on a +server-side snapshot install, verify with a real `npx skills add` that the shared files +arrive intact; if they do not, materialize them (dereference the symlinks into real files at +publish time, or vendor real copies). + ## Testing Locally Editing a skill or hook and pushing straight to a PR only tells you the files @@ -144,15 +212,20 @@ Then, inside a session: ### Validate before you push -Run the same check CI runs, from the repository root: +Run the same checks CI runs, from the repository root: ```bash 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. +```bash +bash scripts/ci/check-frontmatter.sh +``` + +The first validates the manifest, skill frontmatter, hook JSON, MCP config, and file +references. The second is the frontmatter guard (UTF-8 BOM detection plus `agents/**/*.md` +frontmatter). Both are static, so they confirm structure but do not replace the live checks +above. ### Troubleshooting @@ -170,10 +243,12 @@ Every pull request runs the following checks automatically: | Check | What it does | Config | | ----- | ------------ | ------ | +| Markdown lint | Lints all `*.md` files (except `CHANGELOG.md`) | `config/custom.markdownlint.jsonc` | | Spelling | Runs cspell on all `*.md` files | `config/cspell.json` | -| File size | Ensures no file exceeds 50 KB | `scripts/check_large_files.sh` | -| Skill validation | Validates `SKILL.md` frontmatter and structure | `scripts/validate_skills.sh` | -| Plugin validation | Validates and test-installs the plugin | `claude plugin validate .` | +| 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` | +| Frontmatter guard | Fails on a UTF-8 BOM in any `SKILL.md` or agent file (Gemini-fatal, passes validate-skill) and validates `agents/**/*.md` frontmatter, which no other check covers | `scripts/ci/check-frontmatter.sh` | +| Plugin validation | Validates the plugin manifests via the Claude Code CLI | `claude plugin validate .` | +| Script tests | Runs the hook script unit tests | `hooks/scripts/*_test.sh` | If the spelling check flags a legitimate word, add it to `config/cspell.json` in the `words` array. diff --git a/agents/flutter-reviewer.md b/agents/flutter-reviewer.md index 760c7ee..61e3c06 100644 --- a/agents/flutter-reviewer.md +++ b/agents/flutter-reviewer.md @@ -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 diff --git a/config/cspell.json b/config/cspell.json index 2ebb3a6..9c1313e 100644 --- a/config/cspell.json +++ b/config/cspell.json @@ -3,11 +3,13 @@ "words": [ "activatable", "adversarial", + "agentskills", "antipattern", "Automator", "Bidirectionality", "Bienvenido", "bypassable", + "Codex", "dartdoc", "CSPRNG", "Cupertino", @@ -37,6 +39,7 @@ "mocktail", "monorepo", "Mundo", + "opencode", "pasteable", "prefs", "pubspec", diff --git a/scripts/ci/check-frontmatter.sh b/scripts/ci/check-frontmatter.sh new file mode 100755 index 0000000..473314a --- /dev/null +++ b/scripts/ci/check-frontmatter.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Frontmatter portability guard. +# +# Covers the two silent-skip gaps validate-skill@v1 leaves open: +# 1. A UTF-8 BOM before the opening `---` passes validate-skill but makes +# Gemini CLI (and any parser anchoring /^---/) skip the file silently. +# Checked for every skills/**/SKILL.md and agents/**/*.md. +# 2. agents/**/*.md frontmatter has no validator at all (validate-skill +# hard-requires the filename SKILL.md; `claude plugin validate` exits 0 +# on a broken agent block). Checked here: frontmatter first, closed, +# non-empty name + description, name matches the filename stem. +# +# Skill frontmatter content is deliberately NOT re-validated here — +# Flash-Brew-Digital/validate-skill owns that in the validate-skills CI job. +# +# Parsing is plain-text matching, not YAML-aware: keys are expected as bare +# `name: value` / `description: value` lines (the only forms used in this +# repo). Quoted values would be flagged as mismatches — keep values unquoted. + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + +errors=0 + +fail() { + echo "error: $1" >&2 + errors=$((errors + 1)) +} + +# Strip trailing CR (CRLF files) and trailing whitespace. +trim() { + sed 's/[[:space:]]*$//' +} + +# --- 1. BOM guard (skills + agents) --------------------------------------- +# find -L so symlinked SKILL.md files are checked too. +while IFS= read -r file; do + if [ "$(head -c 3 "$file" | od -An -tx1 | tr -d ' \n')" = "efbbbf" ]; then + fail "$file starts with a UTF-8 BOM — Gemini CLI silently skips it" + fi +done < <(find -L skills -name SKILL.md -type f; find -L agents -name '*.md' -type f) + +# --- 2. Agent frontmatter (agents only) ----------------------------------- +while IFS= read -r file; do + if [ "$(head -n 1 "$file" | trim)" != "---" ]; then + fail "$file: frontmatter must start on line 1 with ---" + continue + fi + close_line=$(awk 'NR > 1 && $0 ~ /^---[[:space:]]*$/ { print NR; exit }' "$file") + if [ -z "$close_line" ]; then + fail "$file: frontmatter block is never closed with ---" + continue + fi + block=$(sed -n "2,$((close_line - 1))p" "$file" | trim) + name=$(printf '%s\n' "$block" | sed -n 's/^name:[[:space:]]*//p' | head -n 1) + if [ -z "$name" ]; then + fail "$file: frontmatter has no non-empty name" + elif [ "$name" != "$(basename "$file" .md)" ]; then + fail "$file: name '$name' does not match filename stem" + fi + if ! printf '%s\n' "$block" | grep -q '^description:'; then + fail "$file: frontmatter has no description" + else + desc=$(printf '%s\n' "$block" | sed -n 's/^description:[[:space:]]*//p' | head -n 1) + case "$desc" in + "" | "|" | "|-" | "|+" | ">" | ">-" | ">+") + # Empty or block-scalar-only value: require at least one non-empty + # indented continuation line, else the description is effectively + # empty — the exact silent-skip case on Gemini/Codex/OpenCode. + if ! printf '%s\n' "$block" | awk ' + /^description:/ { in_block = 1; next } + in_block && /^[^[:space:]]/ { in_block = 0 } + in_block && /[^[:space:]]/ { found = 1 } + END { exit !found } + '; then + fail "$file: description is empty" + fi + ;; + esac + fi +done < <(find -L agents -name '*.md' -type f) + +if [ "$errors" -gt 0 ]; then + echo "check-frontmatter: $errors problem(s) found" >&2 + exit 1 +fi + +echo "check-frontmatter: all skills and agents pass" diff --git a/skills/accessibility/SKILL.md b/skills/accessibility/SKILL.md index 67cd184..30d4cb5 100644 --- a/skills/accessibility/SKILL.md +++ b/skills/accessibility/SKILL.md @@ -61,6 +61,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, ask the same question as plain numbered text and wait for the reply before proceeding. See [interaction fallbacks](references/interaction-fallbacks.md). + ### Phase 1: Conformance Level Selection Use `AskUserQuestion` to ask: diff --git a/skills/accessibility/references/interaction-fallbacks.md b/skills/accessibility/references/interaction-fallbacks.md new file mode 120000 index 0000000..9492dce --- /dev/null +++ b/skills/accessibility/references/interaction-fallbacks.md @@ -0,0 +1 @@ +../../shared/references/interaction-fallbacks.md \ No newline at end of file diff --git a/skills/create-project/SKILL.md b/skills/create-project/SKILL.md index cc1361f..a071b72 100644 --- a/skills/create-project/SKILL.md +++ b/skills/create-project/SKILL.md @@ -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 that MCP server connected, run the equivalent `very_good create …` and `very_good packages get` commands directly. On a host without `AskUserQuestion`, ask the same questions as plain numbered text. See [interaction fallbacks](references/interaction-fallbacks.md). + --- ## Core Standards diff --git a/skills/create-project/references/interaction-fallbacks.md b/skills/create-project/references/interaction-fallbacks.md new file mode 120000 index 0000000..9492dce --- /dev/null +++ b/skills/create-project/references/interaction-fallbacks.md @@ -0,0 +1 @@ +../../shared/references/interaction-fallbacks.md \ No newline at end of file diff --git a/skills/dart-flutter-sdk-upgrade/SKILL.md b/skills/dart-flutter-sdk-upgrade/SKILL.md index 8f41d8b..627eaaf 100644 --- a/skills/dart-flutter-sdk-upgrade/SKILL.md +++ b/skills/dart-flutter-sdk-upgrade/SKILL.md @@ -48,9 +48,10 @@ for the target Flutter release before editing any files. 3. Note the Dart version listed alongside it The target version comes from `$ARGUMENTS` (e.g. `3.41.0`) when the user supplied one. If -`$ARGUMENTS` is empty, look up the latest Flutter stable release from that same page. For pure -Dart packages (no Flutter dependency), the Dart version is whatever `$ARGUMENTS` specifies or -the latest stable — no Flutter mapping needed. +`$ARGUMENTS` is empty or still shows the literal text `$ARGUMENTS` (the host did not substitute +it), look up the latest Flutter stable release from that same page. For pure Dart packages (no +Flutter dependency), the Dart version is whatever `$ARGUMENTS` specifies or the latest stable — +no Flutter mapping needed. Confirm both resolved versions with the user before editing files. diff --git a/skills/green-gate/SKILL.md b/skills/green-gate/SKILL.md index 61fe2a2..b1d9511 100644 --- a/skills/green-gate/SKILL.md +++ b/skills/green-gate/SKILL.md @@ -42,7 +42,11 @@ Apply these to ALL green-gate work: coverage via `mcp__very-good-cli__test`. The Bash test path (`very_good test`, `flutter test`, `dart test`) is hook-blocked by `block-cli-workarounds.sh`; `dart analyze` via Bash is redundant with the MCP - tool. **Bash is reserved for parsing `coverage/lcov.info` only.** + tool. **Bash is reserved for parsing `coverage/lcov.info` only.** This MCP-only + rule is a Claude Code constraint enforced by that hook; it 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. - **Never cache green** — re-evaluate every gate every round. Fixing analyze or test failures and writing new test files shifts both formatting and the coverage denominator, so a previously green gate can regress. diff --git a/skills/layered-architecture/SKILL.md b/skills/layered-architecture/SKILL.md index 3a57f01..0a5b185 100644 --- a/skills/layered-architecture/SKILL.md +++ b/skills/layered-architecture/SKILL.md @@ -29,6 +29,8 @@ Apply these standards to ALL layered architecture work: - **Repositories accept data layer dependencies via constructor injection** — never instantiate clients internally - **App bootstrap wires all layers** — `main_.dart` creates clients and repositories, provides them via `RepositoryProvider` +> **Cross-harness fallback.** This skill scaffolds and tests packages via the Very Good CLI MCP server. On a host without that MCP server connected, run the equivalent `very_good create dart_package …`, `very_good packages get`, and `very_good test` commands directly. + ## Architecture Overview | Layer | Responsibility | Location | Depends On | Example | diff --git a/skills/license-compliance/SKILL.md b/skills/license-compliance/SKILL.md index bb1296a..d60fc2a 100644 --- a/skills/license-compliance/SKILL.md +++ b/skills/license-compliance/SKILL.md @@ -17,6 +17,8 @@ effort: medium Dependency license auditor for Dart and Flutter projects — verifies that all package dependencies use licenses compatible with the project's requirements using the Very Good CLI MCP tools. +> **Cross-harness fallback.** On Claude Code the `packages_check_licenses` MCP tool is the execution path. On a host without the Very Good CLI MCP server connected, run `very_good packages check licenses --licenses` directly (add `--directory ` for a project in a subdirectory) and read its output the same way — never block on a missing MCP server. + --- ## Core Standards diff --git a/skills/shared/references/interaction-fallbacks.md b/skills/shared/references/interaction-fallbacks.md new file mode 100644 index 0000000..f3bd61a --- /dev/null +++ b/skills/shared/references/interaction-fallbacks.md @@ -0,0 +1,26 @@ +# Interaction fallbacks + +Cross-harness fallbacks for the Claude-specific interaction constructs. A strong host +(Claude Code) uses the primary construct and ignores these notes; a weaker host follows +them. Each is a diff against the primary path, not a replacement. + +## No structured-question tool (`AskUserQuestion`) + +`AskUserQuestion` is Claude-specific. On a host without it, ask the same question as +**plain numbered text** and treat the user's next reply as the choice. Preserve the same +options and the same default; a multi-select becomes "reply with all that apply". + +**Before an operation that writes or is hard to undo** — scaffolding a project into an +existing directory, applying a batch of code fixes, or anything that overwrites files — +never auto-proceed on a default. Stop, state what the operation will do, ask in plain +text, and wait for an explicit reply. A headless or autonomous host must halt here rather +than pick the default option. + +## Narrow `allowed-tools` + +`allowed-tools` is an experimental spec field; some hosts treat the list as an +exhaustive allow-list rather than a permission hint. A skill whose body needs to read, +write, edit, or run commands beyond its declared list must use whatever tools the task +requires — the list is a permission hint for Claude Code, not a cap on the skill's work. +If a host enforces the list and blocks a needed tool, that is the signal to widen the +frontmatter list to the full set the skill uses. diff --git a/skills/testing/SKILL.md b/skills/testing/SKILL.md index 78c5ed4..b2e68ea 100644 --- a/skills/testing/SKILL.md +++ b/skills/testing/SKILL.md @@ -28,6 +28,7 @@ Apply these standards to ALL test work: - **Tag all golden tests** — annotate with `TestTag.golden` so goldens can run/update independently - **Pass `directory` to the `test` MCP tool when the project is not at the workspace root** — monorepos with the Flutter project in a subdirectory (e.g. `mobile/`) require `directory: 'mobile'`; omit it only when `pubspec.yaml` is at the workspace root - **Pass `timeout_seconds` to the `test` MCP tool** — Flutter tests can hang indefinitely when `pumpAndSettle()` is called without a timeout; set a cap (e.g. `timeout_seconds: 120`) so the run is killed instead of stalling +- **Cross-harness fallback for the `test` MCP tool** — on Claude Code use `mcp__very-good-cli__test`; on a host without that MCP server connected, run `very_good test` (or `flutter test` / `dart test`) directly with the same coverage and timeout options — never block on a missing MCP server ## Test Structure diff --git a/skills/ui-package/SKILL.md b/skills/ui-package/SKILL.md index d842516..a90cfdf 100644 --- a/skills/ui-package/SKILL.md +++ b/skills/ui-package/SKILL.md @@ -83,3 +83,5 @@ my_ui/ ## Creating the Package Use the Very Good CLI MCP tool to scaffold the `app_ui_package`. + +> **Cross-harness fallback.** On a host without the Very Good CLI MCP server connected, run the equivalent `very_good create` command for the UI package template directly. diff --git a/skills/very-good-analysis-upgrade/SKILL.md b/skills/very-good-analysis-upgrade/SKILL.md index fe95178..0cc45c2 100644 --- a/skills/very-good-analysis-upgrade/SKILL.md +++ b/skills/very-good-analysis-upgrade/SKILL.md @@ -31,7 +31,8 @@ These standards apply to every `very_good_analysis` upgrade. Confirm two things before proceeding: 1. **Target version** — use `$ARGUMENTS` as the target version when the user supplied one - (e.g. `10.0.0`). If `$ARGUMENTS` is empty, fetch the latest from the pub.dev API and use + (e.g. `10.0.0`). If `$ARGUMENTS` is empty or still shows the literal text `$ARGUMENTS` + (the host did not substitute it), fetch the latest from the pub.dev API and use that. Don't ask — just look it up and proceed: ```bash From 6279bcf93541e789ba9c1213f6e915e43a486d73 Mon Sep 17 00:00:00 2001 From: Dominik Simonik Date: Fri, 24 Jul 2026 14:49:53 +0200 Subject: [PATCH 2/4] ci: move frontmatter guard script under .github/ Relocate check-frontmatter.sh from scripts/ci/ to .github/scripts/ so the CI-only helper lives alongside the workflow that runs it. Update the ci.yaml job command and the CONTRIBUTING references, and drop the scripts/ node from the CLAUDE.md structure tree (dotfile CI dirs are not enumerated there). Co-Authored-By: Claude Opus 4.8 --- {scripts/ci => .github/scripts}/check-frontmatter.sh | 0 .github/workflows/ci.yaml | 2 +- CLAUDE.md | 3 --- CONTRIBUTING.md | 6 +++--- 4 files changed, 4 insertions(+), 7 deletions(-) rename {scripts/ci => .github/scripts}/check-frontmatter.sh (100%) diff --git a/scripts/ci/check-frontmatter.sh b/.github/scripts/check-frontmatter.sh similarity index 100% rename from scripts/ci/check-frontmatter.sh rename to .github/scripts/check-frontmatter.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0af0ec5..77c97fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -80,7 +80,7 @@ jobs: # passes validate-skill) and agents/**/*.md frontmatter (validate-skill # only accepts files named SKILL.md). - name: Check frontmatter portability - run: bash scripts/ci/check-frontmatter.sh + run: bash .github/scripts/check-frontmatter.sh plugin-validate: name: 📦 Plugin Validation runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index 68d8033..fd208d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,9 +26,6 @@ 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 -scripts/ - ci/ - check-frontmatter.sh # CI frontmatter guard: UTF-8 BOM + agents/**/*.md frontmatter skills/ accessibility/SKILL.md accessibility/references/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fe4189..e76b6d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,7 +104,7 @@ extras (`when_to_use`, `argument-hint`, `effort`, `model`) are not spec fields, Code reads them and nothing else breaks. 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, and -`scripts/ci/check-frontmatter.sh` guards the gaps it leaves — a UTF-8 BOM (Gemini-fatal but +`.github/scripts/check-frontmatter.sh` guards the gaps it leaves — a UTF-8 BOM (Gemini-fatal but passes `validate-skill`) and `agents/**/*.md` frontmatter, which no other check covers. **MCP references** — this plugin registers two MCP servers in `.mcp.json`: `dart` (Dart and @@ -219,7 +219,7 @@ claude plugin validate . ``` ```bash -bash scripts/ci/check-frontmatter.sh +bash .github/scripts/check-frontmatter.sh ``` The first validates the manifest, skill frontmatter, hook JSON, MCP config, and file @@ -246,7 +246,7 @@ Every pull request runs the following checks automatically: | Markdown lint | Lints all `*.md` files (except `CHANGELOG.md`) | `config/custom.markdownlint.jsonc` | | Spelling | Runs cspell on all `*.md` files | `config/cspell.json` | | 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` | -| Frontmatter guard | Fails on a UTF-8 BOM in any `SKILL.md` or agent file (Gemini-fatal, passes validate-skill) and validates `agents/**/*.md` frontmatter, which no other check covers | `scripts/ci/check-frontmatter.sh` | +| Frontmatter guard | Fails on a UTF-8 BOM in any `SKILL.md` or agent file (Gemini-fatal, passes validate-skill) and validates `agents/**/*.md` frontmatter, which no other check covers | `.github/scripts/check-frontmatter.sh` | | Plugin validation | Validates the plugin manifests via the Claude Code CLI | `claude plugin validate .` | | Script tests | Runs the hook script unit tests | `hooks/scripts/*_test.sh` | From 9a9fd593c5e8f1e359669a71cf67d3b49f73e7d7 Mon Sep 17 00:00:00 2001 From: Dominik Simonik Date: Fri, 24 Jul 2026 15:59:53 +0200 Subject: [PATCH 3/4] docs: note metadata escape hatch for non-spec frontmatter Match the CONTRIBUTING frontmatter guidance in VeryGoodOpenSource/vgv-wingspan#230: the spec's optional skills-ref linter rejects top-level fields outside the six it allows, and nesting the Claude Code extras under metadata: is the escape hatch if strict conformance is ever needed. Co-Authored-By: Claude Opus 4.8 --- CONTRIBUTING.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e76b6d6..e88f692 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,10 +100,12 @@ opening `---` on line 1, close the block with `---`, and include a non-empty `na (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 — keep them top-level so Claude -Code reads them and nothing else breaks. 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, and +`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, and `.github/scripts/check-frontmatter.sh` guards the gaps it leaves — a UTF-8 BOM (Gemini-fatal but passes `validate-skill`) and `agents/**/*.md` frontmatter, which no other check covers. From 05e29faea25df40693901fbbbc246ff97a7559ef Mon Sep 17 00:00:00 2001 From: Dominik Simonik Date: Fri, 24 Jul 2026 16:08:06 +0200 Subject: [PATCH 4/4] docs: name tier-2 harnesses and tighten the skill name rule Reconcile with VeryGoodOpenSource/vgv-wingspan#230: name the non-Claude harnesses (Codex, Gemini CLI, OpenCode) the portability section targets, and state the full spec-conformance rules for the skill name field (kebab-case, no leading/trailing/consecutive hyphen, 1-64 chars, matches the directory). Co-Authored-By: Claude Opus 4.8 --- CONTRIBUTING.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e88f692..9755156 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ 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 | | `argument-hint` | No | Placeholder hint shown to the user | @@ -73,10 +73,12 @@ Add the new skill directory and files to the repository structure tree in `CLAUD 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). 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. +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