From dd36bf06c4ad3d4e9126bc583ff41de074c05b20 Mon Sep 17 00:00:00 2001 From: Dominik Simonik Date: Tue, 29 Sep 2026 08:45:38 +0200 Subject: [PATCH] fix: match plugin-scoped Very Good CLI MCP tool names Rename the MCP server to very-good-cli, grant the marketplace tool names in ffca-feature allowed-tools, and add a PreToolUse hook that checks the CLI version and auto-approves its tool calls. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yaml | 8 +++ .mcp.json | 2 +- AGENTS.md | 12 ++-- CLAUDE.md | 6 +- CONTRIBUTING.md | 11 ++- README.md | 38 +++++++++-- hooks/check_vgv_cli.sh | 67 ++++++++++++++++++ hooks/check_vgv_cli_test.sh | 127 +++++++++++++++++++++++++++++++++++ hooks/hooks.json | 14 +++- skills/ffca-feature/SKILL.md | 2 +- 10 files changed, 269 insertions(+), 18 deletions(-) create mode 100755 hooks/check_vgv_cli.sh create mode 100755 hooks/check_vgv_cli_test.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b33efb5..b46d1ce 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,6 +56,14 @@ jobs: - name: Test run: dart test + hooks: + name: 🪝 Hook Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Check VGV CLI hook tests + run: bash hooks/check_vgv_cli_test.sh + skills-lint: name: 🔍 Skills Lint uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/skills_lint.yml@v1.21.1 diff --git a/.mcp.json b/.mcp.json index e17db8e..4d41035 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,6 +1,6 @@ { "mcpServers": { - "very_good_cli": { + "very-good-cli": { "command": "very_good", "args": ["mcp"] } diff --git a/AGENTS.md b/AGENTS.md index 2cf635d..06ba364 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,9 @@ config/ cspell.json # Spell check dictionary custom.markdownlint.jsonc # markdownlint rules hooks/ - hooks.json # Hook definitions (PostToolUse) + hooks.json # Hook definitions (PreToolUse, PostToolUse) + check_vgv_cli.sh # Checks the Very Good CLI version and auto-approves its MCP tools + check_vgv_cli_test.sh # Tests for check_vgv_cli.sh validate_layers.sh # Runs the validator on edited pubspec.yaml files references/ ffca/ # Byte mirror of the VGV Engineering FFCA pages, single source of truth @@ -61,7 +63,7 @@ Every `SKILL.md` follows this structure: 1. **YAML frontmatter** with these fields: - `name`: required. Must match the skill's folder name exactly, lowercase letters, numbers, and hyphens only, prefixed `ffca-`. - `description`: required. What the skill covers, when to use it, the trigger phrases that should activate it, and the deferral to `layered-architecture` for non-FFCA repos. - - `allowed-tools`: space-separated list of tools the skill may use, for example `Read Glob Grep`. Read-only skills stop there. Skills that write code add `Write Edit`, and MCP tools use their full name, for example `mcp__very_good_cli__create`. + - `allowed-tools`: space-separated list of tools the skill may use, for example `Read Glob Grep`. Read-only skills stop there. Skills that write code add `Write Edit`, and MCP tools use their full plugin-scoped name, for example `mcp__plugin_vgv-ffca-plugin_very-good-cli__create`. - `effort`: reasoning effort while the skill is active. Every skill here sets `high`. 2. **H1 title**, the human-readable skill name. 3. **A short purpose paragraph**, then numbered workflow steps. @@ -103,9 +105,9 @@ Documentation drifts when an asset changes and the docs describing it do not. Up - **The FFCA architecture changes.** Update the VGV Engineering page first, then run `dart run scripts/sync_reference.dart` to re-sync `references/ffca/`. Check every skill, the agent, and the code templates for section names that moved or rules that changed. If a dependency rule changed, update the rules table at the top of `scripts/validate_layers.dart` and its tests. - **A skill's scope or triggers change.** Update `description` and the matching row in the `README.md` Skills table. -- **The validator's rules or flags change.** Update `scripts/test/validate_layers_test.dart` and its fixtures, the **Hook** section of `README.md`, and the `## Hooks` section of `CLAUDE.md` if the hook's behavior changes. -- **A hook changes in `hooks/hooks.json`.** Update the **Hook** section of `README.md` and the `## Hooks` section of `CLAUDE.md`. -- **An MCP tool is added, renamed, or removed.** Check every skill's `allowed-tools`. Nothing validates those names. +- **The validator's rules or flags change.** Update `scripts/test/validate_layers_test.dart` and its fixtures, the **Hooks** section of `README.md`, and the `## Hooks` section of `CLAUDE.md` if the hook's behavior changes. +- **A hook changes in `hooks/hooks.json`.** Update the **Hooks** section of `README.md` and the `## Hooks` section of `CLAUDE.md`. If it is `check_vgv_cli.sh`, update `hooks/check_vgv_cli_test.sh`. +- **An MCP tool is added, renamed, or removed.** Check every skill's `allowed-tools` and the **MCP Integration** section of `README.md`. Nothing validates those names. ## Checks diff --git a/CLAUDE.md b/CLAUDE.md index f645d2f..055faeb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,10 +5,14 @@ ## Hooks -`hooks/hooks.json` defines one PostToolUse hook. +`hooks/hooks.json` defines one PostToolUse hook and one PreToolUse hook. - `Edit|Write` matcher → `validate_layers.sh`. When the edited file is a `pubspec.yaml` inside an FFCA-shaped repo, meaning a `features/` folder exists above it, the hook runs `dart run scripts/validate_layers.dart --file `. The validator checks the edited package and its direct dependents. Exit 2 means a layer violation. The hook prints the rule and the fix to stderr and exits 2, which blocks Claude until the dependency is fixed. - Any other file, or a repo with no `features/` folder, exits 0 silently. - The hook skips with exit 0 when `jq` or `dart` is missing from `PATH`. A validator failure other than exit 2 is reported to stderr but never blocks. +- `mcp__.*very-good-cli__.*` matcher → `check_vgv_cli.sh`. For a Very Good CLI MCP tool, it returns `allow` when `very_good --version` is 1.3.0 or newer and `deny` with an install or upgrade message when the CLI is missing or older. It stands aside with exit 0 for any other tool, when the version cannot be read, or when `jq` is missing. `allowed-tools` grants only last for the invoking turn, so this hook is what keeps the tools approved afterwards. + +`hooks/check_vgv_cli_test.sh` covers the PreToolUse hook against a stubbed `very_good` and runs in CI under the **Hook Tests** job. + `scripts/test/validate_layers_test.dart` covers the validator and runs in CI under the **Layer Validator** job. Add a fixture and a case there when changing a rule. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f3b219..f43a338 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,9 +92,9 @@ claude --plugin-dir . | **Skills** | Run `/help`. Skills appear namespaced as `/vgv-ffca-plugin:`, for example `/vgv-ffca-plugin:ffca-feature`. Invoke one to confirm it triggers | | **Hook** | In an FFCA repo, have Claude add a forbidden dependency to a `pubspec.yaml`, for example a `_data` package to a `_presentation` package. The edit must be blocked with the rule and the fix | | **Agent** | Run `/agents` and confirm `ffca-layer-auditor` is listed, or run `/vgv-ffca-plugin:ffca-audit` and confirm it dispatches the agent | -| **MCP server** | Run `/mcp` and confirm `very_good_cli` shows connected | +| **MCP server** | Run `/mcp` and confirm `plugin:vgv-ffca-plugin:very-good-cli` shows connected. Invoke `/vgv-ffca-plugin:ffca-feature` and confirm the `create` call runs without a permission prompt | -After editing a `SKILL.md`, an agent, or `.claude-plugin/plugin.json`, restart the session to pick up the change. Edits to `hooks/validate_layers.sh` take effect on the next matching tool call. +After editing a `SKILL.md`, an agent, or `.claude-plugin/plugin.json`, restart the session to pick up the change. Edits to the hook scripts in `hooks/` take effect on the next matching tool call. ### Run the validator tests @@ -110,6 +110,12 @@ Run the validator against a real FFCA workspace with: dart run scripts/validate_layers.dart --all ``` +The Very Good CLI hook has a shell test suite that stubs `very_good`: + +```bash +bash hooks/check_vgv_cli_test.sh +``` + ### Validate before you push Run the same plugin check CI runs, from the repository root: @@ -129,6 +135,7 @@ Every pull request runs the following checks from `.github/workflows/ci.yaml`: | Markdown Quality | Lints all `*.md` files with markdownlint-cli2, except `CHANGELOG.md` and `references/ffca/` | `config/custom.markdownlint.jsonc` | | Spelling Check | Runs cspell on all `*.md`, `*.yml`, and `*.yaml` files except `CHANGELOG.md` | `config/cspell.json` | | Layer Validator | Runs `dart analyze --fatal-infos`, `dart format --set-exit-if-changed`, and `dart test` in `scripts/` | `scripts/pubspec.yaml` | +| Hook Tests | Runs `bash hooks/check_vgv_cli_test.sh` | `hooks/check_vgv_cli_test.sh` | | Skills Lint | Validates every `SKILL.md` in `skills/` | Very Good Workflows `skills_lint` | | Plugin Validation | Validates the plugin | `claude plugin validate .` | diff --git a/README.md b/README.md index 350b8cb..a08bf19 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Developed with 💙 by [Very Good Ventures](https://verygood.ventures) 🦄 VGV FFCA Plugin teaches Claude the Feature-First Clean Architecture conventions and enforces its layer rules as you work. It ships three asset types: - **Skills** that guide FFCA workflows: where code lives, how to scaffold a feature, how to wire routing, how to couple features, and how to audit a repo. -- **A blocking validation hook** that runs on every `pubspec.yaml` edit and stops the edit when it breaks a layer dependency rule, with the rule and the fix in the message so Claude self-corrects. +- **Hooks**: a blocking validation hook that runs on every `pubspec.yaml` edit and stops the edit when it breaks a layer dependency rule, with the rule and the fix in the message so Claude self-corrects, plus a Very Good CLI check that gates its MCP tool calls. - **An MCP configuration** that wires the Very Good CLI server for project and package operations. The conventions themselves live in `references/ffca/`, a byte mirror of the canonical FFCA documentation on [VGV Engineering](https://engineering.verygood.ventures/architecture/ffca/overview/), regenerated by `scripts/sync_reference.dart`. The skills never restate the conventions: they point into the mirror by file and section, so the architecture has exactly one source of truth. @@ -112,13 +112,14 @@ Skills activate automatically when Claude detects an FFCA repo or an FFCA-shaped /ffca-audit ``` -## Hook +## Hooks -A PostToolUse hook runs on every `Edit` or `Write`. When the edited file is a `pubspec.yaml` inside an FFCA-shaped repo, it validates the package's layer dependencies. +A PostToolUse hook runs on every `Edit` or `Write`. When the edited file is a `pubspec.yaml` inside an FFCA-shaped repo, it validates the package's layer dependencies. A PreToolUse hook gates every Very Good CLI MCP tool call. -| Hook | Behavior | -| --- | --- | -| **Validate layers** | Runs the FFCA validator incrementally on the edited package and its direct dependents. Exits 2 on a violation (blocking: Claude must fix the dependency before continuing), printing the rule and the fix. Passes silently otherwise | +| Hook | Event | Behavior | +| --- | --- | --- | +| **Validate layers** (`validate_layers.sh`) | PostToolUse (`Edit`/`Write`) | Runs the FFCA validator incrementally on the edited package and its direct dependents. Exits 2 on a violation (blocking: Claude must fix the dependency before continuing), printing the rule and the fix. Passes silently otherwise | +| **Check VGV CLI** (`check_vgv_cli.sh`) | PreToolUse (`mcp__.*very-good-cli__.*`) | Auto-approves Very Good CLI MCP tool calls when the CLI is installed at 1.3.0 or newer, so they work in every run mode. Denies with an install or upgrade message when the CLI is missing or outdated. Stands aside for any other tool, or when the CLI version cannot be read | The validator is also runnable directly for CI and audits, across the whole workspace: @@ -129,7 +130,30 @@ dart run scripts/validate_layers.dart --all ### Prerequisites - **Dart SDK** must be available on your `PATH`. The validator imports only `dart:io`, so it runs with just the SDK, no `dart pub get` required. -- **jq** is used to parse the hook payload. The hook is skipped gracefully if `jq` or `dart` is not installed. +- **jq** is used to parse the hook payload. The hooks are skipped gracefully if `jq` is not installed, and the validation hook also if `dart` is not installed. + +Run the hook tests with: + +```bash +bash hooks/check_vgv_cli_test.sh +``` + +## MCP Integration + +The plugin's `.mcp.json` connects Claude Code to the **Very Good CLI MCP server** (`very_good mcp`) under the server name `very-good-cli`, the same name vgv-ai-flutter-plugin uses. The `ffca-feature` skill calls it to scaffold and resolve each feature package. + +| Tool | What it does | +| --- | --- | +| `create` | Scaffold packages from templates. FFCA uses `dart_package` for domain and data, `flutter_package` for presentation | +| `packages_get` | Get dependencies for a single package or recursively across the monorepo | +| `test` | Run tests with coverage enforcement | +| `packages_check_licenses` | Audit dependency licenses against an allowed list | + +When installed from a marketplace, Claude Code names these tools `mcp__plugin_vgv-ffca-plugin_very-good-cli__`, and the skills' `allowed-tools` use that full name. + +The plugin does not bundle the Dart MCP server (`dart mcp-server`). No FFCA skill calls it, and vgv-ai-flutter-plugin already provides it for analyze and format. + +The server needs **Very Good CLI** 1.3.0 or newer. Install it with `dart pub global activate very_good_cli`. ## Agent diff --git a/hooks/check_vgv_cli.sh b/hooks/check_vgv_cli.sh new file mode 100755 index 0000000..4919ba4 --- /dev/null +++ b/hooks/check_vgv_cli.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -euo pipefail + +# PreToolUse hook: gate Very Good CLI MCP tool calls. +# Mirrors check-vgv-cli.sh in vgv-ai-flutter-plugin. When the CLI is installed +# and new enough, auto-approve the call so it works in every run mode, since a +# skill's allowed-tools grant only lasts for the turn that invoked it. +# Otherwise deny with an install or upgrade message instead of letting the +# call fail silently. + +MIN_VERSION="1.3.0" + +# Read the hook payload from stdin. +input=$(cat) + +# Graceful skip if jq is unavailable: normal permission handling applies. +if ! command -v jq &>/dev/null; then + echo "check_vgv_cli hook: jq not found, skipping" >&2 + exit 0 +fi + +decide() { + jq -n --arg decision "$1" --arg reason "$2" '{ + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: $decision, + permissionDecisionReason: $reason + } + }' + exit 0 +} + +# The matcher is not a guarantee, so confirm the caller is a Very Good CLI +# tool. Covers mcp__very-good-cli__ and the marketplace form +# mcp__plugin__very-good-cli__. +tool_name=$(jq -r '.tool_name // empty' <<<"$input") +if [[ "$tool_name" != mcp__*very-good-cli__* ]]; then + exit 0 +fi + +# Resolve very_good from PATH, falling back to the pub cache, which a hook +# subprocess does not necessarily have on its PATH. +vgv_bin=$(command -v very_good || true) +if [[ -z "$vgv_bin" ]]; then + fallback="${PUB_CACHE:-$HOME/.pub-cache}/bin/very_good" + [[ -x "$fallback" ]] && vgv_bin=$fallback +fi +if [[ -z "$vgv_bin" ]]; then + decide deny "Very Good CLI is not installed. This tool requires Very Good CLI >= ${MIN_VERSION}. Install with: dart pub global activate very_good_cli" +fi + +# The very_good shim execs dart, so an unreadable version is inconclusive, not +# missing. Stand aside rather than deny a genuine call. +version=$("$vgv_bin" --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true) +if [[ -z "$version" ]]; then + exit 0 +fi + +IFS='.' read -r major minor patch <<<"$version" +IFS='.' read -r min_major min_minor min_patch <<<"$MIN_VERSION" +if ((major < min_major || + (major == min_major && minor < min_minor) || + (major == min_major && minor == min_minor && patch < min_patch))); then + decide deny "Very Good CLI ${version} is too old. This tool requires Very Good CLI >= ${MIN_VERSION}. Update with: dart pub global activate very_good_cli" +fi + +decide allow "Very Good CLI >= ${MIN_VERSION} verified; auto-approving Very Good CLI MCP tool call." diff --git a/hooks/check_vgv_cli_test.sh b/hooks/check_vgv_cli_test.sh new file mode 100755 index 0000000..071c96d --- /dev/null +++ b/hooks/check_vgv_cli_test.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# Tests for check_vgv_cli.sh +# +# Usage: bash hooks/check_vgv_cli_test.sh +# +# The hook reads a JSON payload from stdin and either emits a decision JSON on stdout +# (allow/deny) or exits silently, meaning it stood aside. A non-zero exit is its own +# outcome, never mistaken for standing aside. Every case runs against a +# stubbed very_good on a PATH that contains nothing else, so results do not depend on +# what is installed on the machine running the tests. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HOOK="$SCRIPT_DIR/check_vgv_cli.sh" + +PASSED=0 +FAILED=0 + +STUB_DIR="$(mktemp -d)" +trap 'rm -rf "$STUB_DIR"' EXIT + +BASE_PATH="$(dirname "$(command -v jq)"):/usr/bin:/bin:/usr/sbin:/sbin" + +# Install a stubbed very_good. With a version argument the stub reports that version; +# with no argument it fails the way the real shim does when `dart` is missing from PATH. +# Usage: stub_cli [version] [--in-pub-cache] +stub_cli() { + local version="${1:-}" + local location="${2:-}" + local target="$STUB_DIR/very_good" + rm -rf "$STUB_DIR/pub-cache" "$STUB_DIR/very_good" + if [ "$location" = "--in-pub-cache" ]; then + mkdir -p "$STUB_DIR/pub-cache/bin" + target="$STUB_DIR/pub-cache/bin/very_good" + fi + if [ -z "$version" ]; then + printf '#!/bin/sh\necho "very_good: dart: command not found" >&2\nexit 127\n' > "$target" + else + printf '#!/bin/sh\necho "very_good %s"\n' "$version" > "$target" + fi + chmod +x "$target" +} + +no_cli() { rm -rf "$STUB_DIR/pub-cache" "$STUB_DIR/very_good"; } + +# Prints the permissionDecision ("allow"/"deny"), "aside" when the hook exited 0 with +# no decision, or "exit:" on a non-zero exit, so a crashing hook cannot pass +# as having stood aside. +run_hook() { + local payload="$1" + local output status=0 + output=$(printf '%s' "$payload" \ + | env -i PATH="$STUB_DIR:$BASE_PATH" HOME="$STUB_DIR" PUB_CACHE="$STUB_DIR/pub-cache" \ + bash "$HOOK" 2>/dev/null) || status=$? + if [ "$status" -ne 0 ]; then + echo "exit:$status" + elif [ -z "$output" ]; then + echo "aside" + else + echo "$output" | jq -r '.hookSpecificOutput.permissionDecision // "malformed"' + fi +} + +assert_decision() { + local expected="$1" label="$2" payload="$3" + local result + result=$(run_hook "$payload") + if [ "$result" = "$expected" ]; then + printf " \033[32mPASS\033[0m %-10s %s\n" "$expected" "$label" + PASSED=$((PASSED + 1)) + else + printf " \033[31mFAIL\033[0m expected %s but got %s: %s\n" "$expected" "$result" "$label" + FAILED=$((FAILED + 1)) + fi +} + +VGV_TOOL='{"tool_name":"mcp__very-good-cli__create","tool_input":{}}' +VGV_PLUGIN_TOOL='{"tool_name":"mcp__plugin_vgv-ffca-plugin_very-good-cli__packages_get","tool_input":{}}' + +echo "=== check_vgv_cli tests ===" +echo "" +echo "--- Tools that are not this hook's business (stand aside) ---" +no_cli +assert_decision aside "server-less browser MCP call" '{"tool_name":"MCP:browser_tabs","tool_input":{"action":"list"}}' +assert_decision aside "browser MCP call, Claude naming" '{"tool_name":"mcp__cursor-ide-browser__browser_navigate","tool_input":{}}' +assert_decision aside "shell tool call" '{"tool_name":"Bash","tool_input":{"command":"ls"}}' +assert_decision aside "empty payload" '{}' +assert_decision aside "null tool_name" '{"tool_name":null}' +# A payload with no server segment is indistinguishable from any other server's `test`. +# Standing aside is the safe result. +assert_decision aside "server-less MCP name" '{"tool_name":"MCP:test","tool_input":{}}' +assert_decision aside "old underscore server name" '{"tool_name":"mcp__very_good_cli__create","tool_input":{}}' + +echo "" +echo "--- Very Good CLI tools, CLI present and current ---" +stub_cli 1.5.0 +assert_decision allow "version above minimum" "$VGV_TOOL" +assert_decision allow "marketplace-namespaced tool" "$VGV_PLUGIN_TOOL" +stub_cli 1.3.0 +assert_decision allow "version exactly at minimum" "$VGV_TOOL" + +echo "" +echo "--- Very Good CLI tools, CLI missing or outdated ---" +stub_cli 1.2.9 +assert_decision deny "version below minimum" "$VGV_TOOL" +stub_cli 0.9.0 +assert_decision deny "major version below minimum" "$VGV_TOOL" +no_cli +assert_decision deny "CLI not installed" "$VGV_TOOL" + +echo "" +echo "--- CLI reachable only through the pub-cache fallback ---" +stub_cli 1.5.0 --in-pub-cache +assert_decision allow "resolved from PUB_CACHE, not PATH" "$VGV_TOOL" + +echo "" +echo "--- Version unreadable (shim present, dart missing) ---" +stub_cli +assert_decision aside "inconclusive check does not deny" "$VGV_TOOL" + +echo "" +echo "=== Results: $PASSED passed, $FAILED failed ===" + +if [ "$FAILED" -gt 0 ]; then + exit 1 +fi diff --git a/hooks/hooks.json b/hooks/hooks.json index c3b01bd..2543f4f 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -1,6 +1,18 @@ { - "description": "VGV FFCA plugin hook: enforces Feature-First Clean Architecture layer rules on pubspec.yaml edits", + "description": "VGV FFCA plugin hooks: enforces Feature-First Clean Architecture layer rules on pubspec.yaml edits and gates Very Good CLI MCP tool calls", "hooks": { + "PreToolUse": [ + { + "matcher": "mcp__.*very-good-cli__.*", + "hooks": [ + { + "type": "command", + "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/check_vgv_cli.sh\"", + "timeout": 10 + } + ] + } + ], "PostToolUse": [ { "matcher": "Edit|Write", diff --git a/skills/ffca-feature/SKILL.md b/skills/ffca-feature/SKILL.md index 2e125a9..47e2037 100644 --- a/skills/ffca-feature/SKILL.md +++ b/skills/ffca-feature/SKILL.md @@ -11,7 +11,7 @@ description: > "add a Command", "map this DTO to a domain model", "add a Cubit for this screen", and "wire up the feature Module". If the repo instead uses packages/ with _repository and _api_client packages, defer to vgv-ai-flutter-plugin's layered-architecture skill. -allowed-tools: Read Glob Grep Write Edit mcp__very_good_cli__create mcp__very_good_cli__packages_get +allowed-tools: Read Glob Grep Write Edit mcp__plugin_vgv-ffca-plugin_very-good-cli__create mcp__plugin_vgv-ffca-plugin_very-good-cli__packages_get effort: high ---