Skip to content
Draft
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
35 changes: 30 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ VGV AI Flutter Plugin provides best-practices skills for Flutter and Dart develo
plugin.json # Plugin manifest (name, version, keywords)
agents/
flutter-reviewer.md # Read-only Flutter code reviewer subagent
gemini/ # The only Gemini CLI-specific assets; skills are shared
settings.json # Gemini MCP servers + hooks — users merge it into their Gemini settings
agents/
flutter-reviewer.md # Gemini port of agents/flutter-reviewer.md — users copy it to ~/.gemini/agents/
docs/
plan/ # Planning and design documents
evals/
Expand Down Expand Up @@ -138,9 +142,12 @@ They live in `agents/<name>.md` at the plugin root and are **auto-discovered**
- `skills` _(optional)_ — bare skill names to preload at startup (full skill content is injected)
- `model` _(optional)_ — `inherit` to use the session model
- `hooks` _(optional)_ — agent-scoped hooks, e.g. a PreToolUse `Bash` hook
2. Add an **Agents** table row in `README.md` (agent name links to the `agents/<name>.md` file)
3. Add any new domain terms to the `words` list in `config/cspell.json`
4. Update the repository structure in `AGENTS.md`
2. Port it to `gemini/agents/<agent_name>.md` for Gemini CLI, whose frontmatter schema
is strict and different — see **Maintaining Existing Skills, Hooks, and MCP Tools**
above and `CONTRIBUTING.md` → Gemini CLI
3. Add an **Agents** table row in `README.md` (agent name links to the `agents/<name>.md` file)
4. Add any new domain terms to the `words` list in `config/cspell.json`
5. Update the repository structure in `AGENTS.md`

## Maintaining Existing Skills, Hooks, and MCP Tools

Expand All @@ -167,10 +174,28 @@ documentation in the same change:
automatically, so verify each one by hand.
- **Adding or changing a hook** in `hooks/hooks.json` — update the **Hooks**
section in `README.md` (and the `## Hooks` section in `CLAUDE.md` if behavior
changes).
changes), and mirror it in `gemini/settings.json` under Gemini CLI's event
names, tool matchers, and millisecond timeouts. Regenerate that block with
`gemini hooks migrate --from-claude` rather than hand-porting it, then fix the
three things that tool leaves behind — see `CONTRIBUTING.md` → Gemini CLI. A
hook that exists on only one harness is fine, but say which in the script's
header comment.
- **Changing a hook script's response shape** — the `deny` and `allow` helpers in
`vgv-cli-common.sh` branch on `hook_event_name`, so a new response field has to
be written for both harnesses. Each `*_test.sh` suite asserts the Gemini shape
alongside the Claude Code one.
- **Adding or changing an MCP tool** — update the **MCP Integration** tools table
in `README.md`, and check whether any skill's `allowed-tools` names a tool that
was renamed or removed. Nothing validates those names.
was renamed or removed. Nothing validates those names. A new **server** goes in
both `.mcp.json` and `gemini/settings.json`.
- **Changing `agents/flutter-reviewer.md`** — port the same change to
`gemini/agents/flutter-reviewer.md`. The two are separate ports of one
contract, not one shared file: Gemini validates local agent frontmatter with a
**strict** schema (`kind`, `name`, `description`, `display_name`, `tools`,
`mcp_servers`, `model`, `temperature`, `max_turns`, `timeout_mins`) and drops
any agent carrying a key outside it — `skills` and `hooks` included. Its output
contract (the four-column findings table) is consumed verbatim by callers on
both harnesses, so the two must not drift.

## Evals

Expand Down
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,20 @@ These run **after** a tool call completes:
- `Edit|Write` matcher → `format.sh` — runs `dart format` on the modified `.dart` file; always exits 0 (non-blocking)

All hook scripts require **jq** to parse the hook payload (they skip gracefully if `jq` is not installed).

### Gemini CLI

`gemini/settings.json` is a copy-me config that runs these same scripts under Gemini's names:
`SessionStart` stays put, `PreToolUse` becomes `BeforeTool`, `PostToolUse` becomes `AfterTool`,
matchers become `run_shell_command`, `replace|write_file` and `mcp_very-good-cli_.*`, and timeouts
are milliseconds rather than seconds. Scripts resolve through `${VGV_PLUGIN_ROOT}`, since Gemini
has no plugin-root variable for settings-level hooks.

The scripts read `hook_event_name` off the payload and emit whichever response shape the firing
harness reads — `hookSpecificOutput.permissionDecision` for Claude Code, top-level
`decision`/`reason` for Gemini. Two things do not carry over: Gemini has no auto-approve for a
`BeforeTool` hook (so `check-vgv-cli.sh` only enforces the version gate there, and `"trust": true`
on the MCP server covers approval), and it has no agent-scoped hooks (so `allow-readonly-git.sh` is
Claude-only and `gemini/agents/flutter-reviewer.md` holds its read-only contract by granting no
shell tool at all). Change a hook or the reviewer and both harnesses are affected — see `AGENTS.md`
→ Maintaining Existing Skills, Hooks, and MCP Tools.
57 changes: 56 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ 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
Claude Code construct, ported to Gemini CLI at `gemini/agents/flutter-reviewer.md`; 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.

Expand All @@ -193,6 +194,58 @@ frontmatter equivalent, and `interface.short_description`, which takes precedenc
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.

**Gemini CLI runtime** — Gemini CLI reads the Agent Skills standard directly, so `skills/` needs
nothing: `gemini skills install <repo-url> --path skills` lands all 15. The enforcement layer does
not carry over as cleanly, and `gemini/` holds the ported pieces. Verified against Gemini CLI
0.59.0:

- **Skill discovery is the standard path.** Gemini scans `.agents/skills` and `~/.agents/skills`
alongside its own `.gemini/skills`, so a spec-conformant skill is found with no Gemini-specific
file. This is the whole reason `skills/` is untouched here.
- **Hook events are renamed, not absent.** `PreToolUse` is `BeforeTool`, `PostToolUse` is
`AfterTool`, `UserPromptSubmit` is `BeforeAgent`, `Stop` is `AfterAgent`; `SessionStart` keeps its
name. `gemini hooks migrate --from-claude` does that mapping and rewrites tool matchers
(`Bash` → `run_shell_command`, `Edit|Write` → `replace|write_file`), which is why
`gemini/settings.json` is generated rather than hand-written. It leaves three things wrong, all
of which fail silently: `${CLAUDE_PLUGIN_ROOT}` is not rewritten and has no Gemini equivalent for
settings-level hooks (hence `${VGV_PLUGIN_ROOT}`); `timeout` is copied verbatim, but Gemini reads
**milliseconds** where Claude Code reads seconds, so a `10` becomes a 10ms budget; and MCP
matchers keep Claude's `mcp__<server>__<tool>` naming when Gemini names them
`mcp_<server>_<tool>`. An event name it does not recognize is skipped with a one-line warning.
- **The response shape differs, so the scripts branch.** Gemini blocks a tool on a top-level
`{"decision": "deny", "reason": ...}` rather than
`hookSpecificOutput.permissionDecision`. `deny()` and `allow()` in `vgv-cli-common.sh` read
`hook_event_name` off the payload and emit whichever the firing harness reads, so one script
serves both. A payload with no `hook_event_name` gets the Claude Code shape. Everything else is
shared verbatim: both harnesses put the shell command in `tool_input.command` and the edited path
in `tool_input.file_path`, so `block-cli-workarounds.sh`, `analyze.sh` and `format.sh` need no
payload changes.
- **There is no auto-approve.** A Gemini `BeforeTool` hook can block or stand aside; it has no
equivalent of Claude Code's `permissionDecision: "allow"`. `check-vgv-cli.sh` therefore only
enforces the version gate on Gemini, and `"trust": true` on the MCP server covers approval. Do
not weaken the Claude Code auto-approve to make the two match.
- **A plugin cannot ship a Gemini subagent, and the schema is strict.** Gemini loads local agents
from `~/.gemini/agents/` or a project's `.gemini/agents/`, so
`gemini/agents/flutter-reviewer.md` is a file users copy. It cannot be the same file as
`agents/flutter-reviewer.md`: Gemini validates frontmatter against a strict allowlist and drops
any agent carrying a key outside it, so `skills:` and `hooks:` are both rejected, and `tools`
must be a YAML list of Gemini tool names (`read_file`, `glob`, `grep_search`,
`run_shell_command`, `mcp_<server>_<tool>`). Gemini has no agent-scoped hooks and no per-agent
argument narrowing, so the read-only contract that `allow-readonly-git.sh` holds on Claude Code
rests on the tool list instead: the Gemini reviewer is granted no shell tool at all, which is a
stronger guarantee, and the caller passes it the changed files in place of the `git diff` it can
no longer run.
- **Do not weaken the Claude Code path** to make Gemini simpler. `hooks/hooks.json`,
`.mcp.json` and `agents/flutter-reviewer.md` stay authoritative.

Nothing in CI exercises Gemini CLI, so verify a change to any of it by hand. `gemini skills list`
and a headless `gemini -p` both report what loaded — skills, agents, and hook registration — before
any model call, so pointing `GEMINI_BASE_URL` at an unreachable address is enough to read the
verdict without credentials. Do it in a throwaway `HOME` so your own Gemini config is untouched,
and set `GEMINI_CLI_TRUST_WORKSPACE=true`: since 0.59.0 a headless run in an untrusted directory
refuses to start rather than loading with workspace features disabled, which reads as "nothing
loaded" if you do not expect it.

**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. All trigger
Expand All @@ -215,6 +268,8 @@ session and exercise it before you commit.
- **Dart SDK** and **jq** on your `PATH` — the hooks need both.
- **Very Good CLI** ≥ 1.3.0 (`dart pub global activate very_good_cli`) for the
Very Good CLI MCP server tools.
- **Gemini CLI** (`npm install -g @google/gemini-cli`) only if you touch the hooks
or `gemini/`, to verify the change by hand. Everything else runs without it.

See the README [Hooks](README.md#hooks) and [MCP Integration](README.md#mcp-integration)
sections for the full prerequisite details.
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ This plugin ships subagents that Claude Code can dispatch as isolated, specializ
| ----- | ----------- |
| [**Flutter Reviewer**](agents/flutter-reviewer.md) | Read-only reviewer of changed Dart code against the preloaded `bloc`, `testing`, `static-security`, and `accessibility` standards — emits a `location \| problem \| fix \| standard` findings table. Never edits files; Bash is hook-restricted to `git diff`/`git status` |

A Gemini CLI port of the same reviewer lives at
[`gemini/agents/flutter-reviewer.md`](gemini/agents/flutter-reviewer.md) — see
[Gemini CLI](#gemini-cli).

## Hooks

This plugin includes SessionStart, PreToolUse, and PostToolUse hooks that validate the Very Good CLI, guard against CLI bypass, and automatically run Dart analysis and formatting on `.dart` files.
Expand All @@ -81,6 +85,49 @@ This plugin includes SessionStart, PreToolUse, and PostToolUse hooks that valida
- **Dart SDK** — must be available on your `PATH`
- **jq** — used to parse the hook payload; hooks are skipped gracefully if `jq` is not installed

## Gemini CLI

The skills need no setup. Gemini CLI reads the [Agent Skills][agent_skills_link] standard
directly, so installing them is one command and all 15 land:

```bash
gemini skills install https://github.com/VeryGoodOpenSource/vgv-ai-flutter-plugin --path skills
```

The MCP servers, the hooks, and the reviewer agent are the parts Gemini cannot take from the
Claude Code files as they stand — it uses different hook event names, different tool names, and a
stricter agent schema. `gemini/` holds those three, ported. Clone this repo somewhere and point
`VGV_PLUGIN_ROOT` at it, since the hook scripts live here. Put it in your shell profile rather
than one terminal — Gemini resolves it when it loads its settings, so it has to be set wherever
you launch `gemini`:

```bash
export VGV_PLUGIN_ROOT=/path/to/vgv-ai-flutter-plugin
```

Then merge `gemini/settings.json` into your Gemini settings — `~/.gemini/settings.json` for
yourself, or a project's `.gemini/settings.json` to give a whole team the same enforcement with no
per-developer setup. It registers both MCP servers and wires the same hook scripts under Gemini's
event names.

The reviewer agent is a separate copy for the same reason. Pick whichever scope fits.

For yourself, across every project:

```bash
mkdir -p ~/.gemini/agents && cp gemini/agents/flutter-reviewer.md ~/.gemini/agents/
```

For a whole team, commit it into the Flutter project instead:

```bash
mkdir -p .gemini/agents && cp gemini/agents/flutter-reviewer.md .gemini/agents/
```

Then ask Gemini to run `flutter-reviewer`. It holds the same read-only contract Claude Code
enforces with a hook, by a different mechanism: Gemini has no agent-scoped hooks, so the agent is
granted no shell tool at all and the caller passes it the changed files.

## Evals

Skill evals ask whether Claude routes to a skill and follows it. [promptfoo](https://www.promptfoo.dev) sends each case's prompt through the Claude Agent SDK twice — once with this plugin loaded, once sealed with nothing loaded — so a grader that passes in both columns is measuring the model rather than the skill. They authenticate through your local Claude Code session, so they need no API key.
Expand Down Expand Up @@ -187,6 +234,12 @@ The Very Good CLI MCP server exposes Very Good CLI commands to Claude.

The `.mcp.json` file at the project root registers the `dart` and `very-good-cli` MCP servers using stdio transport. When Claude Code detects this configuration, it connects to both servers and gains access to the tools above. The skills continue to provide knowledge and best practices while the MCP tools handle execution.

On Gemini CLI the same two servers are registered in `settings.json` instead — see
[Gemini CLI](#gemini-cli). Skills that drive an MCP tool always name the equivalent `very_good`,
`dart`, or `flutter` command as a fallback, so they keep working on a host where neither server is
connected.

[agent_skills_link]: https://agentskills.io/specification
[marketplace_link]: https://github.com/VeryGoodOpenSource/very-good-claude-code-marketplace
[claude_code_link]: https://claude.ai/code
[vgv_link]: https://verygood.ventures
Expand Down
Loading
Loading