Working with a coding agent
Every generated app has a guide for the coding agents that work on it, at the root of the app:
| File | What it holds |
|---|---|
AGENTS.md | The guide: what the code of the app does not show. |
CLAUDE.md | One line, @AGENTS.md, which gives the same guide to the agents that read CLAUDE.md instead. |
The guide is for an agent to read before it changes anything. It tells the rules of this app, so that you do not have to explain them in each session.
What the guide says
The code of an app shows what it does, and an agent can read it. The guide holds what the code does not show: a rule that holds across files, an order, what not to do and what to do instead, a placeholder, a step that needs a person. Setup that needs a person or an account, such as a Firebase project, stays in the README.md of the app.
The guide starts with a short introduction and has a section for each part of the app that has notes for agents. The section of the app entry comes first, and the others follow in the order of their headings. This is the section "Preferences" of an app with shared_preferences:
AppPreferencesinlib/core/preferences/app_preferences.dartremembers the settings of the app between its launches, such as the theme mode. It is not encrypted: never save a token, a password, an API key or an encryption key in it.- To remember a setting, write a function that takes the
AppPreferencesand add it to_restorersin that file. In it, read the setting, put it into the state that the widgets listen to, and keep the preferences in that state for its writes. The function awaits nothing, and keeps the current value when nothing is saved.- Name a key
<owner id>.<setting>, such astheme.mode, where the owner is the feature or the concern whose code keeps the setting.- In the code of the app, do not call
createAppPreferences()orinitPreferences().bootstrap()opens the preferences, and code gets them as the argument of its function in_restorers, or, with a DI container in the app, from the container. Widgets use the state and do not touch the preferences. A test may call both:initPreferences()opens the preferences again, as the next launch of the app does, andcreateAppPreferences()returns them.With
shared_preferences:
- In
lib/, onlylib/core/preferences/shared_app_preferences.dartimports the package. Other code usesAppPreferences.- A number is read only as the type that it was saved as:
getDouble()returnsnullfor a key with anint, andgetInt()for a key with adouble.- In a test that runs
bootstrap(), first setSharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(). Without it, opening the preferences throws. Both names are from the packageshared_preferences_platform_interface: add it as a dev dependency.
The section of a role has two parts. First comes what holds whichever module provides the role, such as how code remembers a setting. Then the module that provides the role adds what comes with it, here after the line "With shared_preferences:". Replace the module with another provider of the role, and only the second part changes.
The sections of an app
A section is in the guide only when the app has what it describes. The table names the built-in module that brings each section:
| Section | In an app with | What it tells |
|---|---|---|
| App entry | every app | The order of main(), the phases of bootstrap(), and where shared code and features go. flutter_core adds the commands to run after a change, the root widget with the one MaterialApp of the app, and where the test of a file goes. |
| Analytics | firebase_analytics | That code records what users do through AnalyticsService and never through the SDK of a provider, where it gets the service, and how to add a provider. In an app with a router, the module adds that screen views need no code. |
| Crash reporting | firebase_crashlytics | Which function reports the errors that nothing handles, how code reports an error that it catches, and how to add a provider. The module adds the fix that flutter build ipa needs after flutterfire configure runs on macOS. |
| Dependency injection | get_it | What a service is, where the services are registered, and that only the composition file of a feature resolves them. The module adds how its file registers a service. |
| Events | event_bus | What an event is, where code gets the one event service of the app, and what events are for. The module adds when a listener gets an event. |
| Firebase | firebase_core | That lib/firebase_options.dart is a placeholder until flutterfire configure writes it, that an agent runs that command only when asked, and what a test that starts the app needs. |
| Home | home | What the start screen is for, and where the image that it shows is declared. |
| Layout | bottom_tabs | Who creates the shell of the main navigation, what a destination is, how its label follows the language, how to add one, and how code reaches a destination from a page shown over the main navigation. The module adds that it draws its bar itself, when the bar is hidden, how many destinations it should have, where the bar takes its colors from, and what a tab says to a screen reader. |
| Localization | gen_l10n | How code reads a text, where the languages of the app are, how code changes the language, and what a new language needs. The module adds where the texts are, how to add one, and the command to run after a change. |
| Onboarding | onboarding | That no code navigates to the onboarding or away from it, where its pages are and what the screen keeps around them, that the animations of a page end, how code finishes the onboarding and starts it again, and what a test of another screen does first. |
| Preferences | shared_preferences | What the preferences are for, what never goes into them, and how code remembers a setting. The module adds how it reads numbers and what a test that runs bootstrap() needs. |
| Router | go_router | How code navigates with context.nav, the three places that a route is in, and the types of its values. In an app with guards, also where the guards are and how code adds one. The module adds how its file writes a route and, in an app with a layout, where a destination of the main navigation goes. |
| Settings screen | settings | What an entry of the screen is and how to add one. The module adds where its screen is, what it shows with entries and without, and how code opens it in an app without a main navigation. |
| State management | bloc or riverpod | Where the state of a screen lives with the state manager of the app, and that a widget talks only to that state, never to a service. |
| Theme | material_theme | Where the look of the app is, how code changes and reads the theme mode, and where a screen takes its colors from. The module adds how its file creates both themes, where their colors and their font are, and what a change of the file must keep. |
Where the sections come from
The app entry role generates both files in every app, whichever module provides the role. The roles and the modules of the app contribute the sections, as they contribute start-up code: a module knows nothing of the notes of the other modules, and smf create puts the sections together. So the guide of an app covers only what that app has.
SMF checks the paths that a guide names. In its tests, each file or directory that a note names in backticks below a top-level directory of a Flutter project, such as lib/ or android/, and each path to a Dart file, must be in the app of that guide. A module author reads more in Notes for coding agents.
Keep it true
The guide is a file of your app, like the rest of the code. Nothing writes it again, so it is right only as long as you keep it so, and its first paragraph asks the agent to do its part: "When you change what a section describes, change the section too."
- When you change how a part of the app works, such as where its files are or which function code must call, change the section of that part.
- When you add a part of your own with a rule that the code does not show, add a section for it under a heading of its own.
- Keep to what the code does not show. A note that repeats the code makes the other notes harder to find.