A Claude Code plugin that operationalizes Feature-First Clean Architecture (FFCA) for Flutter monorepos.
Developed with π by Very Good 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.
- Hooks: a blocking validation hook that runs on every
pubspec.yamledit 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, 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.
references/ffca/ holds one file per page of the canonical documentation, fetched verbatim from the FFCA section of VGV Engineering:
| File | Covers |
|---|---|
overview.md |
Goals, the three-part structure, feature archetypes, shared libraries, the layer model |
domain.md |
Models, Commands and Queries, repository interfaces, composing features, the Summary pattern |
data.md |
Data sources, DTOs, converters and mappers |
presentation.md |
Modules, localizations, widgets that own state, widget slots, subfeature barrels |
navigation.md |
Callback injection, typed routes, splitting the routing table, deep links |
project_structure.md |
Dependency rules, deferred loading, naming, folder layout, tooling, add-to-app |
faq.md |
Callable classes, auth and user profiles, one big OpenAPI spec, nested objects |
Do not edit these by hand. Fix the architecture at the source, then sync:
dart run scripts/sync_reference.dart # rewrite the mirror
dart run scripts/sync_reference.dart --check # fail if it is staleA scheduled workflow runs --check weekly, so the mirror cannot quietly fall behind upstream.
This plugin is the structure layer of Very Good Ventures' AI-assisted engineering stack. It composes with the other two plugins rather than replacing them.
| Layer | Plugin | Role |
|---|---|---|
| Workflow | vgv-wingspan | brainstorm, plan, build, review |
| Structure | vgv-ffca-plugin | Monorepo structure, layer rules, FFCA conventions |
| Code quality | vgv-ai-flutter-plugin | Bloc, testing, a11y, theming, analyze and format hooks |
Each FFCA skill self-scopes to FFCA repos through its trigger description, so the plugin coexists with vgv-ai-flutter-plugin without conflicts. The detection signal is a features/ folder containing {feature}_domain, {feature}_data, or {feature}_presentation packages.
Both this plugin and vgv-ai-flutter-plugin ship an architecture skill, and they target different structures. Use the signal in the repo to tell them apart:
- This plugin's
ffca-architectureapplies to FFCA monorepos: afeatures/folder of{feature}_domain,{feature}_data, and{feature}_presentationpackages, withapps/andshared/alongside. - vgv-ai-flutter-plugin's
layered-architectureapplies to the standard VGV layered app: apackages/folder of_repositoryand_api_clientpackages with business logic and presentation in the app'slib/.
The ffca-architecture skill defers to layered-architecture when it sees the packages/ + _repository/_api_client shape, and the validation hook only runs on repos that have a features/ folder, so a layered repo never triggers FFCA enforcement.
One-line install from your terminal:
claude plugin marketplace add VeryGoodOpenSource/very-good-claude-code-marketplace && claude plugin install vgv-ffca-pluginOr inside an active Claude Code session, run these as two separate commands (the second only after the first completes):
-
Add the marketplace:
/plugin marketplace add VeryGoodOpenSource/very-good-claude-code-marketplace -
Install the plugin:
/plugin install vgv-ffca-plugin
For more details, see the Very Good Claude Marketplace.
Load the plugin from a local checkout, validate it, and run the validator tests:
claude --plugin-dir .
claude plugin validate .
cd scripts && dart test| Skill | Description |
|---|---|
| FFCA Architecture | Orientation: where code lives across apps/, features/, shared/, the naming conventions, the layer dependency rules, and the anti-patterns to reject |
| FFCA Feature | Scaffold and extend a feature: the three-package domain, data, and presentation structure, headless and presentation-only features, models, repositories, Commands and Queries, DTOs, mappers, Cubits, and Modules |
| FFCA Routing | Navigation: callback injection, go_router_builder typed routes, splitting the routing table, deferred imports, the $extra hydration pattern, and the feature-isolation constraints |
| FFCA Cross-Feature | Coupling features: domain-to-domain dependencies, the Summary pattern, Queries that combine repositories, sharing widgets, widget slots, and composing features |
| FFCA Audit | Whole-repo health check: dispatches the ffca-layer-auditor agent, which runs the mechanical layer, naming, and cycle checks plus a qualitative review and returns a per-package verdict table |
Skills activate automatically when Claude detects an FFCA repo or an FFCA-shaped question. You can also invoke them directly:
/ffca-architecture
/ffca-feature
/ffca-routing
/ffca-cross-feature
/ffca-audit
A PostToolUse hook runs on every Edit, MultiEdit, or Write and ignores any other tool call. When the edited file is a pubspec.yaml inside an FFCA-shaped repo, it validates the package's layer dependencies. When it is a Dart file, it validates that file's imports and exports. A PreToolUse hook gates every Very Good CLI MCP tool call.
| Hook | Event | Behavior |
|---|---|---|
Validate layers (validate_layers.sh) |
PostToolUse (Edit/MultiEdit/Write) |
Runs the FFCA validator incrementally: on the edited package and its direct dependents for a pubspec edit (errors only for dependents), and on the edited file alone for a Dart edit. Exits 2 on an error (blocking: Claude must fix the dependency before continuing), printing the rule and the fix. Passes warnings to Claude as non-blocking context. 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 checks package naming under features/, the layer dependency rules, that nothing depends on an app, and cycles. It also checks that each feature's packages fit together: a data package depends on its own _domain under dependencies, a data package never exists without a _domain in its feature, and a _domain package never depends on the Flutter SDK. In lib/ and bin/, it checks that each feature package has a primary barrel lib/{package}.dart, that imports of other workspace packages go through a barrel at the root of their lib/, that another feature's presentation is imported through a dedicated barrel rather than its primary one, that a data barrel never exports DTOs or mappers, that every declared workspace dependency is imported, and that every imported workspace package is declared. An import of an undeclared package is checked against the same layer rules as a pubspec dependency, since a Dart workspace resolves it anyway. On a pubspec edit, a package with no lib/ yet is not required to have a barrel, so a new package can be scaffolded pubspec first.
The validator is also runnable directly for CI and audits, across the whole workspace:
dart run scripts/validate_layers.dart --allEvery finding carries a rule ID in brackets. An error exits 2 and blocks the hook. A warning is reported but exits 0.
| Rule ID | Severity | Finding |
|---|---|---|
package-name |
error | A package under features/{f}/ is not named {f}_domain, {f}_data, {f}_data_{backend}, or {f}_presentation |
depends-on-app |
error | Any package depends on an app |
shared-depends-on-feature |
error | A shared package depends on a feature package |
presentation-depends-on-data |
error | A presentation package depends on a data package |
domain-depends-on-data |
error | A domain package depends on a data package |
domain-depends-on-presentation |
error | A domain package depends on a presentation package |
data-depends-on-presentation |
error | A data package depends on a presentation package |
data-depends-on-feature-data |
error | A data package depends on another feature's data package |
layer-dependency |
error | Any other dependency the rules table does not allow |
dependency-cycle |
error | Packages depend on each other in a cycle |
data-missing-own-domain |
error | A data package does not depend on its own _domain under dependencies |
data-without-domain |
error | A data package's feature has no _domain package |
domain-uses-flutter |
error | A domain package depends on the Flutter SDK |
presentation-missing-own-domain |
warning | A presentation package does not depend on its feature's existing _domain |
missing-primary-barrel |
error | A feature package has no lib/{package}.dart |
deep-import |
error | A Dart file imports or exports another workspace package past the barrels at the root of its lib/ |
cross-feature-primary-barrel |
error | A feature imports another feature's primary presentation barrel instead of a dedicated one |
data-barrel-exports-internals |
error | A data package's primary barrel exports from dtos/ or mappers/. Data sources may be exported, since the app constructs them |
undeclared-dependency |
error | A Dart file imports a workspace package its pubspec does not declare under dependencies. When the layer rules forbid that dependency, the layer rule's ID is reported instead |
unused-dependency |
warning | A package declares a workspace dependency under dependencies but never imports it in lib/ or bin/ |
suppression-missing-reason |
error | An ffca-ignore comment has no reason |
suppression-unknown-rule |
error | An ffca-ignore comment names a rule that does not exist or cannot be suppressed, or a // comment in a Dart file names a rule reported on the pubspec |
unused-suppression |
warning | An ffca-ignore comment matches no finding (--all only) |
To accept a finding as a deliberate exception, add a comment with the rule ID and a reason to the pubspec of the package it is reported on:
# ffca-ignore: data-missing-own-domain β legacy repository, moving to orders_domain in #123
name: orders_dataA finding in a Dart file can also be suppressed in that file, with //:
// ffca-ignore: deep-import β the model is not exported from orders_domain yet
import 'package:orders_domain/models/order.dart';The separator after the rule ID can be an em dash, an en dash, a hyphen, or a colon. A suppression in a pubspec covers every finding with that rule ID in that package, and one in a Dart file covers that file. Rules reported on the pubspec, such as unused-dependency or missing-primary-barrel, can only be suppressed in the pubspec. The reason is required, so each exception explains itself in review. The last three rules cannot be suppressed. --all reports how many findings were suppressed, and flags suppressions that no longer match anything so they can be removed.
- Dart SDK must be available on your
PATH. The validator imports onlydart:io, so it runs with just the SDK, nodart pub getrequired. - jq is used to parse the hook payload. The hooks are skipped gracefully if
jqis not installed, and the validation hook also ifdartis not installed.
Run the hook tests with:
bash hooks/check_vgv_cli_test.sh
bash hooks/validate_layers_test.shThe 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__<tool>, 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 | Behavior |
|---|---|
| ffca-layer-auditor | Read-only architecture auditor. Runs the validator in --all mode, then adds source-level checks (barrel scope, DTO leakage, Command/Query necessity, module entry, split routing tables, deferred-loading reachability, misplaced packages, high fan-in) and returns a per-package verdict table. Reports violations, never auto-fixes |
The auditor runs in its own context, so the same architecture review can be dispatched from the ffca-audit skill, a refactor, or a pre-PR flow without crowding the main conversation. The per-edit hook prevents bad pubspec dependencies as they are written; the agent answers whether the whole repo is healthy on demand.
The hook keeps individual pubspec edits compliant. The skills teach the conventions and workflows. The ffca-layer-auditor agent answers whether the whole repo is healthy, on demand and reusable across flows. All of them read from the same references/ffca/ mirror, so when the architecture evolves, a sync is the only change.