Skip to content

fix: layered-architecture lets the app declare the data packages its bootstrap constructs - #165

Merged
ryzizub merged 2 commits into
VeryGoodOpenSource:mainfrom
mark-wint:fix/layered-architecture-app-wires-data-clients
Oct 5, 2026
Merged

ryzizub merged 2 commits into
VeryGoodOpenSource:mainfrom
mark-wint:fix/layered-architecture-app-wires-data-clients

Conversation

@mark-wint

@mark-wint mark-wint commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

The layered-architecture skill told agents the root app pubspec lists repository packages only, and in the same file had main_<flavor>.dart import and construct the data clients. depend_on_referenced_packages rejects that combination, so an agent had to break one rule, and it broke injection: the repository built or defaulted its own client so the app pubspec could stay clean. This PR makes the two sections describe the same app. The evidence and the survey of VGV apps are in the linked issue.

Closes #164

SKILL.md

  • The root app pubspec declares its repositories and every data package its bootstrap constructs. The Dependency Graph YAML now lists user_api_client for the app.
  • A new Core Standard moves the layer boundary onto imports: only the app's entrypoints and bootstrap import a data package, and blocs, widgets and app tests reach data through a repository. This restates the existing "never skip a layer" rule in a form that stays checkable once the pubspec declares the data packages.
  • The injection standard now covers every external source, SDK objects like FirebaseAuth.instance as well as data clients, and names defaulting inside the repository as out of bounds.
  • Repository rules gain one line: a repository with no external source takes no constructor arguments. The Architecture table's "Data layer packages" becomes "Zero or more data layer packages."
  • The App Bootstrap snippet shows its imports again, so it is visible that bootstrap depends on the data packages.
  • "Connecting a Repository to a Feature" adds the data packages to the root pubspec and ends on a grep over lib/ and test/ for data-package imports.

references/pubspec.md lists the data packages in both root-app examples and adds an Import Boundary section. That section covers which files count as entrypoints and bootstrap across app layouts, the grep and how to fix a hit, and how the rule applies when a third-party SDK is the data layer. When a bloc needs a data-layer type, the fix is a domain type the repository defines and maps to, the way UserRepository already turns UserApiException into UserNotFoundException.

Dropped from the issue. The issue proposed that repository barrels re-export the data-layer models, enums and exceptions their API exposes, following flutter_todos and news_toolkit. That contradicts the existing rule to transform data models into domain models and never leak API response shapes upstream, and an early eval run flagged it: layered-architecture-refuses-domain-model-in-data-layer went red on all three with-plugin runs with the re-export line in place. The PR keeps the transformation rule as it is.

Evals. A new case, layered-architecture-injects-client-the-app-pubspec-declares, asks for a repository wired into an app whose pubspec should list only the repository. It passes when the repository requires its client and never builds one, main_development.dart constructs it, and the app pubspec declares the data package. Without the plugin, the model reproduces the failure the issue describes. The Verification section has the details. NOTES.md and the case counts in AGENTS.md and evals/README.md are updated. The dependency-chain grader in layered-architecture-lays-out-four-layers already treats bootstrap constructing data clients as the expected pattern, so no existing grader changes.

Line budget. SKILL.md grows from 369 to 384 lines. Most of it is the six import lines in the bootstrap snippet and the new Core Standard. The rest of the detail went to references/pubspec.md.

Out of scope. No mechanical CI check for the import rule ships here. It can follow once the question below is settled.

Open question: blocs that take a data-layer resource

The issue asked whether a bloc taking a data-layer resource directly is an accepted shortcut. It had no answer, so this PR keeps the layer ban the skill already states and writes the import rule as a plain directive.

io_crossword is the one VGV app that skips the layer consistently. Its Firestore reads go through repositories while calls to its own backend go through api_client resources straight from blocs, so HintBloc and several others import api_client. If that split should be allowed, it is a one-sentence exception on the new Core Standard, for example: "A bloc may take a data-layer resource directly when a repository would only pass its calls through." I'm happy to add it if you want it.

Verification

  • claude plugin validate . passes, with the root CLAUDE.md warning that is already on main.
  • markdownlint-cli2 and cspell pass on every changed file with the repo's configs.
  • Evals, claude plugin eval . --scaffold --tag layered-architecture --runs 3 --threshold 0.8 on Claude Code 2.1.277, both arms:
Case With plugin Without
injects-client-the-app-pubspec-declares (new) 1.00, 1.00, 1.00 0.22, 0.11, 0.00
keeps-flutter-out-of-data-packages 1.00, 0.80, 1.00 0.40, 0.40, 0.40
lays-out-four-layers 1.00, 1.00, 1.00 0.28, 0.28, 0.57
refuses-domain-model-in-data-layer 0.60, 1.00, 1.00 0.20, 0.20, 0.00
refuses-repository-to-repository-dependency 1.00, 1.00, 1.00 0.20, 0.40, 0.20
stays-out-of-single-file-work 1.00, 1.00, 1.00 1.00, 1.00, 1.00
transforms-models-in-the-repository 1.00, 1.00, 1.00 0.62, 0.50, 0.50
wires-repositories-in-bootstrap 1.00, 1.00, 1.00 0.50, 0.50, 0.50

Two with-plugin runs dipped, and neither touches the changed text. The refuses-domain-model-in-data-layer run declined the request and explained the response-model and domain-model split, then, finding nothing on disk to move, closed by offering to put User in the data package anyway if the user had a reason. The judge failed both content graders on that offer. The same case scored 3/3 on main in a separate with-plugin run. The keeps-flutter-out-of-data-packages dip is a split judge vote, PASS FAIL FAIL, on removes-flutter-dependency for a response that says to drop the import.

In the new case, the no-plugin arm reproduces the failure from the issue. It gives WeatherRepository a baseUrl and builds the client in a redirecting constructor, with a doc comment saying it does so "so that the app package never has to depend on weather_api_client."

Type of Change

  • New feature (feat)
  • Bug fix (fix)
  • Code refactor (refactor)
  • Documentation (docs)
  • CI change (ci)
  • Chore (chore)

🤖 Generated with Claude Code

…bootstrap constructs

The Dependency Graph section required a repositories-only app pubspec
while App Bootstrap had main_<flavor>.dart import and construct the
data clients. depend_on_referenced_packages rejects that combination,
so an agent following the skill kept the pubspec clean and had the
repository build or default its own client, breaking the injection
standard.

The app pubspec now declares the data packages its bootstrap
constructs, and the layer boundary is an import rule: only the
entrypoints and bootstrap import a data package. Injection covers SDK
objects as well as data clients, and a repository with no external
source is valid. The pubspec reference gains an Import Boundary
section, and a new eval case covers a request to keep the app pubspec
to repositories only.

Refs VeryGoodOpenSource#164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mark-wint
mark-wint marked this pull request as ready for review September 29, 2026 16:48
@mark-wint
mark-wint requested a review from a team as a code owner September 29, 2026 16:48
@ryzizub

ryzizub commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@vgvbot rebase this PR

@unicoderbot

unicoderbot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

On it, I'll rebase this PR onto the target branch and resolve any conflicts.

  • Preparing environment
    • Cloning repository
    • Setting up dev environment
  • Fetching target branch
  • Rebasing onto target
  • Replaying commits
  • Updating PR branch

I can't rebase a PR from a fork — my installation doesn't have write access to the fork.

🔍 View this task run

@ryzizub

ryzizub commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@mark-wint can i ask to resolve conflicts. Otherwise it LGTM

Resolve the Core Standards conflict in skills/layered-architecture/SKILL.md
by keeping both new bullets: this branch's import-boundary rule and the
Dart 3.13 primary-constructor rule from VeryGoodOpenSource#167.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@ryzizub ryzizub left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ryzizub
ryzizub merged commit f6abb07 into VeryGoodOpenSource:main Oct 5, 2026
5 checks passed
@vgvbot vgvbot mentioned this pull request Oct 1, 2026
ryzizub added a commit that referenced this pull request Oct 5, 2026
#165 added a case and bumped 100 to 101; this branch did the same for its own.
Together that is 102. Both sides' edits merged cleanly to 101, which was wrong.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: layered-architecture bars the app pubspec from the data packages its own bootstrap constructs

2 participants