Skip to main content

What is the .claude/ folder in a DartWay project?

Every project created by dartway create ships with a .claude/ directory: the methodology of the framework, written for an agent instead of for a reader. dartway setup-ai installs the same thing into a project you already have.

dartway setup-ai --base-branch develop

Why the framework ships this at all

DartWay is highly opinionated: less freedom in how to do things, more consistency and speed. All Flutter ↔ server traffic goes through CRUD configs, not endpoints. A feature is a folder with exactly one public file. Styles live in the app's own UI kit and nowhere else. None of that is guessable from the API surface — an agent reading only your pubspec.yaml will write correct Dart in the wrong shape, confidently, at speed, everywhere.

That is the failure mode worth naming. An agent that does not know the conventions does not produce compile errors you can fix in a morning; it produces a second architecture living inside the first one, and it produces it faster than a person can review. The toolkit exists so the agent writes code in the same conventions a person on the project would — and so the conventions checker agrees with it afterwards.

Toolkit and code evolve in the same repository and the same pull request: a change to a package's public API updates the affected skills alongside it. A skill that has fallen behind the API is worse than a missing one — the agent writes non-working code with full confidence.

What gets installed

.claude/
CLAUDE.md # the methodology, always in the agent's context
skills/dartway-*/SKILL.md # 15 skills, loaded by relevance to the task
commands/commit.md
commands/dartway-checkup.md

CLAUDE.md is the always-loaded brain: the cross-stack laws (CRUD first, domain-first, a feature is end-to-end), naming rules, what each package is for, why the project uses no build_runner, and where a feature's description lives — in DwFeatureSpec next to the code, not in a doc beside it.

The installer detects your package layout by directory suffix (*_server, *_client, *_flutter, optional *_shared) and substitutes the names into the installed markdown, so the skills talk about my_app_flutter, not about a placeholder. --base-branch is substituted the same way, so the commit and PR skills diff against the branch your project actually uses.

Commit .claude/ to your repository. It is a generated-but-committed artifact, like the Serverpod client: a clone comes with the skills already in place, and the history records which version of the methodology a piece of code was written under.

Managed files, and how to customize

Reinstalling overwrites only what the toolkit manages: CLAUDE.md, every skill directory named dartway-*, and the commit / dartway-checkup commands. Anything else in .claude/ — your own skills, your own commands — is never touched.

So do not edit a dartway-* skill in place; the next setup-ai will drop your changes on the floor. To customize, copy the skill under a different name and edit the copy. The source of truth is the toolkit in the monorepo, and there is no reverse sync.

Updating is a deliberate act with a visible diff: run dartway update, read what changed, commit. It installs the toolkit the way setup-ai does and then reports what else has moved — which framework packages this project is behind on, and which migration notes it still owes an edit to. What was installed is written down beside it, in .claude/dartway-toolkit.json — repository, channel, commit, CLI version — because the files themselves do not say which channel they follow, and --channel defaults to stable. Update a project that had been moved to master with the plain command and it would be rolled back, with the diff looking like any other update; instead the command refuses and asks for the channel by name.

.claude/settings.json sits on neither side of that line, and is the third kind of file here: a toolkit default the project extends. It pre-approves this stack's build commands — dart pub get, docker compose up, serverpod generate, the test runners — so a first run is not a queue of permission prompts, and it denies reading config/passwords.yaml, which turns a rule the skills merely state into one the harness enforces. Nothing destructive is on the allow list: docker compose down, commits and pushes still ask.

An update merges it: entries the toolkit has and the project lacks are added, everything the project added stays, and every added entry is printed. It used to be written once and never touched, which sounds like the safe choice and is only half of one — a changed default then reached an existing project only if somebody deleted the file first, and a new deny rule reached none of them, which is precisely the half the harness is supposed to enforce rather than state. The one cost of merging is an entry a project removed on purpose coming back; that is why every addition is named in the output rather than applied quietly.

That is also why a finding about the framework leaves the project entirely. A rule that did not catch a mistake, two skills that disagree, an API the app had to work around — none of that can be fixed in the installed copy, and all of it is worth keeping: the rules are only ever proven wrong by real code. Such a finding is filed as an issue in the framework's tracker, and dartway-finish writes the issue text and offers it at the end of a task. Unless dartway setup-ai --notes-tracker owner/repo names another repository, that tracker is the framework's own; --notes-tracker none keeps everything inside the project, and nothing reaches the network.

The default is deliberate. A project that never decided where its findings should go was a project whose findings stayed put — waiting in a git-ignored file that nothing else in the world could see.

There used to be a journal in between, and it is worth saying why it is gone. It kept a status of its own beside each entry, which is a second copy of a state that changes elsewhere: one project's journal advertised eleven open findings a fortnight after all eleven had shipped, the entry asking for this mechanism among them, because the fixes landed in the monorepo and nothing wrote back to the laptop. Being git-ignored, it was invisible in every place work is actually reviewed — and a git worktree remove deleted a copy of it without a word, since git status says nothing about ignored files. Making that decision a precondition would have reproduced the failure in every project that skipped it.

The half that does not travel is the reason filing is not a copy. An entry earns its keep here by naming this codebase — the file and line, the class, the workaround the app wrote, the marker beside it — and that is precisely what cannot go into a repository other people read. So CLAUDE.md asks for four things before an issue exists: the finding restated so it stands without this project's code (if nothing survives that, it was never about the framework), English rather than the project's language, a search of the tracker first because three projects meeting one API gap is one issue, and an explicit yes — creating a public issue is the only step in the journal that cannot be taken back.

One kind of finding needs a second half, in the code. A workaround over a dartway_* API is written down as an entry and marked where it lives — // TODO(dartway, checked: 518ae6d): …, naming the framework version it was last confirmed against. The reason is that the entry alone answers the wrong question: it says the workaround exists, never that it is still needed, and the framework moves while the code does not. A real one outlived its fix by weeks on a project already pinned past it, and it was not an idle duplicate — it threw where the framework had chosen to degrade softly, so the app died on an offline start. dartway-finish compares checked: against the version resolved in pubspec.lock and raises the marker only when they have diverged, which is the only moment the answer can have changed; /dartway-checkup runs the same comparison across the whole project, for the workarounds no task has touched.

The skills

The lifecycle of a task runs left to right: dartway-requirementsdartway-plan → implementation with the layer skills → dartway-finish.

dartway-run — bring the project up locally and confirm it is alive: dependencies, Postgres in Docker, migrations, the first administrator, server, app. Knows the order that matters, the real ports, where the sign-in code is printed, and how to read the failures people actually hit.

dartway-requirements — read-only analysis before a task: what the project already has on the topic, blocking debt versus adjacent debt, the questions worth asking, and 2–3 implementation options along the escalation ladder with tradeoffs, risks and a rough estimate.

dartway-plan — read-only planning once the requirements are agreed: a step-by-step end-to-end plan (models → migrations → CRUD configs → server logic → Flutter → tests → docs), the subtleties and risks, and a checklist to verify against afterwards.

dartway-clean-code — the cleanliness contract for all Dart/Flutter work: self-explanatory naming, one responsibility per file, never passing BuildContext or WidgetRef as parameters, no _buildXxx() widget methods, ref.invalidate only where the user asked for it, a failed read that looks like neither an empty list nor a spinner, plus SOLID/KISS/DRY/YAGNI and when tests are required.

dartway-feature-scaffold — building a feature end to end: navigation → entry point → state and logic → CRUD configs → models → tests, with the feature structure, its isolation, and the DwFeatureSpec written in the feature's own file.

dartway-models — Serverpod .spy.yaml models: base versus event models, nullable discipline, bidirectional relations and onDelete, indexes, enums, and the edit → generate → migrate → config workflow.

dartway-crud-config — the server playbook: DwCrudConfig<T> and its hooks, read configs and their mandatory accessFilter, DwModelWrapper, the pure-domain versus session-aware boundary. All server logic goes through configs, not endpoints.

dartway-data-layer — the Flutter data layer: reads and writes through dw.repo, lists via dwBuildListAsync, backend filtering versus local filtering, actions through dw.action, notifications through dw.notify.* rather than SnackBar, the profile getters.

dartway-navigation — the DartWay router: zones as enums, route descriptors, zone guards, type-safe enum parameters, and how the router is assembled.

dartway-ui-kit — the kit lives as source inside the app: the framework ships no buttons, no text widget and no theme. App widgets with named constructors, DwActionBuilder, and the ban on raw styles inside features.

dartway-push-delivery — server-side push through the optional dartway_push_server module: the engine, recipient resolution, FCM/RuStore transports, idempotent enqueue, retries, campaign progress. Opt-in — an app that does not depend on it has no push tables.

dartway-testing — where a test goes and how to write it: a rule from a DwCrudConfig is an integration test on the server against a live database, plain logic is a unit test, and a feature is a widget test with the core booted and the server standing in as a recording transport. A DartWay feature saves for itself and hands no callback out, so the technique is not guessable — and the skeleton ships a worked example of each layer in its test/ folder. Also what is deliberately not tested, and why there are no coverage thresholds.

dartway-finish — the definition of done before a commit or PR: audits the diff against the contract, checks the feature's description for drift and the test coverage, then shows suggestions and applies only what you confirm.

dartway-update — moving the project onto a newer framework, which is a job of its own rather than part of a task: run dartway update, read the framework's migration notes, make the edits they ask for, and only then move the package versions. That order is the whole point — moving the packages first turns a readable instruction into a screen of compile errors.

Two commands come with them. /commit — one conventional-commit line in English, and nothing local decided for you: whether commits carry a ticket, and whether anything checks the message, belongs to the project's own CLAUDE.md. It used to demand a ticket as a required argument and stop without one, which in a project with no tracker stalled the agent mid-task on a question that has no answer. And /dartway-checkup — the state of the project and what is worth taking into work next.

The checkup answers two questions for the same person on different days: how bad is it and what do I fix first. It runs the project's own gates before reading anything — the checker, the lints, the analyzer, the tests — then compares that against what CI actually executes, because a rule declared and never run reads as covered while enforcing nothing. It measures how far the project's pin trails the framework, since a workaround here may already be a duplicate of something upstream now does. Only then does it spend reading on a handful of features, chosen from a coverage table so that successive runs go deeper instead of skimming the same surface.

What it finds gets placed rather than announced, and the test is whether the finding has an address in code: one that belongs to a feature becomes a line in its knownIssues, one about the framework becomes an issue in the tracker, and a cross-cutting risk with no address at all becomes a file under docs/dev_notes/ — tracked and committed, so it travels out in a pull request and is visible in review. The defect itself lives in the tracker either way; the entry beside the code only references it, which is why neither carries a status of its own.

What the toolkit does not do

It never changes code silently. dartway-requirements and dartway-plan write nothing at all; dartway-finish shows what it would change and applies only the confirmed part, leaving anything architectural to you with a note. The point is a reviewer that is awake at 2 a.m., not an autopilot.